199 self._cache = {} |
199 self._cache = {} |
200 self._query_cache = TimedCache(2*60) |
200 self._query_cache = TimedCache(2*60) |
201 |
201 |
202 def init(self): |
202 def init(self): |
203 """method called by the repository once ready to handle request""" |
203 """method called by the repository once ready to handle request""" |
|
204 self.info('ldap init') |
204 self.repo.looping_task(self._interval, self.synchronize) |
205 self.repo.looping_task(self._interval, self.synchronize) |
205 self.repo.looping_task(self._query_cache.ttl.seconds/10, |
206 self.repo.looping_task(self._query_cache.ttl.seconds/10, |
206 self._query_cache.clear_expired) |
207 self._query_cache.clear_expired) |
207 |
208 |
208 def synchronize(self): |
209 def synchronize(self): |
219 try: |
220 try: |
220 cursor = session.system_sql("SELECT eid, extid FROM entities WHERE " |
221 cursor = session.system_sql("SELECT eid, extid FROM entities WHERE " |
221 "source='%s'" % self.uri) |
222 "source='%s'" % self.uri) |
222 for eid, b64extid in cursor.fetchall(): |
223 for eid, b64extid in cursor.fetchall(): |
223 extid = b64decode(b64extid) |
224 extid = b64decode(b64extid) |
|
225 self.debug('ldap eid %s', eid) |
224 # if no result found, _search automatically delete entity information |
226 # if no result found, _search automatically delete entity information |
225 res = self._search(session, extid, BASE) |
227 res = self._search(session, extid, BASE) |
|
228 self.debug('ldap search %s', res) |
226 if res: |
229 if res: |
227 ldapemailaddr = res[0].get(ldap_emailattr) |
230 ldapemailaddr = res[0].get(ldap_emailattr) |
228 if ldapemailaddr: |
231 if ldapemailaddr: |
229 rset = execute('Any X,A WHERE ' |
232 rset = execute('Any X,A WHERE ' |
230 'X address A, U use_email X, U eid %(u)s', |
233 'X address A, U use_email X, U eid %(u)s', |
267 defined in this source, else raise `AuthenticationError` |
270 defined in this source, else raise `AuthenticationError` |
268 |
271 |
269 two queries are needed since passwords are stored crypted, so we have |
272 two queries are needed since passwords are stored crypted, so we have |
270 to fetch the salt first |
273 to fetch the salt first |
271 """ |
274 """ |
|
275 self.info('ldap authenticate %s', login) |
272 if password is None: |
276 if password is None: |
273 raise AuthenticationError() |
277 raise AuthenticationError() |
274 searchfilter = [filter_format('(%s=%s)', (self.user_login_attr, login))] |
278 searchfilter = [filter_format('(%s=%s)', (self.user_login_attr, login))] |
275 searchfilter.extend([filter_format('(%s=%s)', ('objectClass', o)) |
279 searchfilter.extend([filter_format('(%s=%s)', ('objectClass', o)) |
276 for o in self.user_classes]) |
280 for o in self.user_classes]) |
341 """return result from this source for a rql query (actually from a rql |
345 """return result from this source for a rql query (actually from a rql |
342 syntax tree and a solution dictionary mapping each used variable to a |
346 syntax tree and a solution dictionary mapping each used variable to a |
343 possible type). If cachekey is given, the query necessary to fetch the |
347 possible type). If cachekey is given, the query necessary to fetch the |
344 results (but not the results themselves) may be cached using this key. |
348 results (but not the results themselves) may be cached using this key. |
345 """ |
349 """ |
|
350 self.debug('ldap syntax tree search') |
346 # XXX not handled : transform/aggregat function, join on multiple users... |
351 # XXX not handled : transform/aggregat function, join on multiple users... |
347 assert len(union.children) == 1, 'union not supported' |
352 assert len(union.children) == 1, 'union not supported' |
348 rqlst = union.children[0] |
353 rqlst = union.children[0] |
349 assert not rqlst.with_, 'subquery not supported' |
354 assert not rqlst.with_, 'subquery not supported' |
350 rqlkey = rqlst.as_string(kwargs=args) |
355 rqlkey = rqlst.as_string(kwargs=args) |
492 #conn.sasl_interactive_bind_s('', sasl.gssapi()) |
497 #conn.sasl_interactive_bind_s('', sasl.gssapi()) |
493 |
498 |
494 def _search(self, session, base, scope, |
499 def _search(self, session, base, scope, |
495 searchstr='(objectClass=*)', attrs=()): |
500 searchstr='(objectClass=*)', attrs=()): |
496 """make an ldap query""" |
501 """make an ldap query""" |
|
502 self.info('ldap search %s %s %s %s %s', self.uri, base, scope, searchstr, list(attrs)) |
497 cnx = session.pool.connection(self.uri).cnx |
503 cnx = session.pool.connection(self.uri).cnx |
498 try: |
504 try: |
499 res = cnx.search_s(base, scope, searchstr, attrs) |
505 res = cnx.search_s(base, scope, searchstr, attrs) |
500 except ldap.PARTIAL_RESULTS: |
506 except ldap.PARTIAL_RESULTS: |
501 res = cnx.result(all=0)[1] |
507 res = cnx.result(all=0)[1] |
502 except ldap.NO_SUCH_OBJECT: |
508 except ldap.NO_SUCH_OBJECT: |
|
509 self.info('ldap NO SUCH OBJECT') |
503 eid = self.extid2eid(base, 'CWUser', session, insert=False) |
510 eid = self.extid2eid(base, 'CWUser', session, insert=False) |
504 if eid: |
511 if eid: |
505 self.warning('deleting ldap user with eid %s and dn %s', |
512 self.warning('deleting ldap user with eid %s and dn %s', |
506 eid, base) |
513 eid, base) |
507 entity = session.entity_from_eid(eid, 'CWUser') |
514 entity = session.entity_from_eid(eid, 'CWUser') |
539 rec_dict[key] = value = value[0] |
546 rec_dict[key] = value = value[0] |
540 rec_dict['dn'] = rec_dn |
547 rec_dict['dn'] = rec_dn |
541 self._cache[rec_dn] = rec_dict |
548 self._cache[rec_dn] = rec_dict |
542 result.append(rec_dict) |
549 result.append(rec_dict) |
543 #print '--->', result |
550 #print '--->', result |
|
551 self.info('ldap built results %s', result) |
544 return result |
552 return result |
545 |
553 |
546 def before_entity_insertion(self, session, lid, etype, eid): |
554 def before_entity_insertion(self, session, lid, etype, eid): |
547 """called by the repository when an eid has been attributed for an |
555 """called by the repository when an eid has been attributed for an |
548 entity stored here but the entity has not been inserted in the system |
556 entity stored here but the entity has not been inserted in the system |
549 table yet. |
557 table yet. |
550 |
558 |
551 This method must return the an Entity instance representation of this |
559 This method must return the an Entity instance representation of this |
552 entity. |
560 entity. |
553 """ |
561 """ |
|
562 self.info('ldap before entity insertion') |
554 entity = super(LDAPUserSource, self).before_entity_insertion(session, lid, etype, eid) |
563 entity = super(LDAPUserSource, self).before_entity_insertion(session, lid, etype, eid) |
555 res = self._search(session, lid, BASE)[0] |
564 res = self._search(session, lid, BASE)[0] |
556 for attr in entity.e_schema.indexable_attributes(): |
565 for attr in entity.e_schema.indexable_attributes(): |
557 entity[attr] = res[self.user_rev_attrs[attr]] |
566 entity[attr] = res[self.user_rev_attrs[attr]] |
558 return entity |
567 return entity |
559 |
568 |
560 def after_entity_insertion(self, session, dn, entity): |
569 def after_entity_insertion(self, session, dn, entity): |
561 """called by the repository after an entity stored here has been |
570 """called by the repository after an entity stored here has been |
562 inserted in the system table. |
571 inserted in the system table. |
563 """ |
572 """ |
|
573 self.info('ldap after entity insertion') |
564 super(LDAPUserSource, self).after_entity_insertion(session, dn, entity) |
574 super(LDAPUserSource, self).after_entity_insertion(session, dn, entity) |
565 for group in self.user_default_groups: |
575 for group in self.user_default_groups: |
566 session.execute('SET X in_group G WHERE X eid %(x)s, G name %(group)s', |
576 session.execute('SET X in_group G WHERE X eid %(x)s, G name %(group)s', |
567 {'x': entity.eid, 'group': group}) |
577 {'x': entity.eid, 'group': group}) |
568 # search for existant email first |
578 # search for existant email first |