diff -r 8968c50818db -r 43c14e0e8972 server/sources/native.py --- a/server/sources/native.py Fri Feb 12 12:57:14 2010 +0100 +++ b/server/sources/native.py Fri Feb 12 12:57:56 2010 +0100 @@ -96,11 +96,6 @@ """adapter for source using the native cubicweb schema (see below) """ sqlgen_class = SQLGenerator - - passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P" - auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s" - _sols = ({'X': 'CWUser', 'P': 'Password'},) - options = ( ('db-driver', {'type' : 'string', @@ -148,6 +143,7 @@ def __init__(self, repo, appschema, source_config, *args, **kwargs): SQLAdapterMixIn.__init__(self, source_config) + self.authentifiers = [LoginPasswordAuthentifier(self)] AbstractSource.__init__(self, repo, appschema, source_config, *args, **kwargs) # sql generator @@ -182,6 +178,11 @@ # consuming, find another way return SQLAdapterMixIn.get_connection(self) + def add_authentifier(self, authentifier): + self.authentifiers.append(authentifier) + authentifier.source = self + authentifier.set_schema(self.schema) + def reset_caches(self): """method called during test to reset potential source caches""" self._cache = Cache(self.repo.config['rql-cache-size']) @@ -230,12 +231,15 @@ def map_attribute(self, etype, attr, cb): self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb + def unmap_attribute(self, etype, attr): + self._rql_sqlgen.attr_map.pop('%s.%s' % (etype, attr), None) + # ISource interface ####################################################### - def compile_rql(self, rql): + def compile_rql(self, rql, sols): rqlst = self.repo.vreg.rqlhelper.parse(rql) rqlst.restricted_vars = () - rqlst.children[0].solutions = self._sols + rqlst.children[0].solutions = sols self.repo.querier.sqlgen_annotate(rqlst) set_qdata(self.schema.rschema, rqlst, ()) return rqlst @@ -249,10 +253,8 @@ self._rql_sqlgen.schema = schema except AttributeError: pass # __init__ - if 'CWUser' in schema: # probably an empty schema if not true... - # rql syntax trees used to authenticate users - self._passwd_rqlst = self.compile_rql(self.passwd_rql) - self._auth_rqlst = self.compile_rql(self.auth_rql) + for authentifier in self.authentifiers: + authentifier.set_schema(self.schema) def support_entity(self, etype, write=False): """return true if the given entity's type is handled by this adapter @@ -273,30 +275,16 @@ def may_cross_relation(self, rtype): return True - def authenticate(self, session, login, password): - """return CWUser eid for the given login/password if this account is - defined in this source, else raise `AuthenticationError` - - two queries are needed since passwords are stored crypted, so we have - to fetch the salt first + def authenticate(self, session, login, **kwargs): + """return CWUser eid for the given login and other authentication + information found in kwargs, else raise `AuthenticationError` """ - args = {'login': login, 'pwd' : password} - if password is not None: - rset = self.syntax_tree_search(session, self._passwd_rqlst, args) + for authentifier in self.authentifiers: try: - pwd = rset[0][0] - except IndexError: - raise AuthenticationError('bad login') - # passwords are stored using the Bytes type, so we get a StringIO - if pwd is not None: - args['pwd'] = Binary(crypt_password(password, pwd.getvalue()[:2])) - # get eid from login and (crypted) password - # XXX why not simply compare password? - rset = self.syntax_tree_search(session, self._auth_rqlst, args) - try: - return rset[0][0] - except IndexError: - raise AuthenticationError('bad password') + return authentifier.authenticate(session, login, **kwargs) + except AuthenticationError: + continue + raise AuthenticationError() def syntax_tree_search(self, session, union, args=None, cachekey=None, varmap=None): @@ -534,7 +522,7 @@ if extid is not None: assert isinstance(extid, str) extid = b64encode(extid) - attrs = {'type': entity.id, 'eid': entity.eid, 'extid': extid, + attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid, 'source': source.uri, 'mtime': datetime.now()} session.system_sql(self.sqlgen.insert('entities', attrs), attrs) @@ -564,7 +552,7 @@ def fti_index_entity(self, session, entity): """add text content of a created/modified entity to the full text index """ - self.info('reindexing %r', entity.eid) + self.debug('reindexing %r', entity.eid) try: self.indexer.cursor_reindex_object(entity.eid, entity, session.pool['system']) @@ -644,3 +632,49 @@ result += 'GRANT ALL ON deleted_entities TO %s;\n' % user result += 'GRANT ALL ON entities_id_seq TO %s;\n' % user return result + + +class BaseAuthentifier(object): + + def __init__(self, source=None): + self.source = source + + def set_schema(self, schema): + """set the instance'schema""" + pass + +class LoginPasswordAuthentifier(BaseAuthentifier): + passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P" + auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s" + _sols = ({'X': 'CWUser', 'P': 'Password'},) + + def set_schema(self, schema): + """set the instance'schema""" + if 'CWUser' in schema: # probably an empty schema if not true... + # rql syntax trees used to authenticate users + self._passwd_rqlst = self.source.compile_rql(self.passwd_rql, self._sols) + self._auth_rqlst = self.source.compile_rql(self.auth_rql, self._sols) + + def authenticate(self, session, login, password=None, **kwargs): + """return CWUser eid for the given login/password if this account is + defined in this source, else raise `AuthenticationError` + + two queries are needed since passwords are stored crypted, so we have + to fetch the salt first + """ + args = {'login': login, 'pwd' : password} + if password is not None: + rset = self.source.syntax_tree_search(session, self._passwd_rqlst, args) + try: + pwd = rset[0][0] + except IndexError: + raise AuthenticationError('bad login') + # passwords are stored using the Bytes type, so we get a StringIO + if pwd is not None: + args['pwd'] = Binary(crypt_password(password, pwd.getvalue()[:2])) + # get eid from login and (crypted) password + rset = self.source.syntax_tree_search(session, self._auth_rqlst, args) + try: + return rset[0][0] + except IndexError: + raise AuthenticationError('bad password')