server/sources/native.py
changeset 3647 2941f4a0aab9
parent 3503 06bced8edddf
child 3720 5376aaadd16b
equal deleted inserted replaced
3646:3bba270202ef 3647:2941f4a0aab9
    92 
    92 
    93 class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
    93 class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
    94     """adapter for source using the native cubicweb schema (see below)
    94     """adapter for source using the native cubicweb schema (see below)
    95     """
    95     """
    96     sqlgen_class = SQLGenerator
    96     sqlgen_class = SQLGenerator
    97 
       
    98     passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P"
       
    99     auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s"
       
   100     _sols = ({'X': 'CWUser', 'P': 'Password'},)
       
   101 
       
   102     options = (
    97     options = (
   103         ('db-driver',
    98         ('db-driver',
   104          {'type' : 'string',
    99          {'type' : 'string',
   105           'default': 'postgres',
   100           'default': 'postgres',
   106           'help': 'database driver (postgres or sqlite)',
   101           'help': 'database driver (postgres or sqlite)',
   144           }),
   139           }),
   145     )
   140     )
   146 
   141 
   147     def __init__(self, repo, appschema, source_config, *args, **kwargs):
   142     def __init__(self, repo, appschema, source_config, *args, **kwargs):
   148         SQLAdapterMixIn.__init__(self, source_config)
   143         SQLAdapterMixIn.__init__(self, source_config)
       
   144         self.authentifiers = [LoginPasswordAuthentifier(self)]
   149         AbstractSource.__init__(self, repo, appschema, source_config,
   145         AbstractSource.__init__(self, repo, appschema, source_config,
   150                                 *args, **kwargs)
   146                                 *args, **kwargs)
   151         # sql generator
   147         # sql generator
   152         self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper,
   148         self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper,
   153                                              self.encoding, ATTR_MAP.copy())
   149                                              self.encoding, ATTR_MAP.copy())
   178         # XXX: sqlite connections can only be used in the same thread, so
   174         # XXX: sqlite connections can only be used in the same thread, so
   179         #      create a new one each time necessary. If it appears to be time
   175         #      create a new one each time necessary. If it appears to be time
   180         #      consuming, find another way
   176         #      consuming, find another way
   181         return SQLAdapterMixIn.get_connection(self)
   177         return SQLAdapterMixIn.get_connection(self)
   182 
   178 
       
   179     def add_authentifier(self, authentifier):
       
   180         self.authentifiers.append(authentifier)
       
   181         authentifier.source = self
       
   182         authentifier.set_schema(self.schema)
       
   183 
   183     def reset_caches(self):
   184     def reset_caches(self):
   184         """method called during test to reset potential source caches"""
   185         """method called during test to reset potential source caches"""
   185         self._cache = Cache(self.repo.config['rql-cache-size'])
   186         self._cache = Cache(self.repo.config['rql-cache-size'])
   186 
   187 
   187     def clear_eid_cache(self, eid, etype):
   188     def clear_eid_cache(self, eid, etype):
   228     def map_attribute(self, etype, attr, cb):
   229     def map_attribute(self, etype, attr, cb):
   229         self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb
   230         self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb
   230 
   231 
   231     # ISource interface #######################################################
   232     # ISource interface #######################################################
   232 
   233 
   233     def compile_rql(self, rql):
   234     def compile_rql(self, rql, sols):
   234         rqlst = self.repo.vreg.rqlhelper.parse(rql)
   235         rqlst = self.repo.vreg.rqlhelper.parse(rql)
   235         rqlst.restricted_vars = ()
   236         rqlst.restricted_vars = ()
   236         rqlst.children[0].solutions = self._sols
   237         rqlst.children[0].solutions = sols
   237         self.repo.querier.sqlgen_annotate(rqlst)
   238         self.repo.querier.sqlgen_annotate(rqlst)
   238         set_qdata(self.schema.rschema, rqlst, ())
   239         set_qdata(self.schema.rschema, rqlst, ())
   239         return rqlst
   240         return rqlst
   240 
   241 
   241     def set_schema(self, schema):
   242     def set_schema(self, schema):
   245         self.schema = schema
   246         self.schema = schema
   246         try:
   247         try:
   247             self._rql_sqlgen.schema = schema
   248             self._rql_sqlgen.schema = schema
   248         except AttributeError:
   249         except AttributeError:
   249             pass # __init__
   250             pass # __init__
   250         if 'CWUser' in schema: # probably an empty schema if not true...
   251         for authentifier in self.authentifiers:
   251             # rql syntax trees used to authenticate users
   252             authentifier.set_schema(self.schema)
   252             self._passwd_rqlst = self.compile_rql(self.passwd_rql)
       
   253             self._auth_rqlst = self.compile_rql(self.auth_rql)
       
   254 
   253 
   255     def support_entity(self, etype, write=False):
   254     def support_entity(self, etype, write=False):
   256         """return true if the given entity's type is handled by this adapter
   255         """return true if the given entity's type is handled by this adapter
   257         if write is true, return true only if it's a RW support
   256         if write is true, return true only if it's a RW support
   258         """
   257         """
   269         return True #not rtype == 'content_for'
   268         return True #not rtype == 'content_for'
   270 
   269 
   271     def may_cross_relation(self, rtype):
   270     def may_cross_relation(self, rtype):
   272         return True
   271         return True
   273 
   272 
   274     def authenticate(self, session, login, password):
   273     def authenticate(self, session, login, **kwargs):
   275         """return CWUser eid for the given login/password if this account is
   274         """return CWUser eid for the given login and other authentication
   276         defined in this source, else raise `AuthenticationError`
   275         information found in kwargs, else raise `AuthenticationError`
   277 
   276         """
   278         two queries are needed since passwords are stored crypted, so we have
   277         for authentifier in self.authentifiers:
   279         to fetch the salt first
       
   280         """
       
   281         args = {'login': login, 'pwd' : password}
       
   282         if password is not None:
       
   283             rset = self.syntax_tree_search(session, self._passwd_rqlst, args)
       
   284             try:
   278             try:
   285                 pwd = rset[0][0]
   279                 return authentifier.authenticate(session, login, **kwargs)
   286             except IndexError:
   280             except AuthenticationError:
   287                 raise AuthenticationError('bad login')
   281                 continue
   288             # passwords are stored using the Bytes type, so we get a StringIO
   282         raise AuthenticationError()
   289             if pwd is not None:
       
   290                 args['pwd'] = crypt_password(password, pwd.getvalue()[:2])
       
   291         # get eid from login and (crypted) password
       
   292         rset = self.syntax_tree_search(session, self._auth_rqlst, args)
       
   293         try:
       
   294             return rset[0][0]
       
   295         except IndexError:
       
   296             raise AuthenticationError('bad password')
       
   297 
   283 
   298     def syntax_tree_search(self, session, union, args=None, cachekey=None,
   284     def syntax_tree_search(self, session, union, args=None, cachekey=None,
   299                            varmap=None):
   285                            varmap=None):
   300         """return result from this source for a rql query (actually from
   286         """return result from this source for a rql query (actually from
   301         a rql syntax tree and a solution dictionary mapping each used
   287         a rql syntax tree and a solution dictionary mapping each used
   638         result += 'ALTER TABLE entities_id_seq OWNER TO %s;\n' % user
   624         result += 'ALTER TABLE entities_id_seq OWNER TO %s;\n' % user
   639     result += 'GRANT ALL ON entities TO %s;\n' % user
   625     result += 'GRANT ALL ON entities TO %s;\n' % user
   640     result += 'GRANT ALL ON deleted_entities TO %s;\n' % user
   626     result += 'GRANT ALL ON deleted_entities TO %s;\n' % user
   641     result += 'GRANT ALL ON entities_id_seq TO %s;\n' % user
   627     result += 'GRANT ALL ON entities_id_seq TO %s;\n' % user
   642     return result
   628     return result
       
   629 
       
   630 
       
   631 class BaseAuthentifier(object):
       
   632 
       
   633     def __init__(self, source=None):
       
   634         self.source = source
       
   635 
       
   636     def set_schema(self, schema):
       
   637         """set the instance'schema"""
       
   638         pass
       
   639 
       
   640 class LoginPasswordAuthentifier(BaseAuthentifier):
       
   641     passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P"
       
   642     auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s"
       
   643     _sols = ({'X': 'CWUser', 'P': 'Password'},)
       
   644 
       
   645     def set_schema(self, schema):
       
   646         """set the instance'schema"""
       
   647         if 'CWUser' in schema: # probably an empty schema if not true...
       
   648             # rql syntax trees used to authenticate users
       
   649             self._passwd_rqlst = self.source.compile_rql(self.passwd_rql, self._sols)
       
   650             self._auth_rqlst = self.source.compile_rql(self.auth_rql, self._sols)
       
   651 
       
   652     def authenticate(self, session, login, password=None, **kwargs):
       
   653         """return CWUser eid for the given login/password if this account is
       
   654         defined in this source, else raise `AuthenticationError`
       
   655 
       
   656         two queries are needed since passwords are stored crypted, so we have
       
   657         to fetch the salt first
       
   658         """
       
   659         args = {'login': login, 'pwd' : password}
       
   660         if password is not None:
       
   661             rset = self.source.syntax_tree_search(session, self._passwd_rqlst, args)
       
   662             try:
       
   663                 pwd = rset[0][0]
       
   664             except IndexError:
       
   665                 raise AuthenticationError('bad login')
       
   666             # passwords are stored using the Bytes type, so we get a StringIO
       
   667             if pwd is not None:
       
   668                 args['pwd'] = crypt_password(password, pwd.getvalue()[:2])
       
   669         # get eid from login and (crypted) password
       
   670         rset = self.source.syntax_tree_search(session, self._auth_rqlst, args)
       
   671         try:
       
   672             return rset[0][0]
       
   673         except IndexError:
       
   674             raise AuthenticationError('bad password')