summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJens Vagelpohl <jens@netz.ooo>2005-09-19 11:43:29 +0000
committerJens Vagelpohl <jens@netz.ooo>2005-09-19 11:43:29 +0000
commit33dee1569e464fa7bccc5ccb6666ba1429bfc388 (patch)
tree5b4e8f06cd31642a45d36ee5125f51ca17431939
parent9d755ca0e820cfb565f85f0d0237ce75bc0b5451 (diff)
downloadCMFLDAP-33dee1569e464fa7bccc5ccb6666ba1429bfc388.zip
CMFLDAP-33dee1569e464fa7bccc5ccb6666ba1429bfc388.tar.gz
- checkpoint
-rw-r--r--LDAPMemberDataTool.py229
-rw-r--r--LDAPMembershipTool.py14
-rw-r--r--TODO.txt30
-rw-r--r--dtml/memberdataContents.dtml29
-rw-r--r--skins/cmfldap/join_form.pt121
-rw-r--r--skins/cmfldap/register.py47
-rw-r--r--tests/base/dummy.py34
-rw-r--r--tests/test_LDAPMemberDataTool.py102
-rw-r--r--tests/test_join.py121
-rw-r--r--www/cmfldap_contents.pt13
-rw-r--r--www/cmfldap_memberProperties.pt95
11 files changed, 506 insertions, 329 deletions
diff --git a/LDAPMemberDataTool.py b/LDAPMemberDataTool.py
index 634316a..c350896 100644
--- a/LDAPMemberDataTool.py
+++ b/LDAPMemberDataTool.py
@@ -8,31 +8,54 @@
#####################################################################
__version__='$Revision$'[11:-2]
+# Python imports
+import os
+from copy import deepcopy
-from Products.CMFCore.utils import getToolByName
-from Products.CMFCore.permissions import View
+# General Zope imports
from Globals import InitializeClass
from Globals import DTMLFile
+from Globals import package_home
from Acquisition import aq_base
from AccessControl import ClassSecurityInfo
+from Products.PageTemplates.PageTemplateFile import PageTemplateFile
+
+# CMF imports
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.permissions import View
+from Products.CMFCore.permissions import ManagePortal
from Products.CMFCore.MemberDataTool import MemberDataTool
from Products.CMFCore.MemberDataTool import MemberData
_marker = []
-
+_wwwdir = os.path.join(package_home(globals()), 'www')
class LDAPMemberDataTool(MemberDataTool):
""" This tool wraps user objects, making them act as Member objects. """
security = ClassSecurityInfo()
meta_type = 'LDAP Member Data Tool'
title = 'LDAP Member Data Tool'
- manage_showContents = DTMLFile('dtml/memberdataContents', globals())
+ security.declareProtected(ManagePortal, 'manage_showContents')
+ manage_showContents = PageTemplateFile('cmfldap_contents.pt', _wwwdir)
- #def __init__(self, id='portal_memberdata'):
- # self.id = id
- # MemberDataTool.__init__(self)
+ security.declareProtected(ManagePortal, 'manage_memberProperties')
+ manage_memberProperties = PageTemplateFile( 'cmfldap_memberProperties.pt'
+ , _wwwdir
+ )
+
+ manage_options = ( ( { 'label' : 'Member Properties'
+ , 'action' : 'manage_memberProperties'
+ }
+ ,
+ )
+ + MemberDataTool.manage_options
+ )
+ def __init__(self, id='portal_memberdata'):
+ self.id = id
+ MemberDataTool.__init__(self)
+ self._sorted_attributes = ()
def wrapUser(self, u):
"""
@@ -91,42 +114,6 @@ class LDAPMemberDataTool(MemberDataTool):
return wrapper
- #def getMemberDataContents(self):
- # """ Return the number of members stored in the _members
- # BTree and some other useful info
- # """
- # membertool = getToolByName(self, 'portal_membership')
- # members = self._members
- # temps = self._v_temps
- # user_list = membertool.listMemberIds()
- # member_list = members.keys()
- # member_count = len(members)
- # orphan_count = 0
- # member_temp_count = 0
-
- # if (len(user_list) == 1):
- # # Possible LDAP Error?
- # res_str = user_list[0].lower()
- # if res_str.find('error') != -1:
- # msg = 'No or too many results returned from LDAP'
-
- # return [ { 'member_count' : member_count
- # , 'orphan_count' : msg
- # } ]
-
- # for member in member_list:
- # if member not in user_list:
- # orphan_count = orphan_count + 1
-
- # if temps is not None:
- # member_temp_count=len(temps.keys())
-
- # return [ { 'member_count' : member_count
- # , 'orphan_count' : orphan_count
- # , 'member_temp_count' : member_temp_count
- # } ]
-
-
security.declareProtected(View, 'getMemberProps')
def getMemberProps(self):
""" Return a list of tuples with the property and a "friendly"
@@ -141,6 +128,142 @@ class LDAPMemberDataTool(MemberDataTool):
return self.acl_users.getProperty('_login_attr')
+ #################################################################
+ # CMFLDAP-specific API used in the ZMI only
+ #################################################################
+
+ security.declareProtected(ManagePortal, 'getAvailableMemberProperties')
+ def getAvailableMemberProperties(self):
+ """ Return a list of attributes that have not been assigned yet """
+ uf_schema = deepcopy(self.acl_users.getSchemaConfig())
+
+ return [uf_schema[x] for x in uf_schema.keys()
+ if x not in self._sorted_attributes]
+
+ security.declareProtected(ManagePortal, 'getSortedMemberProperties')
+ def getSortedMemberProperties(self):
+ """ Return a sorted sequence of dictionaries describing the properties
+ available for portal members
+ """
+ sorted_schema = []
+ uf_schema = deepcopy(self.acl_users.getSchemaConfig())
+
+ for property_id in self._sorted_attributes:
+ property_info = uf_schema.get(property_id, None)
+
+ if property_info is not None:
+ sorted_schema.append(property_info)
+
+ return tuple(sorted_schema)
+
+ security.declareProtected(ManagePortal, 'addMemberProperty')
+ def addMemberProperty(self, property_id):
+ """ Add a new property. The property_id represents the true LDAP
+ attribute name
+ """
+ if property_id in self._sorted_attributes:
+ return
+
+ if property_id not in self.acl_users.getSchemaConfig().keys():
+ return
+
+ sorted = list(self._sorted_attributes)
+ sorted.append(property_id)
+ self._sorted_attributes = tuple(sorted)
+
+ security.declareProtected(ManagePortal, 'manage_addMemberProperty')
+ def manage_addMemberProperty(self, property_id, REQUEST=None):
+ """ ZMI wrapper for addMemberProperty """
+ self.addMemberProperty(property_id)
+
+ if REQUEST is not None:
+ msg = 'Property %s added.' % property_id
+ return self.manage_memberProperties(manage_tabs_message=msg)
+
+ security.declareProtected(ManagePortal, 'removeMemberProperty')
+ def removeMemberProperty(self, property_id):
+ """ Remove a member property. The property_id represents the true
+ LDAP attribute name
+ """
+ if property_id not in self._sorted_attributes:
+ return
+
+ sorted = list(self._sorted_attributes)
+ sorted.remove(property_id)
+ self._sorted_attributes = tuple(sorted)
+
+ security.declareProtected(ManagePortal, 'manage_removeMemberProperty')
+ def manage_removeMemberProperty(self, property_id=None, REQUEST=None):
+ """ ZMI wrapper for removeMemberProperty """
+ if property_id is None:
+ msg = 'Please select a property.'
+ else:
+ self.removeMemberProperty(property_id)
+ msg = 'Property %s removed.' % property_id
+
+ if REQUEST is not None:
+ return self.manage_memberProperties(manage_tabs_message=msg)
+
+ security.declareProtected(ManagePortal, 'moveMemberPropertyUp')
+ def moveMemberPropertyUp(self, property_id):
+ """ Move a member property up in the sort ranking. The property_id
+ represents the true LDAP attribute name.
+ """
+ if property_id not in self._sorted_attributes:
+ return
+
+ sorted = list(self._sorted_attributes)
+ property_idx = sorted.index(property_id)
+ prior_idx = property_idx - 1
+
+ if property_idx > 0:
+ current_occupier = sorted[prior_idx]
+ sorted[prior_idx] = property_id
+ sorted[property_idx] = current_occupier
+ self._sorted_attributes = tuple(sorted)
+
+ security.declareProtected(ManagePortal, 'manage_moveMemberPropertyUp')
+ def manage_moveMemberPropertyUp(self, property_id=None, REQUEST=None):
+ """ ZMI wrapper for moveMemberPropertyUp """
+ if property_id is None:
+ msg = 'Please select a property.'
+ else:
+ self.moveMemberPropertyUp(property_id)
+ msg = 'Property %s moved.' % property_id
+
+ if REQUEST is not None:
+ return self.manage_memberProperties(manage_tabs_message=msg)
+
+ security.declareProtected(ManagePortal, 'moveMemberPropertyDown')
+ def moveMemberPropertyDown(self, property_id):
+ """ Move a member property down in the sort ranking. The property_id
+ represents the true LDAP attribute name.
+ """
+ if property_id not in self._sorted_attributes:
+ return
+
+ sorted = list(self._sorted_attributes)
+ property_idx = sorted.index(property_id)
+ next_idx = property_idx + 1
+
+ if property_idx < len(sorted) - 1:
+ current_occupier = sorted[next_idx]
+ sorted[next_idx] = property_id
+ sorted[property_idx] = current_occupier
+ self._sorted_attributes = tuple(sorted)
+
+ security.declareProtected(ManagePortal, 'manage_moveMemberPropertyDown')
+ def manage_moveMemberPropertyDown(self, property_id=None, REQUEST=None):
+ """ ZMI wrapper for moveMemberPropertyDown """
+ if property_id is None:
+ msg = 'Please select a property.'
+ else:
+ self.moveMemberPropertyDown(property_id)
+ msg = 'Property %s moved.' % property_id
+
+ if REQUEST is not None:
+ return self.manage_memberProperties(manage_tabs_message=msg)
+
InitializeClass(LDAPMemberDataTool)
@@ -165,9 +288,12 @@ class LDAPMemberData(MemberData):
for mapped_attr in mapped_attrs:
if ( not mapping.has_key(mapped_attr[0])
and mapping.has_key(mapped_attr[1]) ):
- mapping.set( mapped_attr[0]
- , mapping[mapped_attr[1]]
- )
+ mapping[mapped_attr[0]] = mapping[mapped_attr[1]]
+
+ # Special-case a couple keys which are pretty much "hard-coded"
+ # in CMF
+ if mapping.has_key('email') and not mapping.has_key('mail'):
+ mapping['mail'] = mapping['email']
change_vals = filter( None
, map( lambda x, lsk=ldap_schemakeys: x in lsk
@@ -181,12 +307,15 @@ class LDAPMemberData(MemberData):
rdn_attr = acl.getProperty('_rdnattr')
if not mapping.has_key(rdn_attr):
- mapping.set( rdn_attr
- , user_obj.getUserName()
- )
+ try:
+ mapping.set( rdn_attr
+ , user_obj.getUserName()
+ )
+ except AttributeError:
+ # This is not a REQUEST object...
+ mapping[rdn_attr] = user_obj.getUserName()
acl.manage_editUser(user_obj.getUserDN(), kwargs=mapping)
- acl._expireUser(user_obj)
except:
pass
diff --git a/LDAPMembershipTool.py b/LDAPMembershipTool.py
index 78129a3..ddb3453 100644
--- a/LDAPMembershipTool.py
+++ b/LDAPMembershipTool.py
@@ -37,10 +37,21 @@ class LDAPMembershipTool(MembershipTool):
"""
args = {}
acl = self.acl_users
+
+ login_attribute = acl.getProperty('_login_attr')
+ rdn_attribute = acl.getProperty('_rdnattr')
+
+ if login_attribute != rdn_attribute:
+ # XXX This is currently not supported
+ raise ValueError, 'Login and RDN attribute in your user folder ' \
+ 'are not the same, cannot create a member record.'
args['user_pw'] = args['confirm_pw'] = password
- args[acl.getProperty('_login_attr')] = id
+ args[login_attribute] = id
args['user_roles'] = roles
+
+ if properties.get('email', None):
+ args['mail'] = properties['email']
acl.manage_addUser(REQUEST=None, kwargs=args)
self.createMemberarea(id)
@@ -94,7 +105,6 @@ class LDAPMembershipTool(MembershipTool):
return tuple(member_ids)
-
#security.declareProtected(View, 'memberExists')
#def memberExists(self, properties):
# """ Does the incoming set of data collide with an existing member? """
diff --git a/TODO.txt b/TODO.txt
index e442dae..b8af30f 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,15 +1,15 @@
-- remove join_form and related skins and use CMF-provided form. Creates
- "naked" records which can be edited using personalize_form.
- addMember should map the login to _rdnattr/_login_attr and "cn"
-
-- Copy personalize_form.pt from CMF. Change to display a sorted list of
- properties. personalize.py from CMF hands through the full REQUEST
- to member.setProperties.
-
-- register.py can be deleted, it is unused (now: members_add_control.py)
-
-- mail_password_form needs reimplementing, CMF handler simply does
- getMemberByID and gets the pw from there. Needs its own handler
- for CMFLDAP.
-
-- fold CMFLDAP into LDAPUserFolder
+[ ] remove join_form and related skins and use CMF-provided form. Creates
+ "naked" records which can be edited using personalize_form.
+ addMember should map the login to _rdnattr/_login_attr and "cn"
+
+[ ] Copy personalize_form.pt from CMF. Change to display a sorted list of
+ properties. personalize.py from CMF hands through the full REQUEST
+ to member.setProperties.
+
+[x] register.py can be deleted, it is unused (now: members_add_control.py)
+
+[ ] mail_password_form needs reimplementing, CMF handler simply does
+ getMemberByID and gets the pw from there. Needs its own handler
+ for CMFLDAP.
+
+[ ] fold CMFLDAP into LDAPUserFolder
diff --git a/dtml/memberdataContents.dtml b/dtml/memberdataContents.dtml
deleted file mode 100644
index 8aad113..0000000
--- a/dtml/memberdataContents.dtml
+++ /dev/null
@@ -1,29 +0,0 @@
-<dtml-var manage_page_header>
-<dtml-var name="manage_tabs">
-
-<h2>Membership data tool contents</h2>
-
-<dtml-if name="button_pressed">
-
- <dtml-call expr="pruneMemberDataContents()">
- <dtml-call expr="REQUEST.RESPONSE.redirect( URL0 )">
-
-</dtml-if>
-
-<dtml-in expr="getMemberDataContents()" mapping>
-
- <p><b>Total number of members stored: &dtml-member_count;</b></p>
- <p><b>"Orphaned" members not in user folder: &dtml-orphan_count;</b></p>
-
- <dtml-if expr="(_.len(_.str(orphan_count)) < 10) and (_.int(orphan_count) > 0)">
-
- <form action="<dtml-var name="URL0">" method="post">
- <input type="submit" name="button_pressed"
- value=" Prune orphaned member records ">
- </form>
-
- </dtml-if>
-
-</dtml-in>
-
-<dtml-var manage_page_footer>
diff --git a/skins/cmfldap/join_form.pt b/skins/cmfldap/join_form.pt
deleted file mode 100644
index 0810ec2..0000000
--- a/skins/cmfldap/join_form.pt
+++ /dev/null
@@ -1,121 +0,0 @@
-<html xmlns:tal="http://xml.zope.org/namespaces/tal"
- xmlns:metal="http://xml.zope.org/namespaces/metal"
- metal:use-macro="here/main_template/macros/master">
-<body>
-<div metal:fill-slot="main">
-<div class="Desktop">
-
-<span tal:replace="request/message"
- tal:condition="request/message|nothing"><hr></span>
-
-<h1>Become a member</h1>
-
-<div tal:define="registered python: here.portal_membership.getAuthenticatedMember().has_role('Member')">
- <div tal:condition="python: registered">
- <p>You are already a member. You may use the
- <a href="personalize_form">personalization form</a>
- to change your membership information. </p>
- </div>
-
-<div tal:condition="python: not(registered)">
- <p> Becoming a member gives you the ability to personalize the site and
- participate in the community.</p>
- <p> It does not cost any money to become a member and your email and other i
- personal information will remain private.</p>
-
- <span tal:condition="python: here.portal_properties.validate_email">
- <p> You must submit a valid email address. This address will be used to
- send you your randomly-generated password. Once you have logged in with
- this password, you may change it to anything you like.</p>
- </span>
-
-<div class="DesktopStatusBar"
- tal:content="request/error|nothing">
- <hr>
-</div>
-
-<form action="register" method="POST">
-<input type="hidden" name="last_visit:date" value=""
- tal:attributes="value here/ZopeTime">
-<input type="hidden" name="prev_visit:date" value=""
- tal:attributes="value here/ZopeTime">
-
-<table class="FormLayout">
- <tr>
- <th> Login Name
- </th>
- <td>
- <input type="text"
- name="username" size="30"
- value=""
- tal:attributes="value request/username|nothing">
- </td>
- </tr>
-<tbody tal:condition="python: not(here.portal_properties.validate_email)">
- <tr>
- <th> Password
- </th>
- <td align="left" valign="top">
- <input type="password" name="password" size="30">
- </td>
- </tr>
- <tr>
- <th> Password (confirm)
- </th>
- <td align="left" valign="top">
- <input type="password" name="confirm" size="30">
- </td>
- </tr>
-</tbody>
-<tbody tal:define="props here/portal_memberdata/getMemberProps;
- l_attr here/portal_memberdata/getLoginAttribute"
- tal:repeat="prop props">
- <tr tal:define="p_name python: prop[0];
- p_label python: prop[1];
- p_value python: request.has_key(p_name) and request[p_name] or ''"
- tal:condition="python: p_name not in ( 'dn', l_attr )">
- <th> <span tal:replace="p_label" />
- </th>
- <td>
- <input type="text" name="" value="" size="30"
- tal:attributes="name p_name;
- value p_value">
- </td>
- </tr>
-</tbody>
-
- <tr>
- <th valign="top">Listed status</th>
- <td>
- <input type="radio" name="public" value="on" id="cb_listed" checked />
- <label for="cb_listed">Listed</label>
- <dl class="FieldHelp">
- <dd>You will show up on the public membership roster.</dd>
- </dl>
-
- <input type="radio" name="public" value="" id="cb_unlisted" />
- <label for="cb_unlisted">Unlisted</label>
- <dl class="FieldHelp">
- <dd> You will <i>not</i> show up on the public membership roster.
- Your Member folder will still be publicly accessible unless
- you change its security settings.</dd>
- </dl>
- </td>
- </tr>
-
- <tr>
- <td><br></td>
- <td>
- <input type="submit" name="submit" value="Register">
- </td>
- </tr>
-</table>
-
-</form>
-</div>
-</div>
-</div>
-</div>
-</body>
-</html>
-
diff --git a/skins/cmfldap/register.py b/skins/cmfldap/register.py
deleted file mode 100644
index f91fab1..0000000
--- a/skins/cmfldap/register.py
+++ /dev/null
@@ -1,47 +0,0 @@
-## Script (Python) "register"
-##title=Register a user
-##bind namespace=_
-##parameters=password='password', confirm='confirm'
-REQUEST=context.REQUEST
-portal_membership = context.portal_membership
-portal_properties = context.portal_properties
-portal_registration = context.portal_registration
-
-if REQUEST.get('email', '') == '':
- if REQUEST.has_key('mail'):
- email_addr = REQUEST.get( 'mail' )
- else:
- email_addr = ''
-
- REQUEST.set('email', email_addr)
-
-if not portal_properties.validate_email:
- failMessage = portal_registration.testPasswordValidity(password, confirm)
- if failMessage:
- REQUEST.set('error', failMessage)
- return context.join_form(context, REQUEST, error=failMessage)
-
-failure = portal_membership.memberExists(REQUEST)
-if failure:
- failMessage = 'A member with this %s already exists.' % failure
- REQUEST.set('error', failMessage)
- return context.join_form(context, REQUEST, error=failMessage)
-
-
-failMessage = portal_registration.testPropertiesValidity(REQUEST)
-
-if failMessage:
- REQUEST.set('error', failMessage)
- return context.join_form(context, REQUEST, error=failMessage)
-
-else:
- password=REQUEST.get('password') or portal_registration.generatePassword()
- portal_registration.addMember( REQUEST['username']
- , password
- , properties=REQUEST
- )
-
- if portal_properties.validate_email or REQUEST.get('mail_me', 0):
- portal_registration.registeredNotify(REQUEST['username'])
-
- return context.registered(context, REQUEST)
diff --git a/tests/base/dummy.py b/tests/base/dummy.py
index 1d5f66e..49ab5ba 100644
--- a/tests/base/dummy.py
+++ b/tests/base/dummy.py
@@ -51,6 +51,29 @@ class LDAPDummyUserFolder(DummyUserFolder):
_rdnattr = 'cn'
_login_attr = 'cn'
+ _schema = { 'cn' : { 'ldap_name' : 'cn'
+ , 'friendly_name' : 'Canonical Name'
+ , 'public_name' : 'fullname'
+ , 'multivalued' : False
+ }
+ , 'sn' : { 'ldap_name' : 'sn'
+ , 'friendly_name' : 'Last Name'
+ , 'multivalued' : False
+ }
+ , 'givenName' : { 'ldap_name' : 'givenName'
+ , 'friendly_name' : 'First Name'
+ , 'multivalued' : False
+ }
+ , 'mail' : { 'ldap_name' : 'mail'
+ , 'friendly_name' : 'Email'
+ , 'public_name' : 'email'
+ , 'multivalued' : False
+ }
+ , 'telephoneNumber' : { 'ldap_name' : 'telephoneNumber'
+ , 'friendly_name' : 'Telephone number'
+ , 'multivalued' : False
+ }
+ }
def __init__(self):
self.id = 'acl_users'
@@ -77,7 +100,10 @@ class LDAPDummyUserFolder(DummyUserFolder):
def manage_addUser(self, REQUEST, kwargs={}):
user_id = kwargs.get(self._login_attr)
- setattr(self, user_id, LDAPDummyUser(user_id))
+ setattr( self
+ , user_id
+ , LDAPDummyUser(user_id, roles=kwargs.get('user_roles', []))
+ )
def manage_editUser(self, dn, properties={}):
user = self.getUserByDN(dn)
@@ -108,9 +134,8 @@ class LDAPDummyUserFolder(DummyUserFolder):
def getMappedUserAttrs(self):
return []
-
- def getLDAPSchema(self):
- return []
+ def getSchemaConfig(self):
+ return self._schema
def _expireUser(self, user_ob):
pass
@@ -123,3 +148,4 @@ class LDAPDummyUserFolder(DummyUserFolder):
user_id = rdn.split('=')[1]
return getattr(self, user_id, None)
+
diff --git a/tests/test_LDAPMemberDataTool.py b/tests/test_LDAPMemberDataTool.py
index c081042..f769dfc 100644
--- a/tests/test_LDAPMemberDataTool.py
+++ b/tests/test_LDAPMemberDataTool.py
@@ -62,6 +62,108 @@ class LDAPMemberDataToolTests(TestCase):
self.failIf( tool._members.has_key('user_foo') )
self.failIf( tool.deleteMemberData('user_foo') )
+ def test_MemberPropertyManagement(self):
+ folder = Folder('test_folder')
+ folder._setObject('portal_memberdata', self._makeOne())
+ folder._setObject('acl_users', LDAPDummyUserFolder())
+ tool = folder.portal_memberdata
+ ldap_schema = folder.acl_users.getSchemaConfig()
+
+ # Starting out, no property is registered. All LDAPUserFolder schema
+ # items are available for registration.
+ self.assertEqual(len(tool.getSortedMemberProperties()), 0)
+ available = tool.getAvailableMemberProperties()
+ available_keys = [x['ldap_name'] for x in available]
+ self.assertEqual( len(ldap_schema.keys())
+ , len(available)
+ )
+ for ldap_property in ldap_schema.keys():
+ self.failUnless(ldap_property in available_keys)
+
+ # Now I am adding three properties. I'm also attempting to add an
+ # unknown property, and add one of them twice. Those will be
+ # disregarded.
+ tool.addMemberProperty('sn')
+ tool.addMemberProperty('givenName')
+ tool.addMemberProperty('mail')
+ tool.addMemberProperty('FOO')
+ tool.addMemberProperty('givenName')
+ available = tool.getAvailableMemberProperties()
+ available_keys = [x['ldap_name'] for x in available]
+ assigned = tool.getSortedMemberProperties()
+ self.assertEqual(len(assigned), 3)
+ self.assertEqual( len(ldap_schema.keys())
+ , len(available) + 3
+ )
+ for property_info in assigned:
+ self.failIf(property_info['ldap_name'] in available_keys)
+ self.failIf('FOO' in [x['ldap_name'] for x in assigned])
+
+ # One of the premises is that new attributes are always appended,
+ # they appear last after they have been registered. We can predict
+ # the order.
+ assigned = tool.getSortedMemberProperties()
+ self.assertEquals( [x['ldap_name'] for x in assigned]
+ , ['sn', 'givenName', 'mail']
+ )
+
+ # Now we start sorting them a bit
+ tool.moveMemberPropertyUp('givenName')
+ assigned = tool.getSortedMemberProperties()
+ self.assertEquals( [x['ldap_name'] for x in assigned]
+ , ['givenName', 'sn', 'mail']
+ )
+
+ # Moving the top element up does nothing.
+ tool.moveMemberPropertyUp('givenName')
+ assigned = tool.getSortedMemberProperties()
+ self.assertEquals( [x['ldap_name'] for x in assigned]
+ , ['givenName', 'sn', 'mail']
+ )
+
+ # Moving an unknown element up does nothing.
+ tool.moveMemberPropertyUp('FOO')
+ assigned = tool.getSortedMemberProperties()
+ self.assertEquals( [x['ldap_name'] for x in assigned]
+ , ['givenName', 'sn', 'mail']
+ )
+
+ # Moving one down
+ tool.moveMemberPropertyDown('sn')
+ assigned = tool.getSortedMemberProperties()
+ self.assertEquals( [x['ldap_name'] for x in assigned]
+ , ['givenName', 'mail', 'sn']
+ )
+
+ # Moving the bottom element down does nothing
+ tool.moveMemberPropertyDown('sn')
+ assigned = tool.getSortedMemberProperties()
+ self.assertEquals( [x['ldap_name'] for x in assigned]
+ , ['givenName', 'mail', 'sn']
+ )
+
+ # Moving an unknown element down does nothing
+ tool.moveMemberPropertyDown('FOO')
+ assigned = tool.getSortedMemberProperties()
+ self.assertEquals( [x['ldap_name'] for x in assigned]
+ , ['givenName', 'mail', 'sn']
+ )
+
+ # Now we are deleting one of the elements.
+ tool.removeMemberProperty('mail')
+ assigned = tool.getSortedMemberProperties()
+ available = tool.getAvailableMemberProperties()
+ available_keys = [x['ldap_name'] for x in available]
+ self.assertEquals( [x['ldap_name'] for x in assigned]
+ , ['givenName', 'sn']
+ )
+ self.assertEqual(len(assigned), 2)
+ self.assertEqual( len(ldap_schema.keys())
+ , len(available) + 2
+ )
+ for property_info in assigned:
+ self.failIf(property_info['ldap_name'] in available_keys)
+
class LDAPMemberDataTests(TestCase):
diff --git a/tests/test_join.py b/tests/test_join.py
index 7cb9cbb..03717b5 100644
--- a/tests/test_join.py
+++ b/tests/test_join.py
@@ -1,15 +1,11 @@
-##############################################################################
+#####################################################################
#
-# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# test_join "Functional" tests for adding members
#
-# 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.
+# This software is governed by a license. See
+# LICENSE.txt for the terms of this license.
#
-##############################################################################
+#####################################################################
""" Unit tests for adding members.
$Id: test_join.py 37761 2005-08-06 14:52:28Z jens $
@@ -20,10 +16,11 @@ import Testing
import Zope2
Zope2.startup()
-from Products.CMFCore.tests.base.testcase import TransactionalTest
+from Products.CMFCore.tests.base.testcase import RequestTest
+from Products.CMFDefault.tests.test_join import MembershipTests
-class MembershipTests( TransactionalTest ):
+class LDAPMembershipTests(MembershipTests):
def _makePortal(self):
from Products.CMFLDAP.tests.base.dummy import LDAPDummyUserFolder
@@ -39,61 +36,63 @@ class MembershipTests( TransactionalTest ):
self.root.site.manage_delObjects(['acl_users'])
self.root.site._setObject('acl_users', LDAPDummyUserFolder())
- def test_join( self ):
- self._makePortal()
- site = self.root.site
+ return self.root.site
+
+
+class LDAPMembershipFunctionalTests(RequestTest):
+
+ def _makePortal(self):
+ from Products.CMFLDAP.tests.base.dummy import LDAPDummyUserFolder
+ factory = self.root.manage_addProduct['CMFDefault'].addConfiguredSite
+ factory( 'site'
+ , 'CMFDefault:default'
+ , snapshot=False
+ , extension_ids=('CMFLDAP:default',)
+ )
+
+ # Remove the "standard" user folder and replace it with a
+ # LDAPDummyUserFolder
+ self.root.site.manage_delObjects(['acl_users'])
+ self.root.site._setObject('acl_users', LDAPDummyUserFolder())
+
+ return self.root.site
+
+
+ def test_join_superhighlevel(self):
+ # Make sure the member data wrapper carries correct properties
+ # after joining
+ site = self._makePortal()
member_id = 'test_user'
- site.portal_registration.addMember( member_id
- , 'zzyyzz'
- , properties={ 'username': member_id
- , 'email' : 'foo@bar.com'
- }
- )
- u = site.acl_users.getUser(member_id)
- self.failUnless(u)
-
- def test_join_without_email( self ):
- self._makePortal()
- site = self.root.site
- self.assertRaises(ValueError,
- site.portal_registration.addMember,
- 'test_user',
- 'zzyyzz',
- properties={'username':'test_user', 'email': ''}
- )
-
- def test_join_with_variable_id_policies( self ):
- self._makePortal()
- site = self.root.site
- member_id = 'test.user'
-
- # Test with the default policy: Names with "." should fail
- self.assertRaises(ValueError,
- site.portal_registration.addMember,
- member_id,
- 'zzyyzz',
- properties={ 'username':'Test User'
- , 'email': 'foo@bar.com'
- }
- )
-
- # Now change the policy to allow "."
- #import pdb; pdb.set_trace()
- new_pattern = "^[A-Za-z][A-Za-z0-9_\.]*$"
- site.portal_registration.manage_editIDPattern(new_pattern)
- site.portal_registration.addMember( member_id
- , 'zzyyzz'
- , properties={ 'username': 'TestUser2'
- , 'email' : 'foo@bar.com'
- }
- )
- u = site.acl_users.getUser(member_id)
- self.failUnless(u)
+
+ try:
+ controller_script = site.members_add_control
+ except AttributeError:
+ # Skin path not set up, which is very likely during testing.
+ # Forcing it manually. Eep!
+ site.changeSkin('Basic')
+ controller_script = site.members_add_control
+
+ status = controller_script(member_id, 'zzyyzz', 'foo@bar.com')
+
+ # If everything worked correctly, the status will be False and the
+ # portal_status_message will proclaim success
+ self.assertEqual(status, False)
+ self.assertEqual( str(site.REQUEST.other['portal_status_message'])
+ , 'Success!'
+ )
+
+ m = site.portal_membership.getMemberById('test_user')
+ self.assertEqual(m.getProperty('email'), 'foo@bar.com')
+ self.assertEqual(m.getMemberId(), member_id)
+ self.assertEqual(m.getRoles(), ('Member', 'Authenticated'))
+ print m.__dict__
+ print m.getUser().__dict__
def test_suite():
return TestSuite((
- makeSuite(MembershipTests),
+ makeSuite(LDAPMembershipTests),
+ makeSuite(LDAPMembershipFunctionalTests),
))
if __name__ == '__main__':
diff --git a/www/cmfldap_contents.pt b/www/cmfldap_contents.pt
new file mode 100644
index 0000000..41be930
--- /dev/null
+++ b/www/cmfldap_contents.pt
@@ -0,0 +1,13 @@
+<h1 tal:replace="structure context/manage_page_header">HEADER</h1>
+<h1 tal:replace="structure context/manage_tabs">TABS</h1>
+
+<h3>Membership data tool contents</h3>
+
+<p class="form-help">
+ Retrieving all users in the user folder in order to compare them to the
+ list of members stored in this tool is not supported since retrieving
+ all known user records from LDAP has the potential of bringing the site
+ and your LDAP server to a standstill.
+</p>
+
+<h1 tal:replace="structure context/manage_page_footer">FOOTER</h1>
diff --git a/www/cmfldap_memberProperties.pt b/www/cmfldap_memberProperties.pt
new file mode 100644
index 0000000..b1b41db
--- /dev/null
+++ b/www/cmfldap_memberProperties.pt
@@ -0,0 +1,95 @@
+<h1 tal:replace="structure context/manage_page_header">HEADER</h1>
+<h1 tal:replace="structure context/manage_tabs">TABS</h1>
+
+<h3>Member data properties</h3>
+
+<p class="form-help">
+ Use this view to manage member properties available to portal members.
+ These properties are used on the Join form as well as the member
+ preference screen in the portal.
+</p>
+
+<p class="form-help">
+ The properties on this page are sourced from the LDAP schema items defined
+ on the <tt>LDAP Schema</tt> tab of your LDAPUserFolder instance.
+</p>
+
+<form method="post" action="."
+ tal:define="props context/getSortedMemberProperties"
+ tal:on-error="nothing">
+
+ <table width="98%">
+ <tr class="list-header">
+ <td colspan="2" class="form-label"> Registered member properties </td>
+ </tr>
+
+ <tr tal:repeat="prop_info props">
+ <td with="16">
+ <input type="radio" name="property_id" class="form-element" value=""
+ tal:attributes="value prop_info/ldap_name" />
+ </td>
+ <td class="form-text"
+ tal:define="ln prop_info/ldap_name;
+ fn prop_info/friendly_name|string:n/a;
+ mn prop_info/public_name||nothing"
+ tal:content="string:${ln} (${fn})">
+ sn (Last Name)
+ </td>
+ </tr>
+
+ <tr tal:condition="not: props">
+ <td colspan="2" class="form-help"> No properties registered. </td>
+ </tr>
+
+ <tr tal:condition="props">
+ <td width="16">&nbsp;</td>
+ <td>
+ <input type="submit" name="manage_removeMemberProperty:method"
+ value=" Remove " />
+ <input type="submit" name="manage_moveMemberPropertyUp:method"
+ value=" Move up " />
+ <input type="submit" name="manage_moveMemberPropertyDown:method"
+ value=" Move down " />
+ </td>
+ </tr>
+
+ </table>
+
+</form>
+
+<form method="post" action="manage_addMemberProperty"
+ tal:define="available context/getAvailableMemberProperties">
+
+ <table width="98%">
+ <tr class="list-header">
+ <td class="form-label"> Register member properties </td>
+ </tr>
+
+ <tr>
+ <td>
+ <select name="property_id"
+ tal:condition="available">
+ <option tal:repeat="prop_info available"
+ tal:attributes="value prop_info/ldap_name"
+ tal:content="string:${prop_info/ldap_name} (${prop_info/friendly_name})">
+ sn (Last Name)
+ </option>
+ </select>
+ </td>
+ </tr>
+
+ <tr tal:condition="not: available">
+ <td class="form-help"> All available properties registered. </td>
+ </tr>
+
+ <tr tal:condition="available">
+ <td>
+ <input type="submit" name="manage_addMemberProperty" value=" Add " />
+ </td>
+ </tr>
+
+ </table>
+
+</form>
+
+<h1 tal:replace="structure context/manage_page_footer">FOOTER</h1>