- copying current trunk into new location

git-svn-id: http://svn.dataflake.org/svn/Products.LDAPMultiPlugins/trunk@1506 835909ba-7c00-0410-bfa4-884f43845301
This commit is contained in:
jens 2008-06-05 15:51:44 +00:00
parent 83130f9c8f
commit 5403514ec5
17 changed files with 1892 additions and 0 deletions

View File

@ -0,0 +1,485 @@
##############################################################################
#
# ActiveDirectoryMultiPlugin Shim to use the LDAPUserFolder with the
# PluggableAuthenticationService w/ AD
#
##############################################################################
__doc__ = """ ActiveDirectoryUserFolder shim module """
__version__ = '$Revision$'[11:-2]
# General Python imports
import logging
import os
from urllib import quote_plus
# Zope imports
from Acquisition import aq_base
from Globals import InitializeClass
from Globals import DTMLFile
from Globals import package_home
from AccessControl import ClassSecurityInfo
from Products.LDAPUserFolder import manage_addLDAPUserFolder
from Products.LDAPUserFolder.LDAPDelegate import filter_format
from Products.LDAPUserFolder.utils import BINARY_ATTRIBUTES
from zope.interface import implementedBy
from Products.PluggableAuthService.interfaces.plugins import \
IUserEnumerationPlugin, IGroupsPlugin, IGroupEnumerationPlugin, \
IRoleEnumerationPlugin
from Products.PluggableAuthService.utils import classImplements
from LDAPPluginBase import LDAPPluginBase
logger = logging.getLogger('event.LDAPMultiPlugin')
_dtmldir = os.path.join(package_home(globals()), 'dtml')
addActiveDirectoryMultiPluginForm = DTMLFile('addActiveDirectoryMultiPlugin',
_dtmldir)
def manage_addActiveDirectoryMultiPlugin( self, id, title, LDAP_server
, login_attr
, uid_attr, users_base, users_scope, roles
, groups_base, groups_scope, binduid, bindpwd
, binduid_usage=1, rdn_attr='cn', local_groups=0
, use_ssl=0 , encryption='SHA', read_only=0
, REQUEST=None
):
""" Factory method to instantiate a ActiveDirectoryMultiPlugin """
# Make sure we really are working in our container (the
# PluggableAuthService object)
self = self.this()
# Value needs massaging, there's some magic transcending a simple true
# or false expeced by the LDAP delegate :(
if use_ssl:
use_ssl = 1
else:
use_ssl = 0
# Instantiate the folderish adapter object
lmp = ActiveDirectoryMultiPlugin(id, title=title)
self._setObject(id, lmp)
lmp = getattr(aq_base(self), id)
lmp_base = aq_base(lmp)
# Put the "real" LDAPUserFolder inside it
manage_addLDAPUserFolder(lmp)
luf = getattr(lmp_base, 'acl_users')
host_elems = LDAP_server.split(':')
host = host_elems[0]
if len(host_elems) > 1:
port = host_elems[1]
else:
if use_ssl:
port = '636'
else:
port = '389'
luf.manage_addServer(host, port=port, use_ssl=use_ssl)
luf.manage_edit( title
, login_attr
, uid_attr
, users_base
, users_scope
, roles
, groups_base
, groups_scope
, binduid
, bindpwd
, binduid_usage=binduid_usage
, rdn_attr=rdn_attr
, local_groups=local_groups
, encryption=encryption
, read_only=read_only
, REQUEST=None
)
# clean out the __allow_groups__ bit because it is not needed here
# and potentially harmful
if hasattr(lmp_base, '__allow_groups__'):
del lmp_base.__allow_groups__
uf = lmp.acl_users
uf._ldapschema = { 'cn' : { 'ldap_name' : 'cn'
, 'friendly_name' : 'Canonical Name'
, 'multivalued' : ''
, 'public_name' : ''
}
, 'sn' : { 'ldap_name' : 'sn'
, 'friendly_name' : 'Last Name'
, 'multivalued' : ''
, 'public_name' : 'last_name'
}
}
uf.manage_addLDAPSchemaItem('dn', 'Distinguished Name',
public_name='dn')
uf.manage_addLDAPSchemaItem('sAMAccountName', 'Windows Login Name',
public_name='windows_login_name')
uf.manage_addLDAPSchemaItem('objectGUID', 'AD Object GUID',
public_name='objectGUID')
uf.manage_addLDAPSchemaItem('givenName', 'First Name',
public_name='first_name')
uf.manage_addLDAPSchemaItem('sn', 'Last Name',
public_name='last_name')
uf.manage_addLDAPSchemaItem('memberOf',
'Group DNs',
public_name='memberOf',
multivalued=True)
if REQUEST is not None:
REQUEST.RESPONSE.redirect('%s/manage_main' % self.absolute_url())
class ActiveDirectoryMultiPlugin(LDAPPluginBase):
""" The adapter that mediates between the PAS and the LDAPUserFolder """
security = ClassSecurityInfo()
meta_type = 'ActiveDirectory Multi Plugin'
_properties = LDAPPluginBase._properties + (
{'id':'groupid_attr', 'type':'string', 'mode':'w'},
{'id':'grouptitle_attr', 'type':'string', 'mode':'w'},
{'id':'group_class', 'type':'string', 'mode':'w'},
{'id':'group_recurse', 'type':'int', 'mode':'w'},
{'id':'group_recurse_depth', 'type':'int', 'mode':'w'},
)
groupid_attr = 'objectGUID'
grouptitle_attr = 'cn'
group_class = 'group'
group_recurse = 1
group_recurse_depth = 1
def __init__(self, id, title='', groupid_attr='objectGUID',
grouptitle_attr='cn', group_class='group', group_recurse=1,
group_recurse_depth=1):
""" Initialize a new instance """
self.id = id
self.title = title
self.groupid_attr = groupid_attr
self.grouptitle_attr = grouptitle_attr
self.group_class = group_class
self.group_recurse = group_recurse
self.group_recurse_depth = group_recurse_depth
security.declarePublic('getGroupsForPrincipal')
def getGroupsForPrincipal(self, user, request=None, attr=None):
""" Fulfill GroupsPlugin requirements """
if attr is None:
attr = self.groupid_attr
acl = self._getLDAPUserFolder()
if acl is None:
return ()
view_name = self.getId() + '_getGroupsForPrincipal'
criteria = {'user_id':user.getId(), 'attr':attr}
cached_info = self.ZCacheable_get(view_name = view_name,
keywords = criteria,
default = None)
if cached_info is not None:
logger.debug('returning cached results from getGroupsForPrincipal')
return cached_info
unmangled_userid = self._demangle(user.getId())
if unmangled_userid is None:
return ()
ldap_user = acl.getUserById(unmangled_userid)
if ldap_user is None:
return ()
cns = [ x.split(',')[0] for x in (ldap_user.memberOf or []) ]
if not cns:
return ()
cns = [x.split('=')[1] for x in cns]
cn_flts = [filter_format('(cn=%s)', (cn,)) for cn in cns]
filt = '(&(objectClass=%s)(|%s))' % (self.group_class, ''.join(cn_flts))
delegate = acl._delegate
R = delegate.search(acl.groups_base, acl.groups_scope, filter=filt)
if R['exception']:
logger.error("Failed to locate groups for principal in %s "
"(scope=%s, filter=%s): %s",
acl.groups_base, acl.groups_scope, filt,
R['exception'])
return ()
if self.group_recurse:
groups = self._recurseGroups(R['results'])
else:
groups = R['results']
results = [ x[attr][0] for x in groups]
self.ZCacheable_set(results, view_name=view_name, keywords=criteria)
return results
def _recurseGroups(self, ldap_results, temp=None, seen=None, depth=0):
""" Given a set of LDAP result data for a group search, return
the recursive group memberships for each group: arbitrarily
expensive """
if seen is None:
seen = {}
if temp is None:
temp = []
# Build a single filter so we can do it with a single search.
filt_bits = []
for result in ldap_results:
dn = result['dn']
if seen.has_key(dn):
continue
temp.append(result)
seen[dn] = 1
if result.has_key('memberOf'):
for parent_dn in result['memberOf']:
filt = filter_format('(distinguishedName=%s)', (parent_dn,))
if filt in filt_bits:
continue
filt_bits.append(filt)
if filt_bits:
bits_s = ''.join(filt_bits)
filt = "(&(objectClass=%s)(|%s))" % (self.group_class, bits_s)
acl = self.acl_users
delegate = acl._delegate
R = delegate.search(acl.groups_base, acl.groups_scope, filter=filt)
if R['exception']:
logger.error("Failed to recursively search for group in %s "
"(scope=%s, filter=%s): %s",
acl.groups_base, acl.groups_scope, filt,
R['exception'])
else:
if depth < self.group_recurse_depth:
self._recurseGroups(R['results'], temp, seen, depth + 1)
return temp
security.declarePrivate('enumerateUsers')
def enumerateUsers( self
, id=None
, login=None
, exact_match=0
, sort_by=None
, max_results=None
, **kw
):
""" Fulfill the UserEnumerationPlugin requirements """
view_name = self.getId() + '_enumerateUsers'
criteria = {'id':id, 'login':login, 'exact_match':exact_match,
'sort_by':sort_by, 'max_results':max_results}
criteria.update(kw)
cached_info = self.ZCacheable_get(view_name = view_name,
keywords = criteria,
default = None)
if cached_info is not None:
logger.debug('returning cached results from enumerateUsers')
return cached_info
result = []
acl = self._getLDAPUserFolder()
login_attr = acl.getProperty('_login_attr')
uid_attr = acl.getProperty('_uid_attr')
plugin_id = self.getId()
edit_url = '%s/%s/manage_userrecords' % (plugin_id, acl.getId())
if login_attr in kw:
login = kw[login_attr]
del kw[login_attr]
if uid_attr in kw:
id = kw[uid_attr]
del kw[uid_attr]
if acl is None:
return ()
if exact_match:
if id:
ldap_user = acl.getUserById(id)
elif login:
ldap_user = acl.getUser(login)
else:
msg = 'Exact Match specified but no ID or Login given'
raise ValueError, msg
if ldap_user is not None:
qs = 'user_dn=%s' % quote_plus(ldap_user.getUserDN())
result.append( { 'id' : ldap_user.getId()
, 'login' : ldap_user.getProperty(login_attr)
, 'pluginid' : plugin_id
, 'title': ldap_user.getProperty(login_attr)
, 'editurl' : '%s?%s' % (edit_url, qs)
} )
elif id or login or kw:
l_results = []
seen = []
attrs = (uid_attr, login_attr)
if id:
l_results.extend(acl.findUser(uid_attr, id, attrs=attrs))
if login:
l_results.extend(acl.findUser(login_attr, login, attrs=attrs))
for key, val in kw.items():
l_results.extend(acl.findUser(key, val, attrs=attrs))
for l_res in l_results:
if l_res['dn'] not in seen and l_res.has_key(login_attr):
l_res['id'] = l_res[uid_attr]
l_res['login'] = l_res[login_attr]
l_res['pluginid'] = plugin_id
quoted_dn = quote_plus(l_res['dn'])
l_res['editurl'] = '%s?user_dn=%s' % (edit_url, quoted_dn)
result.append(l_res)
seen.append(l_res['dn'])
if sort_by is not None:
result.sort(lambda a, b: cmp( a.get(sort_by, '').lower()
, b.get(sort_by, '').lower()
) )
if isinstance(max_results, int) and len(result) > max_results:
result = result[:max_results-1]
else:
result = []
for uid, name in acl.getUserIdsAndNames():
tmp = {}
tmp['id'] = uid
tmp['login'] = name
tmp['pluginid'] = plugin_id
tmp['editurl'] = None
result.append(tmp)
if sort_by is not None:
result.sort(lambda a, b: cmp( a.get(sort_by, '').lower()
, b.get(sort_by, '').lower()
) )
if isinstance(max_results, int) and len(result) > max_results:
result = result[:max_results-1]
result = tuple(result)
self.ZCacheable_set(result, view_name=view_name, keywords=criteria)
return result
security.declarePrivate('enumerateGroups')
def enumerateGroups( self
, id=None
, exact_match=0
, sort_by=None
, max_results=None
, **kw
):
""" Fulfill the RoleEnumerationPlugin requirements """
view_name = self.getId() + '_enumerateGroups'
criteria = {'id':id, 'exact_match':exact_match,
'sort_by':sort_by, 'max_results':max_results}
criteria.update(kw)
cached_info = self.ZCacheable_get(view_name = view_name,
keywords = criteria,
default = None)
if cached_info is not None:
logger.debug('returning cached results from enumerateGroups')
return cached_info
acl = self._getLDAPUserFolder()
if acl is None:
return ()
if id is None and exact_match != 0:
raise ValueError, 'Exact Match requested but no id provided'
elif id is None:
id = ''
plugin_id = self.getId()
filt = ['(objectClass=%s)' % self.group_class]
if not id:
filt.append('(%s=*)' % self.groupid_attr)
elif exact_match:
filt.append(filter_format('(%s=%s)',(self.groupid_attr, id)))
elif id:
filt.append(filter_format('(%s=*%s*)',(self.groupid_attr, id)))
filt = '(&%s)' % ''.join(filt)
if self.groupid_attr.lower() in BINARY_ATTRIBUTES:
convert_filter = False
else:
convert_filter = True
delegate = acl._delegate
R = delegate.search( acl.groups_base
, acl.groups_scope
, filter=filt
, convert_filter=convert_filter
)
if R['exception']:
logger.error("Failed to enumerate groups in %s "
"(scope=%s, filter=%s): %s",
acl.groups_base, acl.groups_scope, filt,
R['exception'])
return ()
groups = R['results']
results = []
for group in groups:
tmp = {}
tmp['title'] = '(Group) ' + group[self.grouptitle_attr][0]
id = tmp['id'] = group[self.groupid_attr][0]
tmp['pluginid'] = plugin_id
results.append(tmp)
if sort_by is not None:
results.sort(lambda a, b: cmp( a.get(sort_by, '').lower()
, b.get(sort_by, '').lower()
) )
if isinstance(max_results, int) and len(results) > max_results:
results = results[:max_results+1]
results = tuple(results)
self.ZCacheable_set(results, view_name=view_name, keywords=criteria)
return results
security.declarePrivate('enumerateRoles')
def enumerateRoles( self
, id=None
, exact_match=0
, sort_by=None
, max_results=None
, **kw
):
""" Fulfill the RoleEnumerationPlugin requirements """
return []
classImplements( ActiveDirectoryMultiPlugin
, IUserEnumerationPlugin
, IGroupsPlugin
, IGroupEnumerationPlugin
, IRoleEnumerationPlugin
, *implementedBy(LDAPPluginBase)
)
InitializeClass(ActiveDirectoryMultiPlugin)

View File

@ -0,0 +1,216 @@
Change log for the LDAPMultiPlugins product
1.6 (unreleased)
Bugs fixed
- ActiveDirectoryMultiPlugin.enumerateGroups: In order to support
group searches on the binary objectGUID attribute, utilize a new
flag exposed by the LDAPUserFolder LDAPDelegate search method
that prevents the customary UTF8-encoding of the search filter
expression. **NOTE**: With this change the LDAPUserFolder version
dependency changes to version 2.9 or higher!
(http://www.dataflake.org/tracker/issue_00576 by Wichert Akkerman)
- ActiveDirectoryMultiPlugin.enumerateGroups: If the requested group
id is a binary string, like a objectGUID attribute, it was mangled
by a lowercasing operation. Removed the lowercasing.
(http://www.dataflake.org/tracker/issue_00575 by Wichert Akkerman)
Features added
- Added caching to the getGroupsForPrincipal method. Thanks to Wichert
Akkerman for the patch.
(http://www.dataflake.org/tracker/issue_00571)
1.5 (2007/06/13)
Bugs fixed
- The product will no longer silently fail to install if the
LDAPUserFolder package is not installed. Silent failure does
not look like a good strategy here.
- fixes and import cleanups after running Pyflakes
(http://divmod.org:81/svn/Divmod/trunk/Pyflakes/)
Other
- added some additional configuration hints to the README, thanks go
to Brett Lentz (http://www.dataflake.org/tracker/issue_00559)
1.5-beta (2007/03/03)
Bugs fixed
- LDAPMultiPlugin.enumerateUsers: If no useful search criteria are
given (meaning no user ID or login is specified), fabricate a
criteria set that will return all users, this is the expected
behavior when calling enumerateUsers.
- LDAPMultiPlugin.enumerateUsers: When iterating over search results
from the user folder we now look for the special "fake result"
emitted by the user folder if there is an error. Not elegant, but
needed until error handling is changed in the LDAPUserFolder.
- Instead of throwing exceptions, the ActiveDirectoryMultiPlugin
will now log error conditions and continue, with a patch from
Mark Hammond.
(http://www.dataflake.org/tracker/issue_00554)
- Adjusted an import that has been removed from the
PluggableAuthService utils module.
(http://www.dataflake.org/tracker/issue_00542)
- Remove the ICredentialsUpdatePlugin implementation - it was
implemented wrongly and should not have been part of the contract
at all due to an interface misunderstanding.
(http://www.dataflake.org/tracker/issue_00539)
Other
- Moved the PluggableAuthService dependency up to version 1.4
1.4 (2006/10/16)
Bugs fixed
- The ActiveDirectoryMultiPlugin did not ensure to correctly
escape search filters it constructed internally.
(http://www.dataflake.org/tracker/issue_00507)
- The add form selection whether or not to use SSL for the LDAP
server connection was not handed through correctly, identified
by Olivier Nicole (http://www.dataflake.org/tracker/issue_00526)
Other
- Revamped the way recursive group memberships are found and applied,
not sure if the previous implementation was a bug or not. Many thanks
to John Hannon for a patch. This change includes the ability to
specify a nesting depth to which the recursive search will go.
(http://www.dataflake.org/tracker/issue_00513)
- Added some notes on how to enable caching using the ZCacheable
mechanism
1.3 (2006/07/29)
Bugs fixed
* Update the enumerateGroups method to use the new LDAPUserFolder
method "searchGroups". This changes the LDAPUserFolder dependency
to version 2.7. Patch provided by Leonardo Rochael Almeida.
* The ActiveDirectoryMultiPlugin enumerateUsers method would only
search correctly if login or id were explicitly specified
(thanks to Sidnei da Silva for the patch).
* Make sure to apply the same checks for user existence in
getRolesForPrincipal that are used by getPropertiesForUser
(http://www.dataflake.org/tracker/issue_00503 by Riccardo Lemmi)
* Fixed the enumerateUsers implementation to be more efficient and
use the new searchUsers method on the LDAPUserFolder (thanks to
Wichert Akkerman for the problem description and solution)
Other
* Added simple caching of groups information, provided by
Leonardo Rochael Almeida.
* Software dependencies are now documented in a separate
DEPENDENCIES.txt file. Please note that the packages mentioned
in DEPENDENCIES.txt may have their own dependencies that must be
satisfied as well.
* Replaced all zLOG usage with equivalent calls into the Python
logging module, and reducing the chattiness coded into the
ActiveDirectoryMultiPlugin (INFO -> DEBUG)
* Started on a test suite
1.2 (2006/03/02)
Bugs fixed
* In order to avoid duplicate search results, the enumerateUsers
method used a simple dictionary to store DNs for records that
were already processed. However, the keys put into this dictionary
were munged and really could not be compared to raw search
result DNs anymore. Thanks go to Wichert Akkerman for spotting this
obvious error (http://www.dataflake.org/tracker/issue_00485).
* Speed up enumerateGroups by letting the LDAP server do more of
the filtering (thanks to Wichert Akkerman,
http://www.dataflake.org/tracker/issue_00483)
* Applied a performance fix to the ActiveDirectoryPlugin's
_recurseGroups method (thanks got to Mark Hammond for the patch,
http://www.dataflake.org/tracker/issue_00476)
1.1 (2005/10/29)
Bugs fixed
* The LDAPMultiPlugins ignored default roles configured on the
LDAPUserFolder and would not add it to the set of roles
computed (seen by Sidnei da Silva).
* enumerateUsers now allows you to do exact-match searches on
attributes other than just the user ID and login (patch
by Sidnei da Silva). **Note**: This code now requires
LDAPUserFolder versions 2.6 or higher, which support exact
match searches using LDAPUserFolder.findUsers.
1.0 (2005/08/18)
Other
* The interface machinery expected by the PluggableAuthService has
been changed to use Zope 3-style interfaces. Thanks go to Leonardo
Rochael Almeida who provided a patch to fix the resulting breakage.
* Changed the initialization code for the plugins to conform to the
changed initialization code in the LDAPUserFolder product versions
2.6beta3 and up.
1.0beta3
Other
* Changes to the way the user IDs are mangled/unmangled to be in line
with the changes in the latest PluggableAuthService code
(Patch provided by Mark Hammond)
1.0beta2
Bugs fixed
* When retrieving properties for a user, None values have to be
converted to an empty string to prevent the user propertysheet
machinery from blowing up trying to guess what kind of
property a None value could represent.
1.0beta1
Bugs fixed
* Role retrieval was broken, small fix involves changing a call to the
LDAPUserFolder
LDAPMultiPlugins 0.9
First public release

View File

@ -0,0 +1,9 @@
Copyright (c) 2004-2006 Chris McDonough and Jens Vagelpohl.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.1 (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.

View File

@ -0,0 +1,3 @@
Zope >= 2.8.5
PluggableAuthService >= 1.4 (http://www.zope.org/Products/PluggableAuthService)
LDAPUserFolder >= 2.9 (http://www.dataflake.org/software/ldapuserfolder/)

View File

@ -0,0 +1,26 @@
Installing the LDAPMultiPlugins Product
It is assumed that you have installed the PluggableAuthService and
PluginRegistry products already. They are available from cvs.zope.org.
This product does not require any special handling after unzipping
and untarring it in the Zope Products directory. You should do
something like::
$ cp LDAPMultiPlugins-xyz.tgz <zope_root>/lib/python/Products
$ cd <zope_root>/lib/python/Products
$ tar zxvf LDAPMultiPlugins-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 LDAPMultiPlugins
folder to your Zope installation's lib/python/Products-folder.
That's all. Do not forget to restart Zope afterwards. You will then
be able to select the "LDAP Multi Plugin" and the "Active Directory
Multi Plugin" from the list of plugins to add when you navigate to
your PluggableAuthService-based user folder and select the "Contents"
tab in the Zope Management Interface (ZMI).
See README.txt for any other dependencies and requirements.

View File

@ -0,0 +1,346 @@
##############################################################################
#
# LDAPMultiPlugin Shim to use the LDAPUserFolder with the
# PluggableAuthenticationService
#
# This software is governed by a license. See
# LICENSE.txt for the terms of this license.
#
##############################################################################
__doc__ = """ LDAPUserFolder shim module """
__version__ = '$Revision$'[11:-2]
# General Python imports
import logging
import os
from urllib import quote_plus
# Zope imports
from Acquisition import aq_base
from Globals import InitializeClass
from Globals import DTMLFile
from Globals import package_home
from AccessControl import ClassSecurityInfo
from Products.LDAPUserFolder import manage_addLDAPUserFolder
from zope.interface import implementedBy
from Products.PluggableAuthService.interfaces.plugins import \
IUserEnumerationPlugin, IGroupsPlugin, IGroupEnumerationPlugin, \
IRoleEnumerationPlugin
from Products.PluggableAuthService.utils import classImplements
from LDAPPluginBase import LDAPPluginBase
logger = logging.getLogger('event.LDAPMultiPlugin')
_dtmldir = os.path.join(package_home(globals()), 'dtml')
addLDAPMultiPluginForm = DTMLFile('addLDAPMultiPlugin', _dtmldir)
def manage_addLDAPMultiPlugin( self, id, title, LDAP_server, login_attr
, uid_attr, users_base, users_scope, roles
, groups_base, groups_scope, binduid, bindpwd
, binduid_usage=1, rdn_attr='cn', local_groups=0
, use_ssl=0 , encryption='SHA', read_only=0
, REQUEST=None
):
""" Factory method to instantiate a LDAPMultiPlugin """
# Make sure we really are working in our container (the
# PluggableAuthService object)
self = self.this()
# Value needs massaging, there's some magic transcending a simple true
# or false expeced by the LDAP delegate :(
if use_ssl:
use_ssl = 1
else:
use_ssl = 0
# Instantiate the folderish adapter object
lmp = LDAPMultiPlugin(id, title=title)
self._setObject(id, lmp)
lmp = getattr(aq_base(self), id)
lmp_base = aq_base(lmp)
# Put the "real" LDAPUserFolder inside it
manage_addLDAPUserFolder(lmp)
luf = getattr(lmp_base, 'acl_users')
host_elems = LDAP_server.split(':')
host = host_elems[0]
if len(host_elems) > 1:
port = host_elems[1]
else:
if use_ssl:
port = '636'
else:
port = '389'
luf.manage_addServer(host, port=port, use_ssl=use_ssl)
luf.manage_edit( title
, login_attr
, uid_attr
, users_base
, users_scope
, roles
, groups_base
, groups_scope
, binduid
, bindpwd
, binduid_usage=binduid_usage
, rdn_attr=rdn_attr
, local_groups=local_groups
, encryption=encryption
, read_only=read_only
, REQUEST=None
)
# clean out the __allow_groups__ bit because it is not needed here
# and potentially harmful
lmp_base = aq_base(lmp)
if hasattr(lmp_base, '__allow_groups__'):
del lmp_base.__allow_groups__
if REQUEST is not None:
REQUEST.RESPONSE.redirect('%s/manage_main' % self.absolute_url())
class LDAPMultiPlugin(LDAPPluginBase):
""" The adapter that mediates between the PAS and the LDAPUserFolder """
security = ClassSecurityInfo()
meta_type = 'LDAP Multi Plugin'
security.declarePrivate('getGroupsForPrincipal')
def getGroupsForPrincipal(self, user, request=None, attr=None):
""" Fulfill GroupsPlugin requirements """
view_name = self.getId() + '_getGroupsForPrincipal'
criteria = {'id':user.getId(), 'attr':attr}
cached_info = self.ZCacheable_get(view_name = view_name,
keywords = criteria,
default = None)
if cached_info is not None:
logger.debug('returning cached results from enumerateUsers')
return cached_info
acl = self._getLDAPUserFolder()
if acl is None:
return ()
unmangled_userid = self._demangle(user.getId())
if unmangled_userid is None:
return ()
ldap_user = acl.getUserById(unmangled_userid)
if ldap_user is None:
return ()
groups = acl.getGroups(ldap_user.getUserDN(), attr=attr)
result = tuple([x[0] for x in groups])
self.ZCacheable_set(result, view_name=view_name, keywords=criteria)
return result
security.declarePrivate('enumerateUsers')
def enumerateUsers( self
, id=None
, login=None
, exact_match=0
, sort_by=None
, max_results=None
, **kw
):
""" Fulfill the UserEnumerationPlugin requirements """
view_name = self.getId() + '_enumerateUsers'
criteria = {'id':id, 'login':login, 'exact_match':exact_match,
'sort_by':sort_by, 'max_results':max_results}
criteria.update(kw)
cached_info = self.ZCacheable_get(view_name = view_name,
keywords = criteria,
default = None)
if cached_info is not None:
logger.debug('returning cached results from enumerateUsers')
return cached_info
result = []
acl = self._getLDAPUserFolder()
login_attr = acl.getProperty('_login_attr')
uid_attr = acl.getProperty('_uid_attr')
rdn_attr = acl.getProperty('_rdnattr')
plugin_id = self.getId()
edit_url = '%s/%s/manage_userrecords' % (plugin_id, acl.getId())
if acl is None:
return ()
if exact_match and (id or login):
if id:
ldap_user = acl.getUserById(id)
elif login:
ldap_user = acl.getUser(login)
if ldap_user is not None:
qs = 'user_dn=%s' % quote_plus(ldap_user.getUserDN())
result.append( { 'id' : ldap_user.getId()
, 'login' : ldap_user.getProperty(login_attr)
, 'pluginid' : plugin_id
, 'editurl' : '%s?%s' % (edit_url, qs)
} )
else:
l_results = []
seen = []
criteria = {}
if id:
if uid_attr == 'dn':
# Workaround: Due to the way findUser reacts when a DN
# is searched for I need to hack around it... This
# limits the usefulness of searching by ID if the user
# folder uses the full DN aas user ID.
criteria[rdn_attr] = id
else:
criteria[uid_attr] = id
if login:
criteria[login_attr] = login
for key, val in kw.items():
if key not in (login_attr, uid_attr):
criteria[key] = val
# If no criteria are given create a criteria set that will
# return all users
if not login and not id:
criteria[login_attr] = ''
l_results = acl.searchUsers(**criteria)
for l_res in l_results:
# If the LDAPUserFolder returns an error, bail
if ( l_res.get('sn', '') == 'Error' and
l_res.get('cn', '') == 'n/a' ):
return ()
if l_res['dn'] not in seen:
l_res['id'] = l_res[uid_attr]
l_res['login'] = l_res[login_attr]
l_res['pluginid'] = plugin_id
quoted_dn = quote_plus(l_res['dn'])
l_res['editurl'] = '%s?user_dn=%s' % (edit_url, quoted_dn)
result.append(l_res)
seen.append(l_res['dn'])
if sort_by is not None:
result.sort(lambda a, b: cmp( a.get(sort_by, '').lower()
, b.get(sort_by, '').lower()
) )
if isinstance(max_results, int) and len(result) > max_results:
result = result[:max_results-1]
result = tuple(result)
self.ZCacheable_set(result, view_name=view_name, keywords=criteria)
return result
security.declarePrivate('enumerateGroups')
def enumerateGroups( self
, id=None
, exact_match=False
, sort_by=None
, max_results=None
, **kw
):
""" Fulfill the GroupEnumerationPlugin requirements """
view_name = self.getId() + '_enumerateGroups'
criteria = {'id':id, 'exact_match':exact_match,
'sort_by':sort_by, 'max_results':max_results}
criteria.update(kw)
cached_info = self.ZCacheable_get(view_name = view_name,
keywords = criteria,
default = None)
if cached_info is not None:
logger.debug('returning cached results from enumerateGroups')
return cached_info
acl = self._getLDAPUserFolder()
if acl is None:
return ()
if (id is not None and not exact_match and not kw):
# likely from a PAS.getUserById(). In any case 'id' and
# 'exact_match' means only a single result should be
# available so try to fetch specific group info from
# cache.
group_info = self._getGroupInfoCache(id)
if group_info is not None:
return (group_info,)
if id is None and exact_match:
raise ValueError, 'Exact Match requested but no id provided'
elif id is not None:
kw[self.groupid_attr] = id
plugin_id = self.getId()
results = acl.searchGroups(exact_match=exact_match, **kw)
if len(results) == 1 and results[0]['cn'] == 'n/a':
# we didn't give enough known criteria for searches
return ()
if isinstance(max_results, int) and len(results) > max_results:
results = results[:max_results+1]
for rec in results:
rec['pluginid'] = plugin_id
rec['id'] = rec[self.groupid_attr]
self._setGroupInfoCache(rec)
results = tuple(results)
self.ZCacheable_set(results, view_name=view_name, keywords=criteria)
return results
security.declarePrivate('enumerateRoles')
def enumerateRoles( self
, id=None
, exact_match=0
, sort_by=None
, max_results=None
, **kw
):
""" Fulfill the RoleEnumerationPlugin requirements """
# For LDAP, roles and groups are really one and the same thing.
# We can simply call enumerateGroups here.
return self.enumerateGroups( id=id
, exact_match=exact_match
, sort_by=sort_by
, max_results=max_results
, **kw
)
classImplements( LDAPMultiPlugin
, IUserEnumerationPlugin
, IGroupsPlugin
, IGroupEnumerationPlugin
, IRoleEnumerationPlugin
, *implementedBy(LDAPPluginBase)
)
InitializeClass(LDAPMultiPlugin)

View File

@ -0,0 +1,189 @@
##############################################################################
#
# LDAPPluginBase Base class for LDAP-based PAS-Plugins
#
##############################################################################
__doc__ = """ LDAPPluginBase module """
__version__ = '$Revision$'[11:-2]
# General Python imports
import copy
import logging
# Zope imports
from Acquisition import aq_base
from OFS.Folder import Folder
from OFS.Cache import Cacheable
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.PluggableAuthService.interfaces.plugins import \
IAuthenticationPlugin, IRolesPlugin, ICredentialsResetPlugin, \
IPropertiesPlugin
from Products.PluggableAuthService.utils import classImplements
logger = logging.getLogger('event.LDAPMultiPlugin')
class LDAPPluginBase(Folder, BasePlugin, Cacheable):
""" Base class for LDAP-based PAS plugins """
security = ClassSecurityInfo()
manage_options = ( BasePlugin.manage_options[:1]
+ Folder.manage_options
+ Cacheable.manage_options
)
_properties = BasePlugin._properties + Folder._properties
# default 'id' attribute for groups
groupid_attr = 'cn'
def __init__(self, id, title=''):
""" Initialize a new instance """
self.id = id
self.title = title
security.declarePrivate('_getLDAPUserFolder')
def _getLDAPUserFolder(self):
""" Safely retrieve a LDAPUserFolder to work with """
embedded_luf = getattr(aq_base(self), 'acl_users', None)
return embedded_luf
security.declarePrivate('authenticateCredentials')
def authenticateCredentials(self, credentials):
""" Fulfill AuthenticationPlugin requirements """
acl = self._getLDAPUserFolder()
login = credentials.get('login')
password = credentials.get('password')
if not acl or not login or not password:
return None, None
user = acl.getUser(login, pwd=password)
if user is None:
return None, None
return (user.getId(), user.getUserName())
security.declarePrivate('resetCredentials')
def resetCredentials(self, request, response):
""" Fulfill CredentialsResetPlugin requirements """
user = getSecurityManager().getUser()
acl = self._getLDAPUserFolder()
if user:
acl._expireUser(user)
security.declarePrivate('getPropertiesForUser')
def getPropertiesForUser(self, user, request=None):
""" Fullfill PropertiesPlugin requirements """
acl = self._getLDAPUserFolder()
if acl is None:
return {}
unmangled_userid = self._demangle(user.getId())
if unmangled_userid is None:
return {}
ldap_user = acl.getUserById(unmangled_userid)
if ldap_user is None:
return {}
# XXX Direct attribute access. Waaa!
properties = copy.deepcopy(ldap_user._properties)
# Need to clean up: The propertysheet mechanism will
# blow up if "None" is encountered
for key, val in properties.items():
if val is None:
properties[key] = ''
return properties
security.declarePrivate('getRolesForPrincipal')
def getRolesForPrincipal(self, user, request=None):
""" Fullfill RolesPlugin requirements """
acl = self._getLDAPUserFolder()
if acl is None:
return ()
unmangled_userid = self._demangle(user.getId())
if unmangled_userid is None:
return ()
ldap_user = acl.getUserById(unmangled_userid)
if ldap_user is None:
return ()
groups = self.getGroupsForPrincipal(user, request)
roles = list(acl._mapRoles(groups))
roles.extend(acl._roles)
return tuple(roles)
security.declarePrivate('_demangle')
def _demangle(self, princid):
# User must start with our prefix (which is likely to be blank anyway)
if not princid.startswith(self.prefix):
return None
return princid[len(self.prefix):]
# Helper methods for simple group caching
security.declarePrivate('_getGroupInfoCacheKey')
def _getGroupInfoCacheKey(self, gid):
"""_getGroupInfoCacheKey(id) -> (view_name, keywords)
given a group id, return view_name and keywords to be used when
querying and storing into the group cache
"""
view_name = self.getId() + '__GroupInfoCache'
keywords = { 'id' : gid }
return view_name, keywords
security.declarePrivate('_setGroupInfoCache')
def _setGroupInfoCache(self, info):
"""Cache a group info"""
gid = info['id']
view_name, keywords = self._getGroupInfoCacheKey(gid)
self.ZCacheable_set(info, view_name=view_name, keywords=keywords)
security.declarePrivate('_getGroupInfoCache')
def _getGroupInfoCache(self, gid, default=None):
"""Retrieve a group info from cache, given its group id.
Returns None or the passed-in default if the cache
has no group with such id
"""
view_name, keywords = self._getGroupInfoCacheKey(gid)
result = self.ZCacheable_get( view_name=view_name
, keywords=keywords
, default=default
)
return result
classImplements( LDAPPluginBase
, IAuthenticationPlugin
, ICredentialsResetPlugin
, IPropertiesPlugin
, IRolesPlugin
)
InitializeClass(LDAPPluginBase)

View File

@ -0,0 +1,54 @@
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.

View File

@ -0,0 +1,61 @@
README for the Zope LDAPMultiPlugins Product
The LDAPMultiPlugins provides PluggableAuthService plugins that use
LDAP as the backend for the services they provide. The
PluggableAuthService is a Zope user folder product that can be extended
in modular fashion using various plugins. See DEPENDENCIES.txt for
software needed by this package.
Please make sure to read the documentation included in the
LDAPUserFolder package as well.
**Caching**
The results of some calls into the plugins provided by these package can
be cached using the Zope ZCacheable mechanism:
- In the Zope Management Interface (ZMI) of your PluggableAuthService
instance, select 'RAM Cache Manager' from the dropdown, give it an ID
and configure it according to your needs.
- Click on your LDAP/ActiveDirectoryMultiPlugin and use the 'Cache'
ZMI tab on the far right to associate the newly created RAM Cache
Manager object with the plugin.
Now your plugin will use the RAM Cache Manager object to cache results
from some of the possibly expensive API calls.
**Special features - Active Directory Multi Plugin**
* Properties of the ADMultiPlugin instance:
* groupid_attr - the LDAP attribute used for group ids.
* grouptitle_attr - the LDAP attribute used to compose group titles.
* group_class - the LDAP class of group objects.
* group_recurse - boolean indicating whether to determine group
memberships of a user by unrolling nested group relationships
(expensive). This feature is not guaranteed to work at this moment.
**Active Directory configuration hints**
In order for groups support to work correctly, you may have to set the
following properties. Every situation is different, but this has helped
some people succeed:
* On the "Properties" tab for the ActiveDirectoryMultiPlugin, set the
groupid_attr property to "name".
* On the contained LDAPUserFolder's "Configure" tab, choose a
property other than "objectGUID", e.g. "sAMAccountName" for the
User ID property. To get to the LDAPUserFolder, click on the
ActiveDirectoryMultiPlugin "Content" tab.
Please see README.ActiveDirectory from the LDAPUserFolder package for
additional information.

View File

@ -0,0 +1 @@
1.5

View File

@ -0,0 +1,44 @@
##############################################################################
#
# __init__.py Initialization code for the LDAP Multi Plugins
#
# This software is governed by a license. See
# LICENSE.txt for the terms of this license.
#
##############################################################################
__doc__ = """ LDAPUserFolder shims initialization module """
__version__ = '$Revision$'[11:-2]
from AccessControl.Permissions import add_user_folders
from Products.PluggableAuthService.PluggableAuthService import \
registerMultiPlugin
from LDAPMultiPlugin import LDAPMultiPlugin, \
manage_addLDAPMultiPlugin, \
addLDAPMultiPluginForm
from ActiveDirectoryMultiPlugin import ActiveDirectoryMultiPlugin, \
manage_addActiveDirectoryMultiPlugin, \
addActiveDirectoryMultiPluginForm
def initialize(context):
""" Initialize the LDAPMultiPlugin """
registerMultiPlugin(LDAPMultiPlugin.meta_type)
registerMultiPlugin(ActiveDirectoryMultiPlugin.meta_type)
context.registerClass( LDAPMultiPlugin
, permission=add_user_folders
, constructors=( addLDAPMultiPluginForm
, manage_addLDAPMultiPlugin
)
, icon='www/ldapmultiplugin.png'
, visibility=None
)
context.registerClass( ActiveDirectoryMultiPlugin
, permission=add_user_folders
, constructors=( addActiveDirectoryMultiPluginForm
, manage_addActiveDirectoryMultiPlugin
)
, icon='www/admultiplugin.png'
, visibility=None
)

View File

@ -0,0 +1,199 @@
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _,
form_title='Add Active Directory Multi Plugin to the PluggabbleAuthService',
help_product='ActiveDirectoryMultiPlugin',
)">
<p class="form-help">
Add a new Active Directory Multi Plugin to the PluggableAuthService with this form.
</p>
<form action="manage_addActiveDirectoryMultiPlugin" method="POST">
<table cellspacing="0" cellpadding="3">
<tr>
<td align="left" valign="top"><div class="form-label">
ID
</div></td>
<td align="left" valign="top" colspan="3">
<input type="text" name="id" size="40" />
</td>
</tr>
<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" />
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
LDAP Server[:port]
</div></td>
<td align="left" valign="top">
<input type="text" name="LDAP_server" size="40"
value="my.ldap.server" />
</td>
<td align="left" valign="top"><div class="form-label">
Use SSL&nbsp;<input type="checkbox" name="use_ssl" />
</div></td>
<td align="left" valign="top"><div class="form-label">
Read-only&nbsp;<input type="checkbox" name="read_only" />
</div></td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Login Name Attribute
</div></td>
<td align="left" valign="top" colspan="3">
<select name="login_attr">
<option value="sAMAccountName">Windows Login Name (sAMAccountName)
</option>
<option value="cn">Canonical Name (cn)</option>
<option value="sn">Last Name (sn)</option>
<option value="givenName">First Name (givenName)</option>
<option value="dn">Distinguished Name (dn)</option>
<option value="objectGUID">Object GUID (objectGUID)</option>
</select>
</td>
</tr>
<tr>
<td align="LEFT" valign="TOP"><div class="form-label">
User ID Attribute
</div></td>
<td align="LEFT" valign="TOP" colspan="3">
<select name="uid_attr">
<option value="objectGUID">Object GUID (objectGUID)</option>
<option value="cn">Canonical Name (cn)</option>
<option value="givenName">First Name (givenName)</option>
<option value="sn">Last Name (sn)</option>
<option value="dn">Distinguished Name (dn)</option>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
RDN Attribute
</div></td>
<td align="left" valign="top" colspan="3">
<select name="rdn_attr">
<option value="cn">Canonical Name (cn)</option>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Users Base DN
</div></td>
<td align="left" valign="top">
<input type="text" name="users_base" size="40"
value="ou=people,o=Organization,c=US" />
</td>
<td align="left" valign="top"><div class="form-label">
Scope
</div></td>
<td align="left" valign="top">
<select name="users_scope:int">
<option value="0"> BASE </option>
<option value="1"> ONELEVEL </option>
<option value="2" selected> SUBTREE </options>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Group storage
</div></td>
<td align="left" valign="top">
<select name="local_groups:int">
<option value="0" selected>
Groups stored on LDAP server
</option>
<option value="1">
Groups not stored on LDAP server
</option>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Groups Base DN
</div></td>
<td align="left" valign="top">
<input type="text" name="groups_base" size="40"
value="ou=groups,o=Organization,c=US" />
</td>
<td align="left" valign="top"><div class="form-label">
Scope
</div></td>
<td align="left" valign="top">
<select name="groups_scope:int">
<option value="0"> BASE </option>
<option value="1"> ONELEVEL </option>
<option value="2" selected> SUBTREE </option>
</select></td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-optional">
Manager DN
</div></td>
<td align="left" valign="top">
<input type="text" name="binduid:string" size="40" />
</td>
<td align="left" valign="top"><div class="form-optional">
Password
</div></td>
<td align="left" valign="top">
<input type="password" name="bindpwd:string" size="13" />
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
User password encryption
</div></td>
<td align="left" valign="top">
<select name="encryption">
<option selected> SHA </option>
<option> SSHA </option>
<option> crypt </option>
<option> clear </option>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Default User Roles
</div></td>
<td align="left" valign="top" colspan="3">
<input type="text" name="roles" size="40" value="Anonymous" />
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td colspan="3">
<br>
<input type="submit" value=" Add ">
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>

View File

@ -0,0 +1,195 @@
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _,
form_title='Add LDAP Multi Plugin to the PluggabbleAuthService',
help_product='LDAPMultiPlugin',
)">
<p class="form-help">
Add a new LDAP Multi Plugin to the PluggableAuthService with this form.
</p>
<form action="manage_addLDAPMultiPlugin" method="POST">
<table cellspacing="0" cellpadding="3">
<tr>
<td align="left" valign="top"><div class="form-label">
ID
</div></td>
<td align="left" valign="top" colspan="3">
<input type="text" name="id" size="40" />
</td>
</tr>
<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" />
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
LDAP Server[:port]
</div></td>
<td align="left" valign="top">
<input type="text" name="LDAP_server" size="40"
value="my.ldap.server" />
</td>
<td align="left" valign="top"><div class="form-label">
Use SSL&nbsp;<input type="checkbox" name="use_ssl" />
</div></td>
<td align="left" valign="top"><div class="form-label">
Read-only&nbsp;<input type="checkbox" name="read_only" />
</div></td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Login Name Attribute
</div></td>
<td align="left" valign="top" colspan="3">
<select name="login_attr">
<option value="cn">Canonical Name (cn)</option>
<option value="uid">UID (uid)</option>
<option value="sn">Surname (sn)</option>
<option value="dn">Distinguished Name (dn)</option>
</select>
</td>
</tr>
<tr>
<td align="LEFT" valign="TOP"><div class="form-label">
User ID Attribute
</div></td>
<td align="LEFT" valign="TOP" colspan="3">
<select name="uid_attr">
<option value="cn">Canonical Name (cn)</option>
<option value="uid">UID (uid)</option>
<option value="sn">Surname (sn)</option>
<option value="dn">Distinguished Name (dn)</option>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
RDN Attribute
</div></td>
<td align="left" valign="top" colspan="3">
<select name="rdn_attr">
<option value="cn">Canonical Name (cn)</option>
<option value="uid">UID (uid)</option>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Users Base DN
</div></td>
<td align="left" valign="top">
<input type="text" name="users_base" size="40"
value="ou=people,o=Organization,c=US" />
</td>
<td align="left" valign="top"><div class="form-label">
Scope
</div></td>
<td align="left" valign="top">
<select name="users_scope:int">
<option value="0"> BASE </option>
<option value="1"> ONELEVEL </option>
<option value="2" selected> SUBTREE </options>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Group storage
</div></td>
<td align="left" valign="top">
<select name="local_groups:int">
<option value="0" selected>
Groups stored on LDAP server
</option>
<option value="1">
Groups not stored on LDAP server
</option>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Groups Base DN
</div></td>
<td align="left" valign="top">
<input type="text" name="groups_base" size="40"
value="ou=groups,o=Organization,c=US" />
</td>
<td align="left" valign="top"><div class="form-label">
Scope
</div></td>
<td align="left" valign="top">
<select name="groups_scope:int">
<option value="0"> BASE </option>
<option value="1"> ONELEVEL </option>
<option value="2" selected> SUBTREE </option>
</select></td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-optional">
Manager DN
</div></td>
<td align="left" valign="top">
<input type="text" name="binduid:string" size="40" />
</td>
<td align="left" valign="top"><div class="form-optional">
Password
</div></td>
<td align="left" valign="top">
<input type="password" name="bindpwd:string" size="13" />
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
User password encryption
</div></td>
<td align="left" valign="top">
<select name="encryption">
<option selected> SHA </option>
<option> SSHA </option>
<option> crypt </option>
<option> clear </option>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top"><div class="form-label">
Default User Roles
</div></td>
<td align="left" valign="top" colspan="3">
<input type="text" name="roles" size="40" value="Anonymous" />
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td colspan="3">
<br>
<input type="submit" value=" Add ">
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>

View File

@ -0,0 +1 @@
""" This space intentionally left blank """

View File

@ -0,0 +1,63 @@
#####################################################################
#
# test_LDAPMultiPlugins.py
#
# This software is governed by a license. See
# LICENSE.txt for the terms of this license.
#
#####################################################################
""" Unit tests for LDAPMultiPlugin and ActiveDirectoryMultiPlugin
$Id$
"""
from unittest import main
from unittest import makeSuite
from unittest import TestSuite
from unittest import TestCase
from Products.PluggableAuthService.interfaces.plugins import \
IUserEnumerationPlugin, IGroupsPlugin, IGroupEnumerationPlugin, \
IRoleEnumerationPlugin
class LMPBaseTests(TestCase):
def _getTargetClass(self):
from Products.LDAPMultiPlugins.LDAPPluginBase import LDAPPluginBase
return LDAPPluginBase
def test_interfaces(self):
from zope.interface.verify import verifyClass
verifyClass(IUserEnumerationPlugin, self._getTargetClass())
verifyClass(IGroupsPlugin, self._getTargetClass())
verifyClass(IGroupEnumerationPlugin, self._getTargetClass())
verifyClass(IRoleEnumerationPlugin, self._getTargetClass())
class ADMPTests(LMPBaseTests):
def _getTargetClass(self):
from Products.LDAPMultiPlugins.ActiveDirectoryMultiPlugin import \
ActiveDirectoryMultiPlugin
return ActiveDirectoryMultiPlugin
class LMPTests(LMPBaseTests):
def _getTargetClass(self):
from Products.LDAPMultiPlugins.LDAPMultiPlugin import LDAPMultiPlugin
return LDAPMultiPlugin
def test_suite():
return TestSuite((
makeSuite( ADMPTests ),
makeSuite( LMPTests ),
))
if __name__ == '__main__':
main(defaultTest='test_suite')

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B