953e008c814ac2579c89a9a737212efd8a947d12
[Plinn.git] / RegistrationTool.py
1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # © 2005-2013 Benoît PIN <pin@cri.ensmp.fr> #
5 # #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ Plinn registration tool: implements 3 modes to register members:
21 anonymous, manager, reviewed.
22
23
24
25 """
26
27 from Globals import InitializeClass, PersistentMapping
28 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
29 from Products.CMFDefault.RegistrationTool import RegistrationTool as BaseRegistrationTool
30 from AccessControl import ClassSecurityInfo, ModuleSecurityInfo
31 from AccessControl.Permission import Permission
32 from BTrees.OOBTree import OOBTree
33 from Products.CMFCore.permissions import ManagePortal, AddPortalMember
34 from Products.CMFCore.exceptions import AccessControl_Unauthorized
35 from Products.CMFCore.utils import getToolByName
36 from Products.CMFCore.utils import getUtilityByInterfaceName
37 from Products.GroupUserFolder.GroupsToolPermissions import ManageGroups
38 from Products.Plinn.utils import Message as _
39 from DateTime import DateTime
40 from types import TupleType, ListType
41 from uuid import uuid4
42
43 security = ModuleSecurityInfo('Products.Plinn.RegistrationTool')
44 MODE_ANONYMOUS = 'anonymous'
45 security.declarePublic('MODE_ANONYMOUS')
46
47 MODE_MANAGER = 'manager'
48 security.declarePublic('MODE_MANAGER')
49
50 MODE_REVIEWED = 'reviewed'
51 security.declarePublic('MODE_REVIEWED')
52
53 MODES = [MODE_ANONYMOUS, MODE_MANAGER, MODE_REVIEWED]
54 security.declarePublic('MODES')
55
56 DEFAULT_MEMBER_GROUP = 'members'
57 security.declarePublic('DEFAULT_MEMBER_GROUP')
58
59
60
61 class RegistrationTool(BaseRegistrationTool) :
62
63 """ Create and modify users by making calls to portal_membership.
64 """
65
66 meta_type = "Plinn Registration Tool"
67
68 manage_options = ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \
69 BaseRegistrationTool.manage_options
70
71 security = ClassSecurityInfo()
72
73 security.declareProtected( ManagePortal, 'manage_regmode' )
74 manage_regmode = PageTemplateFile('www/configureRegistrationTool', globals(),
75 __name__='manage_regmode')
76
77 def __init__(self) :
78 self._mode = MODE_ANONYMOUS
79 self._chain = ''
80 self._passwordResetRequests = OOBTree()
81
82 security.declareProtected(ManagePortal, 'configureTool')
83 def configureTool(self, registration_mode, chain, REQUEST=None) :
84 """ """
85
86 if registration_mode not in MODES :
87 raise ValueError, "Unknown mode: " + registration_mode
88 else :
89 self._mode = registration_mode
90 self._updatePortalRoleMappingForMode(registration_mode)
91
92 wtool = getToolByName(self, 'portal_workflow')
93
94 if registration_mode == MODE_REVIEWED :
95 if not hasattr(wtool, '_chains_by_type') :
96 wtool._chains_by_type = PersistentMapping()
97 wfids = []
98 chain = chain.strip()
99
100 if chain == '(Default)' :
101 try : del wtool._chains_by_type['Member Data']
102 except KeyError : pass
103 self._chain = chain
104 else :
105 for wfid in chain.replace(',', ' ').split(' ') :
106 if wfid :
107 if not wtool.getWorkflowById(wfid) :
108 raise ValueError, '"%s" is not a workflow ID.' % wfid
109 wfids.append(wfid)
110
111 wtool._chains_by_type['Member Data'] = tuple(wfids)
112 self._chain = ', '.join(wfids)
113 else :
114 wtool._chains_by_type['Member Data'] = tuple()
115
116 if REQUEST :
117 REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.')
118
119 def _updatePortalRoleMappingForMode(self, mode) :
120
121 urlTool = getToolByName(self, 'portal_url')
122 portal = urlTool.getPortalObject()
123
124 if mode in [MODE_ANONYMOUS, MODE_REVIEWED] :
125 portal.manage_permission(AddPortalMember, roles = ['Anonymous', 'Manager'], acquire=1)
126 elif mode == MODE_MANAGER :
127 portal.manage_permission(AddPortalMember, roles = ['Manager', 'UserManager'], acquire=0)
128
129 security.declarePublic('getMode')
130 def getMode(self) :
131 # """ return current mode """
132 return self._mode[:]
133
134 security.declarePublic('getWfId')
135 def getWfChain(self) :
136 # """ return current workflow id """
137 return self._chain
138
139 security.declarePublic('roleMappingMismatch')
140 def roleMappingMismatch(self) :
141 # """ test if the role mapping is correct for the currrent mode """
142
143 mode = self._mode
144 urlTool = getToolByName(self, 'portal_url')
145 portal = urlTool.getPortalObject()
146
147 def rolesOfAddPortalMemberPerm() :
148 p=Permission(AddPortalMember, [], portal)
149 return p.getRoles()
150
151 if mode in [MODE_ANONYMOUS, MODE_REVIEWED] :
152 if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False
153
154 elif mode == MODE_MANAGER :
155 roles = rolesOfAddPortalMemberPerm()
156 if 'Manager' in roles or 'UserManager' in roles and len(roles) == 1 and type(roles) == TupleType :
157 return False
158
159 return True
160
161 security.declareProtected(AddPortalMember, 'addMember')
162 def addMember(self, id, password, roles=(), groups=(DEFAULT_MEMBER_GROUP,), domains='', properties=None) :
163 """ Idem CMFCore but without default role """
164 BaseRegistrationTool.addMember(self, id, password, roles=roles,
165 domains=domains, properties=properties)
166
167 if self.getMode() in [MODE_ANONYMOUS, MODE_MANAGER] :
168 gtool = getToolByName(self, 'portal_groups')
169 mtool = getToolByName(self, 'portal_membership')
170 utool = getToolByName(self, 'portal_url')
171 portal = utool.getPortalObject()
172 isGrpManager = mtool.checkPermission(ManageGroups, portal) ## TODO : CMF2.1 compat
173 aclu = self.aq_inner.acl_users
174
175 for gid in groups:
176 g = gtool.getGroupById(gid)
177 if not isGrpManager :
178 if gid != DEFAULT_MEMBER_GROUP:
179 raise AccessControl_Unauthorized, 'You are not allowed to join arbitrary group.'
180
181 if g is None :
182 gtool.addGroup(gid)
183 aclu.changeUser(aclu.getGroupPrefix() +gid, roles=['Member', ])
184 g = gtool.getGroupById(gid)
185 g.addMember(id)
186
187
188 def afterAdd(self, member, id, password, properties):
189 """ notify member creation """
190 member.notifyWorkflowCreated()
191 member.indexObject()
192
193
194 security.declarePublic('requestPasswordReset')
195 def requestPasswordReset(self, userid):
196 """ add uuid / (userid, expiration) pair and return uuid """
197 self.clearExpiredPasswordResetRequests()
198 mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
199 member = mtool.getMemberById(userid)
200 if member :
201 uuid = str(uuid4())
202 self._passwordResetRequests[uuid] = (userid, DateTime() + 1)
203 mailhost = getUtilityByInterfaceName('Products.MailHost.interfaces.IMailHost')
204 ptool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IPropertiesTool')
205 utool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool')
206 sender = ptool.getProperty('email_from_address')
207 to = member.getProperty('email')
208 message = self.echange_mail_template(From=sender,
209 To=to)
210
211 security.declarePrivate('clearExpiredPasswordResetRequests')
212 def clearExpiredPasswordResetRequests(self):
213 now = DateTime()
214 for uuid, record in self._passwordResetRequests.items() :
215 userid, date = record
216 if date < now :
217 del self._passwordResetRequests[uuid]
218
219
220 security.declarePublic('resetPassword')
221 def resetPassword(self, userid, uuid, password, confirm) :
222 record = self._passwordResetRequests.get(uuid)
223 if not record :
224 return _('Invalid reset password request.')
225
226 recUserid, expiration = record
227
228 if recUserid != userid :
229 return _('Invalid userid.')
230
231 if expiration < now :
232 self.clearExpiredPasswordResetRequests()
233 return _('Your reset password request has expired. You can ask a new one.')
234
235 msg = self.testPasswordValidity(password, confirm=confirm)
236 if not msg : # None if everything ok. Err message otherwise.
237 mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
238 member = mtool.getMemberById(userid)
239 if member :
240 member.setSecurityProfile(password=password)
241 del self._passwordResetRequests[uuid]
242 return _('Password successfully resetted.')
243 else :
244 return _('"%s" username not found.') % userid
245
246
247 InitializeClass(RegistrationTool)