# -*- coding: utf-8 -*-
#######################################################################################
# Plinn - http://plinn.org #
-# Copyright (C) 2005-2007 Benoît PIN <benoit.pin@ensmp.fr> #
+# © 2005-2013 Benoît PIN <pin@cri.ensmp.fr> #
# #
# This program is free software; you can redistribute it and/or #
# modify it under the terms of the GNU General Public License #
# 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.
+
+
-$Id: RegistrationTool.py 1332 2008-07-31 12:09:28Z pin $
-$URL: http://svn.cri.ensmp.fr/svn/Plinn/branches/CMF-2.1/RegistrationTool.py $
"""
from Globals import InitializeClass, PersistentMapping
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'
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