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_PASS_ANONYMOUS
= 'pass_anonymous'
53 security
.declarePublic('MODE_PASS_ANONYMOUS')
55 MODE_MANAGER
= 'manager'
56 security
.declarePublic('MODE_MANAGER')
58 MODE_REVIEWED
= 'reviewed'
59 security
.declarePublic('MODE_REVIEWED')
61 MODES
= [MODE_ANONYMOUS
, MODE_PASS_ANONYMOUS
, MODE_MANAGER
, MODE_REVIEWED
]
62 security
.declarePublic('MODES')
64 DEFAULT_MEMBER_GROUP
= 'members'
65 security
.declarePublic('DEFAULT_MEMBER_GROUP')
69 class RegistrationTool(BaseRegistrationTool
) :
71 """ Create and modify users by making calls to portal_membership.
74 meta_type
= "Plinn Registration Tool"
76 manage_options
= ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \
77 BaseRegistrationTool
.manage_options
79 security
= ClassSecurityInfo()
81 security
.declareProtected( ManagePortal
, 'manage_regmode' )
82 manage_regmode
= PageTemplateFile('www/configureRegistrationTool', globals(),
83 __name__
='manage_regmode')
86 self
._mode
= MODE_ANONYMOUS
88 self
._passwordResetRequests
= OOBTree()
90 security
.declareProtected(ManagePortal
, 'configureTool')
91 def configureTool(self
, registration_mode
, chain
, REQUEST
=None) :
94 if registration_mode
not in MODES
:
95 raise ValueError, "Unknown mode: " + registration_mode
97 self
._mode
= registration_mode
98 self
._updatePortalRoleMappingForMode
(registration_mode
)
100 wtool
= getToolByName(self
, 'portal_workflow')
102 if registration_mode
== MODE_REVIEWED
:
103 if not hasattr(wtool
, '_chains_by_type') :
104 wtool
._chains
_by
_type
= PersistentMapping()
106 chain
= chain
.strip()
108 if chain
== '(Default)' :
109 try : del wtool
._chains
_by
_type
['Member Data']
110 except KeyError : pass
113 for wfid
in chain
.replace(',', ' ').split(' ') :
115 if not wtool
.getWorkflowById(wfid
) :
116 raise ValueError, '"%s" is not a workflow ID.' % wfid
119 wtool
._chains
_by
_type
['Member Data'] = tuple(wfids
)
120 self
._chain
= ', '.join(wfids
)
122 wtool
._chains
_by
_type
['Member Data'] = tuple()
125 REQUEST
.RESPONSE
.redirect(self
.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.')
127 def _updatePortalRoleMappingForMode(self
, mode
) :
129 urlTool
= getToolByName(self
, 'portal_url')
130 portal
= urlTool
.getPortalObject()
132 if mode
in [MODE_ANONYMOUS
, MODE_PASS_ANONYMOUS
, MODE_REVIEWED
] :
133 portal
.manage_permission(AddPortalMember
, roles
= ['Anonymous', 'Manager'], acquire
=1)
134 elif mode
== MODE_MANAGER
:
135 portal
.manage_permission(AddPortalMember
, roles
= ['Manager', 'UserManager'], acquire
=0)
137 security
.declarePublic('getMode')
139 # """ return current mode """
142 security
.declarePublic('getWfId')
143 def getWfChain(self
) :
144 # """ return current workflow id """
147 security
.declarePublic('roleMappingMismatch')
148 def roleMappingMismatch(self
) :
149 # """ test if the role mapping is correct for the currrent mode """
152 urlTool
= getToolByName(self
, 'portal_url')
153 portal
= urlTool
.getPortalObject()
155 def rolesOfAddPortalMemberPerm() :
156 p
=Permission(AddPortalMember
, [], portal
)
159 if mode
in [MODE_ANONYMOUS
, MODE_PASS_ANONYMOUS
, MODE_REVIEWED
] :
160 if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False
162 elif mode
== MODE_MANAGER
:
163 roles
= rolesOfAddPortalMemberPerm()
164 if 'Manager' in roles
or 'UserManager' in roles
and len(roles
) == 1 and type(roles
) == TupleType
:
169 security
.declareProtected(AddPortalMember
, 'addMember')
170 def addMember(self
, id, password
, roles
=(), groups
=(DEFAULT_MEMBER_GROUP
,), domains
='', properties
=None) :
171 """ Idem CMFCore but without default role """
172 BaseRegistrationTool
.addMember(self
, id, password
, roles
=roles
,
173 domains
=domains
, properties
=properties
)
175 if self
.getMode() in [MODE_ANONYMOUS
, MODE_MANAGER
] :
176 gtool
= getToolByName(self
, 'portal_groups')
177 mtool
= getToolByName(self
, 'portal_membership')
178 utool
= getToolByName(self
, 'portal_url')
179 portal
= utool
.getPortalObject()
180 isGrpManager
= mtool
.checkPermission(ManageGroups
, portal
) ## TODO : CMF2.1 compat
181 aclu
= self
.aq_inner
.acl_users
184 g
= gtool
.getGroupById(gid
)
185 if not isGrpManager
:
186 if gid
!= DEFAULT_MEMBER_GROUP
:
187 raise AccessControl_Unauthorized
, 'You are not allowed to join arbitrary group.'
191 aclu
.changeUser(aclu
.getGroupPrefix() +gid
, roles
=['Member', ])
192 g
= gtool
.getGroupById(gid
)
196 def afterAdd(self
, member
, id, password
, properties
):
197 """ notify member creation """
198 member
.notifyWorkflowCreated()
202 security
.declarePublic('requestPasswordReset')
203 def requestPasswordReset(self
, userid
):
204 """ add uuid / (userid, expiration) pair and return uuid """
205 self
.clearExpiredPasswordResetRequests()
206 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
207 member
= mtool
.getMemberById(userid
)
210 checkEmailAddress(userid
)
211 member
= mtool
.searchMembers('email', userid
)
213 userid
= member
[0]['username']
214 member
= mtool
.getMemberById(userid
)
215 except EmailAddressInvalid
:
219 while self
._passwordResetRequests
.has_key(uuid
) :
221 self
._passwordResetRequests
[uuid
] = (userid
, DateTime() + 1)
222 utool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool')
223 ptool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IPropertiesTool')
224 # fuck : mailhost récupéré avec getUtilityByInterfaceName n'est pas correctement
225 # wrappé. Un « unrestrictedTraverse » ne marche pas.
226 # mailhost = getUtilityByInterfaceName('Products.MailHost.interfaces.IMailHost')
227 portal
= utool
.getPortalObject()
228 mailhost
= portal
.MailHost
229 sender
= encodeQuopriEmail(ptool
.getProperty('email_from_name'), ptool
.getProperty('email_from_address'))
230 to
= encodeQuopriEmail(member
.getMemberFullName(nameBefore
=0), member
.getProperty('email'))
231 subject
= translate(_('How to reset your password on the %s website')) % ptool
.getProperty('title')
232 subject
= encodeMailHeader(subject
)
233 options
= {'fullName' : member
.getMemberFullName(nameBefore
=0),
234 'siteName' : ptool
.getProperty('title'),
235 'resetPasswordUrl' : '%s/password_reset_form/%s' % (utool(), uuid
)}
236 body
= self
.password_reset_mail(options
)
237 message
= self
.echange_mail_template(From
=sender
,
240 ContentType
= 'text/plain',
243 mailhost
.send(message
)
246 return _('Unknown user name. Please retry.')
248 security
.declarePrivate('clearExpiredPasswordResetRequests')
249 def clearExpiredPasswordResetRequests(self
):
251 for uuid
, record
in self
._passwordResetRequests
.items() :
252 userid
, date
= record
254 del self
._passwordResetRequests
[uuid
]
257 security
.declarePublic('resetPassword')
258 def resetPassword(self
, uuid
, password
, confirm
) :
259 record
= self
._passwordResetRequests
.get(uuid
)
261 return None, _('Invalid reset password request.')
263 userid
, expiration
= record
265 if expiration
< now
:
266 self
.clearExpiredPasswordResetRequests()
267 return None, _('Your reset password request has expired. You can ask a new one.')
269 msg
= self
.testPasswordValidity(password
, confirm
=confirm
)
270 if not msg
: # None if everything ok. Err message otherwise.
271 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
272 member
= mtool
.getMemberById(userid
)
274 member
.setSecurityProfile(password
=password
)
275 del self
._passwordResetRequests
[uuid
]
276 return userid
, _('Password successfully updated.')
278 return None, _('"%s" username not found.') % userid
281 InitializeClass(RegistrationTool
)