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

Initial import of ZLDAPConnection Product

git-svn-id: 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 7e73db33
ZLDAPConnection CHANGES.txt
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
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.
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.
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
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 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 '' implements getter/setter methods for
all ZLDAPConnection properties. It is mixed into ZLDAPConnection.
o Updated for Zope 2.1.5/2.1.6 new security measures on method-ish
o Has a settable "Browsable" property on the Connection object.
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 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.
This diff is collapsed.
__version__="$Revision: 1.3 $"[11:-2]
class LDAPConnectionAccessors:
""" getters / setters for LDAP Properties """
__ac_permissions__ = (
('Access contents information',
('Manage properties',
('setID','setTitle','setHost','setPort', 'setBindAs','setPW',
def getId(self):
def setId(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 """
def setHost(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 """
def setPW(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
# 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
Programming ZopeLDAP Entry Objects
Author -- "Jeffrey P Shell",
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 />
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[0] == 'Shell': # Works
if == '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 == ['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", ["", ""])'
**setattrs([kwdict, kw])** -- Takes either a mapping argument or
keyword arguments. Or both. Example:
'entry.setattrs({"sn": "Bazzo"}, mail=[",
**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:
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",
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 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
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.
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*.
o Ability to browse an LDAP database like you would browse normal
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.
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
o It currently only supports simple_bind for connecting to the
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
Special Thanks
o Jens Vagelpohl ( for getting the pointy-hairs to
give me time to make 1.1.0 finally happen.
o Anthony Baxter ( for most of the original work
o Scott Robertson ( and Maurice Davice
( for theirs too.
o David Leonard for his LDAP Module and for keeping it pretty much
in alignment with the RFC (rfc1823).
Jeffrey P Shell (
Original Authors:
Anthony Baxter (
Maurice Davice (
Scott Robertson (
This diff is collapsed.
"""LDAP Server Connection Package """
import ZLDAP, Entry
__version__ = ZLDAP.__version__
# use the propert product registration
def initialize(context):
constructors = (ZLDAP.manage_addZLDAPConnectionForm,
icon = 'LDAP_conn_icon.gif',
permissions = ('Manage Entry information',
'Create New Entry Objects',
<head><title>Add LDAP connection</title></head>
<body bgcolor="#FFFFFF">
<h2>Add LDAP connection</h2>
<form action="manage_addZLDAPConnection" method="POST">
<table cellspacing="2">
<th align="LEFT" valign="TOP">Id</th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="id" size="50">
<th align="LEFT" valign="TOP"><em>Title</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="title" size="50">
<th align="LEFT" valign="TOP"><em>LDAP Server (host[:port])</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="hostport" size="50">
<th align="LEFT" valign="TOP"><em>Base DN</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="basedn" size="50">
<th align="LEFT" valign="TOP"><em>Bind As</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="bind_as" size="50">
<th align="LEFT" valign="TOP"><em>Bind Password</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="pw" size="50">
<th align="LEFT" valign="TOP"><em>Open Connection?</em></th>
<td align="LEFT" valign="TOP">
<input type="CHECKBOX" name="openc" CHECKED>
<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><br><input type="SUBMIT" value="Add"></td>
<title>Attributes for &dtml-dn;</title>
<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>
<hr />
<dtml-if tpValues>
<a href="&dtml-tree-item-url;/manage_attributes"
title="click to view this entry">&dtml-id;</a>
<p><em>No subentries for &dtml-id;</em></p>
\ No newline at end of file
<title>Browse &dtml-title_and_id;</title>
<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>
<hr />
<dtml-tree name="getRoot">
<a href="&dtml-tree-item-url;/manage_attributes"
title="click to view this entry">&dtml-id;</a>
<p><em>Connection to <code>&dtml-host;:&dtml-port;</code> is
<dtml-if name="openc">not browsable<dtml-else>closed</dtml-if>.</em></p>
<title>Connection for &dtml-title_or_id;</title>
<style type="text/css"><!--
.open {color: blue}
.close {color: red}
<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 isOpen>
<form action="manage_close">
<input type="submit" value="Close Connection" class="close" />
<form action="manage_open">
<input type="submit" value="Open Connection" class="open" />
\ No newline at end of file
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<!--#var manage_tabs-->
<!--#in "tpValues()" -->
<INPUT TYPE="CHECKBOX" NAME="ids:list" VALUE="<!--#var dn-->">
<A HREF="<!--#var id fmt=url-quote-->/manage_main">
<IMG SRC="<!--#var SCRIPT_NAME-->/<!--#var icon-->"
ALT="[Entry]" BORDER="0"></A>
<A HREF="<!--#var id fmt=url-quote-->/manage_main"><!--#var id--></A>
<!--#if "tpValues()"-->
<INPUT TYPE="SUBMIT" NAME="manage_deleteEntry:method" VALUE="Delete">
<a name="addentryform">
<form action="." method="POST">
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.
<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">