示例#1
0
 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)
示例#2
0
 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)
示例#3
0
 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')
示例#4
0
 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')
示例#5
0
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()
示例#6
0
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()
示例#7
0
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)
示例#8
0
 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)
示例#9
0
 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)
示例#10
0
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
示例#11
0
 class Behavior1(Behavior):
     N = finalize('Behavior1')
示例#12
0
 class Behavior2(Behavior):
     K = finalize('Behavior2')
     L = override('Behavior2')
示例#13
0
 class Behavior2(Behavior):
     M = finalize('Behavior2')
示例#14
0
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
示例#15
0
 class Behavior2(Behavior):
     P = finalize(False)
示例#16
0
 class Behavior1(Behavior):
     O = finalize(False)
示例#17
0
 class Behavior3(Behavior):
     Q = finalize(False)
示例#18
0
 class Behavior4(Behavior):
     Q = finalize(True)
示例#19
0
 class Behavior2(Behavior):
     K = finalize('Behavior2')
     L = default('Behavior2')
示例#20
0
 class Behavior1(Behavior):
     K = override('Behavior1')
     L = finalize('Behavior1')
示例#21
0
            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):
示例#22
0
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
示例#23
0
 class Behavior1(Behavior):
     K = default('Behavior1')
     L = finalize('Behavior1')