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 encodeQuopriEmail
40 from DateTime
import DateTime
41 from types
import TupleType
, ListType
42 from uuid
import uuid4
43 from quopri
import encodestring
45 security
= ModuleSecurityInfo('Products.Plinn.RegistrationTool')
46 MODE_ANONYMOUS
= 'anonymous'
47 security
.declarePublic('MODE_ANONYMOUS')
49 MODE_MANAGER
= 'manager'
50 security
.declarePublic('MODE_MANAGER')
52 MODE_REVIEWED
= 'reviewed'
53 security
.declarePublic('MODE_REVIEWED')
55 MODES
= [MODE_ANONYMOUS
, MODE_MANAGER
, MODE_REVIEWED
]
56 security
.declarePublic('MODES')
58 DEFAULT_MEMBER_GROUP
= 'members'
59 security
.declarePublic('DEFAULT_MEMBER_GROUP')
63 class RegistrationTool(BaseRegistrationTool
) :
65 """ Create and modify users by making calls to portal_membership.
68 meta_type
= "Plinn Registration Tool"
70 manage_options
= ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \
71 BaseRegistrationTool
.manage_options
73 security
= ClassSecurityInfo()
75 security
.declareProtected( ManagePortal
, 'manage_regmode' )
76 manage_regmode
= PageTemplateFile('www/configureRegistrationTool', globals(),
77 __name__
='manage_regmode')
80 self
._mode
= MODE_ANONYMOUS
82 self
._passwordResetRequests
= OOBTree()
84 security
.declareProtected(ManagePortal
, 'configureTool')
85 def configureTool(self
, registration_mode
, chain
, REQUEST
=None) :
88 if registration_mode
not in MODES
:
89 raise ValueError, "Unknown mode: " + registration_mode
91 self
._mode
= registration_mode
92 self
._updatePortalRoleMappingForMode
(registration_mode
)
94 wtool
= getToolByName(self
, 'portal_workflow')
96 if registration_mode
== MODE_REVIEWED
:
97 if not hasattr(wtool
, '_chains_by_type') :
98 wtool
._chains
_by
_type
= PersistentMapping()
100 chain
= chain
.strip()
102 if chain
== '(Default)' :
103 try : del wtool
._chains
_by
_type
['Member Data']
104 except KeyError : pass
107 for wfid
in chain
.replace(',', ' ').split(' ') :
109 if not wtool
.getWorkflowById(wfid
) :
110 raise ValueError, '"%s" is not a workflow ID.' % wfid
113 wtool
._chains
_by
_type
['Member Data'] = tuple(wfids
)
114 self
._chain
= ', '.join(wfids
)
116 wtool
._chains
_by
_type
['Member Data'] = tuple()
119 REQUEST
.RESPONSE
.redirect(self
.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.')
121 def _updatePortalRoleMappingForMode(self
, mode
) :
123 urlTool
= getToolByName(self
, 'portal_url')
124 portal
= urlTool
.getPortalObject()
126 if mode
in [MODE_ANONYMOUS
, MODE_REVIEWED
] :
127 portal
.manage_permission(AddPortalMember
, roles
= ['Anonymous', 'Manager'], acquire
=1)
128 elif mode
== MODE_MANAGER
:
129 portal
.manage_permission(AddPortalMember
, roles
= ['Manager', 'UserManager'], acquire
=0)
131 security
.declarePublic('getMode')
133 # """ return current mode """
136 security
.declarePublic('getWfId')
137 def getWfChain(self
) :
138 # """ return current workflow id """
141 security
.declarePublic('roleMappingMismatch')
142 def roleMappingMismatch(self
) :
143 # """ test if the role mapping is correct for the currrent mode """
146 urlTool
= getToolByName(self
, 'portal_url')
147 portal
= urlTool
.getPortalObject()
149 def rolesOfAddPortalMemberPerm() :
150 p
=Permission(AddPortalMember
, [], portal
)
153 if mode
in [MODE_ANONYMOUS
, MODE_REVIEWED
] :
154 if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False
156 elif mode
== MODE_MANAGER
:
157 roles
= rolesOfAddPortalMemberPerm()
158 if 'Manager' in roles
or 'UserManager' in roles
and len(roles
) == 1 and type(roles
) == TupleType
:
163 security
.declareProtected(AddPortalMember
, 'addMember')
164 def addMember(self
, id, password
, roles
=(), groups
=(DEFAULT_MEMBER_GROUP
,), domains
='', properties
=None) :
165 """ Idem CMFCore but without default role """
166 BaseRegistrationTool
.addMember(self
, id, password
, roles
=roles
,
167 domains
=domains
, properties
=properties
)
169 if self
.getMode() in [MODE_ANONYMOUS
, MODE_MANAGER
] :
170 gtool
= getToolByName(self
, 'portal_groups')
171 mtool
= getToolByName(self
, 'portal_membership')
172 utool
= getToolByName(self
, 'portal_url')
173 portal
= utool
.getPortalObject()
174 isGrpManager
= mtool
.checkPermission(ManageGroups
, portal
) ## TODO : CMF2.1 compat
175 aclu
= self
.aq_inner
.acl_users
178 g
= gtool
.getGroupById(gid
)
179 if not isGrpManager
:
180 if gid
!= DEFAULT_MEMBER_GROUP
:
181 raise AccessControl_Unauthorized
, 'You are not allowed to join arbitrary group.'
185 aclu
.changeUser(aclu
.getGroupPrefix() +gid
, roles
=['Member', ])
186 g
= gtool
.getGroupById(gid
)
190 def afterAdd(self
, member
, id, password
, properties
):
191 """ notify member creation """
192 member
.notifyWorkflowCreated()
196 security
.declarePublic('requestPasswordReset')
197 def requestPasswordReset(self
, userid
):
198 """ add uuid / (userid, expiration) pair and return uuid """
199 self
.clearExpiredPasswordResetRequests()
200 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
201 member
= mtool
.getMemberById(userid
)
204 self
._passwordResetRequests
[uuid
] = (userid
, DateTime() + 1)
205 utool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool')
206 ptool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IPropertiesTool')
207 # fuck : mailhost récupéré avec getUtilityByInterfaceName n'est pas correctement
208 # wrappé. Un « unrestrictedTraverse » ne marche pas.
209 # mailhost = getUtilityByInterfaceName('Products.MailHost.interfaces.IMailHost')
210 portal
= utool
.getPortalObject()
211 mailhost
= portal
.MailHost
212 sender
= encodeQuopriEmail(ptool
.getProperty('email_from_name'), ptool
.getProperty('email_from_address'))
213 to
= encodeQuopriEmail(member
.getMemberFullName(nameBefore
=0), member
.getProperty('email'))
214 subject
= "=?utf-8?q?%s?=" % encodestring('Password reset')
217 pr('%s/password_reset_form/%s' % (utool(), uuid
))
218 body
= '\n'.join(lines
)
219 message
= self
.echange_mail_template(From
=sender
,
222 ContentType
= 'text/plain',
225 mailhost
.send(message
)
227 security
.declarePrivate('clearExpiredPasswordResetRequests')
228 def clearExpiredPasswordResetRequests(self
):
230 for uuid
, record
in self
._passwordResetRequests
.items() :
231 userid
, date
= record
233 del self
._passwordResetRequests
[uuid
]
236 security
.declarePublic('resetPassword')
237 def resetPassword(self
, uuid
, password
, confirm
) :
238 record
= self
._passwordResetRequests
.get(uuid
)
240 return _('Invalid reset password request.')
242 userid
, expiration
= record
244 if expiration
< now
:
245 self
.clearExpiredPasswordResetRequests()
246 return _('Your reset password request has expired. You can ask a new one.')
248 msg
= self
.testPasswordValidity(password
, confirm
=confirm
)
249 if not msg
: # None if everything ok. Err message otherwise.
250 mtool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
251 member
= mtool
.getMemberById(userid
)
253 member
.setSecurityProfile(password
=password
)
254 del self
._passwordResetRequests
[uuid
]
255 return _('Password successfully resetted.')
257 return _('"%s" username not found.') % userid
260 InitializeClass(RegistrationTool
)