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 Products
.Plinn
.utils
import translate
40 from Products
.Plinn
.utils
import encodeQuopriEmail
41 from Products
.Plinn
.utils
import encodeMailHeader
42 from DateTime
import DateTime
43 from types
import TupleType
, ListType
44 from uuid
import uuid4
46 security
= ModuleSecurityInfo('Products.Plinn.RegistrationTool')
47 MODE_ANONYMOUS
= 'anonymous'
48 security
.declarePublic('MODE_ANONYMOUS')
50 MODE_MANAGER
= 'manager'
51 security
.declarePublic('MODE_MANAGER')
53 MODE_REVIEWED
= 'reviewed'
54 security
.declarePublic('MODE_REVIEWED')
56 MODES
= [MODE_ANONYMOUS
, MODE_MANAGER
, MODE_REVIEWED
]
57 security
.declarePublic('MODES')
59 DEFAULT_MEMBER_GROUP
= 'members'
60 security
.declarePublic('DEFAULT_MEMBER_GROUP')
64 class RegistrationTool(BaseRegistrationTool
) :
66 """ Create and modify users by making calls to portal_membership.
69 meta_type
= "Plinn Registration Tool"
71 manage_options
= ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \
72 BaseRegistrationTool
.manage_options
74 security
= ClassSecurityInfo()
76 security
.declareProtected( ManagePortal
, 'manage_regmode' )
77 manage_regmode
= PageTemplateFile('www/configureRegistrationTool', globals(),
78 __name__
='manage_regmode')
81 self
._mode
= MODE_ANONYMOUS
83 self
._passwordResetRequests
= OOBTree()
85 security
.declareProtected(ManagePortal
, 'configureTool')
86 def configureTool(self
, registration_mode
, chain
, REQUEST
=None) :
89 if registration_mode
not in MODES
:
90 raise ValueError, "Unknown mode: " + registration_mode
92 self
._mode
= registration_mode
93 self
._updatePortalRoleMappingForMode
(registration_mode
)
95 wtool
= getToolByName(self
, 'portal_workflow')
97 if registration_mode
== MODE_REVIEWED
:
98 if not hasattr(wtool
, '_chains_by_type') :
99 wtool
._chains
_by
_type
= PersistentMapping()
101 chain
= chain
.strip()
103 if chain
== '(Default)' :
104 try : del wtool
._chains
_by
_type
['Member Data']
105 except KeyError : pass
108 for wfid
in chain
.replace(',', ' ').split(' ') :
110 if not wtool
.getWorkflowById(wfid
) :
111 raise ValueError, '"%s" is not a workflow ID.' % wfid
114 wtool
._chains
_by
_type
['Member Data'] = tuple(wfids
)
115 self
._chain
= ', '.join(wfids
)
117 wtool
._chains
_by
_type
['Member Data'] = tuple()
120 REQUEST
.RESPONSE
.redirect(self
.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.')
122 def _updatePortalRoleMappingForMode(self
, mode
) :
124 urlTool
= getToolByName(self
, 'portal_url')
125 portal
= urlTool
.getPortalObject()
127 if mode
in [MODE_ANONYMOUS
, MODE_REVIEWED
] :
128 portal
.manage_permission(AddPortalMember
, roles
= ['Anonymous', 'Manager'], acquire
=1)
129 elif mode
== MODE_MANAGER
:
130 portal
.manage_permission(AddPortalMember
, roles
= ['Manager', 'UserManager'], acquire
=0)
132 security
.declarePublic('getMode')
134 # """ return current mode """
137 security
.declarePublic('getWfId')
138 def getWfChain(self
) :
139 # """ return current workflow id """
142 security
.declarePublic('roleMappingMismatch')
143 def roleMappingMismatch(self
) :
144 # """ test if the role mapping is correct for the currrent mode """
147 urlTool
= getToolByName(self
, 'portal_url')
148 portal
= urlTool
.getPortalObject()
150 def rolesOfAddPortalMemberPerm() :
151 p
=Permission(AddPortalMember
, [], portal
)
154 if mode
in [MODE_ANONYMOUS
, MODE_REVIEWED
] :
155 if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False
157 elif mode
== MODE_MANAGER
:
158 roles
= rolesOfAddPortalMemberPerm()
159 if 'Manager' in roles
or 'UserManager' in roles
and len(roles
) == 1 and type(roles
) == TupleType
:
164 security
.declareProtected(AddPortalMember
, 'addMember')
165 def addMember(self
, id, password
, roles
=(), groups
=(DEFAULT_MEMBER_GROUP
,), domains
='', properties
=None) :
166 """ Idem CMFCore but without default role """
167 BaseRegistrationTool
.addMember(self
, id, password
, roles
=roles
,
168 domains
=domains
, properties
=properties
)
170 if self
.getMode() in [MODE_ANONYMOUS
, MODE_MANAGER
] :
171 gtool
= getToolByName(self
, 'portal_groups')
172 mtool
= getToolByName(self
, 'portal_membership')
173 utool
= getToolByName(self
, 'portal_url')
174 portal
= utool
.getPortalObject()
175 isGrpManager
= mtool
.checkPermission(ManageGroups
, portal
) ## TODO : CMF2.1 compat
176 aclu
= self
.aq_inner
.acl_users
179 g
= gtool
.getGroupById(gid
)
180 if not isGrpManager
:
181 if gid
!= DEFAULT_MEMBER_GROUP
:
182 raise AccessControl_Unauthorized
, 'You are not allowed to join arbitrary group.'
186 aclu
.changeUser(aclu
.getGroupPrefix() +gid
, roles
=['Member', ])
187 g
= gtool
.getGroupById(gid
)
191 def afterAdd(self
, member
, id, password
, properties
):
192 """ notify member creation """
193 member
.notifyWorkflowCreated()
197 security
.declarePublic('requestPasswordReset')
198 def requestPasswordReset(self
, userid
):
199 """ add uuid / (userid, expiration) pair and return uuid """
200 self
.clearExpiredPasswordResetRequests()
201 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
202 member
= mtool
.getMemberById(userid
)
205 while self
._passwordResetRequests
.has_key(uuid
) :
207 self
._passwordResetRequests
[uuid
] = (userid
, DateTime() + 1)
208 utool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool')
209 ptool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IPropertiesTool')
210 # fuck : mailhost récupéré avec getUtilityByInterfaceName n'est pas correctement
211 # wrappé. Un « unrestrictedTraverse » ne marche pas.
212 # mailhost = getUtilityByInterfaceName('Products.MailHost.interfaces.IMailHost')
213 portal
= utool
.getPortalObject()
214 mailhost
= portal
.MailHost
215 sender
= encodeQuopriEmail(ptool
.getProperty('email_from_name'), ptool
.getProperty('email_from_address'))
216 to
= encodeQuopriEmail(member
.getMemberFullName(nameBefore
=0), member
.getProperty('email'))
217 subject
= translate(_('How to reset your password on the %s website')) % ptool
.getProperty('title')
218 subject
= encodeMailHeader(subject
)
219 options
= {'fullName' : member
.getMemberFullName(nameBefore
=0),
220 'siteName' : ptool
.getProperty('title'),
221 'resetPasswordUrl' : '%s/password_reset_form/%s' % (utool(), uuid
)}
222 body
= self
.password_reset_mail(options
)
223 message
= self
.echange_mail_template(From
=sender
,
226 ContentType
= 'text/plain',
229 mailhost
.send(message
)
232 return _('Unknown user name. Please retry.')
234 security
.declarePrivate('clearExpiredPasswordResetRequests')
235 def clearExpiredPasswordResetRequests(self
):
237 for uuid
, record
in self
._passwordResetRequests
.items() :
238 userid
, date
= record
240 del self
._passwordResetRequests
[uuid
]
243 security
.declarePublic('resetPassword')
244 def resetPassword(self
, uuid
, password
, confirm
) :
245 record
= self
._passwordResetRequests
.get(uuid
)
247 return _('Invalid reset password request.')
249 userid
, expiration
= record
251 if expiration
< now
:
252 self
.clearExpiredPasswordResetRequests()
253 return _('Your reset password request has expired. You can ask a new one.')
255 msg
= self
.testPasswordValidity(password
, confirm
=confirm
)
256 if not msg
: # None if everything ok. Err message otherwise.
257 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
258 member
= mtool
.getMemberById(userid
)
260 member
.setSecurityProfile(password
=password
)
261 del self
._passwordResetRequests
[uuid
]
262 return _('Password successfully resetted.')
264 return _('"%s" username not found.') % userid
267 InitializeClass(RegistrationTool
)