class LDAPPrincipals(OdictStorage): principal_attrmap = default(None) principal_attraliaser = default(None) @extend def __init__(self, props, cfg): context = LDAPNode(name=cfg.baseDN, props=props) context.search_filter = cfg.queryFilter context.search_scope = int(cfg.scope) context.child_defaults = dict() context.child_defaults['objectClass'] = cfg.objectClasses if hasattr(cfg, 'defaults'): context.child_defaults.update(cfg.defaults) for oc in cfg.objectClasses: for key, val in creation_defaults.get(oc, dict()).items(): if not key in context.child_defaults: context.child_defaults[key] = val # XXX: make these attrs public context._key_attr = cfg.attrmap['id'] context._rdn_attr = cfg.attrmap['rdn'] #if cfg.member_relation: # context.search_relation = cfg.member_relation context._seckey_attrs = ('dn',) if cfg.attrmap.get('login') \ and cfg.attrmap['login'] != cfg.attrmap['id']: context._seckey_attrs += (cfg.attrmap['login'],) context._load_keys() self.expiresAttr = getattr(cfg, 'expiresAttr', None) self.expiresUnit = getattr(cfg, 'expiresUnit', None) self.principal_attrmap = cfg.attrmap self.principal_attraliaser = DictAliaser(cfg.attrmap, cfg.strict) self.context = context @default def idbydn(self, dn, strict=False): """Return a principal's id for a given dn. Raise KeyError if not enlisted. """ self.context.keys() idsbydn = self.context._seckeys['dn'] try: return idsbydn[dn] except KeyError: # It's possible that we got a different string resulting # in the same DN, as every components can have individual # comparison rules (see also node.ext.ldap._node.LDAPNode.DN). # We leave the job to LDAP and try again with the resulting DN. # # This was introduced because a customer has group # member attributes where the DN string differs. # # This would not be necessary, if an LDAP directory # is consistent, i.e does not use different strings to # talk about the same. # # Normalization of DN in python would also be a # solution, but requires implementation of all comparison # rules defined in schemas. Maybe we can retrieve them # from LDAP. if strict: raise KeyError(dn) search = self.context.ldap_session.search try: dn = search(baseDN=dn)[0][0] except ldap.NO_SUCH_OBJECT: raise KeyError(dn) return idsbydn[dn] @extend @property def ids(self): return self.context.keys() @default @locktree def __delitem__(self, key): key = decode_utf8(key) del self.context[key] try: del self.storage[key] except KeyError: pass @default @locktree def __getitem__(self, key): key = decode_utf8(key) try: return self.storage[key] except KeyError: principal = self.principal_factory( self.context[key], attraliaser=self.principal_attraliaser) principal.__name__ = self.context[key].name principal.__parent__ = self self.storage[key] = principal return principal @default @locktree def __iter__(self): return self.context.__iter__() @default @locktree def __setitem__(self, name, vessel): """XXX: mechanism for defining a target container if search scope is SUBTREE """ try: attrs = vessel.attrs except AttributeError: raise ValueError(u"no attributes found, cannot convert.") if name in self: raise KeyError(u"Key already exists: '%s'." % (name,)) nextvessel = AttributedNode() nextvessel.__name__ = name nextvessel.attribute_access_for_attrs = False principal = self.principal_factory( nextvessel, attraliaser=self.principal_attraliaser) principal.__name__ = name principal.__parent__ = self # XXX: cache for key, val in attrs.iteritems(): principal.attrs[key] = val self.context[name] = nextvessel @default @property def changed(self): return self.context.changed @default @locktree def invalidate(self, key=None): """Invalidate LDAPPrincipals. """ key = decode_utf8(key) self.context.invalidate(key) if key is None: self.storage.clear() return try: del self.storage[key] except KeyError: pass @default @locktree def __call__(self): self.context() @default def _alias_dict(self, dct): # XXX: seem to be not reached at all #if dct is None: # return None # XXX: this code does not work if multiple keys map to same value #alias = self.principal_attraliaser.alias #aliased_dct = dict( # [(alias(key), val) for key, val in dct.iteritems()]) #return aliased_dct # XXX: generalization in aliaser needed ret = dict() for key, val in self.principal_attraliaser.iteritems(): for k, v in dct.iteritems(): if val == k: ret[key] = v return ret @default def _unalias_list(self, lst): if lst is None: return None unalias = self.principal_attraliaser.unalias return [unalias(x) for x in lst] @default def _unalias_dict(self, dct): if dct is None: return None unalias = self.principal_attraliaser.unalias unaliased_dct = dict( [(unalias(key), val) for key, val in dct.iteritems()]) return unaliased_dct @default def search(self, criteria=None, attrlist=None, exact_match=False, or_search=False): results = self.context.search( criteria=self._unalias_dict(criteria), attrlist=self._unalias_list(attrlist), exact_match=exact_match, or_search=or_search) if attrlist is None: return results aliased_results = \ [(uid, self._alias_dict(attrs)) for uid, attrs in results] return aliased_results @default def search_paged(self, criteria=None, attrlist=None, exact_match=False, or_search=False, page_size=None, cookie=None, only_values=False): results, cookie = self.context.search_paged( criteria=self._unalias_dict(criteria), attrlist=self._unalias_list(attrlist), exact_match=exact_match, or_search=or_search, page_size=page_size, only_values=only_values, cookie=cookie) if attrlist is None: return results, cookie aliased_results = \ [(uid, self._alias_dict(attrs)) for uid, attrs in results] return aliased_results, cookie @default @locktree def create(self, pid, **kw): vessel = AttributedNode() for k, v in kw.items(): vessel.attrs[k] = v self[pid] = vessel return self[pid]
class Principals(AbstractNode): """Turn a nodespace into a source of principals XXX: Should be based on a LazyNode to cache principal objects """ principals_factory = None def __init__(self, context, principal_attrmap=None): self.context = context # XXX: it seems currently an aliaser is needed any way, is that good? self.principal_attrmap = principal_attrmap self.principal_attraliaser = DictAliaser(principal_attrmap) @property def __name__(self): return self.context.__name__ # principals have ids @property def ids(self): return self.context.keys def __delitem__(self, key): del self.context[key] def __getitem__(self, key): # XXX: should use lazynodes caching, for now: # users['foo'] is not users['foo'] principal = self.principal_factory(self.context[key], attraliaser=self.principal_attraliaser) principal.__name__ = self.context[key].__name__ principal.__parent__ = self return principal def __iter__(self): return self.context.__iter__() def __setitem__(self, name, vessel): try: attrs = vessel.nodespaces["__attrs__"] except KeyError: raise ValueError(u"Attributes need to be set.") nextvessel = AttributedNode() nextvessel.__name__ = name nextvessel.attribute_access_for_attrs = False principal = self.principal_factory(nextvessel, attraliaser=self.principal_attraliaser) principal.__name__ = name principal.__parent__ = self # XXX: we could cache here, given that we are based on a LazyNode or # similar for key, val in attrs.iteritems(): principal.attrs[key] = val self.context[name] = nextvessel def _alias_dict(self, dct): if dct is None: return None # this code does not work if multiple keys map to same value # alias = self.principal_attraliaser.alias # aliased_dct = dict( # [(alias(key), val) for key, val in dct.iteritems()] # ) # return aliased_dct # XXX: maybe some generalization in aliaser needed ret = dict() for key, val in self.principal_attraliaser.iteritems(): for k, v in dct.iteritems(): if val == k: ret[key] = v return ret def _unalias_list(self, lst): if lst is None: return None unalias = self.principal_attraliaser.unalias return [unalias(x) for x in lst] def _unalias_dict(self, dct): if dct is None: return None unalias = self.principal_attraliaser.unalias unaliased_dct = dict([(unalias(key), val) for key, val in dct.iteritems()]) return unaliased_dct def search(self, criteria=None, attrlist=None, exact_match=False, or_search=False): # XXX: stripped down for now, compare to LDAPNode.search # XXX: are single values always lists in results? # is this what we want? results = self.context.search( criteria=self._unalias_dict(criteria), attrlist=self._unalias_list(attrlist), exact_match=exact_match, or_search=or_search, ) if attrlist is None: return results aliased_results = [(id, self._alias_dict(attrs)) for id, attrs in results] return aliased_results