Package ldaptor :: Module ldiftree
[hide private]
[frames] | no frames]

Source Code for Module ldaptor.ldiftree

  1  """ 
  2  Manage LDAP data as a tree of LDIF files. 
  3  """ 
  4  import os, errno, sets 
  5  from zope.interface import implements 
  6  from twisted.internet import defer, error 
  7  from twisted.python import failure 
  8  from ldaptor import entry, interfaces, attributeset, entryhelpers 
  9  from ldaptor.protocols.ldap import ldifprotocol, distinguishedname, ldaperrors 
 10  from twisted.mail.maildir import _generateMaildirName as tempName 
 11   
12 -class LDIFTreeEntryContainsMultipleEntries(Exception):
13 """LDIFTree entry contains multiple LDIF entries."""
14
15 -class LDIFTreeEntryContainsNoEntries(Exception):
16 """LDIFTree entry does not contain a valid LDIF entry."""
17
18 -class LDIFTreeNoSuchObject(Exception):
19 # TODO combine with standard LDAP errors 20 """LDIFTree does not contain such entry."""
21
22 -class LDAPCannotRemoveRootError(ldaperrors.LDAPNamingViolation):
23 """Cannot remove root of LDAP tree"""
24 # TODO share with ldaptor.inmemory? 25 26
27 -class StoreParsedLDIF(ldifprotocol.LDIF):
28 - def __init__(self):
29 self.done = False 30 self.seen = []
31
32 - def gotEntry(self, obj):
33 self.seen.append(obj)
34
35 - def connectionLost(self, reason):
36 self.done = True
37
38 -def get(path, dn):
39 return defer.maybeDeferred(_get, path, dn)
40 -def _get(path, dn):
41 dn = distinguishedname.DistinguishedName(dn) 42 l = list(dn.split()) 43 assert len(l) >= 1 44 l.reverse() 45 46 parser = StoreParsedLDIF() 47 48 entry = os.path.join(path, 49 *['%s.dir'%rdn for rdn in l[:-1]]) 50 entry = os.path.join(entry, '%s.ldif'%l[-1]) 51 f = file(entry) 52 while 1: 53 data = f.read(8192) 54 if not data: 55 break 56 parser.dataReceived(data) 57 parser.connectionLost(failure.Failure(error.ConnectionDone)) 58 59 assert parser.done 60 entries = parser.seen 61 if len(entries) == 0: 62 raise LDIFTreeEntryContainsNoEntries 63 elif len(entries) > 1: 64 raise LDIFTreeEntryContainsMultipleEntries, entries 65 else: 66 return entries[0]
67
68 -def _putEntry(fileName, entry):
69 """fileName is without extension.""" 70 tmp = fileName + '.' + tempName() + '.tmp' 71 f = file(tmp, 'w') 72 f.write(str(entry)) 73 f.close() 74 os.rename(tmp, fileName+'.ldif')
75 # TODO atomicity 76
77 -def _put(path, entry):
78 l = list(entry.dn.split()) 79 assert len(l) >= 1 80 l.reverse() 81 82 entryRDN = l.pop() 83 if l: 84 grandParent = os.path.join(path, 85 *['%s.dir'%rdn for rdn in l[:-1]]) 86 parentEntry = os.path.join(grandParent, '%s.ldif' % l[-1]) 87 parentDir = os.path.join(grandParent, '%s.dir' % l[-1]) 88 if not os.path.exists(parentDir): 89 if not os.path.exists(parentEntry): 90 raise LDIFTreeNoSuchObject, entry.dn.up() 91 try: 92 os.mkdir(parentDir) 93 except OSError, e: 94 if e.errno == errno.EEXIST: 95 # we lost a race to create the directory, safe to ignore 96 pass 97 else: 98 raise 99 else: 100 parentDir = path 101 return _putEntry(os.path.join(parentDir, '%s'%entryRDN), entry)
102
103 -def put(path, entry):
104 return defer.execute(_put, path, entry)
105
106 -class LDIFTreeEntry(entry.EditableLDAPEntry, 107 entryhelpers.DiffTreeMixin, 108 entryhelpers.SubtreeFromChildrenMixin, 109 entryhelpers.MatchMixin, 110 entryhelpers.SearchByTreeWalkingMixin, 111 ):
112 implements(interfaces.IConnectedLDAPEntry) 113
114 - def __init__(self, path, dn=None, *a, **kw):
115 if dn is None: 116 dn = '' 117 entry.BaseLDAPEntry.__init__(self, dn, *a, **kw) 118 self.path = path 119 if dn != '': #TODO DistinguishedName.__nonzero__ 120 self._load()
121
122 - def _load(self):
123 assert self.path.endswith('.dir') 124 entryPath = '%s.ldif' % self.path[:-len('.dir')] 125 126 parser = StoreParsedLDIF() 127 128 try: 129 f = file(entryPath) 130 except IOError, e: 131 if e.errno == errno.ENOENT: 132 return 133 else: 134 raise 135 while 1: 136 data = f.read(8192) 137 if not data: 138 break 139 parser.dataReceived(data) 140 parser.connectionLost(failure.Failure(error.ConnectionDone)) 141 assert parser.done 142 143 entries = parser.seen 144 if len(entries) == 0: 145 raise LDIFTreeEntryContainsNoEntries 146 elif len(entries) > 1: 147 raise LDIFTreeEntryContainsMultipleEntries, entries 148 else: 149 # TODO ugliness and all of its friends 150 for k,v in entries[0].items(): 151 self._attributes[k] = attributeset.LDAPAttributeSet(k, v)
152
153 - def parent(self):
154 # TODO add __nonzero__ to DistinguishedName 155 if self.dn == '': 156 # root 157 return None 158 else: 159 parentPath, _ = os.path.split(self.path) 160 return self.__class__(parentPath, self.dn.up())
161
162 - def _sync_children(self):
163 children = [] 164 try: 165 filenames = os.listdir(self.path) 166 except OSError, e: 167 if e.errno == errno.ENOENT: 168 pass 169 else: 170 raise 171 else: 172 seen = sets.Set() 173 for fn in filenames: 174 base, ext = os.path.splitext(fn) 175 if ext not in ['.dir', '.ldif']: 176 continue 177 if base in seen: 178 continue 179 seen.add(base) 180 181 dn = distinguishedname.DistinguishedName( 182 listOfRDNs=((distinguishedname.RelativeDistinguishedName(base),) 183 + self.dn.split())) 184 e = self.__class__(os.path.join(self.path, base + '.dir'), dn) 185 children.append(e) 186 return children
187
188 - def _children(self, callback=None):
189 children = self._sync_children() 190 if callback is None: 191 return children 192 else: 193 for c in children: 194 callback(c) 195 return None
196
197 - def children(self, callback=None):
198 return defer.maybeDeferred(self._children, callback=callback)
199
200 - def lookup(self, dn):
201 dn = distinguishedname.DistinguishedName(dn) 202 if not self.dn.contains(dn): 203 return defer.fail(ldaperrors.LDAPNoSuchObject(dn)) 204 if dn == self.dn: 205 return defer.succeed(self) 206 207 it = dn.split() 208 me = self.dn.split() 209 assert len(it) > len(me) 210 assert ((len(me)==0) or (it[-len(me):] == me)) 211 rdn = it[-len(me)-1] 212 path = os.path.join(self.path, '%s.dir' % rdn) 213 entry = os.path.join(self.path, '%s.ldif' % rdn) 214 if not os.path.isdir(path) and not os.path.isfile(entry): 215 return defer.fail(ldaperrors.LDAPNoSuchObject(dn)) 216 else: 217 childDN = distinguishedname.DistinguishedName(listOfRDNs=(rdn,)+me) 218 c = self.__class__(path, childDN) 219 return c.lookup(dn)
220
221 - def _addChild(self, rdn, attributes):
222 rdn = distinguishedname.RelativeDistinguishedName(rdn) 223 for c in self._sync_children(): 224 if c.dn.split()[0] == rdn: 225 raise ldaperrors.LDAPEntryAlreadyExists, c.dn 226 227 dn = distinguishedname.DistinguishedName(listOfRDNs= 228 (rdn,) 229 +self.dn.split()) 230 e = entry.BaseLDAPEntry(dn, attributes) 231 if not os.path.exists(self.path): 232 os.mkdir(self.path) 233 fileName = os.path.join(self.path, '%s' % rdn) 234 tmp = fileName + '.' + tempName() + '.tmp' 235 f = file(tmp, 'w') 236 f.write(str(e)) 237 f.close() 238 os.rename(tmp, fileName+'.ldif') 239 # TODO atomicity 240 241 dirName = os.path.join(self.path, '%s.dir' % rdn) 242 243 e = self.__class__(dirName, dn) 244 return e
245
246 - def addChild(self, rdn, attributes):
247 d = self._addChild(rdn, attributes) 248 return d
249
250 - def _delete(self):
251 if self.dn == '': ##TODO DistinguishedName __nonzero__ 252 raise LDAPCannotRemoveRootError 253 if self._sync_children(): 254 raise ldaperrors.LDAPNotAllowedOnNonLeaf( 255 'Cannot remove entry with children: %s' % self.dn) 256 assert self.path.endswith('.dir') 257 entryPath = '%s.ldif' % self.path[:-len('.dir')] 258 os.remove(entryPath) 259 return self
260
261 - def delete(self):
262 return defer.maybeDeferred(self._delete)
263
264 - def _deleteChild(self, rdn):
265 if not isinstance(rdn, distinguishedname.RelativeDistinguishedName): 266 rdn = distinguishedname.RelativeDistinguishedName(stringValue=rdn) 267 for c in self._sync_children(): 268 if c.dn.split()[0] == rdn: 269 return c.delete() 270 raise ldaperrors.LDAPNoSuchObject, rdn
271
272 - def deleteChild(self, rdn):
273 return defer.maybeDeferred(self._deleteChild, rdn)
274
275 - def __repr__(self):
276 return '%s(%r, %r)' % (self.__class__.__name__, 277 self.path, 278 str(self.dn))
279
280 - def __cmp__(self, other):
281 if not isinstance(other, LDIFTreeEntry): 282 return NotImplemented 283 return cmp(self.dn, other.dn)
284
285 - def commit(self):
286 assert self.path.endswith('.dir') 287 entryPath = self.path[:-len('.dir')] 288 return defer.maybeDeferred(_putEntry, entryPath, self)
289
290 - def move(self, newDN):
291 return defer.maybeDeferred(self._move, newDN)
292
293 - def _move(self, newDN):
294 if not isinstance(newDN, distinguishedname.DistinguishedName): 295 newDN = distinguishedname.DistinguishedName(stringValue=newDN) 296 if newDN.up() != self.dn.up(): 297 # climb up the tree to root 298 rootDN = self.dn 299 rootPath = self.path 300 while rootDN != '': 301 rootDN = rootDN.up() 302 rootPath = os.path.dirname(rootPath) 303 root = self.__class__(path=rootPath, dn=rootDN) 304 d = defer.maybeDeferred(root.lookup, newDN.up()) 305 else: 306 d = defer.succeed(None) 307 d.addCallback(self._move2, newDN) 308 return d
309
310 - def _move2(self, newParent, newDN):
311 # remove old RDN attributes 312 for attr in self.dn.split()[0].split(): 313 self[attr.attributeType].remove(attr.value) 314 # add new RDN attributes 315 for attr in newDN.split()[0].split(): 316 # TODO what if the key does not exist? 317 self[attr.attributeType].add(attr.value) 318 newRDN = newDN.split()[0] 319 srcdir = os.path.dirname(self.path) 320 if newParent is None: 321 dstdir = srcdir 322 else: 323 dstdir = newParent.path 324 325 newpath = os.path.join(dstdir, '%s.dir' % newRDN) 326 try: 327 os.rename(self.path, newpath) 328 except OSError, e: 329 if e.errno == errno.ENOENT: 330 pass 331 else: 332 raise 333 basename, ext = os.path.splitext(self.path) 334 assert ext == '.dir' 335 os.rename('%s.ldif' % basename, 336 os.path.join(dstdir, '%s.ldif' % newRDN)) 337 self.dn = newDN 338 self.path = newpath 339 return self.commit()
340 341 if __name__ == '__main__': 342 """ 343 Demonstration LDAP server; serves an LDIFTree from given directory 344 over LDAP on port 10389. 345 """ 346 347 from twisted.internet import reactor, protocol 348 from twisted.python import log 349 import sys 350 log.startLogging(sys.stderr) 351 352 from twisted.python import components 353 from ldaptor.protocols.ldap import ldapserver 354 355 path = sys.argv[1] 356 db = LDIFTreeEntry(path) 357
358 - class LDAPServerFactory(protocol.ServerFactory):
359 - def __init__(self, root):
360 self.root = root
361
362 - class MyLDAPServer(ldapserver.LDAPServer):
363 debug = True
364 365 components.registerAdapter(lambda x: x.root, 366 LDAPServerFactory, 367 interfaces.IConnectedLDAPEntry) 368 369 factory = LDAPServerFactory(db) 370 factory.protocol = MyLDAPServer 371 reactor.listenTCP(10389, factory) 372 reactor.run() 373