diff --git a/browser/resource_macros.pt b/browser/resource_macros.pt index e25a6a9..fadb6ff 100644 --- a/browser/resource_macros.pt +++ b/browser/resource_macros.pt @@ -1,5 +1,5 @@ -
+

Title

@@ -7,7 +7,7 @@ -
+

Title

@@ -16,7 +16,7 @@ -
+

Title

diff --git a/organize/README.txt b/organize/README.txt index 42564b2..4a485b4 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -6,7 +6,7 @@ loops - Linked Objects for Organization and Processing Services Note: This packages depends on cybertools.organize. -Letz's do some basic set up +Let's do some basic set up >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> site = placefulSetUp(True) @@ -161,9 +161,75 @@ concept assigned we should get an error: >>> martha.userId = 'users.john' Traceback (most recent call last): - ... + ... ValueError: ... + + +Member Registrations +==================== + +The member registration needs the whole pluggable authentication stuff +with a principal folder: + + >>> from zope.app.appsetup.bootstrap import ensureUtility + >>> from zope.app.authentication.authentication import PluggableAuthentication + >>> ensureUtility(site, IAuthentication, '', PluggableAuthentication, + ... copy_to_zlog=False) + '' + >>> pau = component.getUtility(IAuthentication, context=site) + + >>> from zope.app.component.site import UtilityRegistration + >>> from zope.app.authentication.principalfolder import PrincipalFolder + >>> from zope.app.authentication.interfaces import IAuthenticatorPlugin + >>> pFolder = PrincipalFolder('loops.') + >>> pau['loops'] = pFolder + >>> reg = UtilityRegistration('loops', IAuthenticatorPlugin, pFolder) + >>> pau.registrationManager.addRegistration(reg) + 'UtilityRegistration' + >>> reg.status = u'Active' + >>> pau.authenticatorPlugins = ('loops',) + +In addition, we have to create at least one node in the view space +and register an IMemberRegistrationManager adapter for the loops root object: + + >>> from loops.view import ViewManager, Node + >>> views = loopsRoot['views'] = ViewManager() + >>> menu = views['menu'] = Node('Home') + >>> menu.nodeType = 'menu' + + >>> from loops.organize.member import MemberRegistrationManager + >>> from loops.organize.interfaces import IMemberRegistrationManager + >>> from loops.interfaces import ILoops + >>> component.provideAdapter(MemberRegistrationManager) + +Now we can enter the registration info for a new member (after having made +sure that a principal object can be served by a corresponding factory): + + >>> from zope.app.authentication.principalfolder import FoundPrincipalFactory + >>> component.provideAdapter(FoundPrincipalFactory) + >>> form = {'field.userId': u'newuser', + ... 'field.passwd': u'quack', + ... 'field.passwdConfirm': u'quack', + ... 'field.lastName': u'Sawyer', + ... 'field.firstName': u'Tom'} + >>> from zope.publisher.browser import TestRequest + >>> request = TestRequest(form=form) + +and register it + + >>> from loops.organize.browser import MemberRegistration + >>> regView = MemberRegistration(menu, request) + >>> personAdapter = regView.register() + + >>> personAdapter.context.__name__, personAdapter.lastName, personAdapter.userId + (u'newuser', u'Sawyer', u'loops.newuser') + +Now we can also retrieve it from the authentication utility: + + >>> pau.getPrincipal('loops.newuser').title + u'Tom Sawyer' + Fin de partie ============= diff --git a/organize/browser.py b/organize/browser.py index 1090254..8d82d42 100644 --- a/organize/browser.py +++ b/organize/browser.py @@ -28,10 +28,14 @@ from zope.app import zapi from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.principalannotation import annotations from zope.cachedescriptors.property import Lazy +from zope.i18nmessageid import MessageFactory from loops.browser.common import BaseView from loops.browser.concept import ConceptRelationView -from loops.organize.interfaces import ANNOTATION_KEY +from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager +from loops.organize.interfaces import raiseValidationError + +_ = MessageFactory('zope') class MyConcepts(BaseView): @@ -65,6 +69,22 @@ class MyConcepts(BaseView): for r in self.person.getResourceRelations(): yield ConceptRelationView(r, self.request, contextIsSecond=True) - @Lazy - def view(self): - return self + +class MemberRegistration(object): + + def __init__(self, context, request): + self.context = context + self.request = request + + def register(self): + form = self.request.form + pw = form.get('field.passwd') + if form.get('field.passwdConfirm') != pw: + raiseValidationError(_(u'Password and password confirmation ' + 'do not match.')) + regMan = IMemberRegistrationManager(self.context.getLoopsRoot()) + person = regMan.register(form.get('field.userId'), pw, + form.get('field.lastName'), + form.get('field.firstName')) + return person + diff --git a/organize/interfaces.py b/organize/interfaces.py index 7ec0324..fcfe63c 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -61,7 +61,8 @@ class UserId(schema.TextLine): if person is not None and person != self.context: raiseValidationError( _(u'There is alread a person ($person) assigned to user $userId.', - mapping={'person': zapi.getName(person), 'userId': userId})) + mapping=dict(person=zapi.getName(person), + userId=userId))) class IPerson(IBasePerson): @@ -76,3 +77,23 @@ class IPerson(IBasePerson): description=_(u'The principal id of a user that should ' 'be associated with this person.'), required=False,) + + +class IMemberRegistrationManager(Interface): + """ Knows what to do for registrating a new member (portal user). + """ + + authPluginId = Attribute(u'The id of an authentication plugin to be ' + 'used for managing members of this loops site') + + def register(userId, password, lastName, firstName=u'', **kw): + """ Register a new member for this loops site. + Return the person adapter for the concept created. + Raise ValidationError if the user could not be created. + """ + + def changePassword(oldPw, newPw): + """ Change the password of the user currently logged-in. + Raise a ValidationError if the oldPw does not match the + current password. + """ diff --git a/organize/member.py b/organize/member.py new file mode 100644 index 0000000..66ab744 --- /dev/null +++ b/organize/member.py @@ -0,0 +1,88 @@ +# +# Copyright (c) 2006 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Member registration adapter(s). + +$Id$ +""" + +from zope.app import zapi +from zope import interface, component +from zope.component import adapts +from zope.interface import implements +from zope.app.authentication.interfaces import IPluggableAuthentication +from zope.app.authentication.interfaces import IAuthenticatorPlugin +from zope.app.authentication.principalfolder import InternalPrincipal +from zope.app.security.interfaces import IAuthentication +from zope.i18nmessageid import MessageFactory +from zope.cachedescriptors.property import Lazy + +from cybertools.typology.interfaces import IType +from loops.interfaces import ILoops +from loops.concept import Concept +from loops.organize.interfaces import IMemberRegistrationManager +from loops.organize.interfaces import raiseValidationError + +_ = MessageFactory('zope') + + +class MemberRegistrationManager(object): + + implements(IMemberRegistrationManager) + adapts(ILoops) + + authPluginId = 'loops' + + def __init__(self, context): + self.context = context + + def register(self, userId, password, lastName, firstName=u'', **kw): + # step 1: create an internal principal in the loops principal folder: + pau = zapi.getUtility(IAuthentication, context=self.context) + if not IPluggableAuthentication.providedBy(pau): + raiseValidationError(_(u'There is no pluggable authentication ' + 'utility available.')) + if not self.authPluginId in pau.authenticatorPlugins: + raiseValidationError(_(u'There is no loops authenticator ' + 'plugin available.')) + pFolder = component.queryUtility(IAuthenticatorPlugin, self.authPluginId, + context=pau) + title = firstName and ' '.join((firstName, lastName)) or lastName + # TODO: encrypt password: + principal = InternalPrincipal(userId, password, title) + pFolder[userId] = principal + # step 1: create a corresponding person concept: + cm = self.context.getConceptManager() + id = userId + num = 0 + while id in cm: + num +=1 + id = userid + str(num) + person = cm[id] = Concept(title) + # TODO: the name of the person type object must be kept flexible! + person.conceptType = cm['person'] + personAdapter = IType(person).typeInterface(person) + personAdapter.firstName = firstName + personAdapter.lastName = lastName + personAdapter.userId = '.'.join((self.authPluginId, userId)) + return personAdapter + + def changePassword(self, oldPw, newPw): + pass +