summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt9
-rw-r--r--INSTALL.txt22
-rw-r--r--LDAPRoleExtender.py545
-rw-r--r--LICENSE.txt88
-rw-r--r--README.txt35
-rw-r--r--VERSION.txt1
-rw-r--r--__init__.py27
-rw-r--r--dtml/addLDAPRoleExtender.dtml97
-rw-r--r--dtml/cache.dtml88
-rw-r--r--dtml/log.dtml27
-rw-r--r--dtml/properties.dtml133
-rw-r--r--help/Add.stx24
-rw-r--r--help/Caches.stx21
-rw-r--r--help/Configure.stx26
-rw-r--r--help/Log.stx32
-rw-r--r--www/ldaproleextender.gifbin0 -> 214 bytes
16 files changed, 1175 insertions, 0 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 0000000..48a5369
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,9 @@
+CHANGES.txt for the LDAPRoleExtender
+
+ This file contains change information for the LDAPRoleExtender product.
+
+
+ 0.1
+
+ Start of the product
+
diff --git a/INSTALL.txt b/INSTALL.txt
new file mode 100644
index 0000000..a9a5e13
--- /dev/null
+++ b/INSTALL.txt
@@ -0,0 +1,22 @@
+Installing the LDAPRoleExtender Product
+
+ You will need Zope version 2.4.0 or higher (which necessitates
+ Python version 2.0 or higher)!
+
+ This product does not require any special handling after unzipping
+ and untarring it in the Zope Products directory. You should do
+ something like::
+
+ $ cp LDAPRoleExtender-xyz.tgz <zope_root>/lib/python/Products
+ $ cd <zope_root>/lib/python/Products
+ $ tar zxvf LDAPRoleExtender-xyz.tgz
+ <watch files being decompressed>
+
+ Windows users can use WinZip or similar, it can handle tarred
+ gzip files. Make sure to move the extracted LDAPRoleExtender
+ folder to your Zope installation's lib/python/Products-folder.
+
+ That's all. Do not forget to restart Zope afterwards.
+
+
+ See README.txt for any other dependencies and requirements.
diff --git a/LDAPRoleExtender.py b/LDAPRoleExtender.py
new file mode 100644
index 0000000..d3da78d
--- /dev/null
+++ b/LDAPRoleExtender.py
@@ -0,0 +1,545 @@
+#####################################################################
+#
+# LDAPRoleExtender Fake user folder to change roles
+#
+# This software is governed by a license. See
+# LICENSE.txt for the terms of this license.
+#
+#####################################################################
+__version__='$Revision$'[11:-2]
+
+import os, time, copy, ldap, urllib
+from types import StringType
+from Globals import package_home, InitializeClass, MessageDialog, DTMLFile
+from Acquisition import aq_base
+from AccessControl import ClassSecurityInfo
+from AccessControl.ZopeSecurityPolicy import _noroles
+from AccessControl.Permissions import view_management_screens, manage_users
+from AccessControl.SpecialUsers import emergency_user
+from OFS.SimpleItem import SimpleItem
+from Products.LDAPUserFolder.LDAPUser import LDAPUser
+from Products.LDAPUserFolder.utils import GROUP_MEMBER_MAP
+
+
+_marker = []
+_dtmldir = os.path.join( package_home( globals() ), 'dtml' )
+addLDAPRoleExtenderForm = DTMLFile( 'addLDAPRoleExtender', _dtmldir )
+CHANGE_LUF_PERMISSION = 'Change user folder'
+
+
+class LDAPRoleExtender( SimpleItem ):
+ """
+ LDAPRoleExtender
+
+ The LDAPRoleExtender is a fake user database. It retrieves
+ user objects through a real LDAPUserFolder and changes the
+ roles on the user object according to a group base DN
+ specified by the administrator.
+ """
+ security = ClassSecurityInfo()
+
+ meta_type = 'LDAPRoleExtender'
+ id = 'acl_users'
+ isAUserFolder = 1
+
+
+ #################################################################
+ #
+ # ZMI management screens
+ #
+ #################################################################
+
+ manage_options=(
+ (
+ {'label' : 'Configure', 'action' : 'manage_main',
+ 'help' : ('LDAPRoleExtender','Configure.stx')},
+ {'label' : 'Caches', 'action' : 'manage_cache',
+ 'help' : ('LDAPRoleExtender', 'Caches.stx')},
+ {'label' : 'Log', 'action' : 'manage_log',
+ 'help' : ('LDAPRoleExtender', 'Log.stx')},
+ )
+ + SimpleItem.manage_options
+ )
+
+ security.declareProtected( view_management_screens, 'manage' )
+ security.declareProtected( view_management_screens, 'manage_main' )
+ manage = manage_main = DTMLFile( 'dtml/properties', globals() )
+ manage_main._setName( 'manage_main' )
+
+ security.declareProtected( view_management_screens, 'manage_log' )
+ manage_log = DTMLFile( 'dtml/log', globals() )
+
+ security.declareProtected( view_management_screens, 'manage_cache' )
+ manage_cache = DTMLFile( 'dtml/cache', globals() )
+
+
+ #################################################################
+ #
+ # Initialization code
+ #
+ #################################################################
+
+
+ def __setstate__( self, v ):
+ """
+ __setstate__ is called whenever the instance is loaded
+ from the ZODB, like when Zope is restarted.
+ """
+ LDAPRoleExtender.inheritedAttribute( '__setstate__' )( self, v )
+
+ self._clearCaches()
+ if not hasattr( self, 'verbose' ):
+ self.verbose = 2
+
+ if self.verbose > 2:
+ self._log( 3,'Re-initialized through __setstate__' )
+
+
+ def __init__( self, luf, groups_base, groups_scope, title='' ):
+ """ Create a new LDAPRoleExtender instance """
+ self.title = title
+ self.groups_base = groups_base
+ self.groups_scope = groups_scope
+ self.verbose = 2 # _log needs it
+ self._v_users = {}
+ self._v_cache = {}
+ self._v_log = []
+
+ if not luf.startswith( '/' ):
+ luf = '/%s' % luf
+
+ self._luf = luf
+
+
+ security.declarePrivate( '_clearCaches' )
+ def _clearCaches( self ):
+ """ Clear all logs and caches for user-related information """
+ self._v_cache = {} # Secondary cache for non-logged in users
+ self._v_users = {} # this is the cache of logged-in users
+ self._v_log = []
+
+
+ #################################################################
+ #
+ # LDAPRoleExtender administration
+ #
+ #################################################################
+
+
+ security.declarePrivate( '_log' )
+ def _log( self, level, logline ):
+ """ Logs a single line and keeps log length at max. 500 lines.
+
+ Level: 1 - Catastrophes
+ 2 - Major Events
+ 3 - Minor events
+ 4 - Login failures
+ 5 - Login Successes
+ 7 - Login success from cache
+ 9 - Debugging
+ """
+ time_str = time.strftime( '%b %d %H:%M:%S'
+ , time.localtime( time.time() )
+ )
+ logged = '(%d) %s: %s' % ( level, time_str, logline )
+
+ self._v_log.append( logged )
+
+ if len( self._v_log ) > 500:
+ self._v_log.pop( 0 )
+
+
+
+ security.declareProtected( view_management_screens, 'getLog' )
+ def getLog( self ):
+ """ return the log contents """
+ return self._v_log
+
+
+ security.declareProtected( manage_users, 'manage_reinit' )
+ def manage_reinit( self, REQUEST=None ):
+ """ re-initialize and clear out users and log """
+ self._clearCaches()
+ self.verbose > 1 and self._log( 2, 'Reinitialized' )
+
+ if REQUEST:
+ msg = 'User caches cleared'
+ return self.manage_cache( self, REQUEST, manage_tabs_message=msg )
+
+
+ security.declareProtected( CHANGE_LUF_PERMISSION, 'manage_edit' )
+ def manage_edit( self
+ , luf
+ , groups_base
+ , groups_scope
+ , verbose=2
+ , title=''
+ , REQUEST=None
+ ):
+ """ Edit the LDAPRoleExtender Object """
+ self.title = title
+ self.verbose = verbose
+ self.groups_base = groups_base
+ self.groups_scope = groups_scope
+
+ if not luf.startswith( '/' ):
+ luf = '/%s' % luf
+
+ self._luf = luf
+
+ if REQUEST:
+ msg = 'Properties changed'
+ return self.manage_main( self, REQUEST, manage_tabs_message=msg )
+
+
+ security.declarePrivate( '_cacheUser' )
+ def _cacheUser( self, name, extended_user, authenticated=0 ):
+ """ Stick something into my internal cache """
+ name = name.lower()
+
+ if ( extended_user is None or
+ name == str( emergency_user.getUserName() ).lower() ):
+ return
+
+ if authenticated:
+ self._v_users[name] = extended_user
+ else:
+ self._v_cache[name] = extended_user
+
+
+ security.declarePrivate( 'twiddleUser' )
+ def _extendRoles( self, raw_user ):
+ """ extend the user roles """
+ if raw_user is None:
+ return raw_user
+
+ raw_roles = list(raw_user.getRoles())
+ user_dn = raw_user.getUserDN()
+ luf = self.getLUF()
+ group_filter = '(|(uniquemember=%s)(member=%s))' % ( user_dn, user_dn )
+
+ add_roles = luf._searchResults( self.groups_base
+ , self.groups_scope
+ , group_filter
+ , attrs = ['dn', 'cn']
+ )
+
+ if isinstance(add_roles, StringType) or len(add_roles) == 0:
+ return raw_user
+
+ add_role_list = []
+
+ for i in range(len(add_roles)):
+ dn = add_roles[i][0]
+ try:
+ cn = add_roles[i][1]['cn'][0]
+ except KeyError: # NDS oddity
+ cn = ldap.explode_dn(dn, 1)[0]
+
+ add_role_list.append(cn)
+
+
+ final_roles = {}
+ raw_roles.extend(add_role_list)
+
+ for role in raw_roles:
+ final_roles[role] = 1
+
+ extended_user = LDAPUser( raw_user.getUserName()
+ , raw_user._getPassword()
+ , final_roles.keys()
+ , raw_user.getDomains()
+ , raw_user.getUserDN()
+ , {}
+ , []
+ )
+ extended_user._properties = raw_user._properties
+
+ return extended_user
+
+
+ security.declareProtected( manage_users, 'getLUF' )
+ def getLUF( self ):
+ """ Return my LDAP User Folder """
+ return self.unrestrictedTraverse(self._luf)
+
+
+ #################################################################
+ #
+ # User Folder interface stuff
+ #
+ #################################################################
+
+ security.declareProtected( manage_users, 'getUsers' )
+ def getUsers( self ):
+ """ Return a list of *cached* user objects """
+ names = self._v_users.keys()
+ names.sort()
+ users = []
+
+ for n in names:
+ user = self._v_users.get( n )
+ if user.notexpired(): # Show only the unexpired
+ users.append( user )
+
+ return users
+
+
+ security.declareProtected( manage_users, 'getUserNames' )
+ def getUserNames( self ):
+ """ Return a list of usernames """
+ luf = self.getLUF()
+
+ return luf.getUserNames()
+
+ user_names = getUserNames
+
+
+ security.declareProtected( manage_users, 'getUser' )
+ def getUser( self, name, pwd=None ):
+ """Return the named user object or None"""
+ name = name.lower()
+
+ if ( pwd is not None and
+ self._v_users.has_key( name ) and
+ self._v_users[name].notexpired() ):
+ self.verbose > 8 and self._log( 9
+ , '"%s" found in auth-cache' % name
+ )
+
+ return self._v_users.get( name )
+
+ if ( pwd is None and
+ self._v_cache.has_key( name ) and
+ self._v_cache[name].notexpired() ):
+ self.verbose > 8 and self._log( 9
+ , '"%s" found in anon-cache' % name
+ )
+
+ return self._v_cache.get( name )
+
+ luf = self.getLUF()
+
+ raw_user = luf.getUser( name, pwd )
+ extended_user = self._extendRoles( raw_user )
+ self._cacheUser( name, extended_user, pwd )
+
+ return extended_user
+
+
+ security.declareProtected( manage_users, 'getUserById' )
+ def getUserById( self, id, default=_marker ):
+ """ Return a user object by ID (in this case by username) """
+ try:
+ return self.getUser( id )
+
+ except:
+ if default is _marker:
+ raise
+
+ return default
+
+
+ security.declareProtected( manage_users, 'getUserByDN' )
+ def getUserByDN( self, user_dn ):
+ """ Make a user object from a DN """
+ luf = self.getLUF()
+ raw_user = luf.getUserByDN( user_dn )
+ extended_user = self._extendRoles( raw_user )
+ self._cacheUser( extended_user.getUserName(), extended_user )
+
+ return extended_user
+
+
+ # This must stay accessible to everyone
+ def validate( self, request, auth='', roles=_noroles ):
+ """ The main engine """
+ luf = self.getLUF()
+ raw_user = luf.validate( request, auth, roles )
+
+ if raw_user is not None:
+ extended_user = self.getUser( raw_user.getUserName()
+ , raw_user._getPassword()
+ )
+ extended_user = extended_user.__of__( luf )
+ else:
+ extended_user = raw_user
+
+ return extended_user
+
+
+ def manage_beforeDelete( self, item, container ):
+ """ Clean up after myself """
+ if ( hasattr( container, '__allow_groups__' ) and
+ container.__allow_groups__ == item ):
+ del container.__allow_groups__
+
+ security.declareProtected( manage_users, 'getGroups' )
+ def getGroups(self, dn='*', attr=None):
+ """ return group records i know about """
+ group_list = []
+ no_show = ( 'Anonymous', 'Authenticated', 'Shared' )
+ group_filter = '(|(uniquemember=%s)(member=%s))' % ( dn, dn )
+ luf = self.getLUF()
+
+ res = luf._searchResults( self.groups_base
+ , self.groups_scope
+ , group_filter
+ , attrs=['dn', 'cn']
+ )
+
+ if isinstance(res, StringType):
+ return group_list
+
+ if len(res) > 0:
+ for i in range( len( res ) ):
+ dn = res[i][0]
+ try:
+ cn = res[i][1]['cn'][0]
+ except KeyError: # NDS oddity
+ cn = ldap.explode_dn( dn, 1 )[0]
+
+ if attr is None:
+ group_list.append( ( cn, dn ) )
+ elif attr == 'cn':
+ group_list.append( cn )
+ elif attr == 'dn':
+ group_list.append( dn )
+
+ return group_list
+
+ security.declareProtected( manage_users, 'getGroupDetails')
+ def getGroupDetails( self, encoded_cn ):
+ """ Return all group details """
+ result = []
+ cn = urllib.unquote( encoded_cn )
+ luf = self.getLUF()
+
+ res = luf._searchResults( self.groups_base
+ , self.groups_scope
+ , 'cn=%s' % cn
+ , ['uniqueMember', 'member']
+ )
+
+ if isinstance(res, StringType):
+ result = ( ( 'Exception', res ), )
+ elif len( res ) > 0:
+ result = res[0][1].items()
+ result.sort()
+
+ return tuple(result)
+
+
+ security.declareProtected( manage_users, 'getGroupedUsers')
+ def getGroupedUsers(self, groups=None):
+ """ Retrieve all users that in the groups i know about """
+ all_dns = {}
+ users = []
+
+ if groups is None:
+ groups = self.getGroups()
+
+ for group_id, group_dn in groups:
+ group_details = self.getGroupDetails( group_id )
+
+ for attribute_name, dn_list in group_details:
+ for dn in dn_list:
+ all_dns[dn] = 1
+
+ for dn in all_dns.keys():
+ user = self.getUserByDN( dn )
+
+ if user is not None:
+ users.append( user.__of__( self ) )
+
+ return tuple( users )
+
+
+ security.declareProtected( manage_users, 'manage_editUserRoles' )
+ def manage_editUserRoles( self, user_dn, role_dns=[], REQUEST=None ):
+ """ Edit the roles (groups) of a user """
+ all_groups = self.getGroups( attr='dn' )
+ cur_groups = self.getGroups( dn=user_dn, attr='dn' )
+ operations = []
+ luf = self.getLUF()
+
+ for group in all_groups:
+ if group in cur_groups and group not in role_dns:
+ operations.append({ 'op' : ldap.MOD_DELETE
+ , 'target' : group
+ , 'type' : luf.getGroupType( group )
+ } )
+ elif group in role_dns and group not in cur_groups:
+ operations.append({ 'op' : ldap.MOD_ADD
+ , 'target' : group
+ , 'type' : luf.getGroupType( group )
+ } )
+
+ if operations:
+ connection = luf._connect()
+
+ for to_do in operations:
+ mod_list = ( ( to_do['op']
+ , GROUP_MEMBER_MAP.get( to_do['type'] )
+ , user_dn
+ ), )
+ try:
+ connection.modify_s( to_do['target'], mod_list )
+ except Exception, e:
+ msg = luf._formatException( self, e )
+
+ msg = 'Roles changed for %s' % ( user_dn )
+ else:
+ msg = 'No roles changed for %s' % ( user_dn )
+
+ user_obj = self.getUserByDN( user_dn )
+ if user_obj is not None:
+ self._expireUser( user_obj )
+
+ if REQUEST:
+ return self.manage_userrecords( self
+ , REQUEST
+ , manage_tabs_message=msg
+ , user_dn=user_dn
+ )
+
+
+ security.declareProtected( manage_users, '_expireUser' )
+ def _expireUser( self, user_obj ):
+ """ Purge user object from caches """
+ user_name = user_obj.getUserName().lower()
+ for cache_dict in ( self._v_users, self._v_cache ):
+ if cache_dict.has_key( user_name ):
+ del cache_dict[user_name]
+
+
+def manage_addLDAPRoleExtender( self
+ , luf
+ , groups_base
+ , groups_scope
+ , title=''
+ , REQUEST=None
+ ):
+ """ Called by Zope to create and install an LDAPRoleExtender """
+
+ if hasattr( aq_base( self ), 'acl_users' ) and REQUEST is not None:
+ msg = 'This object already contains a User Folder'
+
+ return MessageDialog(
+ title = 'Item Exists',
+ message = msg,
+ action = '%s/manage_main' % REQUEST['URL1'])
+
+ n = LDAPRoleExtender( luf, groups_base, groups_scope, title )
+
+ self = self.this()
+ self._setObject( 'acl_users', n )
+ self.__allow_groups__ = self.acl_users
+
+ # return to the parent object's manage_main
+ if REQUEST:
+ url = REQUEST['URL1']
+ REQUEST.RESPONSE.redirect( '%s/manage_main' % url )
+
+
+InitializeClass( LDAPRoleExtender )
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..0a3d1fb
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,88 @@
+##############################################################################
+#
+# License
+# -------
+#
+# Copyright (c) Jens Vagelpohl. All right reserved.
+#
+# 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 above copyright
+# notice, this list of conditions, and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions, and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# 3. Jens Vagelpohl requests that attribution be given to Zope
+# in any manner possible. A significant investment has been put
+# into Zope, and this effort will continue if the Zope community
+# continues to grow. This is one way to assure that growth.
+#
+# 4. All advertising materials and documentation mentioning
+# features derived from or use of this software must display
+# the following acknowledgement:
+#
+# "This product includes software developed by Jens Vagelpohl
+# for use in the Z Object Publishing Environment
+# (http://www.zope.org/)."
+#
+# In the event that the product being advertised includes an
+# intact LDAPRoleExtender distribution (with copyright and license
+# included) then this clause is waived
+#
+# 5. Names associated with Jens Vagelpohl must not be used to
+# endorse or promote products derived from this software without
+# prior written permission from Jens Vagelpohl.
+#
+# 6. Modified redistributions of any form whatsoever must retain
+# the following acknowledgment:
+#
+# "This product includes software developed by Jens Vagelpohl
+# for use in the Z Object Publishing Environment
+# (http://www.zope.org/)."
+#
+# Intact (re-)distributions of any official LDAPRoleExtender
+# release do not require an external acknowledgement.
+#
+# 7. Modifications are encouraged but must be packaged separately as
+# patches to official software releases. Distributions that do not
+# clearly separate the patches from the original work must be clearly
+# labeled as unofficial distributions.
+#
+# 8. In the interest of improving this product the author requests that
+# changes be made available to him. This is not mandatory but part
+# of good open source development etiquette.
+#
+#
+# Disclaimer
+#
+# THIS SOFTWARE IS PROVIDED BY JENS VAGELPOHL ``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 JENS VAGELPOHL OR HIS
+# CONTRIBUTORS 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.
+#
+#
+# This software consists of contributions made by Jens Vagelpohl and
+# many other individuals.
+#
+# Special credits go to Ross Lazarus and Soren Roug which provided the
+# basis for this code.
+#
+# Thanks also go to Florent Guillaume for unicode code.
+#
+# I am grateful to Dirk Datzert for the SSHA encryption module
+#
+##############################################################################
+
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..eb11e77
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,35 @@
+README for the Zope LDAPRoleExtender Product
+
+ This product is a fake user folder. All user objects are
+ retrieved from a "real" LDAPUserFolder and their roles
+ are extended with the help of LDAP group records found
+ underneath the specified groups base DN.
+
+
+ **Warning**
+
+ Using a LDAPRoleExtender will slow down authentication for
+ logged-in users wherever is is in effect. Maybe not by much,
+ but once you start nesting them it might become noticeable.
+
+
+ **Requirements**
+
+ In order for this product to run you will need to provide the
+ following items:
+
+ * a working LDAPUserFolder (see http://www.dataflake.org/)
+
+
+ **Tested Platforms**
+
+ This version of the LDAPRoleExtender has been written on and for
+ Zope 2.5.0 and up. I am not going to support earlier versions of
+ Zope with my product. I have run the LDAPRoleExtender successfully
+ on...
+
+ - Zope 2.5.1
+ - Zope 2.6.0 alpha 1
+
+
+
diff --git a/VERSION.txt b/VERSION.txt
new file mode 100644
index 0000000..49d5957
--- /dev/null
+++ b/VERSION.txt
@@ -0,0 +1 @@
+0.1
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..2ba5720
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# __init__.py Initialization code for the LDAPRoleExtender
+#
+# This software is governed by a license. See
+# LICENSE.txt for the terms of this license.
+#
+##############################################################################
+
+__doc__ = """ LDAPRoleExtender initialization module """
+__version__ = '$Revision$'[11:-2]
+
+from LDAPRoleExtender import addLDAPRoleExtenderForm, \
+ manage_addLDAPRoleExtender, \
+ LDAPRoleExtender
+
+def initialize( context ):
+ context.registerClass( LDAPRoleExtender
+ , permission='Add User Folders'
+ , constructors=( addLDAPRoleExtenderForm
+ , manage_addLDAPRoleExtender
+ )
+ , icon='www/ldaproleextender.gif'
+ )
+
+ context.registerHelp()
+
diff --git a/dtml/addLDAPRoleExtender.dtml b/dtml/addLDAPRoleExtender.dtml
new file mode 100644
index 0000000..03cd0a9
--- /dev/null
+++ b/dtml/addLDAPRoleExtender.dtml
@@ -0,0 +1,97 @@
+<dtml-var manage_page_header>
+
+<dtml-var "manage_form_title(this(), _,
+ form_title='Add LDAP Role Extender',
+ help_product='LDAPRoleExtender',
+ help_topic='Add.stx'
+ )">
+
+<p class="form-help">
+ Add a new LDAPRoleExtender with this form.
+</p>
+
+<dtml-let lufs="superValues( [ 'LDAPUserFolder' ] )">
+ <dtml-if lufs>
+
+ <form action="manage_addLDAPRoleExtender" method="POST">
+ <table cellspacing="0" cellpadding="3">
+
+ <tr>
+ <td align="left" valign="TOP"><div class="form-optional">
+ Title
+ </div></td>
+ <td align="left" valign="TOP"><div class="form-element">
+ <input type="text" name="title" size="40" />
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="TOP"><div class="form-label">
+ LDAP User Folder
+ </div></td>
+ <td align="left" valign="TOP"><div class="form-element">
+ <select name="luf">
+ <dtml-in lufs>
+ <dtml-let luf_path="_.string.join( getPhysicalPath(), '/' )">
+ <option value="&dtml-luf_path;">
+ &dtml-luf_path; (&dtml-meta_type;)
+ </option>
+ </dtml-let>
+ </dtml-in>
+ </select>
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="TOP"><div class="form-label">
+ Groups Base DN
+ </div></td>
+ <td align="left" valign="TOP"><div class="form-element">
+ <input type="text" name="groups_base" size="40" />
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="TOP"><div class="form-optional">
+ Groups search scope *
+ </div></td>
+ <td align="left" valign="TOP"><div class="form-element">
+ <select name="groups_scope:int">
+ <option value="0"> BASE </option>
+ <option value="1"> ONELEVEL </option>
+ <option value="2" selected> SUBTREE </option>
+ </select>
+ </div></td>
+ </tr>
+
+ <tr>
+ <td>&nbsp;</td>
+ <td>
+ <br />
+ <input type="SUBMIT" value=" Add ">
+ </td>
+ </tr>
+
+ </table>
+ </form>
+
+ <ul>
+ <li>
+ BASE: Search only the current object itself
+ </li>
+ <li>
+ ONELEVEL: Search only the object's immediate children
+ </li>
+ <li>
+ SUBTREE: Search the current object and all of its descendants
+ </li>
+ </ul>
+
+ <dtml-else>
+ <p><b>No LDAP User Folder objects in sight. You cannot create a LDAPRoleExtender here.</b></p>
+
+ </dtml-if>
+</dtml-let>
+
+<dtml-var manage_page_footer>
+
diff --git a/dtml/cache.dtml b/dtml/cache.dtml
new file mode 100644
index 0000000..64060eb
--- /dev/null
+++ b/dtml/cache.dtml
@@ -0,0 +1,88 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+This form shows all non-anonymous logged-in users in the cache
+at this moment.
+</p>
+
+<form action="manage_reinit" method="post">
+<table cellpadding="2" cellspacing="0" width="98%">
+ <tr class="section-bar">
+ <td colspan="2" align="left" valign="top"><div class="form-label">
+ Purge all caches
+ </div></td>
+ </tr><tr>
+ <td align="left" valign="top"><div class="form-text">
+ Empty all caches and force reloading of user records
+ from LDAP.
+ </div></td>
+ <td align="left" valign="top"><div class="form-element">
+ <input type="submit" value=" Purge all caches " />
+ </div></td>
+ </tr>
+</table>
+</form>
+
+<p>&nbsp;</p>
+
+<table cellpadding"3" cellspacing="0" width="98%">
+ <tr class="section-bar">
+ <td align="left" valign="top"><div class="form-label">
+ Cached users
+ </div></td>
+ </tr>
+</table>
+
+<dtml-in expr="getUsers()">
+
+ <dtml-if name="sequence-start">
+ <br />
+ <table border="1" cellpadding="2">
+ <tr>
+ <td><div class="form-label">
+ UserID
+ </div></td>
+ <td><div class="form-label">
+ Roles
+ </div></td>
+ <td><div class="form-label">
+ Last Access time
+ </div></td>
+ <td><div class="form-label">
+ Cache Expires
+ </div></td>
+ </tr>
+ </dtml-if>
+
+ <tr>
+ <td valign="top"><div class="form-text">
+ <dtml-var expr="getUserName()">
+ </div></td>
+ <td valign="top"><div class="form-text">
+ <dtml-in expr="getRoles()">
+ <dtml-var name="sequence-item"><br>
+ </dtml-in>
+ </div></td>
+ <td valign="top"><div class="form-text">
+ <dtml-var expr="getLastActiveTime()" fmt=Time>
+ </div></td>
+ <td valign="top"><div class="form-text">
+ <dtml-var expr="getExpireTime()" fmt=Time>
+ </div></td>
+ </tr>
+
+ <dtml-if name="sequence-end">
+ </table>
+ </dtml-if>
+
+<dtml-else>
+ <br />
+ <div class="form-text">
+ No users in cache
+ (superuser is never cached)
+ </div>
+
+</dtml-in>
+
+<dtml-var manage_page_footer>
diff --git a/dtml/log.dtml b/dtml/log.dtml
new file mode 100644
index 0000000..2053752
--- /dev/null
+++ b/dtml/log.dtml
@@ -0,0 +1,27 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+These events were logged by the LDAP Role Extender.
+At present, the log level is <b>&dtml-verbose;</b>.
+</p>
+
+<table cellspacing="0" cellpadding="2" width="98%">
+ <tr class="section-bar">
+ <td align="left" valign="top" colspan="3"><div class="form-label">
+ Log contents
+ </div></td>
+ </tr>
+</table>
+
+<p class="form-text">
+<dtml-in getLog reverse>
+ <dtml-var name="sequence-item"><br>
+
+<dtml-else>
+ Nothing was logged at this log level!
+
+</dtml-in>
+</p>
+
+<dtml-var manage_page_footer>
diff --git a/dtml/properties.dtml b/dtml/properties.dtml
new file mode 100644
index 0000000..9e64b92
--- /dev/null
+++ b/dtml/properties.dtml
@@ -0,0 +1,133 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+ Change the basic properties of your LDAPRoleExtender
+ on this form.
+</p>
+
+<form action="manage_edit" method="POST">
+
+ <table cellspacing="0" cellpadding="3">
+
+ <tr>
+ <td align="LEFT" valign="TOP"><div class="form-optional">
+ Title
+ </div></td>
+ <td align="LEFT" valign="TOP" colspan="3">
+ <input type="TEXT" name="title" size="40" value="&dtml-title;" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="LEFT" valign="TOP"><div class="form-label">
+ LDAP User Folder
+ </div></td>
+ <td align="LEFT" valign="TOP"><div class="form-element">
+ <select name="luf">
+ <dtml-let my_path="_.string.join( this().getPhysicalPath(), '/' )">
+ <dtml-in "superValues( [ 'LDAPUserFolder' ] )">
+ <dtml-let luf_path="_.string.join( getPhysicalPath(), '/' )"
+ sel="luf_path == getLUF().getPhysicalPath() and 'selected' or ''">
+ <dtml-if "my_path != luf_path">
+ <option value="&dtml-luf_path;" &dtml-sel;>
+ &dtml-luf_path;
+ </option>
+ </dtml-if>
+ </dtml-let>
+ </dtml-in>
+ </dtml-let>
+ </select>
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="TOP"><div class="form-label">
+ Groups Base DN
+ </div></td>
+ <td align="left" valign="TOP"><div class="form-element">
+ <input type="text" name="groups_base" size="40"
+ value="&dtml-groups_base;" />
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="TOP"><div class="form-optional">
+ Groups search scope *
+ </div></td>
+ <td align="left" valign="TOP"><div class="form-element">
+ <select name="groups_scope:int">
+ <option value="0" <dtml-if "groups_scope==0">selected</dtml-if>>
+ BASE
+ </option>
+ <option value="1" <dtml-if "groups_scope==1">selected</dtml-if>>
+ ONELEVEL
+ </option>
+ <option value="2"<dtml-if "groups_scope==2">selected</dtml-if>>
+ SUBTREE
+ </options>
+ </select>
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-label">
+ Log Level
+ </div></td>
+ <td align="LEFT" valign="TOP"><div class="form-element">
+ <select name="verbose:int">
+ <option value="0" <dtml-if "verbose==0">selected</dtml-if>>
+ No logging (0)
+ </option>
+ <option value="1" <dtml-if "verbose==1">selected</dtml-if>>
+ Catastrophes (1)
+ </option>
+ <option value="2" <dtml-if "verbose==2">selected</dtml-if>>
+ Major Events (2)
+ </option>
+ <option value="3" <dtml-if "verbose==3">selected</dtml-if>>
+ Minor Events (3)
+ </option>
+ <option value="4" <dtml-if "verbose==4">selected</dtml-if>>
+ Login Failures (4)
+ </option>
+ <option value="5" <dtml-if "verbose==5">selected</dtml-if>>
+ Login Successes (5)
+ </option>
+ <option value="7" <dtml-if "verbose==7">selected</dtml-if>>
+ Login From Cache (7)
+ </option>
+ <option value="9" <dtml-if "verbose==9">selected</dtml-if>>
+ Debugging (9)
+ </option>
+ </select>
+ </div></td>
+ </tr>
+
+ <tr>
+ <td>&nbsp;</td>
+ <td colspan="3">
+ <br>
+ <input type="SUBMIT" value=" Apply Changes ">
+ </td>
+ </tr>
+
+ </table>
+
+</form>
+
+<ul>
+ <li>
+ BASE: Search only the current object itself
+ </li>
+ <li>
+ ONELEVEL: Search only the object's immediate children
+ </li>
+ <li>
+ SUBTREE: Search the current object and all of its descendants
+ </li>
+</ul>
+
+
+<dtml-var manage_page_footer>
+
diff --git a/help/Add.stx b/help/Add.stx
new file mode 100644
index 0000000..29702dc
--- /dev/null
+++ b/help/Add.stx
@@ -0,0 +1,24 @@
+LDAPRoleExtender - Add: Create a new LDAPRoleExtender object
+
+ Description
+
+ LDAPRoleExtender works just like a user folder. Unlike a normal
+ user folder it must have a LDAP User Folder or another LDAP Role
+ Extender to retrieve user objects through. These objects then
+ have their roles mangled extended with those roles constructed
+ from LDAP groups found underneath the groups base specified by
+ the administrator.
+
+ Controls
+
+ 'Title' -- The (optional) title for this adapter
+
+ 'LDAP User Folder' -- The LDAP User Folder used to retrieve user
+ objects
+
+ 'Groups Base DN' -- The DN under which group records can be found
+
+ 'Groups search scope' -- The search scope to use when we search
+ for group records underneath the groups base DN.
+
+ 'Add' -- Instantiate the LDAPRoleExtender.
diff --git a/help/Caches.stx b/help/Caches.stx
new file mode 100644
index 0000000..35d9baf
--- /dev/null
+++ b/help/Caches.stx
@@ -0,0 +1,21 @@
+LDAPRoleExtender - Caches: View Cached Users
+
+ Description
+
+ This view shows the cache of currently authenticated users.
+
+ Elements
+
+ 'Purge All caches' -- This will purge all caches inside the LDAPRoleExtender.
+ This includes the cache of currently authenticated users, the log and any cached
+ username lists.
+
+ 'Cached users' -- These are the users in the cache of currently
+ authenticated users. Anonymous users will not show up in this view.
+
+ Every time an authenticated user makes a request to Zope,
+ the username and password are verified. Depending on site traffic
+ and number of users that log in through the LDAPRoleExtender this
+ process can happen several times a second. Since a lookup on the
+ LDAP Server can be quite slow, the product will cache the user
+ information for 10 minutes. This is the duration of a typical session.
diff --git a/help/Configure.stx b/help/Configure.stx
new file mode 100644
index 0000000..40fe71e
--- /dev/null
+++ b/help/Configure.stx
@@ -0,0 +1,26 @@
+LDAPRoleExtender - Configure: Set the basic configuration for the
+ LDAPRoleExtender
+
+ Description
+
+ This view is used to change the basic settings of a LDAPRoleExtender
+ and to assign the specific LDAP Group to Zope role mappings that
+ are to be used.
+
+ Controls
+
+ 'Title' -- The (optional) title for this adapter
+
+ 'LDAP User Folder' -- The LDAP User Folder used to retrieve user
+ objects
+
+ 'Groups Base DN' -- The DN under which group records can be found
+
+ 'Groups search scope' -- The search scope to use when we search
+ for group records underneath the groups base DN.
+
+ 'Log Level' -- The severity of events that get written to the internal
+ log.
+
+ 'Apply Changes' -- Save your configuration changes.
+
diff --git a/help/Log.stx b/help/Log.stx
new file mode 100644
index 0000000..093e90b
--- /dev/null
+++ b/help/Log.stx
@@ -0,0 +1,32 @@
+LDAPRoleExtender - Log: View log information
+
+ Description
+
+ This screen shows the log kept by the LDAPRoleExtender. A basic
+ log entry has the following elements:
+
+ o The log level that produced this entry
+
+ o The entry timestamp
+
+ o The log message
+
+ You can specify what kinds of events get logged using the
+ "Log verbosity" setting in the "Configure" management tab. The
+ different log levels are:
+
+ o 0: No logging entries will be made
+
+ o 1: Catastrophes, like failures to connect to the LDAP server
+
+ o 2: Major Events, like LDAPRoleExtender property changes
+
+ o 3: Minor Events, like initialization after Zope is restarted
+
+ o 4: Authentication failures
+
+ o 5: Successful authentications
+
+ o 7: Authentication from cache
+
+ o 9: Debugging, includes extra debugging info
diff --git a/www/ldaproleextender.gif b/www/ldaproleextender.gif
new file mode 100644
index 0000000..bf55779
--- /dev/null
+++ b/www/ldaproleextender.gif
Binary files differ