Exemple #1
0
    def __init__(self, name=None, props=None, attrmap=None, child_attrmap=None):
        """LDAP Node expects ``name`` and ``props`` arguments for the root LDAP
        Node or nothing for children. ``attrmap`` is an optional rood node
        argument.
        
        ``name`` 
            Initial base DN for the root LDAP Node.
        
        ``props`` 
            ``bda.ldap.LDAPProperties`` object.

        ``attrmap``
            an optional map of attributes, mapped attributes will be available
            via node.mattrs.


        """
        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(LDAP_CHARACTER_ENCODING)
        self.__name__ = name
        self.__parent__ = None
        self._session = None        
        self._changed = False
        self._action = None
        # 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._seckey_attrs = None
        self._child_dns = {}
        self._reload = False        
        if props:
            self._session = LDAPSession(props)
            self._session.baseDN = self.DN
        super(LDAPNode, self).__init__(name, attrmap)
        self._key_attr = 'rdn'
        self._child_scope = ONELEVEL
        self._child_filter = None
        self._child_criteria = None
        self._child_relation = None
        self._ChildClass = LDAPNode
Exemple #2
0
    def __init__(self, name=None, props=None, attrmap=None, child_attrmap=None):
        """LDAP Node expects ``name`` and ``props`` arguments for the root LDAP
        Node or nothing for children. ``attrmap`` is an optional rood node
        argument.
        
        ``name`` 
            Initial base DN for the root LDAP Node.
        
        ``props`` 
            ``bda.ldap.LDAPProperties`` object.

        ``attrmap``
            an optional map of attributes, mapped attributes will be available
            via node.mattrs.


        """
        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(LDAP_CHARACTER_ENCODING)
        self.__name__ = name
        self.__parent__ = None
        self._session = None        
        self._changed = False
        self._action = None
        self._seckey_attrs = None
        self._reload = False
        self._init_keys()
        if props:
            self._session = LDAPSession(props)
            self._session.baseDN = self.DN
        # XXX: do soemthing about attrmap
        super(LDAPNode, self).__init__(name, index=False)
        self._key_attr = 'rdn'
        self._rdn_attr = None
        self._child_scope = ONELEVEL
        self._child_filter = None
        self._child_criteria = None
        self._child_relation = None
        self._child_objectClasses = None
        self._ChildClass = LDAPNode
        self.attribute_access_for_attrs = False
Exemple #3
0
class LDAPNode(LifecycleNode):
    """An LDAP Node.
    """
    implements(ICallableNode)
    attributes_factory = LDAPNodeAttributes
    
    def __init__(self, name=None, props=None, attrmap=None, child_attrmap=None):
        """LDAP Node expects ``name`` and ``props`` arguments for the root LDAP
        Node or nothing for children. ``attrmap`` is an optional rood node
        argument.
        
        ``name`` 
            Initial base DN for the root LDAP Node.
        
        ``props`` 
            ``bda.ldap.LDAPProperties`` object.

        ``attrmap``
            an optional map of attributes, mapped attributes will be available
            via node.mattrs.


        """
        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(LDAP_CHARACTER_ENCODING)
        self.__name__ = name
        self.__parent__ = None
        self._session = None        
        self._changed = False
        self._action = None
        self._seckey_attrs = None
        self._reload = False
        self._init_keys()
        if props:
            self._session = LDAPSession(props)
            self._session.baseDN = self.DN
        # XXX: do soemthing about attrmap
        super(LDAPNode, self).__init__(name, index=False)
        self._key_attr = 'rdn'
        self._rdn_attr = None
        self._child_scope = ONELEVEL
        self._child_filter = None
        self._child_criteria = None
        self._child_relation = None
        self._child_objectClasses = None
        self._ChildClass = LDAPNode
        self.attribute_access_for_attrs = False

    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
            
    # This is really ldap
    @property
    def DN(self):
        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 self.__name__
        else:
            return u''

    # This is really ldap
    def child_dn(self, key):
        return self._child_dns[key]

    # a keymapper
    def _calculate_key(self, dn, attrs):
        if self._key_attr == 'rdn':
            # explode_dn is ldap world
            key = decode(explode_dn(encode(dn))[0])
        else:
            key = attrs[self._key_attr]
            if isinstance(key, list):
                if len(key) != 1:
                    raise KeyError(u"Expected one value for '%s' "+
                            u"not %s: '%s'." % \
                                    (self._key_attr, len(key), key))
                key = key[0]
        return key

    # secondary keys
    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:
                raise KeyError(
                        u"Secondary key '%s' missing on: %s." % \
                                (seckey_attr, attrs['dn']))
            else:
                if isinstance(seckey, list):
                    if len(seckey) != 1:
                        raise KeyError(u"Expected one value for '%s' "+
                                u"not %s: '%s'." % \
                                        (seckey_attr, len(seckey), seckey))
                    seckey = seckey[0]
                seckeys[seckey_attr] = seckey
        return seckeys

    @debug(['searching'])
    def search(self, queryFilter=None, criteria=None, relation=None,
            attrlist=None, exact_match=False, or_search=False):
        """Returns a list of matching keys.

        All search criteria are additive and will be ``&``ed. ``queryFilter``
        and ``criteria`` further narrow down the search space defined by
        ``self._child_filter``, ``self._child_criteria`` and
        ``self._child_relation``.

        ``queryFilter``
            ldap queryFilter, e.g. ``(objectClass=foo)``
        ``criteria``
            dictionary of attribute value(s) (string or list of string)
        ``relation``
            the nodes we search has a relation to us.  A relation is defined as
            a string of attribute pairs:
            ``<relation> = '<our_attr>:<child_attr>'``.
            The value of these attributes must match for relation to match.
            Multiple pairs can be or-joined with
        ``attrlist``
            Normally a list of keys is returned. By defining attrlist the
            return format will be ``[(key, {attr1: [value1, ...]}), ...]``. To
            get this format without any attributs, i.e. empty dicts in the
            tuples, specify an empty attrlist. In addition to the normal ldap
            attributes you can also the request the dn to be included.
        ``exact_match``
            raise ValueError if not one match, return format is a single key or
            tuple, if attrlist is specified.
        """
        attrset = set(attrlist or [])
        attrset.discard('dn')
        # fetch also the key attribute
        if not self._key_attr == 'rdn':
            attrset.add(self._key_attr)

#        for attr in attrset:
#            if attr in self._cfg.attrmap:
#                attrset.discard(attr)
#                attrset.add(self._cfg.attrmap[attr])

        # create queryFilter from all filter things - needs only to happen just
        # before ldap, could be in the backedn
        # filter for this search ANDed with the basic filter which is always
        # effective
        search_filter = LDAPFilter(queryFilter)
        search_filter &= LDAPDictFilter(criteria, or_search=or_search)
        _filter = LDAPFilter(self._child_filter)
        _filter &= LDAPDictFilter(self._child_criteria)
        _filter &= search_filter

        # 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)
        # configurable?
        if self._key_attr != 'rdn' and self._key_attr not in _filter:
            _filter &= '(%s=*)' % (self._key_attr,)

        # perform the backend search
        matches = self._session.search(_filter.__str__(),
                                       self._child_scope,
                                       baseDN=self.DN,
                                       force_reload=self._reload,
                                       attrlist=list(attrset))
        if exact_match and len(matches) > 1:
            # XXX: Is ValueError appropriate?
            # XXX: why do we need to fail at all? shouldn't this be about
            # substring vs equality match?
            raise ValueError(u"Exact match asked but search not exact")

        # extract key and desired attributes
        res = []
        for dn, attrs in matches:
            key = self._calculate_key(dn, attrs)
            if attrlist is not None:
                resattr = dict([(k,v) for k,v in attrs.iteritems()
                        if k in attrlist])
                if 'dn' in attrlist:
                    resattr['dn'] = dn
                res.append((key, resattr))
            else:
                res.append(key)
        return res

    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] = None
                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:
                        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))

    def __iter__(self):
        """This is where keys are retrieved from ldap
        """
        if self.__name__ is None:
            return
        if self._reload:
            self._init_keys()
        if self._keys is None and self._action != ACTION_ADD:
            self._load_keys()
        try:
            for key in self._keys:
                yield key
        except TypeError:
            # no keys loaded
            pass
    
    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
        self._keys.sort(cmp=cmp, key=key, reverse=reverse)
    
    def __getitem__(self, key):
        """Here nodes are created for keys, iff they do not exist already
        """
        if isinstance(key, str):
            key = decode(key)
        if not self._keys:
            self._load_keys()
        if not key in self._keys:
            raise KeyError(u"Entry not existent: %s" % key)
        if self._keys[key] is not None:
            return super(LDAPNode, self).__getitem__(key)
        val = self._ChildClass()
        val._session = self._session
        # We are suppressing notification, as val is not really added to us,
        # rather, it is activated.
        self._notify_suppress = True
        # XXX: this looks like val is stored twice. Why?
        super(LDAPNode, self).__setitem__(key, val)
        self._notify_suppress = False
        self._keys[key] = val
        return val

    def _create_suitable_node(self, vessel):
        if isinstance(vessel, AttributedNode):
            node = LDAPNode()
            try:
                attrs = vessel.nodespaces['__attrs__']
            except KeyError:
                raise ValueError(u"Attributes need to be set.")
            for key, val in attrs.iteritems():
                node.attrs[key] = val
            return node
        raise ValueError(u"Don't know what to do with '%s', cannot setitem")

    def __setitem__(self, key, val):
        if isinstance(key, str):
            key = decode(key)
        if self._child_scope is not ONELEVEL:
            # XXX: this would require a default location for new entries
            raise NotImplementedError(
                    u"Adding with scope != ONELEVEL not supported.")
        if self._key_attr != 'rdn' and self._rdn_attr is None:
            raise RuntimeError(u"Adding with key != rdn needs _rdn_attr "
                    u"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' and \
                val.attrs.get(self._rdn_attr) is None:
            raise ValueError(u"'%s' needed in node attributes for rdn." % \
                    (self._rdn_attr,))
        val._session = self._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._notify_suppress = True
        super(LDAPNode, self).__setitem__(key, val)
        self._notify_suppress = False
        self._keys[key] = val    
        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_objectClasses:
            current_ocs = val.attrs.get('objectClass', [])
            needed_ocs = self._child_objectClasses
            val.attrs['objectClass'] = [
                    x for x in current_ocs + needed_ocs if x not in current_ocs
                    ]
        if val._action == ACTION_ADD:
            objectEventNotify(self.events['added'](val, newParent=self, 
                                                   newName=key))
    
    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)
    
    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._keys.values() + getattr(self, '_deleted', []):
            if node is not None and node.changed:
                node()
    
    def printtree(self, indent=0):
        print "%s%s" % (indent * ' ', self.noderepr)
        for node in self._node_impl().itervalues(self):
            try:
                node.printtree(indent+2)
            except AttributeError:
                # Non-Node values are just printed
                print "%s%s" % (indent * ' ', node)

    def __repr__(self):
        # XXX: This is mainly used in doctest, I think
        # doctest fails if we output utf-8
        dn = self.DN.encode('ascii', 'replace')
        name = self.__name__.encode('ascii', 'replace')
        if self.__parent__ is None:
            return "<%s - %s>" % (dn, self.changed)
        return "<%s:%s - %s>" % (dn, name, self.changed)
    
    __str__ = __repr__
    
    @property
    def noderepr(self):
        return repr(self)
    
    def _ldap_add(self):
        """adds self to the ldap directory.
        """
        self._session.add(self.DN, self.attributes)
    
    def _ldap_modify(self):
        """modifies attributs of self on the ldap directory.
        """ 
        modlist = list()
        orgin = self.attributes_factory(self)
        for key in orgin:
            # MOD_DELETE
            if not key in self.attributes:
                moddef = (MOD_DELETE, key, None)
                modlist.append(moddef)
        for key in self.attributes:
            # MOD_ADD
            if key not in orgin:
                moddef = (MOD_ADD, key, self.attributes[key])
                modlist.append(moddef)
            # MOD_REPLACE
            elif self.attributes[key] != orgin[key]:
                moddef = (MOD_REPLACE, key, self.attributes[key])
                modlist.append(moddef)
        if modlist:
            self._session.modify(self.DN, modlist)
    
    def _ldap_delete(self):
        """delete self from the ldap-directory.
        """
        self.__parent__._keys[self.__name__] = None
        super(LifecycleNode, self.__parent__).__delitem__(self.__name__)
        # XXX: Shouldnt this raise a KeyError
        del self.__parent__._keys[self.__name__]
        self._session.delete(self.DN)
    
    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(filter(lambda x: x is not None, self._keys.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 = property(_get_changed, _set_changed) 
    
    @property
    def ldap_session(self):
        return self._session
Exemple #4
0
class LDAPNode(LifecycleNode):
    """An LDAP Node.
    """
    implements(ICallableNode)
    attributes_factory = LDAPNodeAttributes
    
    def __init__(self, name=None, props=None, attrmap=None, child_attrmap=None):
        """LDAP Node expects ``name`` and ``props`` arguments for the root LDAP
        Node or nothing for children. ``attrmap`` is an optional rood node
        argument.
        
        ``name`` 
            Initial base DN for the root LDAP Node.
        
        ``props`` 
            ``bda.ldap.LDAPProperties`` object.

        ``attrmap``
            an optional map of attributes, mapped attributes will be available
            via node.mattrs.


        """
        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(LDAP_CHARACTER_ENCODING)
        self.__name__ = name
        self.__parent__ = None
        self._session = None        
        self._changed = False
        self._action = None
        # 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._seckey_attrs = None
        self._child_dns = {}
        self._reload = False        
        if props:
            self._session = LDAPSession(props)
            self._session.baseDN = self.DN
        super(LDAPNode, self).__init__(name, attrmap)
        self._key_attr = 'rdn'
        self._child_scope = ONELEVEL
        self._child_filter = None
        self._child_criteria = None
        self._child_relation = None
        self._ChildClass = LDAPNode
            
    # This is really ldap
    @property
    def DN(self):
        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 self.__name__
        else:
            return u''

    # This is really ldap
    def child_dn(self, key):
        return self._child_dns[key]

    # a keymapper
    def _calculate_key(self, dn, attrs):
        if self._key_attr == 'rdn':
            # explode_dn is ldap world
            key = decode(explode_dn(encode(dn))[0])
        else:
            key = attrs[self._key_attr]
            if isinstance(key, list):
                if len(key) != 1:
                    raise KeyError(u"Expected one value for '%s' "+
                            u"not %s: '%s'." % \
                                    (self._key_attr, len(key), key))
                key = key[0]
        return key

    # secondary keys
    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:
                raise KeyError(
                        u"Secondary key '%s' missing on: %s." % \
                                (seckey_attr, attrs['dn']))
            else:
                if isinstance(seckey, list):
                    if len(seckey) != 1:
                        raise KeyError(u"Expected one value for '%s' "+
                                u"not %s: '%s'." % \
                                        (seckey_attr, len(seckey), seckey))
                    seckey = seckey[0]
                seckeys[seckey_attr] = seckey
        return seckeys

    @debug(['searching'])
    def search(self, queryFilter=None, criteria=None, relation=None,
            attrlist=None, exact_match=False, or_search=False):
        """Returns a list of matching keys.

        All search criteria are additive and will be ``&``ed. ``queryFilter``
        and ``criteria`` further narrow down the search space defined by
        ``self._child_filter``, ``self._child_criteria`` and
        ``self._child_relation``.

        ``queryFilter``
            ldap queryFilter, e.g. ``(objectClass=foo)``
        ``criteria``
            dictionary of attribute value(s) (string or list of string)
        ``relation``
            the nodes we search has a relation to us.  A relation is defined as
            a string of attribute pairs:
            ``<relation> = '<our_attr>:<child_attr>'``.
            The value of these attributes must match for relation to match.
            Multiple pairs can be or-joined with
        ``attrlist``
            Normally a list of keys is returned. By defining attrlist the
            return format will be ``[(key, {attr1: [value1, ...]}), ...]``. To
            get this format without any attributs, i.e. empty dicts in the
            tuples, specify an empty attrlist. In addition to the normal ldap
            attributes you can also the request the dn to be included.
        ``exact_match``
            raise ValueError if not one match, return format is a single key or
            tuple, if attrlist is specified.
        """
        attrset = set(attrlist or [])
        attrset.discard('dn')
        # fetch also the key attribute
        if not self._key_attr == 'rdn':
            attrset.add(self._key_attr)

#        for attr in attrset:
#            if attr in self._cfg.attrmap:
#                attrset.discard(attr)
#                attrset.add(self._cfg.attrmap[attr])

        # create queryFilter from all filter things - needs only to happen just
        # before ldap, could be in the backedn
        # filter for this search ANDed with the basic filter which is always
        # effective
        search_filter = LDAPFilter(queryFilter)
        search_filter &= LDAPDictFilter(criteria, or_search=or_search)
        _filter = LDAPFilter(self._child_filter)
        _filter &= LDAPDictFilter(self._child_criteria)
        _filter &= search_filter

        # 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)
        # configurable?
        if self._key_attr != 'rdn' and self._key_attr not in _filter:
            _filter &= '(%s=*)' % (self._key_attr,)

        # perform the backend search
        matches = self._session.search(_filter.__str__(),
                                       self._child_scope,
                                       baseDN=self.DN,
                                       force_reload=self._reload,
                                       attrlist=list(attrset))
        if exact_match and len(matches) != 1:
            # XXX: Is ValueError appropriate?
            # XXX: Really also fail, if there are 0 matches?
            raise ValueError(u"Exact match asked but search not exact")

        # extract key and desired attributes
        res = []
        for dn, attrs in matches:
            key = self._calculate_key(dn, attrs)
            if attrlist is not None:
                resattr = dict([(k,v) for k,v in attrs.iteritems()
                        if k in attrlist])
                if 'dn' in attrlist:
                    resattr['dn'] = dn
                res.append((key, resattr))
            else:
                res.append(key)

        if exact_match:
            return res[0]
        else:
            return res

    def __iter__(self):
        """This is where keys are retrieved from ldap
        """
        if self.__name__ is None:
            return
        if self._reload:
            self._keys = None
            self._seckeys = None
            self._child_dns.clear()
        if self._keys is None and self._action != ACTION_ADD:
            self._keys = odict()
            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] = None
                    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:
                            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))
        if self._keys:
            for key in self._keys:
                yield key
    
    iterkeys = __iter__
    
    def iteritems(self):
        for key in self:
            yield key, self[key]

    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
        self._keys.sort(cmp=cmp, key=key, reverse=reverse)
    
    def __getitem__(self, key):
        """Here nodes are created for keys, iff they do not exist already
        """
        if isinstance(key, str):
            key = decode(key)
        if not key in self:
            raise KeyError(u"Entry not existent: %s" % key)
        if self._keys[key] is not None:
            return super(LDAPNode, self).__getitem__(key)
        val = self._ChildClass()
        val._session = self._session
        # We are suppressing notification, as val is not really added to us,
        # rather, it is activated.
        self._notify_suppress = True
        super(LDAPNode, self).__setitem__(key, val)
        self._notify_suppress = False
        self._keys[key] = val
        return val

    def get(self, key, default=None):
        """Otherwise odict/pyodicts __getitem__ is used...

        XXX: Maybe this could be higher up in the hierarchy
        """
        try:
            return self[key]
        except KeyError:
            return default

    def __setitem__(self, key, val):
        if isinstance(key, str):
            key = decode(key)
        if self._child_scope is not ONELEVEL:
            raise NotImplementedError(
                    u"Adding with scope != ONELEVEL not supported.")
        if self._key_attr != 'rdn':
            raise NotImplementedError(u"Adding with key != rdn not supported.")
        val._session = self._session
        if self._keys is None:
            self._keys = odict()
        try:
            # a value with key is already in the directory
            self._keys[key]
        except KeyError, e:
            # the value is not yet in the directory 
            val._action = ACTION_ADD
            val.changed = True
            self.changed = True 
        self._notify_suppress = True
        super(LDAPNode, self).__setitem__(key, val)
        self._notify_suppress = False
        self._keys[key] = val    
        self._child_dns[key] = ','.join((key, self.DN))
        if val._action == ACTION_ADD:
            objectEventNotify(self.events['added'](val, newParent=self, 
                                                   newName=key))