Commit 7c467062 authored by Nicolas Delaby's avatar Nicolas Delaby

Initial import of ZLDAPConnection Product

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@17221 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 7e73db33
ZLDAPConnection CHANGES.txt
1.1.0
Features Added
o A non beta release! Although, this may still be a bit shaky.
o The LDAP Connection object has the ability to be
*non-transactional*, which is currently the preferred way of
using the LDAP Connection object. There is still an outstanding
bug with the way the transactional behavior works when updating
more than one Entry object at a time.
o Entry API is vastly improved, and is not backwards compatible
with ZopeLDAP 1.0. The primary advantage is that now that Python
Scripts are in the core, Entry objects are easier to program
without having to use the 'manage_' methods (although those will
continue to work, and their API has not changed).
o To go along with the Entry API improvement, the Permissions of
Entry objects have been improved as well.
o The LDAP Connection has an Entry Factory which decides whether to
use a Transactional or Non-Transactional Entry object. The usage
to the end user is indistinguishable, except that the
nontransactional entry objects update to the LDAP server
*immediately*.
Bugs Fixed
o The bug in the transaction management behavior of the Connection
object has not been fixed, but the new Non-Transactional ability
should make up for this bug in most situations.
1.0b5
Bugs Fixed
o Entry objects would raise a KeyError on a failed attribute
access, which caused a failure in the Security Machinery
(non-critical - security was not affected, only attribute
access). Now AttributeError is properly raised.
1.0b4
Bugs Fixed
o Entry.manage_newEntry and Entry.manage_newEntryWithAttributes
now return the newly added Entry object.
o Should work properly with Zope 2.2.x, and still work with Zope
2.1.6.
1.0b3
Bugs Fixed
o Fixes an issue with getRawEntry() raising an IndexError
exception in some cases.
o LDAPConnection doesn't keep setting self.openc every time _open()
is called, thus avoiding constant writes to the ZODB.
o Fixed ZLDAPConnection.__init__.py to use the proper product
initialization code (prior versions were using an extremely
outdated mechanism). *This also means registering permissions
which affect Entry objects so that on Folders, permissions to
manage entry objects are settable*.
Features Added
o New module 'LDCAccessors.py' implements getter/setter methods for
all ZLDAPConnection properties. It is mixed into ZLDAPConnection.
1.0b2
o Updated for Zope 2.1.5/2.1.6 new security measures on method-ish
objects.
o Has a settable "Browsable" property on the Connection object.
1.0b1
o This is a pretty major overhaul of the original alpha code,
primarily to include some sort of pseudo-transactional safety (all
done in Zope since LDAP has no concept of transactions).
o Seperated ZLDAPConnection and Entry objects. ZLDAPConnection (the
class) no longer subclasses from an Entry object. (In old code,
it was the Entry object at the root of the connection. In this
version, the root is gotten to by calling the method 'getRoot()'
on the connection).
o Seperated the Entry class into two classes: GenericEntry and
Entry, found in Entry.py. GenericEntry holds all of the Entry
related methods and data for communicating with its Connection
object; Entry has the Zope interface and implements the tree
protocol (tpValues, tpId, tpURL) and the objectXXX protocol
(objectValues, objectItems, objectIds). These calls access the
Entry's sub-entry objects.
o Try to ensure some integrity with Zope transactions. Since LDAP
has no concept of transactions, this is not ideal, but it's a step
up from having no transaction support at all.
"""\
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 Globals
for klass in (GenericEntry, TransactionalEntry, ZopeEntry):
Globals.default__class_init__(klass)
__version__="$Revision: 1.3 $"[11:-2]
class LDAPConnectionAccessors:
""" getters / setters for LDAP Properties """
__ac_permissions__ = (
('Access contents information',
('getId','getTitle','getHost','getPort','getBindAs','getBoundAs',
'getPW','getDN','getOpenConnection','getBrowsable',
'shouldBeOpen','getTransactional',),),
('Manage properties',
('setID','setTitle','setHost','setPort', 'setBindAs','setPW',
'setDN','setOpenConnection','setBrowsable','setBoundAs',
'setTransactional',),),
)
def getId(self):
return self.id
def setId(self, id):
self.id = id
def getTitle(self):
return self.title
def setTitle(self, title):
self.title = title
def getHost(self):
""" returns the host that this connection is connected to """
return self.host
def setHost(self, host):
self.host = host
def getPort(self):
""" returns the port on the host that this connection connects to """
return self.port
def setPort(self, port):
self.port = port
def getBindAs(self):
""" return the DN that this connection is bound as """
return self.bind_as
getBoundAs = getBindAs
def setBindAs(self, bindAs):
self.bind_as = bindAs
setBoundAs = setBindAs
def getPW(self):
""" return the password that this connection is connected with """
return self.pw
def setPW(self, pw):
self.pw = pw
def getDN(self):
return self.dn
def setDN(self, dn):
self.dn = dn
def getOpenConnection(self):
""" self.openc means that the connection is open to Zope. However,
the connection to the LDAP server may or may not be opened. If
this returns false, we shouldn't even try connecting."""
return self.openc
def setOpenConnection(self, openc):
self._v_openc = openc
shouldBeOpen = getOpenConnection
def getBrowsable(self):
""" if true, connection object is set to be browsable through the
management interface """
return getattr(self, '_canBrowse', 0)
def setBrowsable(self, browsable):
self._canBrowse = browsable
def getTransactional(self):
""" If transactional returns TRUE, the TransactionManager stuff
is used. If FALSE, changes are sent to LDAP immediately. """
# Default to '1', to emulate the original behavior
return getattr(self, 'isTransactional', 1)
def setTransactional(self, transactional=1):
self.isTransactional = transactional
self._refreshEntryClass()
# We have a fair amount of transaction-sensitive methods that
# only want to run during a commit, and these are the ones that
# actually send the data to the LDAP server. When in non-transactional
# mode, we want these things to run at any time. In a sense, we're
# always committing.
if not transactional:
self._isCommitting = 1
import Globals
Globals.default__class_init__(LDAPConnectionAccessors)
Programming ZopeLDAP Entry Objects
Author -- "Jeffrey P Shell", mailto:jeffrey@digicool.com
Date -- Dec 18, 2000
Revision -- For ZopeLDAP 1.1.0
1. Getting an Entry Object and its attributes
Using an LDAP Filter object is the best way to get an Entry object.
LDAP Filter objects act like methods\functions and return a sequence
of entry objects (so they can be used with any sort of looping
structure). For example, if you had an LDAP Filter titled
'lookupByEmail' with the parameter 'email' with the content::
mail=<dtml-var name="mail">*
Can be used in DTML like this::
<dtml-in expr="lookupByEmail(email='jef')">
Surname: <dtml-var name="sn"><br />
Common name: <dtml-var name="cn"><br /><br />
</dtml-in>
Or in a Python Script like this::
entries = container.lookupByEmail(email='jef')
for entry in entries:
# do something here...
1.1. Notes about Attributes and Entry Objects
By default, LDAP always returns its attributes as sequences (Python
lists), even if there is only one value. ZopeLDAP Entry objects
use a special class, *AttrWrap*, when returning attributes accessed
through normal __getattr__ (the a.b syntax). AttrWrap behaves and
acts like a normal Python list with the exception that when printed
as a string (ie, with 'dtml-var' or Python 'str()' or '"%s"'), it
printes the results as a comma seperated list. This makes DTML
representations of Entry object significantly easier. When using
the Entry method 'get()', the attribute is returned as a Python
list as returned by PythonLDAP. But if you're wanting to do tests
based on attributes, you have to *remember to qualify the
attribute*, ie::
if entry.sn[0] == 'Shell': # Works
if entry.sn == 'Shell': # Won't work
And also remember that 'getattr' based access returns *AttrWrap*
instances, which means the following::
if entry.get('sn') == ['Shell']: # Works
if entry.sn == ['Shell']: # Won't work
2. Changing attribute values
Changing attributes on Entry objects is protected by the permission
**Manage Entry Information**. This permission can be set anywhere
in Zope, and will work best when set in the folder containing the
LDAP Filter used to access the Entry object. The methods exposed
under this permission are:
**set(key, value)** -- example: 'entry.set("sn", "Shell")',
'entry.set("mail", ["foo@bar.com", "foo@baz.net"])'
**setattrs([kwdict, kw])** -- Takes either a mapping argument or
keyword arguments. Or both. Example:
'entry.setattrs({"sn": "Bazzo"}, mail=["foo@bar.com,
"foo@baz.net"])'
**setAll([kwdict, kw])** -- Same as *setattrs*, except that the
existing data is *replaced* by what's passed in here.
**remove(attr)** -- Deletes the attribute, example:
'entry.remove("comments")'
3. Accessing subentries
Attributes on Entry objects are available through the Python
'getattr' protocol, and subentries are available through the
'getitem' protocol via their Relative Distinguished Name (RDN).
Meaning if you had an Organizational Unit entry for 'Housewares'
and wanted to get the subentry '"Betty Ford"', it would appear like
this (considering 'housewares' is the Entry object)::
betty = housewares["cn=Betty Ford"]
3.1. Adding subentries
Adding subentries to an Entry object is done through the
'addSubentry' method. Adding subentries is protected by the
permission **Create New Entry Objects**.
**addSubentry(rdn, [attrs,kw])** -- Add a new subentry identified by
the rdn. The rdn **MUST** be a string in the form 'attr=value',
such as 'ou=Housewares' or 'cn=Davy Jones'. The rest of the
signature can be a combination of a dictionary of attributes
passed in to 'attrs' or keyword arguments. If no 'objectClass'
attribute is passed in, the default objectClass is 'top'. The
newly created Entry object is returned, wrapped in the acquisition
context of its parent. Some example uses are::
betty = housewares.addSubentry("cn=Betty Ford", {
"objectClass": ["top", "person"],
"sn": "Ford",
})
clinic = betty.addSubentry("sn=Clinic",
objectClass=["top","place"],
description="A good place to go..."
)
3.2. Deleting subentries
Deleting subentries from an Entry object is done through the
'deleteSubentry' method. Deleting subentries is protected by the
permission **Delete Entry Objects**.
**deleteSubentry(entry)** -- You can either delete a subentry by
its 'rdn', or by passing in the subentry object itself. Deletion
is recursive and will delete everything below the specified entry
from the LDAP server as well as the Entry object itself. Some
examples uses are::
betty.deleteSubentry("sn=Clinic") # Delete by rdn (local id)
housewares.deleteSubentry(betty) # Delete by entry itself
4. Other Zope Interfaces supported
Entry objects are a combination of classes: The basic Entry class
(either GenericEntry or TransactionalEntry at present), and one
called Entry, which implements some Zope level Interfaces (of the
programming kind). These include::
- '__bobo_traverse__' -- To traverse to subentries along the URL.
- The Tree Protocol -- Allows Entry objects to be used
automatically with the 'dtml-tree' tag.
- 'objectValues()' -- Returns all of the subentries in a list.
- 'objectIds()' -- returns all of the RDN's of the subentries.
- 'objectItems()' -- returns a list of tuples in the form of
('rdn', 'entry object').
\ No newline at end of file
ZopeLDAP README
ZopeLDAP is based off of work done by Anthony Baxter and also
Maurice Davice and Scott Robertson at CodeIt. It is an attempt to
make LDAP behave more like Zope objects.
It needs David Leonard's ldapmodule, from http://python-ldap.sourceforge.net/
and the compiled module needs to be (naturally) in your PYTHONPATH
(or in ZLDAPConnection/).
It's been tested against the OpenLDAP stable server release, as at
March 14, 1999 and Nov 2, 1999.
IMPORTANT
There is a known bug in the transactional behavior of the LDAP
Connection object, and as of 1.1.0 this feature can be turned off.
The bug could put your ZODB into a nasty state due to a failed
transaction (usually fixed by just restarting Zope), so it is
recommended you run with the Transactional ability turned off. *This
bug only occurs when updating more than one Entry object in a single
transaction space*.
Features
o Ability to browse an LDAP database like you would browse normal
folders.
o In 1.1.0, however, the Transactional behavior may be turned off.
This could speed things up for read-only situations, and is more
stable than the transactional one.
o Entry objects obey the rules of Acquisition.
o In the Zope management interface, LDAPConnections and their
Entries may be browsed.
o LDAP Filters provide another way of accessing Entry
objects. They behave in a similar fashion to ZSQL Methods, but
they are *read-only*. There is no current LDAP Spec for
update/insert type queries. *see Caveats below*
o Improved Entry object API that is Python Script friendly. For
updating\adding\deleting new Entry objects, LDAP Filters (to
retrieve entries) and Python Scripts (to update) go nicely together.
Caveats
o Lack of stunning documentation.
o The only way to strongly protect Entry objects from being written is
to use a connection name/password to the LDAP Server that does not
have any write permissions. Zope security permissions can also be
used.
o It currently only supports simple_bind for connecting to the
server.
o All Entry attributes come back in the form of a list of strings.
This is how the LDAP Module (and presumably LDAP in general) does
this. Attributes accessed through __getattr__ (like dtml-var
accesses) come back as an instance of AttrWrap which subclasses
UserList and whose str() return is a comma seperated list. (This
should prevent needing to do
'(dtml-in mail)(dtml-var sequence-item)(/dtml-in)' on every
attribute, especially where one value is expected.
Known Bugs
o Transactional Behavior breaks when updating more than one Entry
object per LDAP Connection in a single transaction. This behavior
can put the ZODB into a bad state since it fails during the
two-phase commit, however restarting Zope tends to return things to
normal.
Special Thanks
o Jens Vagelpohl (jens@digicool.com) for getting the pointy-hairs to
give me time to make 1.1.0 finally happen.
o Anthony Baxter (anthony@interlink.com.au) for most of the original work
o Scott Robertson (sropertson@codeit.com) and Maurice Davice
(mdavis@codeit.com) for theirs too.
o David Leonard for his LDAP Module and for keeping it pretty much
in alignment with the RFC (rfc1823).
Author:
Jeffrey P Shell (jeffrey@Digicool.com)
Original Authors:
Anthony Baxter (anthony@interlink.com.au)
Maurice Davice (mdavis@codeit.com)
Scott Robertson (srobertson@codeit.com)
"""
An LDAP connection product. Depends on David Leonard's ldapmodule.
$Id: ZLDAP.py,v 1.11 2000/12/18 22:17:50 jeffrey Exp $
Started by Anthony Baxter, <anthony@interlink.com.au>
Continued by the folks @ CodeIt <http://www.codeit.com/>
Now by Jeffrey P Shell <jeffrey@Digicool.com>.
"""
__version__ = "$Revision: 1.11 $"[11:-2]
import Acquisition, AccessControl, OFS, string
from Globals import HTMLFile, MessageDialog, Persistent
import ldap, urllib
import LDCAccessors
from Entry import ZopeEntry, GenericEntry, TransactionalEntry
ConnectionError='ZLDAP Connection Error'
manage_addZLDAPConnectionForm = HTMLFile('add', globals())
class NoBrainer:
""" Empty class for mixin to EntryFactory """
class ZLDAPConnection(
Acquisition.Implicit,
Persistent, OFS.SimpleItem.Item,
LDCAccessors.LDAPConnectionAccessors,
AccessControl.Role.RoleManager):
'''LDAP Connection Object'''
isPrincipiaFolderish=1
meta_type='LDAP Connection'
manage_options=(
{'label':'Connection Properties','action':'manage_main'},
{'label':'Open/Close','action':'manage_connection'},
{'label':'Browse', 'action':'manage_browse'},
{'label':'Security','action':'manage_access'},
)
__ac_permissions__=(
('Access contents information',
('canBrowse',),),
('View management screens',('manage_tabs','manage_main'),
('Manager',)),
('Edit connection',('manage_edit',),('Manager',)),
('Change permissions',('manage_access',)),
('Open/Close Connection',('manage_connection',
'manage_open','manage_close',),
('Manager',)),
('Browse Connection Entries', ('manage_browse',),('Manager',),),
)
manage_browse=HTMLFile('browse',globals())
manage_connection=HTMLFile('connection',globals())
### dealing with browseability on the root.
def canBrowse(self):
""" Returns true if the connection is open *and* the '_canBrowse'
property is set to true """
return self.shouldBeOpen() and self.getBrowsable()
### constructor
def __init__(self, id, title, host, port, basedn, bind_as, pw, openc,
transactional=1):
"init method"
self._v_conn = None
self._v_delete = []
self._v_openc = openc
self.openc = openc
self.setId(id)
self.setTitle(title)
self.setHost(host)
self.setPort(port)
self.setBindAs(bind_as)
self.setPW(pw)
self.setDN(basedn)
self.setOpenConnection(openc)
self.setTransactional(transactional)
# if connection is specified to be open, open it up
if openc: self._open()
### upgrade path...
def __setstate__(self, state):
# Makes sure we have an Entry class now
self._refreshEntryClass()
Persistent.__setstate__(self, state)
### Entry Factory stuff
def _refreshEntryClass(self):
""" This class sets up the Entry class used to return results. """
transactional = self.getTransactional()
if transactional:
EntryBase = TransactionalEntry
else:
EntryBase = GenericEntry
class LdapEntry(EntryBase, ZopeEntry):
pass
self._v_entryclass = LdapEntry
return LdapEntry
def _EntryFactory(self):
""" Stamps out an Entry class to be used for every entry returned,
taking into account transactional versus non-transactional """
return getattr(self, '_v_entryclass', self._refreshEntryClass())
### Tree stuff
def __bobo_traverse__(self, REQUEST, key):
key=urllib.unquote(key)
if hasattr(self, key):
return getattr(self, key)
return self.getRoot()[key]
def tpId(self):
return self.id
def tpURL(self):
return self.id
def tpValues(self):
if self.canBrowse():
return self.getRoot().tpValues()
else:
return []
#### TransactionalObjectManager stuff #####
def tpc_begin(self,*ignored):
#make sure we're open!
if not self.__ping(): #we're not open
raise (ConnectionError,
'LDAP Connection is not open for commiting')
self._v_okobjects=[]
def commit(self, o, *ignored):
' o = object to commit '
# check to see if object exists
oko=[]
if self.hasEntry(o.dn):
oko.append(o)
elif o._isNew or o._isDeleted:
oko.append(o)
self._v_okobjects=oko
def tpc_finish(self, *ignored):
" really really commit and DON'T FAIL "
oko=self._v_okobjects
self._isCommitting=1
d=getattr(self,'_v_delete',[])
for deldn in d: self._deleteEntry(deldn)
self._v_delete=[]
for o in oko:
try:
if o._isDeleted:
pass
# we shouldn't need to do anything now that
# the mass delete has happened
elif o._isNew:
self._addEntry(o.dn, o._data.items())
o._isNew=0
del self._v_add[o.dn]
else:
o._modify()
o._registered=0
except:
pass #XXX We should log errors here
del self._v_okobjects
del self._isCommitting
self.GetConnection().destroy_cache()
def tpc_abort(self, *ignored):
" really really rollback and DON'T FAIL "
try:
self._abort()
except:
pass #XXX We should also log errors here
def abort(self, o, *ignored):
if o.dn in getattr(self,'_v_delete',()):
self._v_delete.remove(o.dn)
if o._isDeleted: o.undelete()
o._rollback()
o._registered=0
if o._isNew:
if o.dn in getattr(self,'_v_add',{}).keys():
del self._v_add[o.dn]
self.GetConnection().destroy_cache()
def _abort(self):
oko=self._v_okobjects
for o in oko:
self.abort(o)
self.GetConnection().destroy_cache()
def tpc_vote(self, *ignored):
pass
### getting entries and attributes
def hasEntry(self, dn):
if getattr(self, '_v_add',{}).has_key(dn):
#object is marked for adding
return 1
elif dn in getattr(self,'_v_delete',()):
#object is marked for deletion
return 0
try:
e=self._connection().search_s(dn, ldap.SCOPE_BASE,
'objectclass=*')
if e: return 1
except ldap.NO_SUCH_OBJECT:
return 0
return 0
def getRawEntry(self, dn):
" return raw entry from LDAP module "
if getattr(self, '_v_add',{}).has_key(dn):
return (dn, self._v_add[dn]._data)
elif dn in getattr(self,'_v_delete',()):
raise ldap.NO_SUCH_OBJECT, "Entry '%s' has been deleted" % dn
try:
e=self._connection().search_s(
dn, ldap.SCOPE_BASE, 'objectclass=*'
)
if e: return e[0]
except:
raise ldap.NO_SUCH_OBJECT, "Cannot retrieve entry '%s'" % dn
def getEntry(self, dn, o=None):
" return **unwrapped** Entry object, unless o is specified "
Entry = self._EntryFactory()
if getattr(self, '_v_add',{}).has_key(dn):
e=self._v_add[dn]
else:
e=self.getRawEntry(dn)
e=Entry(e[0],e[1],self)
if o is not None:
return e.__of__(o)
else:
return e
def getRoot(self):
" return root entry object "
return self.getEntry(self.dn, self)
def getAttributes(self, dn):
" get raw attributes from entry from LDAP module "
return self.getRawEntry(dn)[1]
### listing subentries
def getRawSubEntries(self, dn):
" get the raw entry objects of entry dn's immediate children "
# XXX Do something soon to account for added but noncommited..?
if dn in getattr(self,'_v_delete',()):
raise ldap.NO_SUCH_OBJECT
results=self._connection().search_s(
dn, ldap.SCOPE_ONELEVEL, 'objectclass=*')
r=[]
for entry in results:
#make sure that the subentry isn't marked for deletion
if entry[0] not in getattr(self, '_v_delete',()):
r.append(entry)
return r
def getSubEntries(self, dn, o=None):
Entry = self._EntryFactory()
r=[]
se=self.getRawSubEntries(dn)
for entry in se:
e=Entry(entry[0],entry[1],self)
if o is not None:
e=e.__of__(o)
r.append(e)
return r
### modifying entries
def _modifyEntry(self, dn, modlist):
if not getattr(self,'_isCommitting',0):
raise AccessError, 'Cannot modify unless in a commit'
#someone's trying to be sneaky and modify an object
#outside of a commit. We're not going to allow that!
c=self._connection()
c.modify_s(dn, modlist)
### deleting entries
def _registerDelete(self, dn):
" register DN for deletion "
d=getattr(self,'_v_delete',[])
if dn not in d:
d.append(dn)
self._v_delete=d
def _unregisterDelete(self, dn):
" unregister DN for deletion "
d=getattr(self, '_v_delete',[])
if dn in d: d.remove(dn)
self._v_delete=d
self._unregisterAdd(dn)
def _deleteEntry(self, dn):
if not getattr(self, '_isCommitting',0):
raise AccessError, 'Cannot delete unless in a commit'
c=self._connection()
c.delete_s(dn)
### adding entries
def _registerAdd(self, o):
a=getattr(self, '_v_add',{})
if not a.has_key(o.dn):
a[o.dn]=o
self._v_add=a
def _unregisterAdd(self, o=None, dn=None):
a=getattr(self, '_v_add',{})
if o and o in a.values():
del a[o.dn]
elif dn and a.has_key(dn):
del a[dn]
self._v_add=a
def _addEntry(self, dn, attrs):
if not getattr(self, '_isCommitting',0):
raise AccessError, 'Cannot add unless in a commit'
c=self._connection()
c.add_s(dn, attrs)
### other stuff
def title_and_id(self):
"title and id, with conn state"
s=ZLDAPConnection.inheritedAttribute('title_and_id')(self)
if self.shouldBeOpen():
s="%s (connected)" % s
else:
s='%s (<font color="red"> not connected</font>)' % s
return s
### connection checking stuff
def _connection(self):
if self.openc:
if not self.isOpen(): self._open()
return self._v_conn
else:
raise ConnectionError, 'Connection Closed'
GetConnection=_connection
def isOpen(self):
" quickly checks to see if the connection's open "
if not hasattr(self, '_v_conn'):
self._v_conn = None
if self._v_conn is None or not self.shouldBeOpen():
return 0
else:
return 1
def __ping(self):
" more expensive check on the connection and validity of conn "
try:
self._connection().search_s(self.dn,ldap.SCOPE_BASE,
'objectclass=*')
return 1
except:
self._close()
return 0
def _open(self):
""" open a connection """
try:
self._close()
except:
pass
self._v_conn = ldap.open(self.host, self.port)
#Nicolas the version of pythonldap doesn't use the enable_cache method
#self._v_conn.enable_cache()
try:
self._v_conn.simple_bind_s(self.bind_as, self.pw)
except ldap.NO_SUCH_OBJECT:
return """
Error: LDAP Server returned `no such object' for %s. Possibly
the bind string or password are incorrect"""%(self.bind_as)
self._v_openc = 1
def manage_open(self, REQUEST=None):
""" open a connection. """
self.setOpenConnection(1)
ret = self._open()
if not getattr(self, '_v_openc', 0):
return ret
if REQUEST is not None:
m='Connection has been opened.'
return self.manage_connection(self,REQUEST,manage_tabs_message=m)
def _close(self):
""" close a connection """
if self.getOpenConnection() == 0:
#I'm already closed, but someone is still trying to close me
self._v_conn = None
self._v_openc = 0
else:
try: self._v_conn.unbind_s()
except AttributeError: pass
self._v_conn = None
self._v_openc = 0
def manage_close(self, REQUEST=None):
""" close a connection. """
self._close()
if REQUEST is not None:
m='Connection has been closed.'
return self.manage_connection(self,REQUEST,manage_tabs_message=m)
def manage_clearcache(self, REQUEST=None):
""" clear the cache """
self._connection().destroy_cache()
if REQUEST is not None:
m='Cache has been cleared.'
return self.manage_connection(self,REQUEST,manage_tabs_message=m)
manage_main=HTMLFile("edit",globals())
def manage_edit(self, title, hostport, basedn, bind_as, pw, openc=0,
canBrowse=0, transactional=1, REQUEST=None):
""" handle changes to a connection """
self.title = title
host, port = splitHostPort(hostport)
if self.host != host:
self._close()
self.setHost(host)
if self.port != port:
self._close()
self.setPort(port)
if self.bind_as != bind_as:
self._close()
self.setBindAs(bind_as)
if self.pw != pw:
self._close()
self.setPW(pw)
if openc and not self.getOpenConnection():
self.setOpenConnection(1)
ret = self._open()
if not self._v_openc:
return ret
if not openc and self.getOpenConnection():
self.setOpenConnection(0)
self._close()
self.setBrowsable(canBrowse)
self.setTransactional(transactional)
self.setDN(basedn)
if REQUEST is not None:
return MessageDialog(
title='Edited',
message='<strong>%s</strong> has been edited.' % self.id,
action ='./manage_main',
)
def _isAnLDAPConnection(self):
return 1
def splitHostPort(hostport):
import string
l = string.split(hostport,':')
host = l[0]
if len(l) == 1:
port = 389
else:
port = string.atoi(l[1])
return host, port
def manage_addZLDAPConnection(self, id, title, hostport,
basedn, bind_as, pw, openc,
REQUEST=None):
"""create an LDAP connection and install it"""
host, port = splitHostPort(hostport)
conn = ZLDAPConnection(id, title, host, port, basedn, bind_as, pw, openc)
self._setObject(id, conn)
if REQUEST is not None:
return self.manage_main(self, REQUEST)
"""LDAP Server Connection Package """
import ZLDAP, Entry
__version__ = ZLDAP.__version__
# use the propert product registration
def initialize(context):
context.registerClass(
ZLDAP.ZLDAPConnection,
constructors = (ZLDAP.manage_addZLDAPConnectionForm,
ZLDAP.manage_addZLDAPConnection),
icon = 'LDAP_conn_icon.gif',
permissions = ('Manage Entry information',
'Create New Entry Objects',
),
)
<html>
<head><title>Add LDAP connection</title></head>
<body bgcolor="#FFFFFF">
<h2>Add LDAP connection</h2>
<form action="manage_addZLDAPConnection" method="POST">
<table cellspacing="2">
<tr>
<th align="LEFT" valign="TOP">Id</th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="id" size="50">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Title</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="title" size="50">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>LDAP Server (host[:port])</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="hostport" size="50">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Base DN</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="basedn" size="50">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Bind As</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="bind_as" size="50">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Bind Password</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="pw" size="50">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Open Connection?</em></th>
<td align="LEFT" valign="TOP">
<input type="CHECKBOX" name="openc" CHECKED>
</td>
</tr>
<tr>
<th align="left" valign="top"><em>Transactional?</em></th>
<td align="left" valign="top">
<input type="checkbox" name="transactional:int" value="1" checked>
<input type="hidden" name="transactional:default:int" value="0">
</td>
</tr>
<tr>
<td></td>
<td><br><input type="SUBMIT" value="Add"></td>
</tr>
</table>
</form>
</body>
</html>
<html>
<head>
<title>Attributes for &dtml-dn;</title>
</head>
<body bgcolor="#FFFFFF">
<dtml-var manage_tabs>
<h3>Attributes for <em>&dtml-id;</em></h3>
<table border="1" cellpadding="2" cellspacing="0" rules="rows" frame="void">
<dtml-in attributesMap>
<tr valign="top">
<th align="left">&dtml-sequence-key;</th>
<td><dtml-in sequence-item>&dtml-sequence-item;<br /></dtml-in></td>
</tr>
</dtml-in>
</table>
<hr />
<h3>Subentries</h3>
<dtml-if tpValues>
<dtml-tree>
<a href="&dtml-tree-item-url;/manage_attributes"
title="click to view this entry">&dtml-id;</a>
</dtml-tree>
<dtml-else>
<p><em>No subentries for &dtml-id;</em></p>
</dtml-if>
</body>
</html>
\ No newline at end of file
<html>
<head>
<title>Browse &dtml-title_and_id;</title>
</head>
<body bgcolor="#ffffff">
<dtml-var manage_tabs>
<dtml-if name="canBrowse">
<dtml-with name="getRoot">
<h3>Attributes for <em>&dtml-id;</em></h3>
<table border="1" cellpadding="2" cellspacing="0" rules="rows" frame="void">
<dtml-in name="attributesMap">
<tr valign="top">
<th align="left">&dtml-sequence-key;</th>
<td><dtml-in name="sequence-item">&dtml-sequence-item;<br /></dtml-in></td>
</tr>
</dtml-in>
</table>
</dtml-with>
<hr />
<h3>Subentries</h3>
<dtml-tree name="getRoot">
<a href="&dtml-tree-item-url;/manage_attributes"
title="click to view this entry">&dtml-id;</a>
</dtml-tree>
<dtml-else>
<p><em>Connection to <code>&dtml-host;:&dtml-port;</code> is
<dtml-if name="openc">not browsable<dtml-else>closed</dtml-if>.</em></p>
</dtml-if>
</body>
</html>
<html>
<head>
<title>Connection for &dtml-title_or_id;</title>
<style type="text/css"><!--
.open {color: blue}
.close {color: red}
--></style>
</head>
<body bgcolor="#FFFFFF">
<dtml-var manage_tabs>
<h2>Connection to <code>&dtml-host;:&dtml-port;</code> is
<dtml-if isOpen><span class="open"><em>open</em></span>
<dtml-else><span class="close"><em>closed</em></span>
</dtml-if>.</h2>
<dtml-if isOpen>
<form action="manage_close">
<input type="submit" value="Close Connection" class="close" />
</form>
<dtml-else>
<form action="manage_open">
<input type="submit" value="Open Connection" class="open" />
</form>
</dtml-if>
</body>
</html>
\ No newline at end of file
<HTML>
<HEAD>
<TITLE>Contents</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<!--#var manage_tabs-->
<P>
<FORM ACTION="." METHOD="POST">
<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="2">
<!--#in "tpValues()" -->
<TR>
<TD ALIGN="LEFT" VALIGN="TOP" WIDTH="16">
<INPUT TYPE="CHECKBOX" NAME="ids:list" VALUE="<!--#var dn-->">
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<A HREF="<!--#var id fmt=url-quote-->/manage_main">
<IMG SRC="<!--#var SCRIPT_NAME-->/<!--#var icon-->"
ALT="[Entry]" BORDER="0"></A>
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<A HREF="<!--#var id fmt=url-quote-->/manage_main"><!--#var id--></A>
</TD>
</TR>
<!--#/in-->
</TABLE>
<TABLE BORDER="0" CELLSPACING="0" CELLPADDING=2>
<TR>
<TD ALIGN="LEFT" VALIGN="TOP" WIDTH="16"></TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<!--#if "tpValues()"-->
<INPUT TYPE="SUBMIT" NAME="manage_deleteEntry:method" VALUE="Delete">
<!--#/if-->
</TD>
</TR>
</TABLE>
</FORM>
<a name="addentryform">
<form action="." method="POST">
<p>
To add a new entry, enter an RDN (Relative Distinguished Name) for the entry
to appear under the above DN (Distinguished Name) and click the &quot;Add New Entry&quot;
button. For example enter &quot;uid=spam&quot; for the RDN. An attribute of &quot;uid: spam&quot;
will be automatically added to the entry. Other attributes may be entered by using the attribute management screens.
</p>
<table>
<tr>
<th align="left" valign="top">RDN</th>
<td align="left" valign="top"><input type="text" name="rdn" size="20"></td>
<td align="right" valign="top">
<INPUT TYPE="SUBMIT" NAME="manage_newEntry:method" VALUE="Add New Entry">
</td>
</tr>
</table>
</form>
<br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br>
</BODY>
</HTML>
<html>
<head><title>Edit LDAP connection</title></head>
<body bgcolor="#FFFFFF">
<dtml-var name="manage_tabs">
<h2>Edit LDAP connection <dtml-var name="title_and_id"></h2>
<form action="manage_edit" method="POST">
<table cellspacing="2">
<tr>
<th align="LEFT" valign="TOP">Id</th>
<td align="LEFT" valign="TOP">
<dtml-var name="getId">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Title</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="title" size="50" value="&dtml-getTitle;">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>LDAP server (host[:port])</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="hostport" size="50" value="&dtml-getHost;:&dtml-getPort;">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Base DN</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="basedn" size="50" value="&dtml-getDN;">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Bind As</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="bind_as" size="50" value="&dtml-getBindAs;">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Bind Password</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="pw" size="50" value="&dtml-getPW;">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em><label for="cb-openc">Open Connection?</label></em></th>
<td align="LEFT" valign="TOP">
<input type="CHECKBOX" name="openc" <dtml-if name="shouldBeOpen">CHECKED</dtml-if> id="cb-openc" value="1">
</td>
</tr>
<tr>
<th align="left" valign="top"><em><label for="cb-canBrowse">Make connection object browsable?</label></em></th>
<td align="left" valign="top">
<input type="checkbox" name="canBrowse" value="1" <dtml-if name="getBrowsable">checked</dtml-if> id="cb-canBrowse">
</td>
</tr>
<tr>
<th align="left" valign="top"><em>
<label for="cb-transactional">Make connection and entries
transactional?</label></em></th>
<td align="left" valign="top">
<input type="checkbox" name="transactional:int" value="1"
<dtml-if name="getTransactional">checked</dtml-if>
id="cb-transactional">
<input type="hidden" name="transactional:default:int" value="0">
</td>
</tr>
<tr>
<td></td>
<td><br><input type="SUBMIT" value="Change"></td>
</tr>
</table>
</form>
</body>
</html>
<html>
<head><title>help for LDAP connections</title></head>
<body>
<h1>Help for LDAP connections</h1>
<b>Arguments are as follows:</b>
<dl>
<dt>LDAP server<dd>The hostname, and optionally the port, where the LDAP
server is running. The standard port for LDAP is 389 - this will be used
if no port is specified.
<dt>Bind As<dd>Who to bind to the LDAP server as. An example:
"cn=Anthony, o=ekorp.com, c=AU"
<dt>Password<dd>The password (if any) to be used to bind to the server.
</dl>
</body></html>
ZPL 2.1
Zope Public License (ZPL) Version 2.1
A copyright notice accompanies this license document that identifies the copyright holders.
This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF).
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders.
4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders.
5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# core of LDAP Filter Methods.
from Globals import HTMLFile, HTML
__version__ = "$Revision: 1.10 $"[11:-2]
try:
import ldap
from ldap import modlist
# see if it's on a regular path
except ImportError:
from Products.ZLDAPConnection import ldap
from Products.ZLDAPConnection.ldap import modlist
import string
from Shared.DC.ZRDB import Aqueduct
from Shared.DC.ZRDB.Aqueduct import parse, decodestring, default_input_form
from Shared.DC.ZRDB.Results import Results
import Acquisition, Globals, AccessControl.Role, OFS.SimpleItem
from Globals import HTMLFile, MessageDialog, Persistent
import DocumentTemplate
import ExtensionClass
import sys
from zLOG import LOG
from ldif import LDIFRecordList, is_dn, valid_changetype_dict, CHANGE_TYPES
import ldifvar
from AccessControl.DTML import RestrictedDTML
try:
from AccessControl import getSecurityManager
except ImportError:
getSecurityManager = None
MODIFY_MAPPING_DICT = {'add' : ldap.MOD_ADD,
#'replace' : ldap.MOD_REPLACE,
'delete' : ldap.MOD_DELETE}
class ERP5LDIFRecordList(LDIFRecordList):
def parse(self):
"""
Continously read and parse LDIF records
"""
self._line = self._input_file.readline()
while self._line and \
(not self._max_entries or self.records_read<self._max_entries):
# Reset record
version = None; dn = None; changetype = None; modop = None; entry = {};
attr_type,attr_value = self._parseAttrTypeandValue()
while attr_type!=None and attr_value!=None:
if attr_type=='dn':
# attr type and value pair was DN of LDIF record
if dn!=None:
raise ValueError, 'Two lines starting with dn: in one record.'
if not is_dn(attr_value):
raise ValueError, 'No valid string-representation of distinguished name %s.' % (repr(attr_value))
dn = attr_value
elif attr_type=='version' and dn is None:
version = 1
elif attr_type=='changetype':
# attr type and value pair was DN of LDIF record
if dn is None:
raise ValueError, 'Read changetype: before getting valid dn: line.'
if changetype!=None:
raise ValueError, 'Two lines starting with changetype: in one record.'
if not valid_changetype_dict.has_key(attr_value):
raise ValueError, 'changetype value %s is invalid.' % (repr(attr_value))
changetype = attr_value
attr_type, attr_value = self._parseAttrTypeandValue()
modify_list = []
entry[changetype] = []
while (attr_type and attr_value) is not None:
mod_op = MODIFY_MAPPING_DICT[attr_type]
mod_type = attr_value
multivalued_list = []
while attr_value is not None:
attr_type, attr_value = self._parseAttrTypeandValue()
if attr_value is not None:
multivalued_list.append(attr_value)
modify_list.append((mod_op, mod_type, multivalued_list))
entry[changetype] = [modify_list]
attr_type, attr_value = self._parseAttrTypeandValue()
#don't add new entry for the same dn
break
elif attr_value not in (None, '') and \
not self._ignored_attr_types.has_key(string.lower(attr_type)):
# Add the attribute to the entry if not ignored attribute
if entry.has_key(attr_type):
entry[attr_type].append(attr_value)
else:
entry[attr_type]=[attr_value]
# Read the next line within an entry
attr_type, attr_value = self._parseAttrTypeandValue()
if entry:
# append entry to result list
self.handle(dn, entry)
self.records_read = self.records_read+1
return # parse()
class Filter(DocumentTemplate.HTML):
"""
Subclass of DocumentTemplate.HTML for Variable Interpolation.
Special LDAP Specific tags would go here. Since there aren't
any (like ldapvar or ldaptest or whatever), we don't have to worry.
It's just nice to have a nice name that reflects what this is. :)
"""
pass
class nvLDIF(DocumentTemplate.HTML):
# Non-validating Ldif Template for use by LDIFFiles.
commands={}
for k, v in DocumentTemplate.HTML.commands.items(): commands[k] = v
commands['ldifvar' ] = ldifvar.LDIFVar
commands['ldifline' ] = ldifvar.LDIFLine
_proxy_roles=()
class Ldif(RestrictedDTML, ExtensionClass.Base, nvLDIF):
pass
def LDAPConnectionIDs(self):
"""find LDAP connections in the current folder and parents
Returns list of ids.
"""
ids={}
StringType = type('')
have_key = ids.has_key
while self is not None:
if hasattr(self, 'objectValues'):
for o in self.objectValues():
if (hasattr(o,'_isAnLDAPConnection')
and o._isAnLDAPConnection() and hasattr(o,'id')):
id=o.id
if type(id) is not StringType: id=id()
if not ids.has_key(id):
if hasattr(o,'title_and_id'): o=o.title_and_id()
else: o=id
ids[id]=id
if hasattr(self, 'aq_parent'): self=self.aq_parent
else: self=None
ids=map(lambda item: (item[1], item[0]), ids.items())
ids.sort()
return ids
manage_addZLDAPMethodForm = HTMLFile('add', globals())
def manage_addZLDAPMethod(self, id, title, connection_id, scope, basedn,
filters, arguments, getfromconnection=0,
REQUEST=None, submit=None):
"""Add an LDAP Method """
l=LDAPMethod(id, title, connection_id, scope, basedn,
arguments, filters)
self._setObject(id, l)
if getfromconnection:
getattr(self,id).recomputeBaseDN()
if REQUEST is not None:
u=REQUEST['URL1']
if submit==" Add and Edit ":
u="%s/%s/manage_main" % (u,id)
elif submit==" Add and Test ":
u="%s/%s/manage_testForm" % (u,id)
else:
u=u+'/manage_main'
REQUEST.RESPONSE.redirect(u)
return ''
_ldapScopes = { "ONELEVEL": ldap.SCOPE_ONELEVEL,
"SUBTREE": ldap.SCOPE_SUBTREE,
"BASE": ldap.SCOPE_BASE }
class LDAPMethod(Aqueduct.BaseQuery,
Acquisition.Implicit,
Globals.Persistent,
AccessControl.Role.RoleManager,
OFS.SimpleItem.Item,
):
'LDAP Method'
meta_type = 'LDAP Method'
manage_main = HTMLFile('edit', globals())
manage_options = (
{'label':'Edit', 'action':'manage_main'},
{'label':'Test', 'action':'manage_testForm'},
{'label':'Security', 'action':'manage_access'},
)
__ac_permissions__=(
('View management screens', ('manage_tabs','manage_main',),),
('Change LDAP Methods', ('manage_edit',
'manage_testForm','manage_test')),
('Use LDAP Methods', ('__call__',''), ('Anonymous','Manager')),
)
#manage_testForm = HTMLFile("testForm", globals())
def manage_testForm(self, REQUEST):
" "
input_src=default_input_form(self.title_or_id(),
self._arg, 'manage_test',
'<!--#var manage_tabs-->')
return DocumentTemplate.HTML(input_src)(self,REQUEST,HTTP_REFERER='')
def __init__(self, id, title, connection_id, scope, basedn,
arguments, filters):
""" init method """
self.id = id
self.title = title
self.connection_id = connection_id
self._scope = _ldapScopes[scope]
self.scope = scope
self.basedn = basedn
self.arguments_src=self.arguments=arguments
self._arg=parse(arguments)
self.filters = filters
def recomputeBaseDN(self):
' recompute base DN based on connection '
cdn=self._connection().dn
if self.basedn:
self.basedn='%s, %s' % (self.basedn, cdn)
else:
self.basedn=cdn
return self.basedn
def manage_edit(self, title, connection_id, scope, basedn,
arguments, filters, REQUEST=None):
""" commit changes """
self.title = title
self.connection_id = connection_id
self._scope = _ldapScopes[scope]
self.scope = scope
self.basedn = basedn
self.arguments_src=self.arguments=arguments
self._arg=parse(arguments)
self.filters = filters
if REQUEST is not None:
return MessageDialog(
title='Edited',
message='<strong>%s</strong> has been changed.' % self.id,
action ='./manage_main', )
def cleanse(self,s):
import string
# kill line breaks &c.
s = string.join(string.split(s))
return s
def _connection(self):
' return actual ZLDAP Connection Object '
if hasattr(self,'connection_id') and hasattr(self,self.connection_id):
return getattr(self, self.connection_id)
def _getConn(self):
return self._connection().GetConnection()
# Hacky, Hacky
GetConnection=_getConn
def manage_test(self, REQUEST):
""" do the test query """
src="Could not render the filter template!"
res=()
t=v=tb=None
try:
try:
src=self(REQUEST, src__=1)
res=self(REQUEST, tst__=1)
r=self.prettyResults(res)
except:
t, v, tb = sys.exc_info()
r='<strong>Error, <em>%s</em>:</strong> %s' % (t,v)
report=DocumentTemplate.HTML(
'<html><body bgcolor="#ffffff">\n'
'<!--#var manage_tabs-->\n<hr>%s\n\n'
'<hr><strong>Filter used:</strong><br>\n<pre>\n%s\n</pre>\n<hr>\n'
'</body></html>' % (r, src)
)
report=apply(report,(self,REQUEST),{self.id:res})
if tb is not None:
self.raise_standardErrorMessage(
None, REQUEST, t, v, tb, None, report)
return report
finally: tb=None
def prettyResults(self, res):
s = ""
if not res or not len(res):
s = "no results"
else:
for dn,attrs in res:
s = s + ('<ul><li><b>DN: %s</b></li>\n<ul>' % dn)
s = s + str(pretty_results(attrs=attrs.items()))
s = s + '</ul></ul>'
return s
def __call__(self, REQUEST=None, src__=0, tst__=0, **kw):
""" call the object """
if REQUEST is None:
if kw: REQUEST = kw
else:
if hasattr(self, 'REQUEST'): REQUEST=self.REQUEST
else: REQUEST={}
c = self._getConn()
if not c:
raise "LDAPError", "LDAP Connection not open"
if hasattr(self, 'aq_parent'):
p = self.aq_parent
else: p = None
argdata = self._argdata(REQUEST) #use our BaseQuery's magic. :)
# Also need the authenticated user.
auth_user = REQUEST.get('AUTHENTICATED_USER', None)
if auth_user is None:
auth_user = getattr(self, 'REQUEST', None)
if auth_user is not None:
try: auth_user = auth_user.get('AUTHENTICATED_USER', None)
except: auth_user = None
if auth_user is not None:
if getSecurityManager is None:
# working in a pre-Zope 2.2.x instance
from AccessControl.User import verify_watermark
verify_watermark(auth_user)
argdata['AUTHENTICATED_USER'] = auth_user
f = Filter(self.filters) # make a FilterTemplate
f.cook()
if getSecurityManager is None:
# working in a pre-Zope 2.2 instance
f = apply(f, (p,argdata)) #apply the template
else:
# Working with the new security manager (Zope 2.2.x ++)
security = getSecurityManager()
security.addContext(self)
try: f = apply(f, (p,), argdata) # apply the template
finally: security.removeContext(self)
f = str(f) #ensure it's a string
if src__: return f #return the rendered source
f = self.cleanse(f)
### run the search
res = c.search_s(self.basedn, self._scope, f)
if tst__: return res #return test-friendly data
### instantiate Entry objects based on results
l = [] #list of entries to return
conn=self._connection() #ZLDAPConnection
Entry = conn._EntryFactory()
for dn, attrdict in res:
e = Entry(dn, attrdict, conn).__of__(self)
l.append(e)
return l
manage_addZLDIFMethodForm = HTMLFile('addLdif', globals())
def manage_addZLDIFMethod(self, id, title, connection_id, basedn, arguments, ldif, getfromconnection=0, REQUEST=None, submit=None):
"""Add an LDIF Method """
l=LDIFMethod(id, title, connection_id, basedn, arguments, ldif)
self._setObject(id, l)
if getfromconnection:
getattr(self,id).recomputeBaseDN()
if REQUEST is not None:
u=REQUEST['URL1']
if submit == " Add and Edit ":
u = "%s/%s/manage_main" % (u, id)
elif submit == " Add and Test ":
u = "%s/%s/manage_testForm" % (u, id)
else:
u = u + '/manage_main'
REQUEST.RESPONSE.redirect(u)
return ''
class LDIFMethod(LDAPMethod):
'LDIF Method'
meta_type = 'LDIF Method'
manage_main = HTMLFile('editLdif', globals())
manage_options = (
{'label':'Edit', 'action':'manage_main'},
{'label':'Test', 'action':'manage_testForm'},
{'label':'Security', 'action':'manage_access'},
)
__ac_permissions__=(
('View management screens', ('manage_tabs', 'manage_main',),),
('Change LDAP Methods', ('manage_edit',
'manage_testForm', 'manage_test')),
('Use LDAP Methods', ('__call__', ''), ('Anonymous', 'Manager')),
)
#manage_testForm = HTMLFile("testLdifForm", globals())
def __init__(self, id, title, connection_id, basedn, arguments, ldif, **kw):
""" init method """
self.id = id
self.title = title
self.connection_id = connection_id
self.basedn = basedn
self.arguments_src=self.arguments=arguments
self._arg=parse(arguments)
self.ldif = str(ldif)
def manage_edit(self, title, connection_id, basedn, arguments, ldif, REQUEST=None, **kw):
""" commit changes """
self.title = title
self.connection_id = connection_id
self.basedn = basedn
self.arguments_src = self.arguments = arguments
self._arg = parse(arguments)
self.ldif = str(ldif)
if REQUEST is not None:
return MessageDialog(
title = 'Edited',
message = '<strong>%s</strong> has been changed.' % self.id,
action = './manage_main', )
def __call__(self, REQUEST=None, src__=0, tst__=0, **kw):
""" call the object """
if REQUEST is None:
if kw: REQUEST = kw
else:
if hasattr(self, 'REQUEST'): REQUEST = self.REQUEST
else: REQUEST={}
c = self._connection().GetConnection()
if not c:
raise "LDAPError", "LDAP Connection not open"
if hasattr(self, 'aq_parent'):
p = self.aq_parent
else: p = None
argdata = self._argdata(REQUEST) #use our BaseQuery's magic. :)
# Also need the authenticated user.
auth_user = REQUEST.get('AUTHENTICATED_USER', None)
if auth_user is None:
auth_user = getattr(self, 'REQUEST', None)
if auth_user is not None:
try: auth_user = auth_user.get('AUTHENTICATED_USER', None)
except: auth_user = None
if auth_user is not None:
if getSecurityManager is None:
# working in a pre-Zope 2.2.x instance
from AccessControl.User import verify_watermark
verify_watermark(auth_user)
argdata['AUTHENTICATED_USER'] = auth_user
ldif = Ldif(self.ldif) # make a FilterTemplate
ldif.cook()
if getSecurityManager is None:
# working in a pre-Zope 2.2 instance
ldif = apply(ldif, (p, argdata)) #apply the template
else:
# Working with the new security manager (Zope 2.2.x ++)
security = getSecurityManager()
security.addContext(self)
try: ldif = apply(ldif, (p,), argdata) # apply the template
finally: security.removeContext(self)
ldif = str(ldif) #ensure it's a string
#LOG('ldif', 0, ldif)
if src__: return ldif #return the rendered source
### Apply Query
from cStringIO import StringIO
file = StringIO(ldif)
l = ERP5LDIFRecordList(file)
l.parse()
res = l.all_records
for record in res:
dn = record[0]
entry = record[1]
if type(entry) == type({}):
authorized_modify_key = [key for key in entry.keys() if key in CHANGE_TYPES]
if len(authorized_modify_key):
for key in authorized_modify_key:
tuple_list = entry[key]
if key == 'delete':
try:
c.delete_s(dn)
except ldap.NO_SUCH_OBJECT:
pass
#LOG('LDIFMethod can\'t delete NO SUCH OBJECT',0,dn)
else:
for mod_tuple in tuple_list:
c.modify_s(dn, mod_tuple)
else:
mod_list = modlist.addModlist(entry)
try:
c.add_s(dn, mod_list)
except ldap.ALREADY_EXISTS:
pass
#LOG('LDIFMethod can\'t add, entry allready exists',0,dn)
#except ldap.SERVER_DOWN:
#c = self._connection().GetConnection()
#try:
#c.add_s(dn, mod_list)
#except ldap.ALREADY_EXISTS:
#pass
else:
LOG('LDIFMethod Type unknow',0,'')
return res
class LDAP(LDAPMethod):
"backwards compatibility. blech. XXX Delete Me!"
pretty_results=DocumentTemplate.HTML("""\
<table border="1" cellpadding="2" cellspacing="0" rules="rows" frame="void">
<dtml-in attrs>
<tr valign="top">
<th align="left">&dtml-sequence-key;</th>
<td><dtml-in name="sequence-item">&dtml-sequence-item;<br /></dtml-in></td>
</tr>
</dtml-in>
</table>""")
import Globals
Globals.default__class_init__(LDAPMethod)
Globals.default__class_init__(LDIFMethod)
\ No newline at end of file
Quick README on ZLDAP Filter Methods
Current
ZLDAP Filter Methods are a way of executing LDAP Searches according
to RFC1558 'A String Representation of LDAP Search Filters'. They
can be used like ZSQL Methods and use DTML inside of them. For
example, one could do a Filter of 'uid=<dtml-var foo>'. ZLDAP Filter
Methods return Entry objects.
ZLDIF Filter Methods
ZLDIF Filter Methods are a way to generate ldif document which are parsed to modify LDAP database.
This is a sample of dtml code <dtml-ldifline attr="uidNumber" expr="'03ERRRNN981'" type="string">
for generating valid LDIF message.
"""LDAP Filter Methods Package """
import LM
def initialize(context):
context.registerClass(
LM.LDAPMethod,
constructors = (LM.manage_addZLDAPMethodForm,
LM.manage_addZLDAPMethod),
icon = "LDAP_Method_icon.gif",
legacy = (LM.LDAPConnectionIDs,), #special baby to add to ObjectManagers
)
context.registerClass(
LM.LDIFMethod,
constructors = (LM.manage_addZLDIFMethodForm,
LM.manage_addZLDIFMethod),
icon = "LDAP_Method_icon.gif",
legacy = (LM.LDAPConnectionIDs,), #special baby to add to ObjectManagers
)
\ No newline at end of file
<HTML><HEAD><TITLE>New LDAP Method</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<H2>New LDAP Method</H2>
<dtml-if LDAPConnectionIDs>
<form action="manage_addZLDAPMethod" method="post">
<table>
<tr> <th align='LEFT'>Id</th>
<td align='LEFT'><input name="id" size="40"></td></tr>
<tr> <th align='LEFT'><em>Title<em></th>
<td align='LEFT'><input name="title" size="40"></td></tr>
<tr> <th align='LEFT'>Connection id</th>
<td align='LEFT'><select name="connection_id">
<dtml-in LDAPConnectionIDs>
<option value="&dtml-sequence-item;"
><dtml-var sequence-key></option>
</dtml-in>
</select></td></tr>
<tr> <th align='LEFT'><em>Query Scope</em></th>
<td align='LEFT'><select name="scope">
<dtml-in expr="['SUBTREE', 'ONELEVEL', 'BASE']">
<option value="&dtml-sequence-item;"
><dtml-var sequence-item></option>
</dtml-in>
</select></td></tr>
<tr> <th align='LEFT' valign="top"><em>Base DN</em></th>
<td align='LEFT'><input name="basedn" size="40"><br />
<input type="checkbox" name="getfromconnection:int" value="1" id="gfc">
<em><label for="gfc">Append Connection's Base DN</label></em></td></tr>
<tr> <th align='LEFT'><em>Arguments<em></th>
<td align='LEFT'><input name="arguments" size="40"></td></tr>
<tr> <td colspan=2 align='LEFT'><strong>Query Filter</strong><br>
<textarea name="filters:text" rows=9 cols=50>objectclass=*
</textarea></td></tr>
<tr><td colspan=2>
<input type="hidden" name="key" value="">
<input type="SUBMIT" name="submit" value=" Add ">
<input type="SUBMIT" name="submit" value=" Add and Edit ">
<input type="SUBMIT" name="submit" value=" Add and Test ">
</td></tr>
</table>
</form>
<dtml-else>
<em><strong>Warning:</strong>
There are no LDAP connections. You need
to add a Zope LDAP connection
before you can use a Zope LDAP Method.
</em>
</dtml-if>
</body></html>
<HTML><HEAD><TITLE>New LDIF Method</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<H2>New LDIF Method</H2>
<dtml-if LDAPConnectionIDs>
<form action="manage_addZLDIFMethod" method="post">
<table>
<tr> <th align='LEFT'>Id</th>
<td align='LEFT'><input name="id" size="40"></td></tr>
<tr> <th align='LEFT'><em>Title<em></th>
<td align='LEFT'><input name="title" size="40"></td></tr>
<tr> <th align='LEFT'>Connection id</th>
<td align='LEFT'><select name="connection_id">
<dtml-in LDAPConnectionIDs>
<option value="&dtml-sequence-item;"><dtml-var sequence-key></option>
</dtml-in>
</select></td></tr>
<tr> <th align='LEFT' valign="top"><em>Base DN</em></th>
<td align='LEFT'><input name="basedn" size="40"><br />
<input type="checkbox" name="getfromconnection:int" value="1" id="gfc">
<em><label for="gfc">Append Connection's Base DN</label></em></td></tr>
<tr> <th align='LEFT'><em>Arguments<em></th>
<td align='LEFT'><textarea name="arguments:text" rows="4" cols="40"></textarea></td></tr>
<tr> <td colspan=2 align='LEFT'><strong>ldif</strong><br>
<textarea name="ldif:text" rows=9 cols=70></textarea></td></tr>
<tr><td colspan=2>
<input type="hidden" name="key" value="">
<input type="SUBMIT" name="submit" value=" Add ">
<input type="SUBMIT" name="submit" value=" Add and Edit ">
<input type="SUBMIT" name="submit" value=" Add and Test ">
</td></tr>
</table>
</form>
<dtml-else>
<em><strong>Warning:</strong>
There are no LDAP connections. You need
to add a Zope LDAP connection
before you can use a Zope LDAP Method.
</em>
</dtml-if>
</body></html>
<HTML><HEAD><TITLE>New LDAP Method</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<!--#var manage_tabs-->
<H2>Edit LDAP Method</H2>
<!--#if LDAPConnectionIDs-->
<form action="manage_edit" method="post">
<table>
<tr> <th align='LEFT'>Id</th>
<td align='LEFT'><!--#var id--></td></tr>
<tr> <th align='LEFT'><em>Title<em></th>
<td align='LEFT'><input name="title" size="40" value="<!--#var title-->"></td></tr>
<tr> <th align='LEFT'>Connection id</th>
<td align='LEFT'><select name="connection_id">
<!--#in LDAPConnectionIDs-->
<option value="<!--#var sequence-item-->"
<!--#if expr="connection_id==_vars['sequence-item']"-->
SELECTED<!--#/if-->>
<!--#var sequence-key--></option>
<!--#/in-->
</select></td></tr>
<tr> <th align='LEFT'><em>Query Scope</em></th>
<td align='LEFT'><select name="scope">
<!--#in "['SUBTREE', 'ONELEVEL', 'BASE']"-->
<option value="<!--#var sequence-item-->"
<!--#if expr="scope==_vars['sequence-item']"-->
SELECTED<!--#/if-->><!--#var sequence-item--></option>
<!--#/in-->
</select></td></tr>
<tr> <th align='LEFT'><em>Base DN</em></th>
<td align='LEFT'><input name="basedn" size="40" value="<!--#var basedn fmt=html-quote-->"></td></tr>
<tr> <th align='LEFT'><em>Arguments<em></th>
<td align='LEFT'><input name="arguments" size="40" value="<!--#var arguments-->"></td></tr>
<tr> <td colspan=2 align='LEFT'><strong>Query Filter</strong><br>
<textarea name="filters:text" rows=9 cols=50><!--#var filters--></textarea>
</td></tr>
<tr><td colspan=2>
<input type="SUBMIT" name="submit" value="Change">
</td></tr>
</table>
</form>
<!--#else-->
<em><strong>Warning:</strong>
There are no LDAP connections. You need
to add a Zope LDAP connection
before you can use a Zope LDAP Method.
</em>
<!--#/if-->
</body></html>
<HTML><HEAD><TITLE>Edit LDIF Method</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<!--#var manage_tabs-->
<H2>Edit LDIF Method</H2>
<!--#if LDAPConnectionIDs-->
<form action="manage_edit" method="post">
<table>
<tr> <th align='LEFT'>Id</th>
<td align='LEFT'><!--#var id--></td></tr>
<tr> <th align='LEFT'><em>Title<em></th>
<td align='LEFT'><input name="title" size="40" value="<!--#var title-->"></td></tr>
<tr> <th align='LEFT'>Connection id</th>
<td align='LEFT'><select name="connection_id">
<!--#in LDAPConnectionIDs-->
<option value="<!--#var sequence-item-->"
<!--#if expr="connection_id==_vars['sequence-item']"-->
SELECTED<!--#/if-->>
<!--#var sequence-key--></option>
<!--#/in-->
</select></td></tr>
<tr> <th align='LEFT'><em>Base DN</em></th>
<td align='LEFT'><input name="basedn" size="40" value="<!--#var basedn fmt=html-quote-->"></td></tr>
<tr> <th align='LEFT'><em>Arguments<em></th>
<td align='LEFT'><textarea name="arguments:text" rows="4" cols="50"><!--#var arguments--></textarea></td></tr>
<tr> <td colspan=2 align='LEFT'><strong>ldif</strong><br>
<textarea name="ldif:text" rows=20 cols=100><!--#var ldif--></textarea>
</td></tr>
<tr><td colspan=2>
<input type="SUBMIT" name="submit" value="Change">
</td></tr>
</table>
</form>
<!--#else-->
<em><strong>Warning:</strong>
There are no LDAP connections. You need
to add a Zope LDAP connection
before you can use a Zope LDAP Method.
</em>
<!--#/if-->
</body></html>
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
'''Inserting values with the 'ldifvar' tag
The 'ldifvar' tag is used to type-safely insert values into LDIF
text. The 'ldifvar' tag is similar to the 'var' tag, except that
it replaces text formatting parameters with LDIF type information.
The sqlvar tag has the following attributes:
attr -- The name of the attribute to insert. As with other
DTML tags.
type -- The data type of the value to be inserted. This
attribute is required and may be one of 'string',
'int', 'float', or 'nb'. The 'nb' data type indicates a
string that must have a length that is greater than 0.
optional -- A flag indicating that a value is optional. If a
value is optional and is not provided (or is blank
when a non-blank value is expected), then the string
'null' is inserted.
For example, given the tag::
<dtml-ldifvar x attr=objectClass type=nb optional>
if the value of 'x' is::
inetOrgPerson
then the text inserted is:
objectClass: inetOrgPerson
however, if x is ommitted or an empty string, then the value
inserted is ''.
'''
#__rcs_id__='$Id: sqlvar.py,v 1.14.10.2 2005/09/02 23:02:00 tseaver Exp $'
############################################################################
# Copyright
#
# Copyright 1996 Digital Creations, L.C., 910 Princess Anne
# Street, Suite 300, Fredericksburg, Virginia 22401 U.S.A. All
# rights reserved.
#
############################################################################
#__version__='$Revision: 1.14.10.2 $'[11:-2]
from DocumentTemplate.DT_Util import ParseError, parse_params, name_param
from string import find, split, join, atoi, atof
import sha
StringType=type('')
str=__builtins__['str']
class LDIFVar:
name='ldifvar'
def __init__(self, args):
args = parse_params(args, name='', expr='', type=None, optional=1)
name,expr=name_param(args,'ldifvar',1)
if expr is None: expr=name
else: expr=expr.eval
self.__name__, self.expr = name, expr
self.args=args
if not args.has_key('type'):
raise ParseError, ('the type attribute is required', 'dtvar')
t=args['type']
if not valid_type(t):
raise ParseError, ('invalid type, %s' % t, 'dtvar')
def render(self, md):
name=self.__name__
args=self.args
t=args['type']
try:
expr=self.expr
if type(expr) is type(''): v=md[expr]
else: v=expr(md)
except:
if args.has_key('optional') and args['optional']:
return
if type(expr) is not type(''):
raise
raise ValueError, 'Missing input variable, <em>%s</em>' % name
if v is None:
return ''
if t=='int':
try:
if type(v) is StringType:
if v[-1:]=='L':
v=v[:-1]
atoi(v)
else: v=str(int(v))
except:
if not v and args.has_key('optional') and args['optional']:
return
raise ValueError, (
'Invalid integer value for <em>%s</em>' % name)
elif t=='float':
try:
if type(v) is StringType:
if v[-1:]=='L':
v=v[:-1]
atof(v)
else: v=str(float(v))
except:
if not v and args.has_key('optional') and args['optional']:
return
raise ValueError, (
'Invalid floating-point value for <em>%s</em>' % name)
else:
if not isinstance(v, (str, unicode)):
v=str(v)
if not v and t=='nb':
if args.has_key('optional') and args['optional']:
return
else:
raise ValueError, (
'Invalid empty string value for <em>%s</em>' % name)
return v
__call__=render
class LDIFLine:
name='ldifline'
def __init__(self, args):
args = parse_params(args, name='', expr='', attr='', type=None, optional=1)
name,expr=name_param(args,'ldifvar',1)
if expr is None: expr=name
else: expr=expr.eval
self.__name__, self.expr = name, expr
self.args=args
if not args.has_key('type'):
raise ParseError, ('the type attribute is required', 'ldifattr')
t=args['type']
if not valid_type(t):
raise ParseError, ('invalid type, %s' % t, 'dtvar')
if not args.has_key('attr'):
raise ParseError, ('the attr attribute is required', 'ldifattr')
a=args['attr']
def render(self, md):
name=self.__name__
args=self.args
t=args['type']
a=args['attr']
default = '%s:' % (a)
try:
expr=self.expr
if type(expr) is type(''): v=md[expr]
else: v=expr(md)
except:
if args.has_key('optional') and args['optional']:
return default
if type(expr) is not type(''):
raise
raise ValueError, 'Missing input variable, <em>%s</em>' % name
if v is None:
if args.has_key('optional') and args['optional']:
return default
else:
raise ValueError, 'Missing input variable, <em>%s</em>' % name
if a in ['',None]:
return default
if t=='int':
try:
if type(v) is StringType:
if v[-1:]=='L':
v=v[:-1]
atoi(v)
else: v=str(int(v))
except:
if not v and args.has_key('optional') and args['optional']:
return default
raise ValueError, (
'Invalid integer value for <em>%s</em>' % name)
elif t=='float':
try:
if type(v) is StringType:
if v[-1:]=='L':
v=v[:-1]
atof(v)
else: v=str(float(v))
except:
if not v and args.has_key('optional') and args['optional']:
return default
raise ValueError, (
'Invalid floating-point value for <em>%s</em>' % name)
else:
if not isinstance(v, (str, unicode)):
v=str(v)
if not v and t=='nb':
if args.has_key('optional') and args['optional']:
return default
else:
raise ValueError, (
'Invalid empty string value for <em>%s</em>' % name)
return '%s: %s' % (a, v)
__call__=render
valid_type={'int':1, 'float':1, 'string':1, 'nb': 1}.has_key
<HTML><HEAD><TITLE>New LDAP Method</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<!--#var manage_tabs-->
<H2>Test LDAP Method <!--#var title_or_id--></H2>
<form action="./manage_test">
<table>
<tr> <th align='LEFT'>Connection id</th>
<td align='LEFT'><!--#var connection_id--></td></tr>
<tr> <th align='LEFT'><em>Query Scope</em></th>
<td align='LEFT'><!--#var scope--></td></tr>
<tr> <th align='LEFT'><em>Base DN</em></th>
<td align='LEFT'><!--#var basedn fmt=html-quote-->
<tr> <th align='LEFT'><em>Arguments<em></th>
<td align='LEFT'><!--#var arguments-->
<tr><td colspan=2>
<input type="SUBMIT" name="submit" value="Test">
</td></tr>
</table>
</form>
</BODY>
</HTML>
<HTML><HEAD><TITLE>New LDAP Method</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<!--#var manage_tabs-->
<H2>Test LDAP Method <!--#var title_or_id--></H2>
<form action="./manage_test">
<table>
<tr> <th align='LEFT'>Connection id</th>
<td align='LEFT'><!--#var connection_id--></td></tr>
<tr> <th align='LEFT'><em>Query Scope</em></th>
<td align='LEFT'><!--#var scope--></td></tr>
<tr> <th align='LEFT'><em>Base DN</em></th>
<td align='LEFT'><!--#var basedn fmt=html-quote-->
<tr> <th align='LEFT'><em>Arguments<em></th>
<td align='LEFT'><!--#var arguments-->
<tr><td colspan=2>
<input type="SUBMIT" name="submit" value="Test">
</td></tr>
</table>
</form>
</BODY>
</HTML>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment