diff --git a/CHANGES.txt b/CHANGES.txt index ffa33bb..1f4a5dc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,8 @@ $Id$ New features +- form for user registration (``register_user.html``), controlled by options: + ``registration_principalfolder``, ``registration_groups`` - new action: ``create task`` - quick search field - external file/media asset type: options for hiding or showing fields on editing diff --git a/browser/concept.py b/browser/concept.py index 9e958c2..e9f47d4 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -197,7 +197,7 @@ class ConceptView(BaseView): if (cont is not None and not IUnauthenticatedPrincipal.providedBy( self.request.principal)): cont.macros.register('portlet_right', 'parents', title=_(u'Parents'), - subMacro=self.template.macros['parents'], + subMacro=concept_macros.macros['parents'], priority=20, info=self) @Lazy diff --git a/browser/concept_macros.pt b/browser/concept_macros.pt index 261cc8f..15f8fca 100644 --- a/browser/concept_macros.pt +++ b/browser/concept_macros.pt @@ -48,7 +48,7 @@ renderer field/displayRenderer" tal:condition="nocall:value"> : - + diff --git a/organize/README.txt b/organize/README.txt index a97a541..1096d85 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -407,7 +407,8 @@ Send Email to Members >>> from loops.organize.browser.party import SendEmailForm >>> form = SendEmailForm(menu, TestRequest()) >>> form.members - [{'email': 'john@loopz.org', 'title': u'John'}] + [{'email': 'john@loopz.org', 'title': u'John'}, + {'email': u'tommy@sawyer.com', 'title': u'Tom Sawyer'}] >>> form.subject u"loops Notification from '$site'" >>> form.mailBody diff --git a/organize/auth.py b/organize/auth.py index fa50f01..cd94083 100644 --- a/organize/auth.py +++ b/organize/auth.py @@ -27,6 +27,7 @@ from zope.app.container.contained import Contained from zope import component from zope.interface import Interface, implements from zope.app.authentication.interfaces import IAuthenticatorPlugin +from zope.app.authentication.principalfolder import IInternalPrincipal from zope.app.authentication.principalfolder import PrincipalInfo from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility from zope.app.security.interfaces import IAuthentication diff --git a/organize/browser/member.py b/organize/browser/member.py index 4875298..38f6cad 100644 --- a/organize/browser/member.py +++ b/organize/browser/member.py @@ -76,15 +76,20 @@ class PersonalInfo(ConceptView): class MemberRegistration(NodeView, CreateForm): interface = IMemberRegistration - message = _(u'You have been registered.') + message = _(u'The user account has been created.') formErrors = dict( confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')), + duplicate_loginname=FormError(_('Login name already taken.')), ) label = _(u'Member Registration') label_submit = _(u'Register') + permissions_key = u'registration.permissions' + roles_key = u'registration.roles' + registration_adapter_key = u'registration.adapter' + @Lazy def macro(self): return schema_macros.macros['form'] @@ -123,11 +128,20 @@ class MemberRegistration(NodeView, CreateForm): return True login = form.get('loginName') regMan = IMemberRegistrationManager(self.context.getLoopsRoot()) - self.object = regMan.register(login, pw, - form.get('lastName'), form.get('firstName')) + result = regMan.register(login, pw, + form.get('lastName'), form.get('firstName'), + email=form.get('email'), + phoneNumbers=form.get('phoneNumbers')) + if isinstance(result, dict): + fi = formState.fieldInstances[result['fieldName']] + fi.setError(result['error'], self.formErrors) + formState.severity = max(formState.severity, fi.severity) + return True + self.object = result msg = self.message - self.request.response.redirect('%s/login.html?login=%s&message=%s' - % (self.url, login, msg)) + #self.request.response.redirect('%s/login.html?login=%s&message=%s' + # % (self.url, login, msg)) + self.request.response.redirect('%s?message=%s' % (self.url, msg)) return False diff --git a/organize/member.py b/organize/member.py index 4d7907b..37c5045 100644 --- a/organize/member.py +++ b/organize/member.py @@ -24,10 +24,13 @@ $Id$ from zope import interface, component, schema from zope.app.component import queryNextUtility +from zope.app.container.interfaces import INameChooser +from zope.cachedescriptors.property import Lazy 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 IInternalPrincipal from zope.app.authentication.principalfolder import InternalPrincipal from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent from zope.app.security.interfaces import IAuthentication, PrincipalLookupError @@ -35,10 +38,12 @@ from zope.event import notify from zope.i18nmessageid import MessageFactory from zope.cachedescriptors.property import Lazy +from cybertools.meta.interfaces import IOptions from cybertools.typology.interfaces import IType from loops.common import adapted from loops.concept import Concept from loops.interfaces import ILoops +from loops.organize.auth import IPersonBasedAuthenticator from loops.organize.interfaces import IMemberRegistrationManager from loops.organize.util import getPrincipalFolder, getGroupsFolder from loops.organize.util import getInternalPrincipal @@ -51,54 +56,60 @@ class MemberRegistrationManager(object): implements(IMemberRegistrationManager) adapts(ILoops) + person_typeName = 'person' + default_principalfolder = 'loops' + principalfolder_key = u'registration.principalfolder' + groups_key = u'registration.groups' + def __init__(self, context): self.context = context def register(self, userId, password, lastName, firstName=u'', groups=[], useExisting=False, **kw): + concepts = self.context.getConceptManager() + personType = adapted(concepts[self.person_typeName]) + options = IOptions(personType) + pfName = options(self.principalfolder_key, + (self.default_principalfolder,))[0] # step 1: create an internal principal in the loops principal folder: - pFolder = getPrincipalFolder(self.context) - # if isinstance(pFolder, PersonBasedAuthenticator): - # pFolder.setPassword(userId, password) - # else: - title = firstName and ' '.join((firstName, lastName)) or lastName - principal = InternalPrincipal(userId, password, title) - if useExisting: - if userId not in pFolder: - pFolder[userId] = principal + pFolder = getPrincipalFolder(self.context, pfName) + if IPersonBasedAuthenticator.providedBy(pFolder): + pFolder.setPassword(userId, password) else: - pFolder[userId] = principal + title = firstName and ' '.join((firstName, lastName)) or lastName + principal = InternalPrincipal(userId, password, title) + if useExisting: + if userId not in pFolder: + pFolder[userId] = principal + else: + if userId in pFolder: + return dict(fieldName='loginName', error='duplicate_loginname') + else: + pFolder[userId] = principal # step 2 (optional): assign to group(s) - personType = self.context.getLoopsRoot().getConceptManager()['person'] - od = getOptionsDict(adapted(personType).options) - groupInfo = od.get('group') - if groupInfo: - gfName, groupNames = groupInfo.split(':') + groups = options(self.groups_key, ()) + for groupInfo in groups: + names = groupInfo.split(':') + if len(names) == 1: + gName, gfName = names[0], None + else: + gName, gfName = names gFolder = getGroupsFolder(gfName) - if not groups: - groups = groupNames.split(',') - else: - gFolder = getGroupsFolder() - if gFolder is not None: - for g in groups: - group = gFolder.get(g) + if gFolder is not None: + group = gFolder.get(gName) if group is not None: members = list(group.principals) members.append(pFolder.prefix + userId) group.principals = members # step 3: create a corresponding person concept: - cm = self.context.getConceptManager() - id = baseId = 'person.' + userId - # TODO: use NameChooser - if useExisting and id in cm: - person = cm[id] + name = baseId = 'person.' + userId + if useExisting and name in concepts: + person = concepts[name] else: - num = 0 - while id in cm: - num +=1 - id = baseId + str(num) - person = cm[id] = Concept(title) - person.conceptType = cm['person'] + person = Concept(title) + name = INameChooser(concepts).chooseName(name, person) + concepts[name] = person + person.conceptType = personType.context personAdapter = adapted(person) personAdapter.firstName = firstName personAdapter.lastName = lastName @@ -110,7 +121,7 @@ class MemberRegistrationManager(object): return personAdapter def changePassword(self, principal, oldPw, newPw): - if not isinstance(principal, InternalPrincipal): + if not IInternalPrincipal.providedBy(principal): principal = getInternalPrincipal(principal.id) if not principal.checkPassword(oldPw): return False diff --git a/organize/util.py b/organize/util.py index 78a9459..e31f7fc 100644 --- a/organize/util.py +++ b/organize/util.py @@ -59,8 +59,9 @@ def getGroupsFolder(context=None, name='gloops'): return getPrincipalFolder(authPluginId=name, ignoreErrors=True) -def getInternalPrincipal(id, context=None): - pau = component.getUtility(IAuthentication, context=context) +def getInternalPrincipal(id, context=None, pau=None): + if pau is None: + pau = component.getUtility(IAuthentication, context=context) if not IPluggableAuthentication.providedBy(pau): raise ValueError(u'There is no pluggable authentication ' 'utility available.') @@ -68,7 +69,8 @@ def getInternalPrincipal(id, context=None): next = queryNextUtility(pau, IAuthentication) if next is None: raise PrincipalLookupError(id) - return next.getPrincipal(id) + #return next.getPrincipal(id) + return getInternalPrincipal(id, context, pau=next) id = id[len(pau.prefix):] for name, authplugin in pau.getAuthenticatorPlugins(): if not id.startswith(authplugin.prefix): @@ -79,7 +81,8 @@ def getInternalPrincipal(id, context=None): return principal next = queryNextUtility(pau, IAuthentication) if next is not None: - return next.getPrincipal(pau.prefix + id) + #return next.getPrincipal(pau.prefix + id) + return getInternalPrincipal(id, context, pau=next) raise PrincipalLookupError(id)