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') |