1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
"""\
LDAP Entry Objects
"""
__version__ = "$Revision: 1.13 $"[11:-2]
import Acquisition, AccessControl, OFS, string
from App.special_dtml import HTMLFile
from App.Dialogs import MessageDialog
import ldap, urllib, UserList
ConnectionError='ZLDAP Connection Error'
def isNotBlank(s):
#test for non-blank strings
if type(s) is type('a') and s=='':
return 0
else: return 1
class AttrWrap(UserList.UserList):
"""simple attr-wrapper for LDAP attributes"""
def __str__(self):
return string.join(self.data,', ')
class GenericEntry(Acquisition.Implicit):
"""\
The GenericEntry class holds all the LDAP-Entry specific information.
"""
__ac_permissions__ = (
('Access contents information',
('get',), ('Anonymous',),),
('Manage Entry information',
('set', 'setattrs', 'setAll','remove',),),
('Create New Entry Objects',
('addSubentry',),),
('Delete Entry Objects',
('deleteSubentry',),),
)
__name__ = "GenericEntry"
def __init__(self, dn, attrs=None, connection=None, isNew=0):
self.id = ldap.explode_dn(dn)[0] # Split the DN into a list.
self.dn = dn # Our actually unique ID in tree
self.__connection = None
if attrs is None and connection is not None:
# We have no passed in attributes, but we do have a connection
# to get them from.
self._init(connection)
elif attrs and connection is not None:
# Attributes were passed in, so we don't need to go to our
# connection to retrieve them
self._data = attrs
self.__connection = connection
else:
# We're totally blank and disconnected
self._data = {}
self._isNew = isNew
if isNew:
pass # XXX need to handle creation here
self._isDeleted = 0 # Deletion flag
self.__subentries = {} # subentries
self._mod_delete = []
def _init(self, connection):
self.__connection = connection
if not self._isNew:
self._data = connection.getAttributes(self.dn)
else:
self._data = {}
def _reset(self):
if self._isNew: self._data = {}
else: self._data = self._connection().getAttributes(self.dn)
def __repr__(self):
r="<Entry instance at %s; %s>" % (id(self), self.dn)
return r
def _connection(self):
if self.__connection is None:
raise ConnectionError, 'No connection object for this entry'
else:
return self.__connection
#### Subentry and Attribute Access Machinery ##########
def __getitem__(self, key):
"""getitem is used to get sub-entries, not attributes"""
if self.__subentries:
se=self._subentries()
if se.has_key(key):
return se[key]
key = '%s, %s' % (urllib.unquote(key), self.dn)
conn= self._connection()
if conn.hasEntry(key):
return conn.getEntry(key, self)
else:
raise IndexError, key
def __getattr__(self, attr):
if self._data.has_key(attr):
return AttrWrap(self._data[attr])
else:
raise AttributeError, attr
#### Direct access for setting/getting/unsetting attributes
def get(self, attr):
if self._data.has_key(attr):
return self._data[attr]
else:
raise AttributeError, attr
def set(self, key, value):
""" Sets individual items """
self.setattrs({ key:value })
def setattrs(self, kwdict={}, **kw):
""" Sets one or more attributes on the entry object, taking in
both a dictionary AND\OR keywork arguments """
kwdict.update(kw)
data = self._data
for attr, value in kwdict.items():
if type(value) is type(''):
value = [value]
data[attr] = value
self._modify()
def setAll(self, kwdict={}, **kw):
""" The dictionary\keywords passed in become ALL of the new
attributes for the Entry (old data is lost) """
kwdict.update(kw) # Merge passed in dict with keywords
self._data = {} # Clear our Entry attributes
self.setattrs(kwdict) # And call self.setattrs to do the work
def remove(self, attr):
""" Remove the attribute\attribute list """
if type(attr) is type(''):
attr = (attr,)
data = self._data
mod_d = self._mod_delete
for item in attr:
if data.has_key(item):
del data[attr]
mod_d.append(attr)
self._modify() # Send the changes to LDAP
### These methods actually change the object. In the Generic Model,
### a .set calls this directly, while in the TransactionalModel this
### gets called by the Transaction system at commit time.
def _modify(self):
modlist = []
for attribute, values in self._data.items():
modlist.append((ldap.MOD_REPLACE, attribute, values))
for attribute in self._mod_delete:
modlist.append((ldap.MOD_DELETE, attribute, None))
self._connection()._modifyEntry(self.dn, modlist)
self._mod_delete=[]
self.__subentries={}
#### Get the ZLDAPConnection object.
def _connection(self):
if self.__connection is None:
raise ConnectionError, 'Cannot Get Connection'
else:
return self.__connection
def _setConnection(self, connection):
self.__connection = connection
### Subentries
def _subentries(self):
if not self.__subentries:
# self.__subentries is empty, look up our subentries
# in the connection object and then set self.__subentries
r = {}
se = self._connection().getSubEntries(self.dn,self)
for subentry in se:
r[subentry.id] = subentry
self.__subentries = r
return self.__subentries
def _clearSubentries(self):
self.__subentries = {}
def _setSubentry(self, entryid, entry):
self.__subentries[entryid] = entry
def _delSubentry(self, entryid):
subs = self.__subentries
if subs.has_key(entryid): del self.__subentries[entryid]
### Deleting Subentries
def _beforeDelete(self, **ignored):
""" Go through all the subentries and delete them too """
conn = self._connection()
for entry in self._subentries().values():
entry._beforeDelete()
conn._deleteEntry(entry.dn) # Delete from the server
self._delSubentry(entry.id) # Delete our own reference
def _delete(self, entry):
conn = self._connection()
entry._beforeDelete()
conn._deleteEntry(entry.dn)
entry._isDeleted = 1
self._delSubentry(entry.id)
def _delete_dn(self, rdn):
""" Delete by relative dn, ( - entry._delete_dn('sn=Shell') - ) """
entry = self[rdn] # Get the subentry
self._delete(entry)
### Adding subentries
def addSubentry(self, rdn, attrs={}, **kw):
""" Create a new subentry of myself """
conn = self._connection()
nkw = {}
nkw.update(attrs); nkw.update(kw)
attrs = nkw
# Create the full new DN (Distinguished Name) for the new subentry
# and verify that it doesn't already exist
dn = "%s,%s" % (string.strip(rdn), self.dn)
if conn.hasEntry(dn): # Check the LDAP server directly
raise KeyError, "DN '%s' already exists" % dn
# Now split out the first attr based on the RDN (ie 'cn=bob') and
# turn it into one of our attributes (ie attr[cn] = 'bob')
key, value = map(string.strip, string.split(rdn,'='))
attrs[key] = value
# If the objectclass is not already set in the attrs, set it now
if not attrs.has_key('objectclass'):
attrs['objectclass'] = ['top']
# Instantiate the instance based on the connections EntryFactory
Entry = conn._EntryFactory()
entry = Entry(dn, attrs, conn, isNew=1).__of__(self)
conn._addEntry(dn, attrs.items()) # Physically add the new entry
self._setSubentry(entry.id, entry)
return entry
### Public API for deleting subentries
def deleteSubentry(self, entry):
""" Delete a subentry (may be specified either by an rdn (string)
or an Entry object instance """
if type(entry) is type(''):
self._delete_dn(entry) # Delete by the RDN ('cn=...')
else:
self._delete(entry) # Delete by Entry object itself
class TransactionalEntry(GenericEntry): #Acquisition.Implicit
"""\
The TransactionalEntry class holds all the LDAP-Entry specific information,
registers itself with the transaction manager, etc. It's faceless.
All Zope UI/Management methods will be implemented in the Entry class.
"""
__name__ = "TransactionalEntry"
__ac_permissions__ = (
('Manage Entry information',
('undelete', 'setattrs','remove'),),
('Create New Entry Objects',
('addSubentry',),),
)
_registered=None #denotes if we've registered with the
#transaction manager
def __init__(self, dn, attrs=None, connection=None, isNew=0):
self.id=ldap.explode_dn(dn)[0] #split the DN into a list.
self.dn=dn #Our actually unique ID in tree
self._p_jar=None #actually, the connection
self._setConnection(None)
if attrs is None and connection is not None:
self._init(connection)
elif attrs and connection is not None:
self._data=attrs
self._p_jar=connection
self._setConnection(connection)
else:
self._data={}
self._isNew=isNew
if isNew:
get_transaction().register(self)
self._registered=1
self._isDeleted=0 #deletion flag
self._clearSubentries()
self._mod_delete=[]
# We override _set here because we will be physically updated by
# the transaction manager (we don't call self._modify(), the transaction
# machinery will)
def setattrs(self, kwdict={}, **kw):
"""\
Set attributes in self._data and register ourselves with the
transaction machinery. Data is not committed to LDAP when this
is called.
"""
if not self._registered:
get_transaction().register(self)
self._registered=1
kwdict.update(kw)
data = self._data
for attr, value in kwdict.items():
if type(value) is type(''):
value = [value]
data[attr] = value
# We override _remove (previously '_unSet') here because we don't call
# self._modify() (the transaction manager will)
def remove(self, attr):
"""\
Unset (delete) an attribute
"""
if not self._registered:
get_transaction().register(self)
self._registered=1
if type(attr) is type(''):
attr = (attr,)
data = self._data
mod_d = self._mod_delete
for item in attr:
if data.has_key(item):
del data[item]
mod_d.append(item)
### Transaction Related methods
def _reset(self):
self._rollback()
def _rollback(self):
conn=self._connection()
if not self._isNew:
self._data=conn.getAttributes(self.dn)
self._clear_subentries={}
else:
self._data={}
### Adding and Deleting sub-entries.
def _beforeDelete(self, **ignored):
c=self._connection()
for entry in self._subentries().values():
entry.manage_beforeDelete()
c._registerDelete(entry.dn)
entry._isDeleted=1
del self._subentries()[entry.id]
def _delete(self, o):
c=self._connection()
o._beforeDelete()
c._registerDelete(o.dn)
o._isDeleted=1
if not o._registered:
get_transaction().register(o)
o._registered=1
del self._subentries()[o.id]
def _delete_dn(self, rdn):
o=self[rdn]
self._delete(o)
def undelete(self):
'''undelete myself'''
## c=self._connection()
## c._unregisterDelete(self.dn)
self._isDeleted=0
def addSubentry(self, rdn, attrs={}, **kw):
''' create a new subentry of myself '''
c=self._connection()
nkw = {}
nkw.update(attrs); nkw.update(kw)
attrs = nkw
#create the new full DN for new subentry and check its existance
dn='%s,%s' % (string.strip(rdn), self.dn)
if c.hasEntry(dn):
raise KeyError, "DN '%s' already exists" % dn
# now split out the first attr based on the rdn (ie 'cn=bob', turns
# into attr['cn'] = 'bob'
key, value = map(string.strip,string.split(rdn,'='))
attrs[key] = value
#if objectclass is not set in the attrs, set it now
if not attrs.has_key('objectclass'):
attrs['objectclass']=['top']
#instantiate the instance based on current instances class
#and register it to be added at commit time
Entry = c._EntryFactory()
entry = Entry(dn,attrs,c,isNew=1).__of__(self)
c._registerAdd(entry) # Register new Entry (added by TM)
self._setSubentry(entry.id, entry)
return entry
class ZopeEntry(OFS.SimpleItem.Item):
'''Entry Object'''
#### Initialazation Routines ##############
manage_options=(
{'label':'Attributes','action':'manage_attributes'},
)
__ac_permissions__=(
('Access contents information', ('manage_attributes',),
('Manager','Anonymous',),),
('Manage Entry information', ('manage_changeAttributes',
'manage_addAttribute',
'manage_editAttributes',),
('Manager',),),
('Create New Entry Objects',
('manage_newEntry', 'manage_newEntryWithAttributes'),
('Manager',),),
)
manage_attributes=HTMLFile("attributes",globals())
manage_main=manage_attributes
isPrincipiaFolderish=1
#### Entry & Attribute Access Machinery #####################
def attributesMap(self):
return self._data.items()
def __bobo_traverse__(self, REQUEST, key):
' allow traversal to subentries '
key=urllib.unquote(key)
if key in self.objectIds(): return self[key]
else: return getattr(self,key)
###### Tree Machinery ######
def tpValues(self):
return self._subentries().values()
def tpId(self):
return self.id
def tpURL(self):
"""Return string to be used as URL relative to parent."""
return urllib.quote(self.id)
### Object Manager-ish Machinery
def objectValues(self):
return self._subentries().values()
def objectIds(self):
return self._subentries().keys()
def objectItems(self):
return self._subentries().items()
### Zope management stuff
def manage_deleteEntry(self, ids, REQUEST=None):
'''Delete marked Entries and all their sub-entries.'''
for rdn in ids:
self._delete_dn(rdn)
if REQUEST is not None:
return self.manage_contents(self, REQUEST)
def manage_newEntry(self, rdn, REQUEST=None):
'''Add a new entry'''
e = self.addSubentry(rdn)
if REQUEST is not None:
return self.manage_contents(self, REQUEST)
else:
return e
def manage_newEntryWithAttributes(self, rdn, attributes={}, **kw):
""" add a new entry with attributes """
attributes.update(kw) # merge the keyword args in
e = self.addSubentry(rdn, attributes)
return e # return the new entry
def manage_addAttribute(self, id, values, REQUEST=None):
'''Add an attribute to an LDAP entry'''
self.set(id, values)
if REQUEST is not None:
return self.manage_attributes(self, REQUEST)
def manage_editAttributes(self, REQUEST):
"""Edit entry's attributes via the web."""
for attribute in self._data.keys():
values = REQUEST.get(attribute, [])
values = filter(isNotBlank, values) #strip out blanks
self.set(attribute, values)
return MessageDialog(
title ='Success!',
message='Your changes have been saved',
action ='manage_attributes')
def manage_changeAttributes(self, REQUEST=None, **kw):
"""Change existing Entry's Attributes.
Change entry's attributes by passing either a mapping object
of name:value pairs {'foo':6} or passing name=value parameters
"""
if REQUEST and not kw:
kw=REQUEST
datakeys = self._data.keys()
if kw:
for name, value in kw.items():
if name in datakeys:
self.set(name, value)
if REQUEST is not None:
return MessageDialog(
title ='Success!',
message='Your changes have been saved',
action ='manage_propertiesForm')
import App.class_init
for klass in (GenericEntry, TransactionalEntry, ZopeEntry):
App.class_init.default__class_init__(klass)