X-Git-Url: https://scm.cri.ensmp.fr/git/Plinn.git/blobdiff_plain/99afe0ce8700bf835ff7bbedf77bb46e58f996d8..e932032bdbb367869cd8c7346a98f6b7660c41dd:/RegistrationTool.py diff --git a/RegistrationTool.py b/RegistrationTool.py index 77f8bdc..8c2911d 100644 --- a/RegistrationTool.py +++ b/RegistrationTool.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ####################################################################################### # Plinn - http://plinn.org # -# Copyright (C) 2005-2007 Benoît PIN # +# © 2005-2013 Benoît PIN # # # # This program is free software; you can redistribute it and/or # # modify it under the terms of the GNU General Public License # @@ -17,8 +17,8 @@ # along with this program; if not, write to the Free Software # # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # ####################################################################################### -""" Plinn registration tool: implements 3 modes to register members : - anonymous, manager, reviewed. +""" Plinn registration tool: implements 3 modes to register members: + anonymous, manager, reviewed. @@ -29,23 +29,36 @@ from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.CMFDefault.RegistrationTool import RegistrationTool as BaseRegistrationTool from AccessControl import ClassSecurityInfo, ModuleSecurityInfo from AccessControl.Permission import Permission +from BTrees.OOBTree import OOBTree from Products.CMFCore.permissions import ManagePortal, AddPortalMember from Products.CMFCore.exceptions import AccessControl_Unauthorized +from Products.CMFDefault.exceptions import EmailAddressInvalid from Products.CMFCore.utils import getToolByName +from Products.CMFCore.utils import getUtilityByInterfaceName +from Products.CMFDefault.utils import checkEmailAddress from Products.GroupUserFolder.GroupsToolPermissions import ManageGroups +from Products.Plinn.utils import Message as _ +from Products.Plinn.utils import translate +from Products.Plinn.utils import encodeQuopriEmail +from Products.Plinn.utils import encodeMailHeader +from DateTime import DateTime from types import TupleType, ListType +from uuid import uuid4 security = ModuleSecurityInfo('Products.Plinn.RegistrationTool') MODE_ANONYMOUS = 'anonymous' security.declarePublic('MODE_ANONYMOUS') +MODE_PASS_ANONYMOUS = 'pass_anonymous' +security.declarePublic('MODE_PASS_ANONYMOUS') + MODE_MANAGER = 'manager' security.declarePublic('MODE_MANAGER') MODE_REVIEWED = 'reviewed' security.declarePublic('MODE_REVIEWED') -MODES = [MODE_ANONYMOUS, MODE_MANAGER, MODE_REVIEWED] +MODES = [MODE_ANONYMOUS, MODE_PASS_ANONYMOUS, MODE_MANAGER, MODE_REVIEWED] security.declarePublic('MODES') DEFAULT_MEMBER_GROUP = 'members' @@ -55,133 +68,237 @@ security.declarePublic('DEFAULT_MEMBER_GROUP') class RegistrationTool(BaseRegistrationTool) : - """ Create and modify users by making calls to portal_membership. - """ - - meta_type = "Plinn Registration Tool" - - manage_options = ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \ - BaseRegistrationTool.manage_options - - security = ClassSecurityInfo() - - security.declareProtected( ManagePortal, 'manage_regmode' ) - manage_regmode = PageTemplateFile('www/configureRegistrationTool', globals(), - __name__='manage_regmode') - - def __init__(self) : - self._mode = MODE_ANONYMOUS - self._chain = '' - - security.declareProtected(ManagePortal, 'configureTool') - def configureTool(self, registration_mode, chain, REQUEST=None) : - """ """ - - if registration_mode not in MODES : - raise ValueError, "Unknown mode: " + registration_mode - else : - self._mode = registration_mode - self._updatePortalRoleMappingForMode(registration_mode) - - wtool = getToolByName(self, 'portal_workflow') - - if registration_mode == MODE_REVIEWED : - if not hasattr(wtool, '_chains_by_type') : - wtool._chains_by_type = PersistentMapping() - wfids = [] - chain = chain.strip() - - if chain == '(Default)' : - try : del wtool._chains_by_type['Member Data'] - except KeyError : pass - self._chain = chain - else : - for wfid in chain.replace(',', ' ').split(' ') : - if wfid : - if not wtool.getWorkflowById(wfid) : - raise ValueError, '"%s" is not a workflow ID.' % wfid - wfids.append(wfid) - - wtool._chains_by_type['Member Data'] = tuple(wfids) - self._chain = ', '.join(wfids) - else : - wtool._chains_by_type['Member Data'] = tuple() - - if REQUEST : - REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.') - - def _updatePortalRoleMappingForMode(self, mode) : - - urlTool = getToolByName(self, 'portal_url') - portal = urlTool.getPortalObject() - - if mode in [MODE_ANONYMOUS, MODE_REVIEWED] : - portal.manage_permission(AddPortalMember, roles = ['Anonymous', 'Manager'], acquire=1) - elif mode == MODE_MANAGER : - portal.manage_permission(AddPortalMember, roles = ['Manager', 'UserManager'], acquire=0) - - security.declarePublic('getMode') - def getMode(self) : - # """ return current mode """ - return self._mode[:] - - security.declarePublic('getWfId') - def getWfChain(self) : - # """ return current workflow id """ - return self._chain - - security.declarePublic('roleMappingMismatch') - def roleMappingMismatch(self) : - # """ test if the role mapping is correct for the currrent mode """ - - mode = self._mode - urlTool = getToolByName(self, 'portal_url') - portal = urlTool.getPortalObject() - - def rolesOfAddPortalMemberPerm() : - p=Permission(AddPortalMember, [], portal) - return p.getRoles() - - if mode in [MODE_ANONYMOUS, MODE_REVIEWED] : - if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False - - elif mode == MODE_MANAGER : - roles = rolesOfAddPortalMemberPerm() - if 'Manager' in roles or 'UserManager' in roles and len(roles) == 1 and type(roles) == TupleType : - return False - - return True - - security.declareProtected(AddPortalMember, 'addMember') - def addMember(self, id, password, roles=(), groups=(DEFAULT_MEMBER_GROUP,), domains='', properties=None) : - """ Idem CMFCore but without default role """ - BaseRegistrationTool.addMember(self, id, password, roles=roles, - domains=domains, properties=properties) - - if self.getMode() in [MODE_ANONYMOUS, MODE_MANAGER] : - gtool = getToolByName(self, 'portal_groups') - mtool = getToolByName(self, 'portal_membership') - utool = getToolByName(self, 'portal_url') - portal = utool.getPortalObject() - isGrpManager = mtool.checkPermission(ManageGroups, portal) ## TODO : CMF2.1 compat - aclu = self.aq_inner.acl_users - - for gid in groups: - g = gtool.getGroupById(gid) - if not isGrpManager : - if gid != DEFAULT_MEMBER_GROUP: - raise AccessControl_Unauthorized, 'You are not allowed to join arbitrary group.' - - if g is None : - gtool.addGroup(gid) - aclu.changeUser(aclu.getGroupPrefix() +gid, roles=['Member', ]) - g = gtool.getGroupById(gid) - g.addMember(id) - - - def afterAdd(self, member, id, password, properties): - """ notify member creation """ - member.notifyWorkflowCreated() - member.indexObject() - + """ Create and modify users by making calls to portal_membership. + """ + + meta_type = "Plinn Registration Tool" + + manage_options = ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \ + BaseRegistrationTool.manage_options + + security = ClassSecurityInfo() + + security.declareProtected( ManagePortal, 'manage_regmode' ) + manage_regmode = PageTemplateFile('www/configureRegistrationTool', globals(), + __name__='manage_regmode') + + def __init__(self) : + self._mode = MODE_ANONYMOUS + self._chain = '' + self._passwordResetRequests = OOBTree() + + security.declareProtected(ManagePortal, 'configureTool') + def configureTool(self, registration_mode, chain, REQUEST=None) : + """ """ + + if registration_mode not in MODES : + raise ValueError, "Unknown mode: " + registration_mode + else : + self._mode = registration_mode + self._updatePortalRoleMappingForMode(registration_mode) + + wtool = getToolByName(self, 'portal_workflow') + + if registration_mode == MODE_REVIEWED : + if not hasattr(wtool, '_chains_by_type') : + wtool._chains_by_type = PersistentMapping() + wfids = [] + chain = chain.strip() + + if chain == '(Default)' : + try : del wtool._chains_by_type['Member Data'] + except KeyError : pass + self._chain = chain + else : + for wfid in chain.replace(',', ' ').split(' ') : + if wfid : + if not wtool.getWorkflowById(wfid) : + raise ValueError, '"%s" is not a workflow ID.' % wfid + wfids.append(wfid) + + wtool._chains_by_type['Member Data'] = tuple(wfids) + self._chain = ', '.join(wfids) + else : + wtool._chains_by_type['Member Data'] = tuple() + + if REQUEST : + REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.') + + def _updatePortalRoleMappingForMode(self, mode) : + + urlTool = getToolByName(self, 'portal_url') + portal = urlTool.getPortalObject() + + if mode in [MODE_ANONYMOUS, MODE_PASS_ANONYMOUS, MODE_REVIEWED] : + portal.manage_permission(AddPortalMember, roles = ['Anonymous', 'Manager'], acquire=1) + elif mode == MODE_MANAGER : + portal.manage_permission(AddPortalMember, roles = ['Manager', 'UserManager'], acquire=0) + + security.declarePublic('getMode') + def getMode(self) : + # """ return current mode """ + return self._mode[:] + + security.declarePublic('getWfId') + def getWfChain(self) : + # """ return current workflow id """ + return self._chain + + security.declarePublic('roleMappingMismatch') + def roleMappingMismatch(self) : + # """ test if the role mapping is correct for the currrent mode """ + + mode = self._mode + urlTool = getToolByName(self, 'portal_url') + portal = urlTool.getPortalObject() + + def rolesOfAddPortalMemberPerm() : + p=Permission(AddPortalMember, [], portal) + return p.getRoles() + + if mode in [MODE_ANONYMOUS, MODE_PASS_ANONYMOUS, MODE_REVIEWED] : + if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False + + elif mode == MODE_MANAGER : + roles = rolesOfAddPortalMemberPerm() + if 'Manager' in roles or 'UserManager' in roles and len(roles) == 1 and type(roles) == TupleType : + return False + + return True + + security.declareProtected(AddPortalMember, 'addMember') + def addMember(self, id, password, roles=(), groups=(DEFAULT_MEMBER_GROUP,), domains='', properties=None) : + """ Idem CMFCore but without default role """ + + if self.getMode() != MODE_REVIEWED : + gtool = getToolByName(self, 'portal_groups') + mtool = getToolByName(self, 'portal_membership') + utool = getToolByName(self, 'portal_url') + portal = utool.getPortalObject() + + if self.getMode() == MODE_PASS_ANONYMOUS : + private_collections = portal.get('private_collections') + if not private_collections : + raise AccessControl_Unauthorized() + return + data = private_collections.data + lines = filter(None, [l.strip() for l in data.split('\n')]) + assert len(lines) % 3 == 0 + collecInfos = {} + for i in xrange(0, len(lines), 3) : + collecInfos[lines[i]] = {'pw' : lines[i+1], + 'path' : lines[i+2]} + if not (collecInfos.has_key(properties.get('collection_id')) and \ + collecInfos[properties.get('collection_id')]['pw'] == properties.get('collection_password')) : + raise AccessControl_Unauthorized('Wrong primary credentials') + return + + + BaseRegistrationTool.addMember(self, id, password, roles=roles, + domains=domains, properties=properties) + + isGrpManager = mtool.checkPermission(ManageGroups, portal) ## TODO : CMF2.1 compat + aclu = self.aq_inner.acl_users + + for gid in groups: + g = gtool.getGroupById(gid) + if not isGrpManager : + if gid != DEFAULT_MEMBER_GROUP: + raise AccessControl_Unauthorized, 'You are not allowed to join arbitrary group.' + + if g is None : + gtool.addGroup(gid) + aclu.changeUser(aclu.getGroupPrefix() +gid, roles=['Member', ]) + g = gtool.getGroupById(gid) + g.addMember(id) + else : + BaseRegistrationTool.addMember(self, id, password, roles=roles, + domains=domains, properties=properties) + + + def afterAdd(self, member, id, password, properties): + """ notify member creation """ + member.notifyWorkflowCreated() + member.indexObject() + + + security.declarePublic('requestPasswordReset') + def requestPasswordReset(self, userid): + """ add uuid / (userid, expiration) pair and return uuid """ + self.clearExpiredPasswordResetRequests() + mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool') + member = mtool.getMemberById(userid) + if not member : + try : + checkEmailAddress(userid) + member = mtool.searchMembers('email', userid) + if member : + userid = member[0]['username'] + member = mtool.getMemberById(userid) + except EmailAddressInvalid : + pass + if member : + uuid = str(uuid4()) + while self._passwordResetRequests.has_key(uuid) : + uuid = str(uuid4()) + self._passwordResetRequests[uuid] = (userid, DateTime() + 1) + utool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool') + ptool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IPropertiesTool') + # fuck : mailhost récupéré avec getUtilityByInterfaceName n'est pas correctement + # wrappé. Un « unrestrictedTraverse » ne marche pas. + # mailhost = getUtilityByInterfaceName('Products.MailHost.interfaces.IMailHost') + portal = utool.getPortalObject() + mailhost = portal.MailHost + sender = encodeQuopriEmail(ptool.getProperty('email_from_name'), ptool.getProperty('email_from_address')) + to = encodeQuopriEmail(member.getMemberFullName(nameBefore=0), member.getProperty('email')) + subject = translate(_('How to reset your password on the %s website')) % ptool.getProperty('title') + subject = encodeMailHeader(subject) + options = {'fullName' : member.getMemberFullName(nameBefore=0), + 'siteName' : ptool.getProperty('title'), + 'resetPasswordUrl' : '%s/password_reset_form/%s' % (utool(), uuid)} + body = self.password_reset_mail(options) + message = self.echange_mail_template(From=sender, + To=to, + Subject=subject, + ContentType = 'text/plain', + charset = 'UTF-8', + body=body) + mailhost.send(message) + return + + return _('Unknown user name. Please retry.') + + security.declarePrivate('clearExpiredPasswordResetRequests') + def clearExpiredPasswordResetRequests(self): + now = DateTime() + for uuid, record in self._passwordResetRequests.items() : + userid, date = record + if date < now : + del self._passwordResetRequests[uuid] + + + security.declarePublic('resetPassword') + def resetPassword(self, uuid, password, confirm) : + record = self._passwordResetRequests.get(uuid) + if not record : + return None, _('Invalid reset password request.') + + userid, expiration = record + now = DateTime() + if expiration < now : + self.clearExpiredPasswordResetRequests() + return None, _('Your reset password request has expired. You can ask a new one.') + + msg = self.testPasswordValidity(password, confirm=confirm) + if not msg : # None if everything ok. Err message otherwise. + mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool') + member = mtool.getMemberById(userid) + if member : + member.setSecurityProfile(password=password) + del self._passwordResetRequests[uuid] + return userid, _('Password successfully updated.') + else : + return None, _('"%s" username not found.') % userid + + InitializeClass(RegistrationTool) \ No newline at end of file