X-Git-Url: https://svn.cri.ensmp.fr/git/Plinn.git/blobdiff_plain/99afe0ce8700bf835ff7bbedf77bb46e58f996d8..61f77ffd5dea8200c9f33d6deb59f86c994539b7:/RegistrationTool.py?ds=sidebyside

diff --git a/RegistrationTool.py b/RegistrationTool.py
index 77f8bdc..1417f30 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 <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                       #
@@ -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,11 +29,19 @@ 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.CMFCore.utils import getToolByName
+from Products.CMFCore.utils import getUtilityByInterfaceName
 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'
@@ -55,133 +63,205 @@ 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_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()
+    
+
+    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 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 reset.')
+            else :
+                return None, _('"%s" username not found.') % userid
+            
+        
 InitializeClass(RegistrationTool)
\ No newline at end of file