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
.CMFDefault
.exceptions
import EmailAddressInvalid
36 from Products
.CMFCore
.utils
import getToolByName
37 from Products
.CMFCore
.utils
import getUtilityByInterfaceName
38 from Products
.CMFDefault
.utils
import checkEmailAddress
39 from Products
.GroupUserFolder
.GroupsToolPermissions
import ManageGroups
40 from Products
.Plinn
.utils
import Message
as _
41 from Products
.Plinn
.utils
import translate
42 from Products
.Plinn
.utils
import encodeQuopriEmail
43 from Products
.Plinn
.utils
import encodeMailHeader
44 from DateTime
import DateTime
45 from types
import TupleType
, ListType
46 from uuid
import uuid4
48 security
= ModuleSecurityInfo('Products.Plinn.RegistrationTool')
49 MODE_ANONYMOUS
= 'anonymous'
50 security
.declarePublic('MODE_ANONYMOUS')
52 MODE_MANAGER
= 'manager'
53 security
.declarePublic('MODE_MANAGER')
55 MODE_REVIEWED
= 'reviewed'
56 security
.declarePublic('MODE_REVIEWED')
58 MODES
= [MODE_ANONYMOUS
, MODE_MANAGER
, MODE_REVIEWED
]
59 security
.declarePublic('MODES')
61 DEFAULT_MEMBER_GROUP
= 'members'
62 security
.declarePublic('DEFAULT_MEMBER_GROUP')
66 class RegistrationTool(BaseRegistrationTool
) :
68 """ Create and modify users by making calls to portal_membership.
71 meta_type
= "Plinn Registration Tool"
73 manage_options
= ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \
74 BaseRegistrationTool
.manage_options
76 security
= ClassSecurityInfo()
78 security
.declareProtected( ManagePortal
, 'manage_regmode' )
79 manage_regmode
= PageTemplateFile('www/configureRegistrationTool', globals(),
80 __name__
='manage_regmode')
83 self
._mode
= MODE_ANONYMOUS
85 self
._passwordResetRequests
= OOBTree()
87 security
.declareProtected(ManagePortal
, 'configureTool')
88 def configureTool(self
, registration_mode
, chain
, REQUEST
=None) :
91 if registration_mode
not in MODES
:
92 raise ValueError, "Unknown mode: " + registration_mode
94 self
._mode
= registration_mode
95 self
._updatePortalRoleMappingForMode
(registration_mode
)
97 wtool
= getToolByName(self
, 'portal_workflow')
99 if registration_mode
== MODE_REVIEWED
:
100 if not hasattr(wtool
, '_chains_by_type') :
101 wtool
._chains
_by
_type
= PersistentMapping()
103 chain
= chain
.strip()
105 if chain
== '(Default)' :
106 try : del wtool
._chains
_by
_type
['Member Data']
107 except KeyError : pass
110 for wfid
in chain
.replace(',', ' ').split(' ') :
112 if not wtool
.getWorkflowById(wfid
) :
113 raise ValueError, '"%s" is not a workflow ID.' % wfid
116 wtool
._chains
_by
_type
['Member Data'] = tuple(wfids
)
117 self
._chain
= ', '.join(wfids
)
119 wtool
._chains
_by
_type
['Member Data'] = tuple()
122 REQUEST
.RESPONSE
.redirect(self
.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.')
124 def _updatePortalRoleMappingForMode(self
, mode
) :
126 urlTool
= getToolByName(self
, 'portal_url')
127 portal
= urlTool
.getPortalObject()
129 if mode
in [MODE_ANONYMOUS
, MODE_REVIEWED
] :
130 portal
.manage_permission(AddPortalMember
, roles
= ['Anonymous', 'Manager'], acquire
=1)
131 elif mode
== MODE_MANAGER
:
132 portal
.manage_permission(AddPortalMember
, roles
= ['Manager', 'UserManager'], acquire
=0)
134 security
.declarePublic('getMode')
136 # """ return current mode """
139 security
.declarePublic('getWfId')
140 def getWfChain(self
) :
141 # """ return current workflow id """
144 security
.declarePublic('roleMappingMismatch')
145 def roleMappingMismatch(self
) :
146 # """ test if the role mapping is correct for the currrent mode """
149 urlTool
= getToolByName(self
, 'portal_url')
150 portal
= urlTool
.getPortalObject()
152 def rolesOfAddPortalMemberPerm() :
153 p
=Permission(AddPortalMember
, [], portal
)
156 if mode
in [MODE_ANONYMOUS
, MODE_REVIEWED
] :
157 if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False
159 elif mode
== MODE_MANAGER
:
160 roles
= rolesOfAddPortalMemberPerm()
161 if 'Manager' in roles
or 'UserManager' in roles
and len(roles
) == 1 and type(roles
) == TupleType
:
166 security
.declareProtected(AddPortalMember
, 'addMember')
167 def addMember(self
, id, password
, roles
=(), groups
=(DEFAULT_MEMBER_GROUP
,), domains
='', properties
=None) :
168 """ Idem CMFCore but without default role """
169 BaseRegistrationTool
.addMember(self
, id, password
, roles
=roles
,
170 domains
=domains
, properties
=properties
)
172 if self
.getMode() in [MODE_ANONYMOUS
, MODE_MANAGER
] :
173 gtool
= getToolByName(self
, 'portal_groups')
174 mtool
= getToolByName(self
, 'portal_membership')
175 utool
= getToolByName(self
, 'portal_url')
176 portal
= utool
.getPortalObject()
177 isGrpManager
= mtool
.checkPermission(ManageGroups
, portal
) ## TODO : CMF2.1 compat
178 aclu
= self
.aq_inner
.acl_users
181 g
= gtool
.getGroupById(gid
)
182 if not isGrpManager
:
183 if gid
!= DEFAULT_MEMBER_GROUP
:
184 raise AccessControl_Unauthorized
, 'You are not allowed to join arbitrary group.'
188 aclu
.changeUser(aclu
.getGroupPrefix() +gid
, roles
=['Member', ])
189 g
= gtool
.getGroupById(gid
)
193 def afterAdd(self
, member
, id, password
, properties
):
194 """ notify member creation """
195 member
.notifyWorkflowCreated()
199 security
.declarePublic('requestPasswordReset')
200 def requestPasswordReset(self
, userid
):
201 """ add uuid / (userid, expiration) pair and return uuid """
202 self
.clearExpiredPasswordResetRequests()
203 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
204 member
= mtool
.getMemberById(userid
)
207 checkEmailAddress(userid
)
208 member
= mtool
.searchMembers('email', userid
)
210 userid
= member
[0]['username']
211 member
= mtool
.getMemberById(userid
)
212 except EmailAddressInvalid
:
216 while self
._passwordResetRequests
.has_key(uuid
) :
218 self
._passwordResetRequests
[uuid
] = (userid
, DateTime() + 1)
219 utool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool')
220 ptool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IPropertiesTool')
221 # fuck : mailhost récupéré avec getUtilityByInterfaceName n'est pas correctement
222 # wrappé. Un « unrestrictedTraverse » ne marche pas.
223 # mailhost = getUtilityByInterfaceName('Products.MailHost.interfaces.IMailHost')
224 portal
= utool
.getPortalObject()
225 mailhost
= portal
.MailHost
226 sender
= encodeQuopriEmail(ptool
.getProperty('email_from_name'), ptool
.getProperty('email_from_address'))
227 to
= encodeQuopriEmail(member
.getMemberFullName(nameBefore
=0), member
.getProperty('email'))
228 subject
= translate(_('How to reset your password on the %s website')) % ptool
.getProperty('title')
229 subject
= encodeMailHeader(subject
)
230 options
= {'fullName' : member
.getMemberFullName(nameBefore
=0),
231 'siteName' : ptool
.getProperty('title'),
232 'resetPasswordUrl' : '%s/password_reset_form/%s' % (utool(), uuid
)}
233 body
= self
.password_reset_mail(options
)
234 message
= self
.echange_mail_template(From
=sender
,
237 ContentType
= 'text/plain',
240 mailhost
.send(message
)
243 return _('Unknown user name. Please retry.')
245 security
.declarePrivate('clearExpiredPasswordResetRequests')
246 def clearExpiredPasswordResetRequests(self
):
248 for uuid
, record
in self
._passwordResetRequests
.items() :
249 userid
, date
= record
251 del self
._passwordResetRequests
[uuid
]
254 security
.declarePublic('resetPassword')
255 def resetPassword(self
, uuid
, password
, confirm
) :
256 record
= self
._passwordResetRequests
.get(uuid
)
258 return None, _('Invalid reset password request.')
260 userid
, expiration
= record
262 if expiration
< now
:
263 self
.clearExpiredPasswordResetRequests()
264 return None, _('Your reset password request has expired. You can ask a new one.')
266 msg
= self
.testPasswordValidity(password
, confirm
=confirm
)
267 if not msg
: # None if everything ok. Err message otherwise.
268 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
269 member
= mtool
.getMemberById(userid
)
271 member
.setSecurityProfile(password
=password
)
272 del self
._passwordResetRequests
[uuid
]
273 return userid
, _('Password successfully updated.')
275 return None, _('"%s" username not found.') % userid
278 InitializeClass(RegistrationTool
)