CHANGES.txt for the LDAPLoginAdapter
This file contains change information for the LDAPLoginAdapter product.
* Zope 2.4x complains about "ambiguous security declarations" when
using the common idion "manage=manage_main...". Usage of the
_setName method prevents this.
* Florent Guillaume provided me with completely rewritten unicode
support which fixed a lot of issues. Thanks Florent!
* findUser was missing a fix which prevented nasty error
messages when using the ZMI to search using an empty DN.
(Tracker Nr. 54)
* getUserDN was unusable from dtml or python since it
somehow tickled the security machinery the wrong way.
a simplified implementation solved this issue.
* Added new method "getGroupDetails" which will return all
uniqueMember and member values for a given group DN.
* Fixed _searchResults so that binding with username and password
is only done if both user and password are given, whereas the
code had only checked for a username before.
* When calling getProperty on a LDAPUser object the behavior has
been changed slightly to return an empty string instead of
None which reduces unexpected code breakages in DTML or having
"None" appear in form fields that use getProperty.
* Cosmetic fixes to the "Configure" management tab
* There was a glaring inconsistency between the default set of
LDAP attributes that are available upon instantitaion of a new
LDAPLoginAdapter and the set offered in the creation screen as
possible RDN-attributes or login names. Whenever a new
LDAPLoginAdapter is created or an existing one is edited the
code will check for the existence of (and will add if needed)
the attributes that are specified as RDN-attribute or login name.
(Tracker issue 45)
* A logic error on my part rendered some LDAP ACLs useless. When
a new user object was created the information from the LDAP
server was retrieved using an anonymous bind. This has been
changed to binding with that specific user's DN and password
when retrieving the user attributes used to instantiate the
LDAPUser object. The penalty is an added call into the LDAP server
whenever a user is not in the cache or has expired.
(Tracker issue 47)
* Some python-ldap packages (namely the RPM) name the ldap
module wrong and _ldap is not available. This is an error
on the part of the python-ldap package. All code now falls
back to importing "ldap" if "_ldap" is not available.
* When in cookie mode cookies would not be set in a specific
border condition where the user was in the cache and got
validated but no cookie was on the user's browser. The
existence of the cookie is now tested for after validating
from cache. (Tracker issue 44)
* All direct references to the ldap module have been changed to
use the name "_ldap" instead. This is the preferred notation
and will fix problems where self-compiled python-ldap modules
did not include the "ldap.py" kludge.
* The entry field for bind password in the Add-form was not of
* Methods common to the LDAPLoginAdapter and the LDAPUserManager
have been removed from the main module and made into a
separate class in module LDAPShared. Both the LDAPLoginAdapter
and the LDAPUserManager now subclass from this shared class.
This will make it easier to keep core functionality synchronized.
* This version introduces the first stab at being able to handle
non-ascii characters in attribute values if your version of
Python has unicode abilities. Please see README.txt for the gory
* More documentation in the doc strings and some visual
cleanup in the code
* Replaced __ac_permissions__ with the new security
framework using ClassSecurityInfo
* Replaced all instances of HTMLFile with DTMLFile
* The LDAPUser object now sports a "_expire" method. This
method should be called by anyone changing the LDAP-side of
the user records, it will guarantee that the cached version
in the LDAPLoginAdapter is updated on the next access.
* The "Purge Caches" button has been moved from the "Advanced"
management tab to the "Caches" tab - undoubtedly a better
place for it.
* When building the CMFLDAP product I realized that it is very
hard to work with user objects if they do not know their own
Distinguished Name attribute. LDAPUser has been expanded to
accept the DN in the constructor and to show it using a new
method named getUserDN.
* Creating a LDAPLoginAdapter object from python has been made
easier by giving REQUEST a default value of None.
* Most pesky MessageDialog invocations have been replaced by
using the ZMI's built-in "manage_tabs_message" facility. This
means that, unless there is an error, normal management screen
actions will not throw up a confirmation dialog anymore. This
makes it more convenient to work in the management interface.
This release fixes no bugs over 1.3beta4, it just enables me (now
that bug reports have died down) to start the 1.4 beta cycle and
stop extending the 1.3 beta cycle forever.
* The mechanism by which groups (-> roles) were gleaned from
records of type groupOfUniqueNames would fail if for some
reason the spacing of the user record's DN and the
corresponding uniqueMember attribute in the group record
was different. A new method, "getGroupsWithInconsistentRecords",
was introduced which can be used instead of the normal
getGroups method. Since the overhead in the new method is much
higher I am requiring anyone who somehow messed up their group
records to edit the LDAPLoginAdapter.py file and go to line 28
where it says "USER_GROUP_INCONSISTENCY = 0". If you replace
0 with 1 here the new method will be called and you might see
As a general remark, there should never be any spaces between
elements in a DN. However, even worse is inconsistent spacing.
* The current setup expected a RDN (Relative Distinguished Name)
component *cn* on the user record. A lot of schemas, especially
those in the Netscape Directory Server, like to use *uid* instead.
Those records were not handled correctly. This is now fixed and
the manager user can select which of the two to use.
RFC 2377 has an explanation on when to use which.
* Parsing the comma-separated list of default user roles more
carefully to make sure that spacing between commas does not
affect the real role names.
* Another one in finduser: If the dn from the user record and
the dn pulled from the group records are different in small
ways, like one of them has spaces after the commas, the record
would not be returned with the search results. I am now using
the explode_dn utility function from the ldap module to split
the dn into components before comparing so that spacing and
even separation characters (commas, semicolons) become
irrelevant. (Tracker items 32 and 33)
* finduser expected that the LDAP server returned the attribute
names correctly capitalized, which some servers do not. It
handles both cases now in computing the list of valid users
in groups. (Tracker item 31)
* Added a missing comma in __ac_permissions__ that broke
changing roles via the "Security" tab on the
* Completely revamped the exception handling in authenticate
so that there will always be a (hopefully) helpful output
in the log
* authenticate had an exeption handler that would try and use a
variable left uninitialized when the exception was thrown.
* The methods that manipulate the publicly available user object
attributes now make sure to flush the cache of user objects
and force all of them to be recreated, thereby making the
changes "grab" immediately and not just whenever the user object
expires all by itself and gets recreated.
* A new management tab called "LDAP Schema" allows the
manager to enter or delete attributes that describe the
LDAP schema used for the LDAP user records. This completely
replaces the misleading "Allowable User Attributes" found
on the Advanced tab which had been abused to find out more
about the LDAP schema in use. All select lists that list
LDAP attributes are now driven by the attributes that are
shown on the LDAP Schema tab.
* The "Special Users" and "Special User Roles" feature has been
deprecated. I considered it a kludge in cases where you cannot
set your LDAP schema correctly. With the advent of the
LDAPUserManager product it has become trivially easy to add
users and groups. This is the much preferred way of conferring
roles to users.
* Mishandled the loop to delete the public attribute mappings
in manage_deletePublicUserAttrs which caused index errors
* Default handling of method calls through the web or from
python was inconsistent in regards to what to return and
what to expect. All method signatures that might expect
REQUEST now set it to a default value of None and in the
method body test to see if it is None. This improves the
use of methods from python where no REQUEST is guaranteed.
* Change capitalization of manage_AddPublicUserAttrs to bring
it in line with the normally used capitalization scheme
* Renamed "Contents" tab to "Custom Forms" to clear up the
meaning of this tab
* Cookie-based authentication with a login page and the
ability to simply drop in custom login pages.
* Complete rewriting of all code connected to the
validate method, which does the actual authentication,
to reflect the way it is done in the latest built-in
user folder object.
* The bunduid and bindpwd attributes which hold the DN and
password of the LDAP server manager user are now safeguarded
from DTML access by changing names to _binduid and _bindpwd.
A (protected) method, getProperty, is now used to get them.
* The LDAP search string created in _lookupuser, the method
which is called by validate to find a user in LDAP, created
search expressions with asterisk wildcard characters around
the search term. These were removed in the interest of an
* Instead of hardcoding fixed publicly available attributes
onto the LDAPUser object you can now take full control
of the mapping from LDAP attribute to public user object
A public user object attribute is an attribute that is
directly accessible on the user object. DTML code like
"AUTHENTICATED_USER.email" is an example of accessing
a directly accessible attribute on the user object. A lot
of legacy DTML code relies on such attributes.
* finduser() now lowercases all DN records from valid groups
and compares it to a lowercased DN from any search results
among user records. This fixes records not showing up if
the capitalization in the group and on the user record
* When a user object was created the code expected a "mail"
attribute on the LDAP record to set the email attribute
used for compatibility with the Tracker. This has been
repaired and will just default to an empty string.
* Users who use tools like PADL's migration script end up
with records that do not have the expected "sn" attribute.
This is not set to a default value in finduser() to avoid
errors in case it is not there.
This will make the LDAPLoginAdapter compatible with
user records of type posixAccount.
* Due to a bug in checking the return values from an LDAP search
the cache can be polluted by invalid records for failed
logins. This did not constitue a security breach, just more
processing than necessary.
* Moved the LDAP search scope translation list from a volatile
attribute on the LDAPLoginAdapter to a module-level attribute.
This avoids any re-initialization calls.
* Eliminated the extra attribute _v_loglines that counted the
length of the log. A simple call to len(self._v_log) replaces
it where log length info is needed.
* Created one centralized method that handles connecting to and
searching the LDAP server. This allows centralized error
handling and makes for less and cleaner code. So far finduser,
getUserDetails, getGroups, getUserNames and _lookupuser have
been converted to use it instead of having their own connection
* Rooted out error that would put a known user into the cache
even though the password was not matched. This was not a
security error since the broken user had the wrong password
and failed any tests in validate()
* Rigorous pruning of overly long lines of code to pare everything
down to 80 chars width max
* Avoiding unnecessary calls to the logging routine by checking
for the correct log level *before* the call and not in the
* Added a file, SAMPLE_RECORDS.txt, that shows a sample group-
and user record. This will hopefully make it easier to
understand the types of LDAP records needed.
* Nicer Search screen adopted from the LDAPUserManager
* Clearer error messages through refactoring of all code that
is responsible for connecting and disconnecting from the LDAP
* Co-operation with the Zope Tracker software has been ensured
by making a full name and email attribute available on the
user object returned from the LDAPLoginAdapter.
* Added API documentation to the Zope Help System
* Various code cleanups
* Added check to see if a server address with a prepended
"ldap://" was entered.
* Updated all docs to clarify the reliance on Zope
version 2.3.0 of higher.
* Vetted all code to make sure that every connection made
to the LDAP server is followed by a formal disconnect,
regardless of processing between connect and disconnect.
* The LDAP record attribute to be used as the user's name
can be selected from a list of attributes
* The list of LDAP attributes to be used as the user's name
can be extended or reduced and custom attributes can be
added to it.
* A Search screen allows the Manager to search the LDAP
database for user records and then view their details.
* The python code has been refactored and the code for the
LDAPUser class has been split off into a separate module.
* All LDAPLoginAdapter-specific management screens have help
screens associated with them, accessible through the built-in
Zope Help System.
* All management screens have been modified to integrate with
the new Zope Management Interface, introduced in Zope 2.3.0.
* ...and many others I forgot to track.
This product started from Ross Lazarus' Zope LDAP Adapter, which has
since seen many improvement and moved to SourceForge. You can see
Ross' and Soren Roug's efforts at:
I decided to use it as a base and develop a customized version for
use in authenticating users in Digital Creations' own intranet. I
have come to the point where it has matured enough to be released
to a wider audience.