summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJens Vagelpohl <jens@netz.ooo>2005-09-20 14:29:33 +0000
committerJens Vagelpohl <jens@netz.ooo>2005-09-20 14:29:33 +0000
commit066634aaa188c2035d6a45a692e5837bcdc28ff0 (patch)
tree57809ab4887c9167cbd85013559e314c5340945a
parent511ff9fe6c461e36f3caa5de1a27850114300bb5 (diff)
downloadCMFLDAP-066634aaa188c2035d6a45a692e5837bcdc28ff0.zip
CMFLDAP-066634aaa188c2035d6a45a692e5837bcdc28ff0.tar.gz
- checkpoint: joining and personalizing works
-rw-r--r--LDAPMemberDataTool.py43
-rw-r--r--LDAPMembershipTool.py1
-rw-r--r--skins/cmfldap/join_form.py66
-rw-r--r--skins/cmfldap/join_template.pt117
-rw-r--r--skins/cmfldap/members_add_control.py37
-rw-r--r--skins/cmfldap/personalize_form.pt190
-rw-r--r--tests/base/dummy.py7
-rw-r--r--tests/test_functional.py (renamed from tests/test_join.py)51
8 files changed, 414 insertions, 98 deletions
diff --git a/LDAPMemberDataTool.py b/LDAPMemberDataTool.py
index c350896..fc90fca 100644
--- a/LDAPMemberDataTool.py
+++ b/LDAPMemberDataTool.py
@@ -18,6 +18,7 @@ from Globals import DTMLFile
from Globals import package_home
from Acquisition import aq_base
from AccessControl import ClassSecurityInfo
+from ZPublisher.HTTPRequest import HTTPRequest
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
# CMF imports
@@ -140,18 +141,26 @@ class LDAPMemberDataTool(MemberDataTool):
return [uf_schema[x] for x in uf_schema.keys()
if x not in self._sorted_attributes]
- security.declareProtected(ManagePortal, 'getSortedMemberProperties')
+ security.declarePublic('getSortedMemberProperties')
def getSortedMemberProperties(self):
""" Return a sorted sequence of dictionaries describing the properties
available for portal members
+
+ This method is declared Public because it is used for the join_form
+ as well.
"""
sorted_schema = []
uf_schema = deepcopy(self.acl_users.getSchemaConfig())
+ uf_login = self.acl_users.getProperty('_login_attr')
+ uf_rdn = self.acl_users.getProperty('_rdnattr')
for property_id in self._sorted_attributes:
property_info = uf_schema.get(property_id, None)
- if property_info is not None:
+ # Filtering out those properties that are either invalid
+ # or provided already by the default machinery.
+ if ( property_info is not None and
+ property_id not in (uf_login, uf_rdn, 'mail') ):
sorted_schema.append(property_info)
return tuple(sorted_schema)
@@ -283,6 +292,9 @@ class LDAPMemberData(MemberData):
acl = self.acl_users
ldap_schemakeys = [x[0] for x in acl.getLDAPSchema()]
+ if isinstance(mapping, HTTPRequest):
+ mapping = mapping.form
+
# back conversion of mapped attributes
mapped_attrs = acl.getMappedUserAttrs()
for mapped_attr in mapped_attrs:
@@ -307,23 +319,24 @@ class LDAPMemberData(MemberData):
rdn_attr = acl.getProperty('_rdnattr')
if not mapping.has_key(rdn_attr):
- try:
- mapping.set( rdn_attr
- , user_obj.getUserName()
- )
- except AttributeError:
- # This is not a REQUEST object...
- mapping[rdn_attr] = user_obj.getUserName()
+ mapping[rdn_attr] = user_obj.getUserName()
- acl.manage_editUser(user_obj.getUserDN(), kwargs=mapping)
+ acl.manage_editUser( user_obj.getUserDN()
+ , kwargs=mapping
+ )
except:
pass
- # To be on the safe side...
- wrapper_keys = self.__dict__.keys()
- for key in ldap_schemakeys:
- if mapping.has_key(key) and key in wrapper_keys:
- setattr(self, key, mapping[key])
+ # Before we hand this over to the default MemberData implementation,
+ # purge out all keys we have already set via LDAP so that we never
+ # shadow a LDAP value on the member data wrapper
+ # We want to hand over the "default" stuff like listed status or
+ # the skin selection.
+ consumed_attributes = [x[0] for x in mapped_attrs]
+ consumed_attributes.extend(ldap_schemakeys)
+ for key in consumed_attributes:
+ if mapping.has_key(key):
+ del mapping[key]
MemberData.setMemberProperties(self, mapping)
diff --git a/LDAPMembershipTool.py b/LDAPMembershipTool.py
index ddb3453..1490411 100644
--- a/LDAPMembershipTool.py
+++ b/LDAPMembershipTool.py
@@ -37,7 +37,6 @@ class LDAPMembershipTool(MembershipTool):
"""
args = {}
acl = self.acl_users
-
login_attribute = acl.getProperty('_login_attr')
rdn_attribute = acl.getProperty('_rdnattr')
diff --git a/skins/cmfldap/join_form.py b/skins/cmfldap/join_form.py
new file mode 100644
index 0000000..ee6e107
--- /dev/null
+++ b/skins/cmfldap/join_form.py
@@ -0,0 +1,66 @@
+##parameters=b_start=0, member_id='', member_email='', password='', confirm='', send_password='', add='', cancel=''
+##
+from Products.CMFCore.utils import getToolByName
+from Products.CMFDefault.permissions import ManageUsers
+from Products.CMFDefault.utils import MessageID as _
+
+atool = getToolByName(script, 'portal_actions')
+mtool = getToolByName(script, 'portal_membership')
+mdtool = getToolByName(script, 'portal_memberdata')
+ptool = getToolByName(script, 'portal_properties')
+utool = getToolByName(script, 'portal_url')
+portal_url = utool()
+validate_email = ptool.getProperty('validate_email')
+is_anon = mtool.isAnonymousUser()
+is_newmember = False
+is_usermanager = mtool.checkPermission(ManageUsers, mtool)
+
+
+form = context.REQUEST.form
+if add and \
+ context.validatePassword(**form) and \
+ context.members_add_control(**form) and \
+ context.setRedirect(atool, 'user/join', b_start=b_start):
+ return
+elif cancel and \
+ context.setRedirect(atool, 'global/manage_members', b_start=b_start):
+ return
+
+
+options = {}
+
+if context.REQUEST.get('portal_status_message', '') == _('Success!'):
+ is_anon = False
+ is_newmember = True
+
+options['title'] = is_usermanager and _('Register Member') \
+ or _('Become a Member')
+options['member_id'] = member_id
+options['member_email'] = member_email
+options['password'] = is_newmember and context.REQUEST.get('password', '') or ''
+options['send_password'] = send_password
+options['portal_url'] = portal_url
+options['isAnon'] = is_anon
+options['isAnonOrUserManager'] = is_anon or is_usermanager
+options['isNewMember'] = is_newmember
+options['isOrdinaryMember'] = not (is_anon or is_newmember or is_usermanager)
+options['validate_email'] = validate_email
+
+added_properties = mdtool.getSortedMemberProperties()
+options['added_properties'] = added_properties
+for property_info in added_properties:
+ p_name = property_info['ldap_name']
+ options[p_name] = form.get(p_name, '')
+
+buttons = []
+if is_newmember:
+ target = atool.getActionInfo('user/logged_in')['url']
+ buttons.append( {'name': 'login', 'value': _('Log in')} )
+else:
+ target = atool.getActionInfo('user/join')['url']
+ buttons.append( {'name': 'add', 'value': _('Register')} )
+ buttons.append( {'name': 'cancel', 'value': _('Cancel')} )
+options['form'] = { 'action': target,
+ 'listButtonInfos': tuple(buttons) }
+
+return context.join_template(**options)
diff --git a/skins/cmfldap/join_template.pt b/skins/cmfldap/join_template.pt
new file mode 100644
index 0000000..496b075
--- /dev/null
+++ b/skins/cmfldap/join_template.pt
@@ -0,0 +1,117 @@
+<html metal:use-macro="context/main_template/macros/master">
+<body>
+
+<metal:slot metal:fill-slot="header" i18n:domain="cmf_default">
+<h1 tal:content="options/title" i18n:translate="">Become a member</h1>
+</metal:slot>
+
+<metal:slot metal:fill-slot="main" i18n:domain="cmf_default"
+ tal:define="form options/form">
+<div class="Desktop">
+
+<tal:case tal:condition="options/isOrdinaryMember">
+ <p i18n:translate=""> You are already a member. You may use the
+ <a href="personalize_form">personalization form</a>
+ to change your membership information. </p>
+</tal:case>
+
+<tal:case tal:condition="options/isNewMember">
+<p i18n:translate="">You have been registered as a member.</p>
+
+<p tal:condition="options/validate_email" i18n:translate="">You will receive
+ an email shortly containing your password and instructions on how to
+ activate your membership.</p>
+
+<tal:case tal:condition="not: options/validate_email">
+<p>Click the button to log in immediately.</p>
+<form action="logged_in" method="post"
+ tal:attributes="action form/action">
+<input type="hidden" name="__ac_name" value=""
+ tal:attributes="value options/member_id" />
+<input type="hidden" name="__ac_password" value=""
+ tal:attributes="value options/password" />
+<metal:macro metal:use-macro="context/form_widgets/macros/buttons" />
+</form>
+</tal:case>
+
+<p><a href="" tal:attributes="href options/portal_url"
+ i18n:translate="">Return to homepage</a></p>
+</tal:case>
+
+<tal:case tal:condition="options/isAnon">
+ <p i18n:translate="">Becoming a member gives you the ability to personalize
+ the site and participate in the community.</p>
+
+ <p i18n:translate="">It does not cost any money to become a member and your
+ email and other personal information will remain private.</p>
+
+ <p tal:condition="options/validate_email" i18n:translate="">
+ You must submit a valid email address. This address will be used
+ to send you a randomly-generated password. Once you have logged
+ in with this password, you may change it to anything you like.</p>
+</tal:case>
+
+<tal:case tal:condition="options/isAnonOrUserManager">
+ <form action="join_form" method="post"
+ tal:attributes="action form/action">
+ <table class="FormLayout">
+ <tr>
+ <th i18n:translate="">Member ID</th>
+ <td>
+ <input type="text" name="member_id" size="30" value=""
+ tal:attributes="value options/member_id" />
+ </td>
+ </tr>
+ <tr>
+ <th i18n:translate="">Email Address</th>
+ <td>
+ <input type="text" name="member_email" size="30" value=""
+ tal:attributes="value options/member_email" />
+ </td>
+ </tr><tal:case tal:condition="not: options/validate_email">
+ <tr>
+ <th i18n:translate="">Password</th>
+ <td>
+ <input type="password" name="password" size="30" />
+ </td>
+ </tr>
+ <tr>
+ <th i18n:translate="">Password (confirm)</th>
+ <td>
+ <input type="password" name="confirm" size="30" />
+ </td>
+ </tr>
+ <tr>
+ <th i18n:translate="">Mail Password?</th>
+ <td>
+ <input type="checkbox" name="send_password" id="cb_send_password"
+ tal:attributes="checked options/send_password" />
+ <em><label for="cb_send_password" i18n:translate="">Check this box to
+ have the password mailed.</label></em>
+ </td>
+ </tr></tal:case>
+ <tr tal:repeat="p_info options/added_properties">
+ <th i18n:domain="cmfldap" i18n:translate=""
+ tal:content="p_info/friendly_name|nothing">Friendly Name</th>
+ <td>
+ <input type="text" name="" size="30" value=""
+ tal:define="p_name p_info/ldap_name"
+ tal:attributes="value options/?p_name;
+ name p_name" />
+ </td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>
+ <metal:macro metal:use-macro="context/form_widgets/macros/buttons" />
+ </td>
+ </tr>
+ </table>
+ </form>
+</tal:case>
+
+</div>
+</metal:slot>
+
+</body>
+</html>
diff --git a/skins/cmfldap/members_add_control.py b/skins/cmfldap/members_add_control.py
new file mode 100644
index 0000000..b8844ec
--- /dev/null
+++ b/skins/cmfldap/members_add_control.py
@@ -0,0 +1,37 @@
+##parameters=member_id, password, member_email, send_password=False, **kw
+##title=Add a member
+##
+from Products.CMFCore.utils import getToolByName
+from Products.CMFDefault.permissions import ManageUsers
+from Products.CMFDefault.utils import MessageID as _
+
+mtool = getToolByName(script, 'portal_membership')
+mdtool = getToolByName(script, 'portal_memberdata')
+ptool = getToolByName(script, 'portal_properties')
+rtool = getToolByName(script, 'portal_registration')
+
+properties = { 'username' : member_id
+ , 'email' : member_email
+ }
+for prop_info in mdtool.getSortedMemberProperties():
+ key = prop_info['ldap_name']
+ value = kw.get(key, None)
+
+ if value is not None:
+ properties[key] = value
+
+try:
+ rtool.addMember( id=member_id
+ , password=password
+ , properties=properties
+ )
+
+except ValueError, errmsg:
+ return context.setStatus(False, errmsg)
+else:
+ if ptool.getProperty('validate_email') or send_password:
+ rtool.registeredNotify(member_id)
+ if mtool.checkPermission(ManageUsers, mtool):
+ return context.setStatus(True, _('Member registered.'))
+ else:
+ return context.setStatus(False, _('Success!'))
diff --git a/skins/cmfldap/personalize_form.pt b/skins/cmfldap/personalize_form.pt
index a208a04..1c3f616 100644
--- a/skins/cmfldap/personalize_form.pt
+++ b/skins/cmfldap/personalize_form.pt
@@ -2,87 +2,117 @@
xmlns:metal="http://xml.zope.org/namespaces/metal"
metal:use-macro="here/main_template/macros/master">
<body>
-<div metal:fill-slot="main">
- <div tal:define="member python: here.portal_membership.getAuthenticatedMember()">
- <div class="Desktop">
- <h1> Member Preferences </h1>
- <span tal:replace="request/msg"
- tal:condition="request/msg|nothing"><hr></span>
- <p><a href="password_form">Click here</a> to change your password.</p>
- <form action="personalize" method="post">
- <table class="FormLayout">
-
- <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: member.getProperty( p_name )"
- tal:condition="python: p_name not in ('dn', l_attr)">
- <th> <span tal:replace="python: p_label" /> </th>
- <td>
- <input type="text" name="" value="" size="30"
- tal:attributes="name p_name;
- value p_value">
- </td>
- </tr>
- </tbody>
-
- <tr tal:define="isPublic python: member.getProperty('listed')">
- <th valign="top">Listed status </th>
- <td>
- <input type="radio" name="listed"
- tal:attributes="checked isPublic"
- value="on" id="cb_listed" />
- <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="listed"
- tal:attributes="checked python: not(isPublic)"
- 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>
-
- <div tal:condition="here/portal_skins | nothing">
- <tr>
- <th>Skin</th>
- <td>
- <select name="portal_skin"
- tal:define="def_skin here/portal_skins/getDefaultSkin;
- mem_skin python: member.getProperty('portal_skin');
- skin_pref python: mem_skin or def_skin">
+
+<div metal:fill-slot="main" i18n:domain="cmf_default">
+
+ <div tal:define="purl here/portal_url;
+ mtool here/portal_membership;
+ mdtool here/portal_memberdata;
+ member mtool/getAuthenticatedMember;
+ ">
+
+ <div tal:condition="python: not( mtool.checkPermission( 'Set own properties'
+ , here ) )">
+ <span id="dummy_for_redirect"
+ tal:define="aurl here/absolute_url;
+ rurl string:${purl}/login_form?came_from=${aurl};
+ response request/RESPONSE;
+ redirect python:response.redirect( rurl )" />
+
+ </div><!-- not Set own properties -->
+
+ <div class="Desktop">
+
+ <h1 i18n:translate="">Member Preferences</h1>
+
+ <span tal:replace="request/msg"
+ tal:condition="request/msg|nothing" />
+
+ <p i18n:translate="">
+ <span i18n:name="link"><a href="password_form"
+ i18n:translate="">Click here</a></span>
+ to change your password.</p>
+
+ <form action="personalize" method="post"
+ tal:attributes="action string:${purl}/personalize"
+ >
+ <table class="FormLayout">
+
+ <tr>
+ <th i18n:translate="">Email address</th>
+ <td><input type="text" name="email" size="30" value=""
+ tal:attributes="value member/email|nothing" />
+ </td>
+ </tr>
+
+ <tr tal:repeat="p_info mdtool/getSortedMemberProperties">
+ <th i18n:domain="cmfldap" i18n:translate=""
+ tal:content="p_info/friendly_name|nothing">Friendly Name</th>
+ <td>
+ <input type="text" name="" size="30" value=""
+ tal:define="p_name p_info/ldap_name"
+ tal:attributes="value member/?p_name|nothing;
+ name p_name" />
+ </td>
+ </tr>
+
+ <tr>
+ <th valign="top" i18n:translate="">Listed status</th>
+ <td tal:define="listed member/listed|nothing">
+ <input type="radio" name="listed"
+ tal:attributes="checked listed"
+ value="on" id="cb_listed" />
+ <label for="cb_listed" i18n:translate="">Listed</label>
+
+ <dl class="FieldHelp">
+ <dd i18n:translate="">You will show up on the public membership
+ roster.</dd>
+ </dl>
+
+ <input type="radio" name="listed"
+ tal:attributes="checked python:( hasattr(member,'listed')
+ and not(member.getProperty( 'listed' ) ) )"
+ value="" id="cb_unlisted" />
+ <label for="cb_unlisted" i18n:translate="">Unlisted</label>
+ <dl class="FieldHelp">
+ <dd i18n:translate="">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 tal:condition="here/portal_skins|nothing">
+ <th i18n:translate="">Skin</th>
+ <td tal:define="s_tool here/portal_skins;
+ current request/portal_skin|nothing;
+ ">
+ <select name="portal_skin">
<option value=""
- tal:repeat="skin here/portal_skins/getSkinSelections"
- tal:attributes="value skin;
- selected python: skin_pref == skin"
- tal:content="skin">
- Skin Name
- </option>
- </select>
- </td>
- </tr>
- </div>
-
- <tr>
- <td></td>
- <td><input type="submit" value=" Change "></td>
- </tr>
- </table>
-</form>
-
-</div>
-</div>
-</div>
+ tal:define="skins python:s_tool.getSkinSelections()"
+ tal:repeat="skin skins"
+ tal:attributes="value skin;
+ selected python:current == skin"
+ tal:content="skin"> skin </option>
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td><br /></td>
+ <td>
+ <input type="submit" value="Change" i18n:attributes="value" />
+ </td>
+ </tr>
+ </table>
+ </form>
+
+ </div><!-- class="Desktop" -->
+
+ </div><!-- tal:define="mtool" -->
+
+</div><!-- metal:fill-slot="main" -->
+
</body>
</html>
diff --git a/tests/base/dummy.py b/tests/base/dummy.py
index b9b5927..fee83a4 100644
--- a/tests/base/dummy.py
+++ b/tests/base/dummy.py
@@ -46,6 +46,8 @@ class LDAPDummyUser(Implicit, DummyUser):
return self.__
+
+
class LDAPDummyUserFolder(DummyUserFolder):
""" LDAP-enabled dummy user folder """
@@ -105,12 +107,13 @@ class LDAPDummyUserFolder(DummyUserFolder):
, LDAPDummyUser(user_id, roles=kwargs.get('user_roles', []))
)
- def manage_editUser(self, dn, properties={}):
+ def manage_editUser(self, dn, REQUEST=None, kwargs={}):
user = self.getUserByDN(dn)
if user is not None:
# XXX Change user here
- pass
+ for key, value in kwargs.items():
+ setattr(user, key, value)
def manage_editUserPassword(self, dn, password):
user = self.getUserByDN(dn)
diff --git a/tests/test_join.py b/tests/test_functional.py
index aac0492..308755c 100644
--- a/tests/test_join.py
+++ b/tests/test_functional.py
@@ -16,6 +16,8 @@ import Testing
import Zope2
Zope2.startup()
+from AccessControl.SecurityManagement import newSecurityManager
+
from Products.CMFCore.tests.base.testcase import RequestTest
from Products.CMFDefault.tests.test_join import MembershipTests
@@ -55,6 +57,12 @@ class LDAPMembershipFunctionalTests(RequestTest):
self.root.site.manage_delObjects(['acl_users'])
self.root.site._setObject('acl_users', LDAPDummyUserFolder())
+ # Register member properties with the shiny new member data tool
+ mdt = self.root.site.portal_memberdata
+ mdt.addMemberProperty('sn')
+ mdt.addMemberProperty('givenName')
+ mdt.addMemberProperty('telephoneNumber')
+
return self.root.site
@@ -87,6 +95,49 @@ class LDAPMembershipFunctionalTests(RequestTest):
self.assertEqual(m.getRoles(), ('Member', 'Authenticated'))
+ def test_personalize(self):
+ site = self._makePortal()
+ member_id = 'test_user'
+
+ try:
+ join_script = site.members_add_control
+ personalize_script = site.personalize
+ except AttributeError:
+ # Skin path not set up, which is very likely during testing.
+ # Forcing it manually. Eep!
+ site.changeSkin('Basic')
+ join_script = site.members_add_control
+ personalize_script = site.personalize
+
+ join_script(member_id, 'zzyyzz', 'foo@bar.com')
+ m = site.portal_membership.getMemberById('test_user')
+
+ # Log in as the new member because the personalization step is
+ # operating on whoever is logged in at that moment
+ newSecurityManager(None, m.getUser())
+
+ # Stuff all values into the request becausse that's what the personalize
+ # script uses to get values from
+ req = self.root.REQUEST
+ req.form = {}
+ req.form['email'] = 'baz@foo.com'
+ req.form['listed'] = 'on'
+ req.form['portal_skin'] = 'Nouvelle'
+ req.form['sn'] = 'Blow'
+ req.form['givenName'] = 'Joe'
+ req.form['telephoneNumber'] = '(888) 555-1212'
+
+ personalize_script()
+
+ m = site.portal_membership.getMemberById('test_user')
+ self.assertEqual(m.getProperty('email'), 'baz@foo.com')
+ self.failUnless(m.getProperty('listed'))
+ self.assertEqual(m.getProperty('portal_skin'), 'Nouvelle')
+ self.assertEqual(m.getProperty('sn'), 'Blow')
+ self.assertEqual(m.getProperty('givenName'), 'Joe')
+ self.assertEqual(m.getProperty('telephoneNumber'), '(888) 555-1212')
+
+
def test_suite():
return TestSuite((
makeSuite(LDAPMembershipTests),