1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # © 2005-2013 Benoît PIN <pin@cri.ensmp.fr> #
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. #
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. #
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.
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
43 security
= ModuleSecurityInfo('Products.Plinn.RegistrationTool')
44 MODE_ANONYMOUS
= 'anonymous'
45 security
.declarePublic('MODE_ANONYMOUS')
47 MODE_MANAGER
= 'manager'
48 security
.declarePublic('MODE_MANAGER')
50 MODE_REVIEWED
= 'reviewed'
51 security
.declarePublic('MODE_REVIEWED')
53 MODES
= [MODE_ANONYMOUS
, MODE_MANAGER
, MODE_REVIEWED
]
54 security
.declarePublic('MODES')
56 DEFAULT_MEMBER_GROUP
= 'members'
57 security
.declarePublic('DEFAULT_MEMBER_GROUP')
61 class RegistrationTool(BaseRegistrationTool
) :
63 """ Create and modify users by making calls to portal_membership.
66 meta_type
= "Plinn Registration Tool"
68 manage_options
= ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \
69 BaseRegistrationTool
.manage_options
71 security
= ClassSecurityInfo()
73 security
.declareProtected( ManagePortal
, 'manage_regmode' )
74 manage_regmode
= PageTemplateFile('www/configureRegistrationTool', globals(),
75 __name__
='manage_regmode')
78 self
._mode
= MODE_ANONYMOUS
80 self
._passwordResetRequests
= OOBTree()
82 security
.declareProtected(ManagePortal
, 'configureTool')
83 def configureTool(self
, registration_mode
, chain
, REQUEST
=None) :
86 if registration_mode
not in MODES
:
87 raise ValueError, "Unknown mode: " + registration_mode
89 self
._mode
= registration_mode
90 self
._updatePortalRoleMappingForMode
(registration_mode
)
92 wtool
= getToolByName(self
, 'portal_workflow')
94 if registration_mode
== MODE_REVIEWED
:
95 if not hasattr(wtool
, '_chains_by_type') :
96 wtool
._chains
_by
_type
= PersistentMapping()
100 if chain
== '(Default)' :
101 try : del wtool
._chains
_by
_type
['Member Data']
102 except KeyError : pass
105 for wfid
in chain
.replace(',', ' ').split(' ') :
107 if not wtool
.getWorkflowById(wfid
) :
108 raise ValueError, '"%s" is not a workflow ID.' % wfid
111 wtool
._chains
_by
_type
['Member Data'] = tuple(wfids
)
112 self
._chain
= ', '.join(wfids
)
114 wtool
._chains
_by
_type
['Member Data'] = tuple()
117 REQUEST
.RESPONSE
.redirect(self
.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.')
119 def _updatePortalRoleMappingForMode(self
, mode
) :
121 urlTool
= getToolByName(self
, 'portal_url')
122 portal
= urlTool
.getPortalObject()
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)
129 security
.declarePublic('getMode')
131 # """ return current mode """
134 security
.declarePublic('getWfId')
135 def getWfChain(self
) :
136 # """ return current workflow id """
139 security
.declarePublic('roleMappingMismatch')
140 def roleMappingMismatch(self
) :
141 # """ test if the role mapping is correct for the currrent mode """
144 urlTool
= getToolByName(self
, 'portal_url')
145 portal
= urlTool
.getPortalObject()
147 def rolesOfAddPortalMemberPerm() :
148 p
=Permission(AddPortalMember
, [], portal
)
151 if mode
in [MODE_ANONYMOUS
, MODE_REVIEWED
] :
152 if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False
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
:
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
)
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
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.'
183 aclu
.changeUser(aclu
.getGroupPrefix() +gid
, roles
=['Member', ])
184 g
= gtool
.getGroupById(gid
)
188 def afterAdd(self
, member
, id, password
, properties
):
189 """ notify member creation """
190 member
.notifyWorkflowCreated()
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 if mtool
.getMemberById(userid
) :
201 self
._passwordResetRequests
[uuid
] = (userid
, DateTime() + 1)
204 security
.declarePrivate('clearExpiredPasswordResetRequests')
205 def clearExpiredPasswordResetRequests(self
):
207 for uuid
, record
in self
._passwordResetRequests
.items() :
208 userid
, date
= record
210 del self
._passwordResetRequests
[uuid
]
213 security
.declarePublic('resetPassword')
214 def resetPassword(self
, userid
, uuid
, password
, confirm
) :
215 record
= self
._passwordResetRequests
.get(uuid
)
217 return _('Invalid reset password request.')
219 recUserid
, expiration
= record
221 if recUserid
!= userid
:
222 return _('Invalid userid.')
224 if expiration
< now
:
225 self
.clearExpiredPasswordResetRequests()
226 return _('Your reset password request has expired. You can ask a new one.')
228 msg
= self
.testPasswordValidity(password
, confirm
=confirm
)
229 if not msg
: # None if everything ok. Err message otherwise.
230 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
231 member
= mtool
.getMemberById(userid
)
233 member
.setSecurityProfile(password
=password
)
234 del self
._passwordResetRequests
[uuid
]
235 return _('Password successfully resetted.')
237 return _('"%s" username not found.') % userid
240 InitializeClass(RegistrationTool
)