root/CPS3/products/CPSDefault/trunk/MembershipTool.py

Revision 53332, 28.0 kB (checked in by madarche, 6 months ago)

Still increasing the reset request validity from 2h to 24h. 24h is a safe choice
and there's no need to be as strict on the validity slot as it used to be,
there's no security risk on that front.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 # (C) Copyright 2005-2009 Nuxeo SAS <http://nuxeo.com>
2 # Authors:
3 # M.-A. Darche <madarche@nuxeo.com>
4 # Florent Guillaume <fg@nuxeo.com>
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License version 2 as published
8 # by the Free Software Foundation.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 # 02111-1307, USA.
19 #
20 # $Id$
21
22 from logging import getLogger
23 from zLOG import LOG, INFO, DEBUG, WARNING, ERROR
24
25 import sys
26 import socket
27 import random
28 import sha
29
30 from urllib import urlencode
31 from time import time
32 from smtplib import SMTPException
33
34 from Globals import InitializeClass
35 from AccessControl import ClassSecurityInfo
36 from AccessControl import Unauthorized
37 from AccessControl.Permissions import manage_users as ManageUsers
38 from Acquisition import aq_inner, aq_parent
39
40 from Products.MailHost.MailHost import MailHostError
41 from Products.CMFCore.utils import getToolByName
42
43 from Products.CPSCore.CPSMembershipTool import CPSMembershipTool
44 from Products.CPSUtil.id import generatePassword
45 from Products.CPSUtil.mail import send_mail
46
47 LOG_KEY = 'CPSDefault.MembershipTool'
48
49
50 class MembershipTool(CPSMembershipTool):
51     """A MembershipTool with additional functionnalities over
52     the CPSCore MembershipTool.
53     """
54     meta_type = 'CPS Membership Tool'
55
56     _properties = CPSMembershipTool._properties + (
57         {'id': 'email_field', 'type': 'string', 'mode': 'w',
58          'label': 'Members directory email field'},
59         {'id': 'enable_password_reset', 'type': 'boolean',
60          'label': 'Password reset enabled', 'mode': 'w'},
61         {'id': 'reset_password_request_validity', 'type': 'int', 'mode': 'w',
62          'label': 'Password reset request validity (seconds)'},
63         {'id': 'enable_password_reminder', 'type': 'boolean',
64          'label': 'Enable sending password reminder', 'mode': 'w'},
65         )
66
67     email_field = 'email'
68     enable_password_reset = True
69     # 30 min is the strict minimum that should be set because greylisting
70     # techniques on SMTP servers may hold back emails for a certain amount of
71     # time. 24 hours is a safe choice.
72     reset_password_request_validity = 24*60*60 # 24 hours
73     enable_password_reminder = False
74
75     # defaults overloaded from base class
76     membersfolder_id = 'members'
77     memberfolder_portal_type = 'Workspace'
78     memberfolder_roles = ('Owner', 'WorkspaceManager')
79
80     security = ClassSecurityInfo()
81
82     #
83     # Members password handling
84     #
85
86     security.declarePublic( 'mailPassword' )
87     def mailPassword(self, who, REQUEST=None):
88         """Email a forgotten password to a member.
89
90         o Raise an exception if user ID is not found.
91         """
92         if not self.enable_password_reminder:
93             raise Unauthorized('Password reminder disabled')
94
95         usernames, email = self._getUsernamesAndEmailFor(who)
96         LOG(LOG_KEY, DEBUG, "usernames=%r, email=%r" % (usernames, email))
97
98         if email is None or not usernames:
99             raise ValueError('The username you entered could not be found.')
100
101         members = [{'login': id,
102                     'password':self.getMemberById(id).getPassword()}
103                    for id in usernames]
104
105         # Rather than have the template try to use the mailhost, we will
106         # render the message ourselves and send it from here (where we
107         # don't need to worry about 'UseMailHost' permissions).
108         mail_text = self.mail_password_template(self, REQUEST,
109                                                 email=email,
110                                                 members=members)
111
112         host = self.MailHost
113         host.send(mail_text)
114
115         return self.mail_password_response(self, REQUEST)
116
117
118     security.declarePublic('requestPasswordReset')
119     def requestPasswordReset(self, who, REQUEST=None):
120         """Generate a reset token for a password reset and send an email with
121         the reset token for confirmation.
122
123         This method can be called with a username or an email address.
124
125         Returns True if a request has been sent.
126         Returns False if the username or email cannot be found, or the
127         mail cannot be sent.
128
129         Note that the return value shouldn't condition what is displayed
130         to a user, as it would leak information about what users exist.
131         """
132         if REQUEST is not None:
133             raise Unauthorized("Not callable TTW")
134         LOG(LOG_KEY, DEBUG, "Request reset for %r" % who)
135
136         translation_service = getToolByName(self, 'translation_service', None)
137         if translation_service is None:
138             LOG(LOG_KEY, ERROR,
139                 "translation_service tool not found, could not proceed.")
140
141         portal = getToolByName(self, 'portal_url').getPortalObject()
142
143         portal_encoding = 'ISO-8859-15'
144         mail_from_address = portal.getProperty('email_from_address')
145         if mail_from_address is None:
146             LOG(LOG_KEY, WARNING,
147                 "The portal has no 'email_from_address' defined. "
148                 "Password reset will not be performed because the users "
149                 "have to trust who sends them the reset email.")
150             return False
151         portal_url = portal.absolute_url()
152
153         # XXX: Here we should setup a mean to prevent potential spam.
154         # For example all requests should be stored in a dictionary (to
155         # assert their uniqueness) and only be processed every hour so
156         # if someone is spammed she will received only 12 password reset
157         # confirmation messages a day.
158
159         usernames, email = self._getUsernamesAndEmailFor(who)
160         LOG(LOG_KEY, DEBUG, "usernames=%r, email=%r" % (usernames, email))
161         if email is None:
162             return False
163
164         # For getClientAddr() to return a meaningful value when CPS is behind
165         # a reverse proxy such as Apache, one must set "trusted-proxy"
166         # configuration directives in etc/zope.conf.
167         client_address = self.REQUEST.getClientAddr()
168         var_mappings = {'portal_url': portal_url,
169                         'email': email,
170                         }
171         # The translation is done using the language currently selected by the
172         # user on the portal.
173         subject = translation_service.translateDefault(
174             'email_password_reset_confirmation_subject',
175             mapping=var_mappings).encode(portal_encoding)
176         now = str(int(time()))
177         args = {
178             'w': who,
179             'd': now,
180             't': self._makeToken(who, now),
181             }
182         visit_url = ('%s/account_reset_password_form?%s'
183                      % (portal_url, urlencode(args)))
184         var_mappings = {'mail_from_address': mail_from_address,
185                         'email': email,
186                         'subject': subject,
187                         'visit_url': visit_url,
188                         'portal_url': portal_url,
189                         'client_address': client_address,
190                         }
191         # The translation is done using the language currently selected by the
192         # user on the portal.
193         body = translation_service.translateDefault(
194             'email_password_reset_confirmation_body',
195             mapping=var_mappings).encode(portal_encoding)
196         try:
197             send_mail(self, mto=email, mfrom=mail_from_address, subject=subject,
198                       body=body)
199         except Exception, e:
200             LOG(LOG_KEY, WARNING, "Error while sending reset email "
201                 "for %s (%s %s)" % (who, e.__class__.__name__,
202                                     str(e)))
203             return False
204         LOG(LOG_KEY, INFO, "Reset confirmation email sent to %s, "
205             "requesting IP was %s" % (email, client_address))
206         return True
207
208     security.declarePrivate('_makeToken')
209     def _makeToken(self, who, time):
210         """Make a cryptographic token.
211         """
212         hash = sha.new()
213         hash.update(self.getNonce())
214         hash.update(who)
215         hash.update(time)
216         return hash.hexdigest()
217
218     security.declarePublic('isPasswordResetRequestValid')
219     def isPasswordResetRequestValid(self, who, time_, token, REQUEST=None):
220         """Return whether a request for a password reset is valid or not.
221         """
222         if REQUEST is not None:
223             raise Unauthorized("Not callable TTW")
224         result = self._makeToken(who, time_)
225         ok = (token == result
226               and int(time_)
227                   + self.getProperty('reset_password_request_validity')
228               >= int(time()))
229         if not ok:
230             LOG(LOG_KEY, WARNING, "Invalid password reset request for %r"
231                 % who)
232         return ok
233
234     security.declarePublic('getUsernamesAndEmailFor')
235     def getUsernamesAndEmailFor(self, who, time, token, REQUEST=None):
236         """Return all the usernames, ie the accounts, that corresponds to the
237         given username or email address.
238
239         This method ensures that a user can only do such a request on from her
240         email.
241         """
242         if REQUEST is not None:
243             raise Unauthorized("Not callable TTW")
244         if not self.isPasswordResetRequestValid(who, time, token):
245             return ([], None)
246         usernames, email = self._getUsernamesAndEmailFor(who)
247         if not usernames:
248             LOG(LOG_KEY, INFO, "No usernames for %r" % who)
249         return (usernames, email)
250
251     def _getUsernamesAndEmailFor(self, who):
252         member = self.getMemberById(who)
253         if member is not None:
254             usernames = [who]
255             email = member.getProperty(self.email_field)
256         elif '@' in who:
257             email = who
258             usernames = self._getUsernamesFromEmail(email)
259         else:
260             usernames = email = None
261         if not usernames or not email:
262             return ([], None)
263         return (usernames, email)
264
265     def _getUsernamesFromEmail(self, email):
266         dir = getToolByName(self, 'portal_directories').members
267         return dir._searchEntries(**{self.email_field: [email]})
268
269     # XXX shouldn't be public at all
270     security.declarePublic('getEmailFromUsername')
271     def getEmailFromUsername(self, username, REQUEST=None):
272         """Looks up an email address via the members directory"""
273         if REQUEST is not None:
274             raise Unauthorized("Not callable TTW")
275         members = getToolByName(self, 'portal_directories', None).members
276         try:
277             member = members._getEntry(username, default=None)
278         except KeyError:
279             return None
280         if member:
281             return member.get(self.email_field)
282         return None
283
284     security.declarePublic('getFullnameFromId')
285     def getFullnameFromId(self, user_id, REQUEST=None):
286         """Return the member full name from id
287         """
288         if REQUEST is not None:
289             raise Unauthorized("Not callable TTW")
290         try:
291             aclu = getToolByName(self, 'acl_users')
292             dir = aclu._getUsersDirectory()
293             fullname = dir._getEntry(user_id)[dir.title_field]
294         except (AttributeError, KeyError):
295             fullname = user_id
296         return fullname
297
298     security.declarePublic('resetPassword')
299     def resetPassword(self, usernames, who, time, token, REQUEST=None):
300         """Reset the password of the users having the given usernames.
301
302         Usually this script is called with only one username but resetPassword
303         works for many users as well.
304
305         This methods returns the new randomly generated password,
306         or None if there was a problem.
307         """
308         if REQUEST is not None:
309             raise Unauthorized("Not callable TTW")
310         if not self.enable_password_reset:
311             raise Unauthorized("Password reset disabled")
312         if not usernames:
313             return None
314         ok_usernames, email = self.getUsernamesAndEmailFor(who, time, token)
315         for username in usernames:
316             if username not in ok_usernames:
317                 # Attempt to hack the usernames field
318                 LOG(LOG_KEY, WARNING, "resetPassword: attempted to use %r "
319                     "for %r" % (usernames, who))
320                 return None
321
322         LOG(LOG_KEY, INFO, "Resetting password for %r" % (usernames,))
323         random.seed()
324         password = generatePassword()
325         for username in usernames:
326             member = self.getMemberById(username)
327             if member is None:
328                 LOG(LOG_KEY, ERROR, "resetPassword: user %r not found"
329                     % username)
330                 continue
331             user = member.getUser()
332             aclu = getToolByName(self, 'acl_users')
333             roles = [r for r in user.getRoles()
334                      if r not in ['Anonymous', 'Authenticated']]
335             aclu._doChangeUser(username, password,
336                                roles, user.getDomains())
337         return password
338
339     #
340     # Member area creation
341     #
342
343     security.declarePrivate('_createMemberContentAsManager')
344     def _createMemberContentAsManager(self, member, member_id, member_folder):
345         """Create the content of the member area.
346
347         Executed with Manager privileges.
348
349         Additional actions done in CPSDefault:
350         - create personal calendar
351         """
352         # inherited method
353         CPSMembershipTool._createMemberContentAsManager(self, member,
354                                                         member_id, member_folder)
355         portal_cpscalendar = getToolByName(self, 'portal_cpscalendar', None)
356         if portal_cpscalendar is not None:
357             create_calendar = getattr(portal_cpscalendar, 'create_member_calendar', 1)
358             if create_calendar:
359                 try:
360                     portal_cpscalendar.createMemberCalendar(member_id)
361                 # If the Calendar portal types has been removed, we will
362                 # get a ValueError exception here.
363                 except ValueError:
364                     pass
365
366     security.declarePrivate('_notifyMemberAreaCreated')
367     def _notifyMemberAreaCreated(self, member, member_id, member_folder):
368         """Perform special actions after member content has been created
369
370         Additional actions done in CPSDefault:
371         - set the member area title using the user's title
372         """
373         # inherited method
374         CPSMembershipTool._notifyMemberAreaCreated(self, member,
375                                                    member_id, member_folder)
376         # get the user's title
377         members_directory = self.portal_directories.members
378         member_entry = members_directory._getEntry(member_id, default=None)
379         if member_entry is None:
380             return
381         member_title = member_entry.get(members_directory.title_field)
382         if not member_title:
383             member_title = member_id
384         # set the member area title, assuming user can edit it
385         doc = member_folder.getEditableContent()
386         doc.setTitle(member_title)
387
388
389     #
390     # Miscellaneous helper methods
391     #
392
393     security.declarePrivate('getNonce')
394     def getNonce(self):
395         """The nonce is a random string different for each instance of
396         CPSMembershipTool that is used to generate unique hash values.
397
398         This is a ZEO-safe method. If ZEO is used, all the ZEO clients
399         will share the same value.
400         """
401         try:
402             # Here we use a instance variable so that each ZEO instance
403             # will have the same value.
404             nonce = self._nonce
405         except AttributeError:
406             self._nonce = ''.join(random.sample('.:;_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
407                                                   random.randint(10, 15)))
408             nonce = self._nonce
409         return nonce
410
411     #
412     # Local roles management utilities
413     #
414
415     # XXX This has to be made more flexible using registries
416     security.declarePublic('getCPSCandidateLocalRoles')
417     def getCPSCandidateLocalRoles(self, obj):
418         """ Get relevant local roles according to the context.
419
420         Roles are already filtered using the base method  getCPSCandidateLocalRoles
421         method, now filter them according to the context.
422         """
423         # List local roles according to the context
424         cps_roles = CPSMembershipTool.getCPSCandidateLocalRoles(self, obj)
425         cps_roles.reverse()
426
427         # Filter them for CPS
428         # TODO: Find a a better way of doing the filtering
429         cps_roles = [x for x in cps_roles if x not in ('Owner', 'Member',
430                                                        'Reviewer', 'Manager',
431                                                        'Authenticated')]
432         # Filter roles by portal type using prefix
433         # TODO: relevant roles should be store in the portal_types tool
434         ptype_role_prefix = {
435             'Section': ('Section', 'Contributor'),
436             'Workspace': ('Workspace', 'Contributor'),
437             'Members Workspace': ('Workspace', 'Contributor'),
438             'Wiki': ('Contributor', 'Reader'),
439             'Calendar': ('Workspace',),
440             'CPSForum': ('Forum',),
441             'Chat': ('Chat',),
442             'CPS Calendar': ('Attendee',),
443             'Blog': ('BlogManager', 'BlogPoster', 'Contributor', 'Reader',
444                      'Section', 'Workspace'),
445             }
446         ptype = obj.portal_type
447         if ptype in ptype_role_prefix.keys():
448             contextual_roles = []
449             for role_prefix in ptype_role_prefix[ptype]:
450                 for cps_role in cps_roles:
451                     if cps_role.startswith(role_prefix):
452                         contextual_roles.append(cps_role)
453             cps_roles = contextual_roles
454
455         return cps_roles
456
457
458     security.declarePublic('getCPSLocalRoles')
459     def getCPSLocalRoles(self, obj, cps_roles=None):
460         """Get local roles dictionnary filtered using relevant roles in
461         context and tell if local roles are blocked using this dictionnary
462         """
463         dict_roles = self.getMergedLocalRolesWithPath(obj)
464         local_roles_blocked = 0
465
466         # get info about role blockings
467         anon_infos = dict_roles.get('group:role:Anonymous')
468         blocked_rpaths = []
469         if anon_infos is not None:
470             for role_info in anon_infos:
471                 if '-' in role_info['roles']:
472                     rpath = role_info['url']
473                     blocked_rpaths.append(rpath)
474
475         blocked_rpath = ''
476         if blocked_rpaths:
477             # consider latest blocking
478             blocked_rpaths.sort()
479             blocked_rpath = blocked_rpaths[-1]
480             # check if roles are blocked at current level
481             url_tool = getToolByName(self, 'portal_url')
482             local_rpath = url_tool.getRpath(obj)
483             if blocked_rpath == local_rpath:
484                 local_roles_blocked = 1
485
486         # filter blocked roles and roles not relevant in context
487         if cps_roles is None:
488             cps_roles = self.getCPSCandidateLocalRoles(obj)
489         for item, role_infos in dict_roles.items():
490             for role_info in role_infos:
491                 if blocked_rpath:
492                     rpath = role_info['url']
493                     # skip roles set STRICTLY above blocking ; roles set at the
494                     # blocking_rpath level have to be kept
495                     if rpath.find(blocked_rpath) == -1:
496                         role_info['roles'] = []
497                         continue
498                 roles = role_info['roles']
499                 role_info['roles'] = [r for r in roles if r in cps_roles]
500
501             # delete role info if no roles are left
502             dict_roles[item] = [x for x in dict_roles[item] if x['roles']]
503
504             # delete items that do not have any role to display
505             if not dict_roles[item]:
506                 del dict_roles[item]
507
508         return dict_roles, local_roles_blocked
509
510
511     def _addBlockedRolesForRender(self, here_roles, blocked_roles):
512         """Add info about blocked_roles in here_roles dict.
513
514         here_roles is as output by getCPSLocalRoles()
515         blocked_roles is as getMergedLocalRoles on the parent folder.
516
517         This is useful because getCPSLocalRolesRender will iterate on
518         here_roles.items().
519
520         >>> mtool = MembershipTool()
521         >>> from pprint import PrettyPrinter
522         >>> pretty_print=PrettyPrinter(width=72).pprint
523
524         >>> here_roles = {'agent1': [{'url': 'some_url', 'roles': ['Owner']}]}
525         >>> blocked_roles = {'agent1': ['Other'],
526         ...                  'agent2': ['Owner']}
527         >>>
528         >>> mtool._addBlockedRolesForRender(here_roles, blocked_roles)
529         >>> pretty_print(here_roles)
530         {'agent1': [{'url': 'some_url', 'roles': ['Owner']},
531                     {'roles': ['Other'], 'blocked': True}],
532          'agent2': [{'roles': ['Owner'], 'blocked': True}]}
533         """
534
535         for agent, roles in blocked_roles.items():
536             if agent not in here_roles:
537                 here_roles[agent] = []
538             here_roles[agent].append({'blocked': True, 'roles': roles})
539
540     security.declarePublic('getCPSLocalRolesRender')
541     def getCPSLocalRolesRender(self, obj, cps_roles, filtered_role=None,
542                                show_blocked_roles=False):
543         """Get dictionnaries that will be used by the template presenting local
544         roles.
545
546         Return 2 lists and 2 dictionnaries: sorted members, members dictionnary
547         with member ids as keys and a dictionnary describing their roles as
548         values, and the same for groups.
549
550         Also return information about local roles blocking.
551
552         If filtered_role is set to one of the relevant local roles, only
553         display users with given role (inherited or not), and their other roles
554         if they have some.
555         """
556         # XXX need to be broken in sub methods
557
558         # directories, used for users/groups rendering
559         dirtool = getToolByName(self, 'portal_directories')
560         mdir = dirtool.members
561         mdir_title_field = mdir.title_field
562         gdir = dirtool.groups
563         gdir_title_field = gdir.title_field
564         dict_roles, local_roles_blocked = self.getCPSLocalRoles(obj, cps_roles)
565
566         # Adding blocked roles to dict_roles.
567         if local_roles_blocked and show_blocked_roles:
568             parent = aq_parent(aq_inner(obj))
569             # GR: getCPSLocalRoles goes from bottom-most folder up
570             # calling it upstairs should not amount to computing things twice
571             blocked_roles = self.getMergedLocalRoles(parent)
572             self._addBlockedRolesForRender(dict_roles, blocked_roles)
573         utool = getToolByName(self, 'portal_url')
574         rpath = utool.getRpath(obj)
575
576         # fill members and groups dictionnaries
577         members = {}
578         groups = {}
579         for item, role_infos in dict_roles.items():
580             # fill info about each role for given item
581             here_roles = {}
582             inherited_roles = {}
583             has_roles = 0
584             has_local_roles = 0
585             # default info for each role to be presented
586             for role in cps_roles:
587                 here_roles[role] = {
588                     'here': 0,
589                     'inherited': 0,
590                     }
591             for role_info in role_infos:
592                 if role_info.get('blocked'):
593                     blocked_roles = role_info['roles']
594                     for role in blocked_roles:
595                         if role in cps_roles:
596                             here_roles[role]['blocked'] = 1
597                             has_roles = True # force display
598                     continue
599                 role_url = role_info['url']
600                 if role_url == rpath:
601                     here = 1
602                 else:
603                     here = 0
604                 # maybe skip inherited blocked roles
605                 if here or not local_roles_blocked:
606                     for role in role_info['roles']:
607                         # take filtering on roles into account
608                         if not filtered_role or role == filtered_role:
609                             has_roles = 1
610                         # fill info even if role is filtered
611                         if here:
612                             here_roles[role]['here'] = 1
613                             has_local_roles = 1
614                         else:
615                             here_roles[role]['inherited'] = 1
616                             if inherited_roles.get(role) is None:
617                                 inherited_roles[role] = [role_url]
618                             else:
619                                 inherited_roles[role].append(role_url)
620             # skip if all roles have been filtered
621             if not has_roles:
622                 continue
623             # fill members and groups rendering info (title, input name) + computed
624             # roles info
625             if item.startswith('user:'):
626                 member_id = item[len('user:'):]
627                 member_title = ''
628                 entry = mdir.getEntry(member_id, None)
629                 if entry is not None:
630                     member_title = entry.get(mdir_title_field)
631                 members[item] = {
632                     'title': member_title or member_id,
633                     'role_input_name': 'role_user_' + member_id,
634                     'here_roles': here_roles,
635                     'inherited_roles': inherited_roles,
636                     'has_local_roles': has_local_roles,
637                     }
638             elif item.startswith('group:'):
639                 group_id = item[len('group:'):]
640                 group_title = ''
641                 entry = gdir.getEntry(group_id, None)
642                 if entry is not None:
643                     group_title = entry.get(gdir_title_field)
644                 groups[item] = {
645                     'title': group_title or group_id,
646                     # XXX AT: no ':' accepted, change it for role:Anonymous and
647                     # role:Authenticated groups
648                     'role_input_name': 'role_group_' + group_id.replace(':', '_'),
649                     'here_roles': here_roles,
650                     'inherited_roles': inherited_roles,
651                     'has_local_roles': has_local_roles,
652                     }
653
654         # sort members and groups on title
655         sort = [(v.get(mdir_title_field), k) for k, v in members.items()]
656         sort.sort()
657         sorted_members = [x[1] for x in sort]
658         sort = [(v.get(gdir_title_field), k) for k, v in groups.items()]
659         sort.sort()
660         sorted_groups = [x[1] for x in sort]
661         return sorted_members, members, sorted_groups, groups, local_roles_blocked
662
663
664     security.declarePublic('blockLocalRoles')
665     def blockLocalRoles(self, obj):
666         """Block local roles acquisition on given object
667
668         Acquisition blocking is made adding the '-' role to the group of
669         anonymous users.
670         """
671         member = self.getAuthenticatedMember()
672         if not member.has_role('Manager'):
673             # Prevent user from losing local roles management rights: readd the
674             # current user as a XyzManager of the current workspace/section
675             # before blocking.
676             member_id = member.getUserName()
677             candidate_roles = self.getCPSCandidateLocalRoles(obj)
678             local_manager_roles = [x for x in candidate_roles
679                                    if x in self.roles_managing_local_roles
680                                    and x != 'Manager']
681             for r in local_manager_roles:
682                 self.setLocalRoles(obj, (member_id,), r, reindex=0)
683         # Block and reindex
684         self.setLocalGroupRoles(obj, ('role:Anonymous',), '-')
685
686
687     security.declarePublic('unblockLocalRoles')
688     def unblockLocalRoles(self, obj):
689         """Block local roles acquisition on given object
690
691         Acquisition blocking is made deleting the '-' role to the group of
692         anonymous users.
693         """
694         self.deleteLocalGroupRoles(obj, ('role:Anonymous',), '-')
695
696
697 InitializeClass(MembershipTool)
698
699
700 def addMembershipTool(dispatcher, **kw):
701     """Add a membership tool"""
702     mt = MembershipTool(**kw)
703     id = mt.getId()
704     container = dispatcher.Destination()
705     container._setObject(id, mt)
706     mt = container._getOb(id)
Note: See TracBrowser for help on using the browser.