def test_finalize(self): # First override wins against following equal overrides and arbitrary # defaults fin1 = finalize(1) self.assertTrue(fin1 + fin1 is fin1) self.assertTrue(fin1 + finalize(1) is fin1) self.assertTrue(fin1 + default(2) is fin1) self.assertTrue(fin1 + override(2) is fin1) # Two unequal finalize collide err = None try: fin1 + finalize(2) except PlumbingCollision as e: err = e finally: self.assertEqual(err.left.__class__.__name__, 'finalize') self.assertEqual(err.left.payload, 1) self.assertEqual(err.right.__class__.__name__, 'finalize') self.assertEqual(err.right.payload, 2) # Everything except default/override collides try: fin1 + Instruction(1) except PlumbingCollision as e: err = e finally: self.assertEqual(err.left.__class__.__name__, 'finalize') self.assertEqual(err.left.payload, 1) self.assertEqual(err.right.__class__.__name__, 'Instruction') self.assertEqual(err.right.payload, 1)
def test_default(self): # First default wins from left to right def1 = default(1) self.assertTrue(def1 + def1 is def1) def2 = default(2) self.assertTrue(def1 + def2 is def1) self.assertTrue(def2 + def1 is def2) # Override wins over default ext3 = override(3) self.assertTrue(def1 + ext3 is ext3) # Finalize wins over default fin4 = finalize(4) self.assertTrue(def1 + fin4 is fin4) # Adding with something else than default/override, raises # ``PlumbingCollision`` err = None try: def1 + Instruction('foo') except PlumbingCollision as e: err = e finally: self.assertEqual(err.left.__class__.__name__, 'default') self.assertEqual(err.left.payload, 1) self.assertEqual(err.right.__class__.__name__, 'Instruction') self.assertEqual(err.right.payload, 'foo')
class AliasedPrincipal(Behavior): @override def __init__(self, context, attraliaser): self.context = context self.attraliaser = attraliaser @default def principal_attributes_factory(self, name=None, parent=None): aliased_attrs = PrincipalAliasedAttributes(self.context.attrs, self.attraliaser) return aliased_attrs attributes_factory = finalize(principal_attributes_factory) @default @locktree def __call__(self): # add object classes from creation defaults. if missing. # happens if object classes are added after principals were already # created with another set of default object classes or if editing # existing principals from a database not created with this # API/configuration. ocs = self.context.attrs['objectClass'] ocs = [ocs] if isinstance(ocs, six.text_type) else ocs ocsc = len(ocs) for oc in self.parent.context.child_defaults['objectClass']: if oc not in ocs: ocs.append(oc) # reset object classes only if changed to avoid unnecessary write # operations to LDAP backend if ocsc != len(ocs): self.context.attrs['objectClass'] = ocs # finally persist self.context()
class AliasedPrincipal(Behavior): @override def __init__(self, context, attraliaser): self.context = context self.attraliaser = attraliaser @default def principal_attributes_factory(self, name=None, parent=None): aliased_attrs = PrincipalAliasedAttributes(self.context.attrs, self.attraliaser) return aliased_attrs attributes_factory = finalize(principal_attributes_factory) @default @locktree def __call__(self): self.context()
class Attributes(Behavior): attribute_access_for_attrs = default(False) attributes_factory = default(NodeAttributes) @finalize @property def attrs(self): try: attrs = self.nodespaces['__attrs__'] except KeyError: attrs = self.nodespaces['__attrs__'] = \ self.attributes_factory(name='__attrs__', parent=self) if self.attribute_access_for_attrs: return AttributeAccess(attrs) return attrs # BBB attributes = finalize(attrs)
def test_override(self): # First override wins against following equal overrides and arbitrary # defaults ext1 = override(1) self.assertTrue(ext1 + ext1 is ext1) self.assertTrue(ext1 + override(1) is ext1) self.assertTrue(ext1 + override(2) is ext1) self.assertTrue(ext1 + default(2) is ext1) fin3 = finalize(3) self.assertTrue(ext1 + fin3 is fin3) # Everything except default/override collides err = None try: ext1 + Instruction(1) except PlumbingCollision as e: err = e finally: self.assertEqual(err.left.__class__.__name__, 'override') self.assertEqual(err.left.payload, 1) self.assertEqual(err.right.__class__.__name__, 'Instruction') self.assertEqual(err.right.payload, 1)
class UUIDAsName(UUIDAware): def _get_name(self): return str(self.uuid) def _set_name(self, name): pass __name__ = finalize(property(_get_name, _set_name)) @finalize def set_uuid_for(self, node, override=False, recursiv=False): if IUUIDAware.providedBy(node): if override or not node.uuid: node.uuid = uuid.uuid4() if recursiv: for k, v in node.items(): self.set_uuid_for(v, override, recursiv) if IUUIDAware.providedBy(v) and override: if IOrdered.providedBy(v): # XXX: improve node.storage.alter_key(k, v.name) else: del node[k] node[v.name] = v
class Behavior1(Behavior): N = finalize('Behavior1')
class Behavior2(Behavior): K = finalize('Behavior2') L = override('Behavior2')
class Behavior2(Behavior): M = finalize('Behavior2')
class LDAPStorage(OdictStorage): """ XXX: only use ``self.storage`` to store real values, ``self._keys` should only contain ``True`` or ``False`` """ attributes_factory = finalize(LDAPNodeAttributes) @finalize def __init__(self, name=None, props=None): """LDAP Node expects ``name`` and ``props`` arguments for the root LDAP Node or nothing for children. name Initial base DN for the root LDAP Node. props ``node.ext.ldap.LDAPProps`` object. """ if (name and not props) or (props and not name): raise ValueError(u"Wrong initialization.") if name and not isinstance(name, unicode): name = name.decode(CHARACTER_ENCODING) self.__name__ = name self.__parent__ = None self._ldap_session = None self._changed = False self._action = None self._seckey_attrs = None self._reload = False self._init_keys() self._multivalued_attributes = {} self._binary_attributes = {} if props: # only at root node self._ldap_session = LDAPSession(props) self._ldap_session.baseDN = self.DN self._ldap_schema_info = LDAPSchemaInfo(props) self._multivalued_attributes = props.multivalued_attributes self._binary_attributes = props.binary_attributes # XXX: make them public self._key_attr = 'rdn' self._rdn_attr = None # search related defaults self.search_scope = ONELEVEL self.search_filter = None self.search_criteria = None self.search_relation = None # creation related default self.child_factory = LDAPNode self.child_defaults = None @finalize def __getitem__(self, key): """Here nodes are created for keys, if they do not exist already """ if isinstance(key, str): key = decode(key) if not self._keys: self._load_keys() if self._keys[key]: return self.storage[key] val = self.child_factory() val._ldap_session = self.ldap_session val.__name__ = key val.__parent__ = self self.storage[key] = val self._keys[key] = True return val @finalize def __setitem__(self, key, val): if isinstance(key, str): key = decode(key) # XXX: scope is search scope, why not add children? # feels like trying to add security the wrong place #if self.search_scope is BASE: # raise NotImplementedError( # u"Seriously? Adding with scope == BASE?") if self._key_attr != 'rdn' and self._rdn_attr is None: raise RuntimeError( u"Adding with key != rdn needs _rdn_attr to be set.") if not isinstance(val, LDAPNode): # create one from whatever we got val = self._create_suitable_node(val) # At this point we need to have an LDAPNode as val if self._key_attr != 'rdn': val.attrs[self._key_attr] = key if val.attrs.get(self._rdn_attr) is None: raise ValueError( u"'%s' needed in node attributes for rdn." % \ (self._rdn_attr,)) else: # set rdn attr if not present rdn, rdn_val = key.split('=') if not rdn in val.attrs: val._notify_suppress = True val.attrs[rdn] = rdn_val val._notify_suppress = False val.__name__ = key val.__parent__ = self val._ldap_session = self.ldap_session if self._keys is None: self._load_keys() try: # a value with key is already in the directory self._keys[key] except KeyError: # the value is not yet in the directory val._action = ACTION_ADD val.changed = True self.changed = True self.storage[key] = val self._keys[key] = True if self._key_attr == 'rdn': rdn = key else: rdn = '%s=%s' % (self._rdn_attr, val.attrs[self._rdn_attr]) self._child_dns[key] = ','.join((rdn, self.DN)) if self.child_defaults: for k, v in self.child_defaults.items(): if k in val.attrs: # skip default if attribute already exists continue if callable(v): v = v(self, key) val.attrs[k] = v @finalize def __delitem__(self, key): """Do not delete immediately. Just mark LDAPNode to be deleted and remove key from self._keys. """ if isinstance(key, str): key = decode(key) val = self[key] val._action = ACTION_DELETE # this will also trigger the changed chain val.changed = True del self._keys[key] try: self._deleted.append(val) except AttributeError: self._deleted = list() self._deleted.append(val) @finalize def __iter__(self): """This is where keys are retrieved from ldap """ if self.name is None: return iter(list()) if self._reload: self._init_keys() if self._keys is None and self._action != ACTION_ADD: self._load_keys() try: return self._keys.__iter__() except Exception: return iter(list()) @finalize def __call__(self): if self.changed and self._action is not None: if self._action == ACTION_ADD: self._ldap_add() elif self._action == ACTION_MODIFY: self._ldap_modify() elif self._action == ACTION_DELETE: self._ldap_delete() try: self.nodespaces['__attrs__'].changed = False except KeyError: pass self.changed = False self._action = None if self._keys is None: return for node in self.storage.values() + getattr(self, '_deleted', []): if node.changed: node() @finalize def __repr__(self): # Doctest fails if we output utf-8 try: dn = self.DN.encode('ascii', 'replace') or '(dn not set)' except KeyError: dn = '(dn not available yet)' if self.parent is None: return "<%s - %s>" % (dn, self.changed) name = self.name.encode('ascii', 'replace') return "<%s:%s - %s>" % (dn, name, self.changed) __str__ = finalize(__repr__) @finalize @property def noderepr(self): return repr(self) @default @property def ldap_session(self): return self._ldap_session # This is really ldap @default @property def DN(self): """ ATTENTION: For one and the same entry, ldap will always return the same DN. However, depending on the individual syntax definition of the DN's components there might be a multitude of strings that equal the same DN, e.g. for cn: 'cn=foo bar' == 'cn=foo bar' -> True """ if self.parent is not None: return self.parent.child_dn(self.name) elif self.name is not None: # We should not have a name if we are not a root node. return decode(self.name) else: return u'' @default @property def rdn_attr(self): """XXX: only tested on LDAPNode, might not work in UGM """ return self.name and self.name.split('=')[0] or None def _get_changed(self): return self._changed def _set_changed(self, value): """Set/Unset the changed flag Set: - if self.attrs are changed (attrs set us) - if a child is changed / added / removed (child sets us) Unset: - if neither a child nor the own attrs are changed (attrs or child tries to unset us) Anyway: - tell our parent in case we changed state """ # only get active, if new state differs from old state oldval = self._changed if value is oldval: return if value: # Setting is easy self._changed = True else: # Unsetting needs more checks try: if self.nodespaces['__attrs__'].changed: return except KeyError: # No attributes loaded, yet - cannot be changed pass childs = getattr(self, '_deleted', []) if self._keys is not None: childs.extend(self.storage.values()) for child in childs: if child.changed: return self._changed = False # And propagate to parent if self._changed is not oldval and self.parent is not None: self.parent.changed = self._changed changed = default(property(_get_changed, _set_changed)) # This is really ldap @default def child_dn(self, key): return decode(self._child_dns[key]) @default @debug def search(self, queryFilter=None, criteria=None, attrlist=None, relation=None, relation_node=None, exact_match=False, or_search=False, or_keys=None, or_values=None, page_size=None, cookie=None): attrset = set(attrlist or []) attrset.discard('dn') # fetch also the key attribute if not self._key_attr == 'rdn': attrset.add(self._key_attr) # Create queryFilter from all filter definitions # filter for this search ANDed with the default filters defined on self search_filter = LDAPFilter(queryFilter) search_filter &= LDAPDictFilter(criteria, or_search=or_search, or_keys=or_keys, or_values=or_values, ) _filter = LDAPFilter(self.search_filter) _filter &= LDAPDictFilter(self.search_criteria) _filter &= search_filter # relation filters if relation_node is None: relation_node = self relations = [relation, self.search_relation] for relation in relations: if not relation: continue if isinstance(relation, LDAPRelationFilter): _filter &= relation else: _filter &= LDAPRelationFilter(relation_node, relation) # XXX: Is it really good to filter out entries without the key attr or # would it be better to fail? (see also __iter__ secondary key) if self._key_attr != 'rdn' and self._key_attr not in _filter: _filter &= '(%s=*)' % (self._key_attr,) # perform the backend search matches = self.ldap_session.search( str(_filter), self.search_scope, baseDN=encode(self.DN), force_reload=self._reload, attrlist=list(attrset), page_size=page_size, cookie=cookie, ) if type(matches) is tuple: matches, cookie = matches # XXX: Is ValueError appropriate? # XXX: why do we need to fail at all? shouldn't this be about # substring vs equality match? if exact_match and len(matches) > 1: raise ValueError(u"Exact match asked but result not unique") if exact_match and len(matches) == 0: raise ValueError(u"Exact match asked but result length is zero") # extract key and desired attributes res = [] for dn, attrs in matches: key = self._calculate_key(dn, attrs) if attrlist is not None: resattr = dict() for k, v in attrs.iteritems(): if k in attrlist: if self.attrs.is_binary(k): resattr[decode(k)] = v else: resattr[decode(k)] = decode(v) if 'dn' in attrlist: resattr[u'dn'] = decode(dn) res.append((key, resattr)) else: res.append(key) if cookie is not None: return (res, cookie) return res @default def invalidate(self, key=None): """Invalidate LDAP node. If key is None: - check if self is changed - if changed, raise RuntimeError - reload self.attrs - set self._reload to True. This reloads the keys forcing cache reload as well. If key is given: - if child in self._keys, check if child has changed - if changed, raise RuntimeError - if not changed, remove item from self.storage and set self._keys[key] to False, which forces it to be reloaded. """ if key is None: if self.changed: raise RuntimeError(u"Invalid tree state. Try to invalidate " u"changed node.") self.storage.clear() self.attrs.load() self._init_keys() self._reload = True return try: child = self.storage[key] if child.changed: raise RuntimeError( u"Invalid tree state. Try to invalidate " u"changed child node '%s'." % (key,)) del self.storage[key] self._keys[key] = False except KeyError: pass #@finalize #def sort(self, cmp=None, key=None, reverse=False): # # XXX: a sort working only on the keys could work without wakeup --> # # sortonkeys() # # first wake up all entries # dummy = self.items() # if not dummy: # return # # second sort them # import pdb;pdb.set_trace() # self._keys.sort(cmp=cmp, key=key, reverse=reverse) @default def _init_keys(self): # the _keys is None or an odict. # if an odict, the value is either None or the value # None means, the value wasnt loaded self._keys = None self._seckeys = None self._child_dns = None @default def _load_keys(self): self._keys = odict() self._child_dns = {} attrlist = ['dn'] if self._seckey_attrs: self._seckeys = dict() attrlist.extend(self._seckey_attrs) for key, attrs in self.search(attrlist=attrlist): try: self._keys[key] except KeyError: self._keys[key] = False self._child_dns[key] = attrs['dn'] for seckey_attr, seckey in \ self._calculate_seckeys(attrs).items(): try: self._seckeys[seckey_attr] except KeyError: self._seckeys[seckey_attr] = {} try: self._seckeys[seckey_attr][seckey] except KeyError: self._seckeys[seckey_attr][seckey] = key else: # XXX: ever reached? raise KeyError( u"Secondary key not unique: %s='%s'." % \ (seckey_attr, seckey)) else: raise RuntimeError(u"Key not unique: %s='%s'." % \ (self._key_attr, key)) # a keymapper @default def _calculate_key(self, dn, attrs): if self._key_attr == 'rdn': # explode_dn is ldap world key = explode_dn(encode(dn))[0] else: key = attrs[self._key_attr] if isinstance(key, list): if len(key) != 1: msg = u"Expected one value for '%s' " % (self._key_attr,) msg += u"not %s: '%s'." % (len(key), key) raise KeyError(msg) key = key[0] return decode(key) # secondary keys @default def _calculate_seckeys(self, attrs): if not self._seckey_attrs: return {} seckeys = {} for seckey_attr in self._seckey_attrs: try: seckey = attrs[seckey_attr] except KeyError: # no sec key found, skip continue else: if isinstance(seckey, list): if len(seckey) != 1: msg = u"Expected one value for '%s' " % (seckey_attr,) msg += "not %s: '%s'." % (len(seckey), seckey) raise KeyError(msg) seckey = seckey[0] seckeys[seckey_attr] = seckey return seckeys @default def _create_suitable_node(self, vessel): try: attrs = vessel.attrs except AttributeError: raise ValueError(u"No attributes found on vessel, cannot convert") node = LDAPNode() for key, val in attrs.iteritems(): node.attrs[key] = val return node @default def _ldap_add(self): """adds self to the ldap directory. """ attrs = {} for key, value in self.attrs.items(): if not self.attrs.is_binary(key): value = encode(value) attrs[encode(key)] = value self.ldap_session.add(encode(self.DN), attrs) @default def _ldap_modify(self): """modifies attributs of self on the ldap directory. """ modlist = list() orgin = self.attributes_factory(name='__attrs__', parent=self) for key in orgin: # MOD_DELETE if not key in self.attrs: moddef = (MOD_DELETE, encode(key), None) modlist.append(moddef) for key in self.attrs: # MOD_ADD value = self.attrs[key] if not self.attrs.is_binary(key): value = encode(value) if key not in orgin: moddef = (MOD_ADD, encode(key), value) modlist.append(moddef) # MOD_REPLACE elif self.attrs[key] != orgin[key]: moddef = (MOD_REPLACE, encode(key), value) modlist.append(moddef) if modlist: self.ldap_session.modify(encode(self.DN), modlist) @default def _ldap_delete(self): """delete self from the ldap-directory. """ self.parent._keys[self.name] = False del self.parent.storage[self.name] del self.parent._keys[self.name] self.ldap_session.delete(encode(self.DN)) @default @property def schema_info(self): if self.parent is not None: return self.root._ldap_schema_info return self._ldap_schema_info
class Behavior2(Behavior): P = finalize(False)
class Behavior1(Behavior): O = finalize(False)
class Behavior3(Behavior): Q = finalize(False)
class Behavior4(Behavior): Q = finalize(True)
class Behavior2(Behavior): K = finalize('Behavior2') L = default('Behavior2')
class Behavior1(Behavior): K = override('Behavior1') L = finalize('Behavior1')
if node is not None and node.changed: node() @finalize def __repr__(self): # Doctest fails if we output utf-8 try: dn = self.DN.encode('ascii', 'replace') or '(dn not set)' except KeyError, e: dn = '(dn not available yet)' if self.parent is None: return "<%s - %s>" % (dn, self.changed) name = self.name.encode('ascii', 'replace') return "<%s:%s - %s>" % (dn, name, self.changed) __str__ = finalize(__repr__) @finalize @property def noderepr(self): return repr(self) @default @property def ldap_session(self): return self._ldap_session # This is really ldap @default @property def DN(self):
class LDAPStorage(OdictStorage): attributes_factory = finalize(LDAPNodeAttributes) @finalize def __init__(self, name=None, props=None): """LDAP Node expects ``name`` and ``props`` arguments for the root LDAP Node or nothing for children. :param name: Initial base DN for the root LDAP Node. :param props: ``node.ext.ldap.LDAPProps`` instance. """ if (name and not props) or (props and not name): raise ValueError(u"Wrong initialization.") if name and not isinstance(name, six.text_type): name = name.decode(CHARACTER_ENCODING) self.__name__ = name self.__parent__ = None self._dn = None self._ldap_session = None self._changed = False self._action = None self._added_children = set() self._modified_children = set() self._deleted_children = set() self._reload = False self._multivalued_attributes = set() self._binary_attributes = set() self._page_size = 1000 if props: # only at root node self._ldap_session = LDAPSession(props) self._ldap_session.baseDN = self.DN self._ldap_schema_info = LDAPSchemaInfo(props) self._multivalued_attributes = props.multivalued_attributes self._binary_attributes = props.binary_attributes self._page_size = props.page_size # search related defaults self.search_scope = ONELEVEL self.search_filter = None self.search_criteria = None self.search_relation = None # creation related default self.child_factory = LDAPNode self.child_defaults = None @finalize def __getitem__(self, key): # nodes are created for keys, if they do not already exist in memory key = ensure_text(key) try: return self.storage[key] except KeyError: val = self.child_factory() val.__name__ = key val.__parent__ = self try: res = self.ldap_session.search( scope=BASE, baseDN=val.DN, attrlist=[''] # no need for attrs ) # remember DN val._dn = res[0][0] val._ldap_session = self.ldap_session self.storage[key] = val return val except (NO_SUCH_OBJECT, INVALID_DN_SYNTAX): raise KeyError(key) @finalize def __setitem__(self, key, val): key = ensure_text(key) if not isinstance(val, LDAPNode): # create one from whatever we got # XXX: raise KeyError instead of trying to create node val = self._create_suitable_node(val) val.__name__ = key val.__parent__ = self val._dn = self.child_dn(key) val._ldap_session = self.ldap_session try: self.ldap_session.search( scope=BASE, baseDN=val.DN, attrlist=[''] # no need for attrs ) except (NO_SUCH_OBJECT, INVALID_DN_SYNTAX): # the value is not yet in the directory val._action = ACTION_ADD val.changed = True self.changed = True self._added_children.add(key) rdn, rdn_val = key.split('=') if rdn not in val.attrs: val._notify_suppress = True val.attrs[rdn] = rdn_val val._notify_suppress = False self.storage[key] = val if self.child_defaults: for k, v in self.child_defaults.items(): if k in val.attrs: # skip default if attribute already exists continue if callable(v): v = v(self, key) val.attrs[k] = v @finalize def __delitem__(self, key): # do not delete immediately. Just mark LDAPNode to be deleted. key = ensure_text(key) # value not persistent yet, remove from storage and add list if key in self._added_children: del self.storage[key] self._added_children.remove(key) self.changed = False return val = self[key] val._action = ACTION_DELETE # this will also trigger the changed chain val.changed = True self._deleted_children.add(key) @finalize def __iter__(self): if self.name is None: return cookie = '' while True: try: res = self.ldap_session.search( scope=ONELEVEL, baseDN=self.DN, attrlist=[''], page_size=self._page_size, cookie=cookie ) except NO_SUCH_OBJECT: # happens if not persisted yet res = list() if isinstance(res, tuple): res, cookie = res for dn, _ in res: key = ensure_text(explode_dn(dn)[0]) # do not yield if node is supposed to be deleted if key not in self._deleted_children: yield key if not cookie: break # also yield keys of children not persisted yet. for key in self._added_children: yield key @finalize def __call__(self): if self.changed and self._action is not None: if self._action == ACTION_ADD: self.parent._added_children.remove(self.name) self._ldap_add() elif self._action == ACTION_MODIFY: if self.parent: self.parent._modified_children.remove(self.name) self._ldap_modify() elif self._action == ACTION_DELETE: self.parent._deleted_children.remove(self.name) self._ldap_delete() try: self.nodespaces['__attrs__'].changed = False except KeyError: pass self.changed = False self._action = None deleted = [self[key] for key in self._deleted_children] for node in self.storage.values() + deleted: if node.changed: node() @finalize def __repr__(self): dn = self.DN or u'(dn not set)' if self.parent is None: ret = u'<{} - {}>'.format(dn, self.changed) else: ret = u'<{}:{} - {}>'.format(dn, self.name, self.changed) return decode(ret.encode('ascii', 'replace')) __str__ = finalize(__repr__) @finalize @property def noderepr(self): return repr(self) @default @property def ldap_session(self): return self._ldap_session @default @property def DN(self): # For one and the same entry, ldap will always return the same DN. # However, depending on the individual syntax definition of the DN's # components there might be a multitude of strings that equal the same # DN, e.g. for cn: # 'cn=foo bar' == 'cn=foo bar' -> True if self.parent: return self.parent.child_dn(self.name) if self.name: # We should not have a name if we are not a root node. return decode(self.name) return u'' @default @property def rdn_attr(self): return self.name and self.name.split('=')[0] or None @property def changed(self): return self._changed @default @changed.setter def changed(self, value): """Set the changed flag. Set: - if self.attrs are changed (attrs set us) - if a child is changed / added / removed (child sets us) Unset: - if neither a child nor the own attrs are changed (attrs or child tries to unset us) Anyway: - tell our parent in case we changed state """ # only get active, if new state differs from old state oldval = self._changed if value is oldval: return # setting is easy if value: self._changed = True # unsetting needs more checks else: # check whether children are added, modified or deleted, cannot # unset changed state if so if len(self._added_children) \ or len(self._modified_children) \ or len(self._deleted_children): return # check whether attributes has changed, cannot unset changed if so try: # access attrs nodespace directly to avoid recursion error if self.nodespaces['__attrs__'].changed: return # No attributes loaded yet, ignore except KeyError: pass # finally unset changed flag self._changed = False # propagate to parent if self._changed is not oldval and self.parent is not None: self.parent.changed = self._changed @default def child_dn(self, key): # return child DN for key if self._dn: return u','.join([decode(key), decode(self._dn)]) return u','.join([decode(key), decode(self.name)]) @default @property def exists(self): try: res = self.ldap_session.search( scope=BASE, baseDN=self.DN, attrlist=[''] ) # this probably never happens if len(res) != 1: # pragma: no cover raise RuntimeError() return True except NO_SUCH_OBJECT: return False @default def node_by_dn(self, dn, strict=False): """Return node from tree by DN. """ root = node = self.root base_dn = root.name if not dn.endswith(base_dn): raise ValueError( u'Invalid DN "{0}" for given base DN "{1}"'.format(dn, base_dn)) dn = dn[:len(dn) - len(base_dn)].strip(',') for rdn in reversed(explode_dn(dn)): try: node = node[rdn] except KeyError: if strict: raise ValueError( u'Tree contains no node by given DN. ' u'Failed at RDN {}'.format(rdn) ) return None return node @default @debug def search(self, queryFilter=None, criteria=None, attrlist=None, relation=None, relation_node=None, exact_match=False, or_search=False, or_keys=None, or_values=None, page_size=None, cookie=None, get_nodes=False): attrset = set(attrlist or []) attrset.discard('dn') attrset.discard('rdn') # Create queryFilter from all filter definitions # filter for this search ANDed with the default filters defined on self search_filter = LDAPFilter(queryFilter) search_filter &= LDAPDictFilter( criteria, or_search=or_search, or_keys=or_keys, or_values=or_values ) _filter = LDAPFilter(self.search_filter) _filter &= LDAPDictFilter(self.search_criteria) _filter &= search_filter # relation filters if relation_node is None: relation_node = self relations = [relation, self.search_relation] for relation in relations: if not relation: continue if isinstance(relation, LDAPRelationFilter): _filter &= relation else: _filter &= LDAPRelationFilter(relation_node, relation) # perform the backend search logger.debug("LDAP search with filter: \n{0}".format(_filter)) matches = self.ldap_session.search( str(_filter), self.search_scope, baseDN=self.DN, force_reload=self._reload, attrlist=list(attrset), page_size=page_size, cookie=cookie ) if type(matches) is tuple: matches, cookie = matches # check exact match if exact_match and len(matches) > 1: raise ValueError(u"Exact match asked but result not unique") if exact_match and len(matches) == 0: raise ValueError(u"Exact match asked but result length is zero") # extract key and desired attributes res = [] for dn, attrs in matches: dn = decode(dn) if attrlist is not None: resattr = dict() for k, v in six.iteritems(attrs): if k in attrlist: # Check binary binary attribute directly from root # data to avoid initing attrs for a simple search. if k in self.root._binary_attributes: resattr[decode(k)] = v else: resattr[decode(k)] = decode(v) if 'dn' in attrlist: resattr[u'dn'] = dn if 'rdn' in attrlist: rdn = explode_dn(dn)[0] resattr[u'rdn'] = decode(rdn) if get_nodes: res.append((self.node_by_dn(dn, strict=True), resattr)) else: res.append((dn, resattr)) else: if get_nodes: res.append(self.node_by_dn(dn, strict=True)) else: res.append(dn) if cookie is not None: return (res, cookie) return res @default def batched_search(self, page_size=None, search_func=None, **kw): """Search generator function which does paging for us. """ if page_size is None: page_size = self.ldap_session._props.page_size if search_func is None: search_func = self.search matches = [] cookie = None kw['page_size'] = page_size while True: kw['cookie'] = cookie matches, cookie = search_func(**kw) for item in matches: yield item if not cookie: break @default def invalidate(self, key=None): """Invalidate LDAP node. If key is None: - check if self is changed - if changed, raise RuntimeError - reload self.attrs - set self._reload to True. This reloads the keys forcing cache reload as well. If key is given: - if changed, raise RuntimeError - if not changed, remove item from self.storage. """ if key is None: if self.changed: raise RuntimeError( u"Invalid tree state. Try to invalidate changed node." ) self.storage.clear() self.attrs.load() # XXX: needs to get unset again somwhere self._reload = True return try: child = self.storage[key] if child.changed: raise RuntimeError( u"Invalid tree state. Try to invalidate " u"changed child node '{}'.".format(key) ) del self.storage[key] except KeyError: pass @default def _create_suitable_node(self, vessel): # convert vessel node to LDAPNode try: attrs = vessel.attrs except AttributeError: raise ValueError(u"No attributes found on vessel, cannot convert") node = LDAPNode() for key, val in six.iteritems(attrs): node.attrs[key] = val return node @default def _ldap_add(self): # adds self to the ldap directory. attrs = {} for key, value in self.attrs.items(): if not self.attrs.is_binary(key): value = encode(value) attrs[key] = value self.ldap_session.add(self.DN, attrs) @default def _ldap_modify(self): # modifies attributs of self on the ldap directory. modlist = list() orgin = self.attributes_factory(name='__attrs__', parent=self) for key in orgin: # MOD_DELETE if key not in self.attrs: moddef = (MOD_DELETE, key, None) modlist.append(moddef) for key in self.attrs: # MOD_ADD value = self.attrs[key] if not self.attrs.is_binary(key): value = encode(value) if key not in orgin: moddef = (MOD_ADD, key, value) modlist.append(moddef) # MOD_REPLACE elif self.attrs[key] != orgin[key]: moddef = (MOD_REPLACE, key, value) modlist.append(moddef) if modlist: self.ldap_session.modify(self.DN, modlist) @default def _ldap_delete(self): # delete self from the ldap-directory. del self.parent.storage[self.name] self.ldap_session.delete(self.DN) @default @property def schema_info(self): if self.parent is not None: return self.root._ldap_schema_info return self._ldap_schema_info
class Behavior1(Behavior): K = default('Behavior1') L = finalize('Behavior1')