"""\ LDAP Entry Objects """ __version__ = "$Revision: 1.13 $"[11:-2] import Acquisition, AccessControl, OFS, string from Globals import HTMLFile, MessageDialog; import Globals 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)