Source code for ldaptor.inmemory

from twisted.internet import defer, error
from twisted.python.failure import Failure
from zope.interface import implementer

from ldaptor import interfaces, entry, entryhelpers
from ldaptor.protocols.ldap import distinguishedname, ldaperrors, ldifprotocol


[docs]class LDAPCannotRemoveRootError(ldaperrors.LDAPNamingViolation): """Cannot remove root of LDAP tree"""
[docs]@implementer(interfaces.IConnectedLDAPEntry) class ReadOnlyInMemoryLDAPEntry( entry.EditableLDAPEntry, entryhelpers.DiffTreeMixin, entryhelpers.SubtreeFromChildrenMixin, entryhelpers.MatchMixin, entryhelpers.SearchByTreeWalkingMixin, ): def __init__(self, *a, **kw): entry.BaseLDAPEntry.__init__(self, *a, **kw) self._parent = None self._children = {}
[docs] def parent(self): return self._parent
[docs] def children(self, callback=None): if callback is None: return defer.succeed(list(self._children.values())) else: for c in self._children.values(): callback(c) return defer.succeed(None)
def _lookup(self, dn): if not self.dn.contains(dn): raise ldaperrors.LDAPNoSuchObject(dn.getText()) if dn == self.dn: return defer.succeed(self) for c in self._children.values(): if c.dn.contains(dn): return c.lookup(dn) raise ldaperrors.LDAPNoSuchObject(dn.getText())
[docs] def lookup(self, dn): if not isinstance(dn, distinguishedname.DistinguishedName): dn = distinguishedname.DistinguishedName(stringValue=dn) return defer.maybeDeferred(self._lookup, dn)
[docs] def fetch(self, *attributes): return defer.succeed(self)
[docs] def addChild(self, rdn, attributes): """TODO ugly API. Returns the created entry.""" rdn = distinguishedname.RelativeDistinguishedName(rdn) rdn_str = rdn.getText() if rdn_str in self._children: raise ldaperrors.LDAPEntryAlreadyExists( self._children[rdn_str].dn.getText() ) dn = distinguishedname.DistinguishedName(listOfRDNs=(rdn,) + self.dn.split()) e = self.__class__(dn, attributes) e._parent = self self._children[rdn_str] = e return e
def _delete(self): if self._parent is None: raise LDAPCannotRemoveRootError() if self._children: raise ldaperrors.LDAPNotAllowedOnNonLeaf(self.dn) return self._parent.deleteChild(self.dn.split()[0])
[docs] def delete(self): return defer.maybeDeferred(self._delete)
def _deleteChild(self, rdn): if not isinstance(rdn, distinguishedname.RelativeDistinguishedName): rdn = distinguishedname.RelativeDistinguishedName(stringValue=rdn) rdn_str = rdn.getText() try: return self._children.pop(rdn_str) except KeyError: raise ldaperrors.LDAPNoSuchObject(rdn.getText())
[docs] def deleteChild(self, rdn): return defer.maybeDeferred(self._deleteChild, rdn)
def _move(self, newDN): if not isinstance(newDN, distinguishedname.DistinguishedName): newDN = distinguishedname.DistinguishedName(stringValue=newDN) if newDN.up() != self.dn.up(): # climb up the tree to root root = self while root._parent is not None: root = root._parent d = defer.maybeDeferred(root.lookup, newDN.up()) else: d = defer.succeed(None) d.addCallback(self._move2, newDN) return d def _move2(self, newParent, newDN): if newParent is not None: newParent._children[newDN.split()[0].getText()] = self del self._parent._children[self.dn.split()[0].getText()] # remove old RDN attributes for attr in self.dn.split()[0].split(): self[attr.attributeType].remove(attr.value) # add new RDN attributes for attr in newDN.split()[0].split(): # TODO what if the key does not exist? self[attr.attributeType].add(attr.value) self.dn = newDN return self
[docs] def move(self, newDN): return defer.maybeDeferred(self._move, newDN)
[docs] def commit(self): return defer.succeed(True)
[docs]class InMemoryLDIFProtocol(ldifprotocol.LDIF): """ Receive LDIF data and gather results into an ReadOnlyInMemoryLDAPEntry. You can override lookupFailed and addFailed to provide smarter error handling. They are called as Deferred errbacks; returning the reason causes error to pass onward and abort the whole operation. Returning None from lookupFailed skips that entry, but continues loading. When the full LDIF data has been read, the completed Deferred will trigger. """ def __init__(self): # Do not access this via db, just to make sure you respect the ordering self.db = None self._deferred = defer.Deferred() self.completed = defer.Deferred() def _addEntry(self, db, entry): d = db.lookup(entry.dn.up()) d.addErrback(self.lookupFailed, entry) def _add(parent, entry): if parent is not None: parent.addChild(rdn=entry.dn.split()[0], attributes=entry) d.addCallback(_add, entry) d.addErrback(self.addFailed, entry) def _passDB(_, db): return db d.addCallback(_passDB, db) return d
[docs] def gotEntry(self, entry): if self.db is None: # first entry, create the db, prepare to process the rest self.db = ReadOnlyInMemoryLDAPEntry(dn=entry.dn, attributes=entry) self._deferred.callback(self.db) else: self._deferred.addCallback(self._addEntry, entry)
[docs] def lookupFailed(self, reason, entry): return reason # pass the error (abort) by default
[docs] def addFailed(self, reason, entry): return reason # pass the error (abort) by default
[docs] def connectionLost(self, reason): super().connectionLost(reason) if not reason.check(error.ConnectionDone): self._deferred.addCallback(lambda db: reason) else: self._deferred.chainDeferred(self.completed) del self._deferred # invalidate it to flush out bugs
[docs]def fromLDIFFile(f): """Read LDIF data from a file.""" p = InMemoryLDIFProtocol() while 1: data = f.read() if not data: break p.dataReceived(data) p.connectionLost(Failure(error.ConnectionDone())) return p.completed