• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Products.CMFCore / 6246931310

20 Sep 2023 09:54AM UTC coverage: 86.008% (-0.3%) from 86.266%
6246931310

Pull #131

github

mauritsvanrees
gha: don't need setup-python on 27 as we use the 27 container.
Pull Request #131: Make decodeFolderFilter and encodeFolderFilter non-public.

2466 of 3689 branches covered (0.0%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 1 file covered. (100.0%)

17297 of 19289 relevant lines covered (89.67%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.98
/src/Products/CMFCore/MembershipTool.py
1
##############################################################################
2
#
3
# Copyright (c) 2001 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE.
11
#
12
##############################################################################
13
""" Basic membership tool.
1✔
14
"""
15

16
import logging
1✔
17
from warnings import warn
1✔
18

19
import six
1✔
20

21
from AccessControl.class_init import InitializeClass
1✔
22
from AccessControl.requestmethod import postonly
1✔
23
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
24
from AccessControl.SecurityManagement import getSecurityManager
1✔
25
from AccessControl.users import nobody
1✔
26
from Acquisition import aq_base
1✔
27
from Acquisition import aq_inner
1✔
28
from Acquisition import aq_parent
1✔
29
from App.Dialogs import MessageDialog
1✔
30
from App.special_dtml import DTMLFile
1✔
31
from OFS.Folder import Folder
1✔
32
from Persistence import PersistentMapping
1✔
33
from ZODB.POSException import ConflictError
1✔
34
from zope.component import getUtility
1✔
35
from zope.component import queryUtility
1✔
36
from zope.component.interfaces import IFactory
1✔
37
from zope.globalrequest import getRequest
1✔
38
from zope.interface import implementedBy
1✔
39
from zope.interface import implementer
1✔
40
from ZPublisher.BaseRequest import RequestContainer
1✔
41

42
from .exceptions import AccessControl_Unauthorized
1✔
43
from .exceptions import BadRequest
1✔
44
from .interfaces import ICookieCrumbler
1✔
45
from .interfaces import IMemberDataTool
1✔
46
from .interfaces import IMembershipTool
1✔
47
from .interfaces import IRegistrationTool
1✔
48
from .interfaces import ISiteRoot
1✔
49
from .interfaces import ITypesTool
1✔
50
from .permissions import AccessContentsInformation
1✔
51
from .permissions import ChangeLocalRoles
1✔
52
from .permissions import ListPortalMembers
1✔
53
from .permissions import ManagePortal
1✔
54
from .permissions import ManageUsers
1✔
55
from .permissions import SetOwnPassword
1✔
56
from .permissions import View
1✔
57
from .PortalFolder import PortalFolder
1✔
58
from .utils import Message as _
1✔
59
from .utils import UniqueObject
1✔
60
from .utils import _checkPermission
1✔
61
from .utils import _dtmldir
1✔
62
from .utils import registerToolInterface
1✔
63

64

65
logger = logging.getLogger('CMFCore.MembershipTool')
1✔
66

67

68
@implementer(IMembershipTool)
1✔
69
class MembershipTool(UniqueObject, Folder):
1✔
70

71
    """ This tool accesses member data through an acl_users object.
72

73
    It can be replaced with something that accesses member data in a
74
    different way.
75
    """
76

77
    id = 'portal_membership'
1✔
78
    meta_type = 'CMF Membership Tool'
1✔
79
    zmi_icon = 'fa fa-users'
1✔
80
    memberareaCreationFlag = 1
1✔
81
    _HOME_FOLDER_FACTORY_NAME = 'cmf.folder.home.bbb1'
1✔
82

83
    security = ClassSecurityInfo()
1✔
84

85
    manage_options = (
1✔
86
        ({'label': 'Configuration', 'action': 'manage_mapRoles'},
87
         {'label': 'Overview', 'action': 'manage_overview'}) +
88
        Folder.manage_options)
89

90
    #
91
    #   ZMI methods
92
    #
93
    security.declareProtected(ManagePortal,  # NOQA: flake8: D001
1✔
94
                              'manage_overview')
95
    manage_overview = DTMLFile('explainMembershipTool', _dtmldir)
1✔
96

97
    #
98
    #   'portal_membership' interface methods
99
    #
100
    security.declareProtected(ManagePortal,  # NOQA: flake8: D001
1✔
101
                              'manage_mapRoles')
102
    manage_mapRoles = DTMLFile('membershipRolemapping', _dtmldir)
1✔
103

104
    @security.protected(SetOwnPassword)
1✔
105
    @postonly
1✔
106
    def setPassword(self, password, domains=None, REQUEST=None):
1✔
107
        """Allows the authenticated member to set his/her own password.
108
        """
109
        if not self.isAnonymousUser():
×
110
            member = self.getAuthenticatedMember()
×
111
            rtool = queryUtility(IRegistrationTool)
×
112
            if rtool is not None:
×
113
                failMessage = rtool.testPasswordValidity(password)
×
114
                if failMessage is not None:
×
115
                    raise BadRequest(failMessage)
×
116
            member.setSecurityProfile(password=password, domains=domains)
×
117
        else:
118
            raise BadRequest('Not logged in.')
×
119

120
    @security.public
1✔
121
    def getAuthenticatedMember(self):
1✔
122
        """
123
        Returns the currently authenticated member object
124
        or the Anonymous User.  Never returns None.
125
        """
126
        u = getSecurityManager().getUser()
1✔
127
        if u is None:
1!
128
            u = nobody
×
129
        return self.wrapUser(u)
1✔
130

131
    @security.private
1✔
132
    def wrapUser(self, u, wrap_anon=0):
1✔
133
        """ Set up the correct acquisition wrappers for a user object.
134

135
        Provides an opportunity for a portal_memberdata tool to retrieve and
136
        store member data independently of the user object.
137
        """
138
        b = getattr(u, 'aq_base', None)
1✔
139
        if b is None:
1!
140
            # u isn't wrapped at all.  Wrap it in self.acl_users.
141
            b = u
×
142
            u = u.__of__(self.acl_users)
×
143
        if (b is nobody and not wrap_anon) or hasattr(b, 'getMemberId'):
1!
144
            # This user is either not recognized by acl_users or it is
145
            # already registered with something that implements the
146
            # member data tool at least partially.
147
            return u
×
148

149
        # Apply any role mapping if we have it
150
        if hasattr(self, 'role_map'):
1!
151
            for portal_role in self.role_map:
×
152
                if (self.role_map.get(portal_role) in u.roles and
×
153
                        portal_role not in u.roles):
154
                    u.roles.append(portal_role)
×
155

156
        mdtool = queryUtility(IMemberDataTool)
1✔
157
        if mdtool is not None:
1✔
158
            try:
1✔
159
                u = mdtool.wrapUser(u)
1✔
160
            except ConflictError:
1!
161
                raise
×
162
            except Exception:
1✔
163
                logger.exception('Error during wrapUser')
1✔
164
        return u
1✔
165

166
    @security.protected(ManagePortal)
1✔
167
    def getPortalRoles(self):
1✔
168
        """
169
        Return all local roles defined by the portal itself,
170
        which means roles that are useful and understood
171
        by the portal object
172
        """
173
        parent = self.aq_inner.aq_parent
1✔
174
        roles = list(parent.userdefined_roles())
1✔
175

176
        # This is *not* a local role in the portal but used by it
177
        roles.append('Manager')
1✔
178
        roles.append('Owner')
1✔
179

180
        return roles
1✔
181

182
    @security.protected(ManagePortal)
1✔
183
    @postonly
1✔
184
    def setRoleMapping(self, portal_role, userfolder_role, REQUEST=None):
1✔
185
        """
186
        set the mapping of roles between roles understood by
187
        the portal and roles coming from outside user sources
188
        """
189
        if not hasattr(self, 'role_map'):
×
190
            self.role_map = PersistentMapping()
×
191

192
        if len(userfolder_role) < 1:
×
193
            del self.role_map[portal_role]
×
194
        else:
195
            self.role_map[portal_role] = userfolder_role
×
196

197
        return MessageDialog(
×
198
               title='Mapping updated',
199
               message='The Role mappings have been updated',
200
               action='manage_mapRoles')
201

202
    @security.protected(ManagePortal)
1✔
203
    def getMappedRole(self, portal_role):
1✔
204
        """
205
        returns a role name if the portal role is mapped to
206
        something else or an empty string if it is not
207
        """
208
        if hasattr(self, 'role_map'):
×
209
            return self.role_map.get(portal_role, '')
×
210
        else:
211
            return ''
×
212

213
    @security.public
1✔
214
    def getMembersFolder(self):
1✔
215
        """ Get the members folder object.
216
        """
217
        parent = aq_parent(aq_inner(self))
1✔
218
        members_folder = getattr(parent, 'Members', None)
1✔
219
        if members_folder is None:
1!
220
            return None
×
221
        request_container = RequestContainer(REQUEST=getRequest())
1✔
222
        return members_folder.__of__(request_container)
1✔
223

224
    @security.protected(ManagePortal)
1✔
225
    def getMemberareaCreationFlag(self):
1✔
226
        """
227
        Returns the flag indicating whether the membership tool
228
        will create a member area if an authenticated user from
229
        an underlying user folder logs in first without going
230
        through the join process
231
        """
232
        return self.memberareaCreationFlag
1✔
233

234
    @security.protected(ManagePortal)
1✔
235
    def setMemberareaCreationFlag(self):
1✔
236
        """
237
        sets the flag indicating whether the membership tool
238
        will create a member area if an authenticated user from
239
        an underlying user folder logs in first without going
240
        through the join process
241
        """
242
        if not hasattr(self, 'memberareaCreationFlag'):
1!
243
            self.memberareaCreationFlag = 0
×
244

245
        if self.memberareaCreationFlag == 0:
1✔
246
            self.memberareaCreationFlag = 1
1✔
247
        else:
248
            self.memberareaCreationFlag = 0
1✔
249

250
        return MessageDialog(
1✔
251
               title='Member area creation flag changed',
252
               message='Member area creation flag has been updated',
253
               action='manage_mapRoles')
254

255
    @security.public
1✔
256
    def createMemberArea(self, member_id=''):
1✔
257
        """ Create a member area for 'member_id' or authenticated user.
258
        """
259
        if not self.getMemberareaCreationFlag():
1✔
260
            return None
1✔
261
        members = self.getMembersFolder()
1✔
262
        if members is None:
1!
263
            return None
×
264
        if self.isAnonymousUser():
1✔
265
            return None
1✔
266
        if member_id:
1!
267
            if not self.isMemberAccessAllowed(member_id):
1✔
268
                return None
1✔
269
            member = self.getMemberById(member_id)
1✔
270
            if member is None:
1!
271
                return None
×
272
        else:
273
            member = self.getAuthenticatedMember()
×
274
            member_id = member.getId()
×
275
        if hasattr(aq_base(members), member_id):
1!
276
            return None
×
277

278
        factory_name = self._HOME_FOLDER_FACTORY_NAME
1✔
279
        portal_type_name = 'Folder'
1✔
280
        ttool = queryUtility(ITypesTool)
1✔
281
        if ttool is not None:
1!
282
            portal_type = ttool.getTypeInfo('Home Folder')
×
283
            if portal_type is not None:
×
284
                factory_name = portal_type.factory
×
285
                portal_type_name = portal_type.getId()
×
286

287
        factory = getUtility(IFactory, factory_name)
1✔
288
        obj = factory(id=member_id)
1✔
289
        obj._setPortalTypeName(portal_type_name)
1✔
290
        members._setObject(member_id, obj)
1✔
291
        f = members._getOb(member_id)
1✔
292
        f.changeOwnership(member)
1✔
293
        return f
1✔
294

295
    security.declarePublic('createMemberarea')  # NOQA: flake8: D001
1✔
296
    createMemberarea = createMemberArea
1✔
297

298
    @security.protected(ManageUsers)
1✔
299
    @postonly
1✔
300
    def deleteMemberArea(self, member_id, REQUEST=None):
1✔
301
        """ Delete member area of member specified by member_id.
302
        """
303
        members = self.getMembersFolder()
1✔
304
        if not members:
1!
305
            return 0
×
306
        if hasattr(aq_base(members), member_id):
1!
307
            members.manage_delObjects(member_id)
1✔
308
            return 1
1✔
309
        else:
310
            return 0
×
311

312
    @security.public
1✔
313
    def isAnonymousUser(self):
1✔
314
        """
315
        Returns 1 if the user is not logged in.
316
        """
317
        u = getSecurityManager().getUser()
1✔
318
        if u is None or u.getUserName() == 'Anonymous User':
1✔
319
            return 1
1✔
320
        return 0
1✔
321

322
    @security.public
1✔
323
    def checkPermission(self, permissionName, object, subobjectName=None):
1✔
324
        """
325
        Checks whether the current user has the given permission on
326
        the given object or subobject.
327
        """
328
        if subobjectName is not None:
×
329
            object = getattr(object, subobjectName)
×
330
        return _checkPermission(permissionName, object)
×
331

332
    @security.protected(ManageUsers)
1✔
333
    def isMemberAccessAllowed(self, member_id):
1✔
334
        """Check if the authenticated user is this member or an user manager.
335
        """
336
        sm = getSecurityManager()
1✔
337
        user = sm.getUser()
1✔
338
        if user is None:
1!
339
            return False
×
340
        if member_id == user.getId():
1✔
341
            return True
1✔
342
        return sm.checkPermission(ManageUsers, self)
1✔
343

344
    @security.public
1✔
345
    def credentialsChanged(self, password, REQUEST=None):
1✔
346
        """
347
        Notifies the authentication mechanism that this user has changed
348
        passwords.  This can be used to update the authentication cookie.
349
        Note that this call should *not* cause any change at all to user
350
        databases.
351
        """
352
        if not self.isAnonymousUser():
×
353
            user = getSecurityManager().getUser()
×
354
            name = user.getUserName()
×
355
            # this really does need to be the user name, and not the user id,
356
            # because we're dealing with authentication credentials
357
            cctool = queryUtility(ICookieCrumbler)
×
358
            if cctool is not None:
×
359
                cctool.credentialsChanged(user, name, password, REQUEST)
×
360

361
    @security.protected(ManageUsers)
1✔
362
    def getMemberById(self, id):
1✔
363
        """
364
        Returns the given member.
365
        """
366
        user = self._huntUser(id, self)
1✔
367
        if user is not None:
1✔
368
            user = self.wrapUser(user)
1✔
369
        return user
1✔
370

371
    def _huntUserFolder(self, member_id, context):
1✔
372
        """Find userfolder containing user in the hierarchy
373
           starting from context
374
        """
375
        uf = context.acl_users
1✔
376
        while uf is not None:
1✔
377
            user = uf.getUserById(member_id)
1✔
378
            if user is not None:
1✔
379
                return uf
1✔
380
            container = aq_parent(aq_inner(uf))
1✔
381
            parent = aq_parent(aq_inner(container))
1✔
382
            uf = getattr(parent, 'acl_users', None)
1✔
383
        return None
1✔
384

385
    def _huntUser(self, member_id, context):
1✔
386
        """Find user in the hierarchy of userfolders
387
           starting from context
388
        """
389
        uf = self._huntUserFolder(member_id, context)
1✔
390
        if uf is not None:
1✔
391
            return uf.getUserById(member_id).__of__(uf)
1✔
392

393
    def __getPUS(self):
1✔
394
        """ Retrieve the nearest user folder
395
        """
396
        warn('__getPUS is deprecated and will be removed in CMF 2.4, '
×
397
             'please acquire "acl_users" instead.', DeprecationWarning,
398
             stacklevel=2)
399
        return self.acl_users
×
400

401
    @security.protected(ManageUsers)
1✔
402
    def listMemberIds(self):
1✔
403
        """Lists the ids of all members.  This may eventually be
404
        replaced with a set of methods for querying pieces of the
405
        list rather than the entire list at once.
406
        """
407
        user_folder = self.acl_users
1✔
408
        return [x.getId() for x in user_folder.getUsers()]
1✔
409

410
    @security.protected(ManageUsers)
1✔
411
    def listMembers(self):
1✔
412
        """Gets the list of all members.
413
        """
414
        return list(map(self.wrapUser, self.acl_users.getUsers()))
×
415

416
    @security.protected(ListPortalMembers)
1✔
417
    def searchMembers(self, search_param, search_term):
1✔
418
        """ Search the membership """
419
        mdtool = queryUtility(IMemberDataTool)
×
420
        if mdtool is not None:
×
421
            return mdtool.searchMemberData(search_param, search_term)
×
422
        return None
×
423

424
    @security.protected(View)
1✔
425
    def getCandidateLocalRoles(self, obj):
1✔
426
        """ What local roles can I assign?
427
        """
428
        member = self.getAuthenticatedMember()
1✔
429
        member_roles = member.getRolesInContext(obj)
1✔
430
        if _checkPermission(ManageUsers, obj):
1✔
431
            local_roles = self.getPortalRoles()
1✔
432
            if 'Manager' not in member_roles:
1!
433
                local_roles.remove('Manager')
×
434
        else:
435
            local_roles = [role for role in member_roles
1✔
436
                           if role not in ('Member', 'Authenticated')]
437
        return tuple(sorted(local_roles))
1✔
438

439
    @security.protected(View)
1✔
440
    @postonly
1✔
441
    def setLocalRoles(self, obj, member_ids, member_role, reindex=1,
1✔
442
                      REQUEST=None):
443
        """ Add local roles on an item.
444
        """
445
        if _checkPermission(ChangeLocalRoles, obj) and \
×
446
           member_role in self.getCandidateLocalRoles(obj):
447
            for member_id in member_ids:
×
448
                roles = list(obj.get_local_roles_for_userid(userid=member_id))
×
449

450
                if member_role not in roles:
×
451
                    roles.append(member_role)
×
452
                    obj.manage_setLocalRoles(member_id, roles)
×
453

454
        if reindex and hasattr(aq_base(obj), 'reindexObjectSecurity'):
×
455
            obj.reindexObjectSecurity()
×
456

457
    @security.protected(View)
1✔
458
    @postonly
1✔
459
    def deleteLocalRoles(self, obj, member_ids, reindex=1, recursive=0,
1✔
460
                         REQUEST=None):
461
        """ Delete local roles of specified members.
462
        """
463
        if _checkPermission(ChangeLocalRoles, obj):
1!
464
            for member_id in member_ids:
1✔
465
                if obj.get_local_roles_for_userid(userid=member_id):
1!
466
                    obj.manage_delLocalRoles(userids=member_ids)
×
467
                    break
×
468

469
        if recursive and hasattr(aq_base(obj), 'contentValues'):
1!
470
            for subobj in obj.contentValues():
×
471
                self.deleteLocalRoles(subobj, member_ids, 0, 1)
×
472

473
        if reindex and hasattr(aq_base(obj), 'reindexObjectSecurity'):
1!
474
            # reindexObjectSecurity is always recursive
475
            obj.reindexObjectSecurity()
1✔
476

477
    @security.private
1✔
478
    def addMember(self, id, password, roles, domains, properties=None):
1✔
479
        """Adds a new member to the user folder.  Security checks will have
480
        already been performed.  Called by portal_registration.
481
        """
482
        self.acl_users._doAddUser(id, password, roles, domains)
×
483

484
        if properties is not None:
×
485
            member = self.getMemberById(id)
×
486
            member.setMemberProperties(properties)
×
487

488
    @security.protected(ManageUsers)
1✔
489
    @postonly
1✔
490
    def deleteMembers(self, member_ids, delete_memberareas=1,
1✔
491
                      delete_localroles=1, REQUEST=None):
492
        """ Delete members specified by member_ids.
493
        """
494
        # Delete members in acl_users.
495
        acl_users = self.acl_users
1✔
496
        if _checkPermission(ManageUsers, acl_users):
1!
497
            if isinstance(member_ids, six.string_types):
1!
498
                member_ids = (member_ids,)
×
499
            member_ids = list(member_ids)
1✔
500
            for member_id in member_ids[:]:
1✔
501
                if not acl_users.getUserById(member_id, None):
1✔
502
                    member_ids.remove(member_id)
1✔
503
            try:
1✔
504
                acl_users.userFolderDelUsers(member_ids)
1✔
505
            except (AttributeError, NotImplementedError):
1✔
506
                raise NotImplementedError('The underlying User Folder '
507
                                          "doesn't support deleting members.")
508
        else:
509
            raise AccessControl_Unauthorized("You need the 'Manage users' "
×
510
                                             'permission for the underlying '
511
                                             'User Folder.')
512

513
        # Delete member data in portal_memberdata.
514
        mdtool = queryUtility(IMemberDataTool)
1✔
515
        if mdtool is not None:
1!
516
            for member_id in member_ids:
1✔
517
                mdtool.deleteMemberData(member_id)
1✔
518

519
        # Delete members' home folders including all content items.
520
        if delete_memberareas:
1!
521
            for member_id in member_ids:
1✔
522
                self.deleteMemberArea(member_id)
1✔
523

524
        # Delete members' local roles.
525
        if delete_localroles:
1!
526
            self.deleteLocalRoles(getUtility(ISiteRoot), member_ids,
1✔
527
                                  reindex=1, recursive=1)
528

529
        return tuple(member_ids)
1✔
530

531
    @security.public
1✔
532
    def getHomeFolder(self, id=None, verifyPermission=0):
1✔
533
        """Returns a member's home folder object or None.
534
        Set verifyPermission to 1 to return None when the user
535
        doesn't have the View permission on the folder.
536
        """
537
        return None
×
538

539
    @security.public
1✔
540
    def getHomeUrl(self, id=None, verifyPermission=0):
1✔
541
        """Returns the URL to a member's home folder or None.
542
        Set verifyPermission to 1 to return None when the user
543
        doesn't have the View permission on the folder.
544
        """
545
        return None
×
546

547

548
InitializeClass(MembershipTool)
1✔
549
registerToolInterface('portal_membership', IMembershipTool)
1✔
550

551

552
@implementer(IFactory)
1✔
553
class HomeFolderFactoryBase(object):
1✔
554

555
    """Creates a home folder.
556
    """
557

558
    title = _(u'Home Folder')
1✔
559
    description = _(u'A home folder for portal members.')
1✔
560

561
    def __call__(self, id, title=None, *args, **kw):
1✔
562
        if title is None:
1!
563
            title = "{0}'s Home".format(id)
1✔
564
        item = PortalFolder(id, title, *args, **kw)
1✔
565
        item.manage_setLocalRoles(id, ['Owner'])
1✔
566
        return item
1✔
567

568
    def getInterfaces(self):
1✔
569
        return implementedBy(PortalFolder)
×
570

571

572
class _BBBHomeFolderFactory(HomeFolderFactoryBase):
1✔
573

574
    """Creates a home folder.
575
    """
576

577
    description = _(u'Classic CMFCore home folder for portal members.')
1✔
578

579
    def __call__(self, id, title=None, *args, **kw):
1✔
580
        item = super(_BBBHomeFolderFactory,
1✔
581
                     self).__call__(id, title=title, *args, **kw)
582

583
        item.manage_permission(View,
1✔
584
                               ['Owner', 'Manager', 'Reviewer'], 0)
585
        item.manage_permission(AccessContentsInformation,
1✔
586
                               ['Owner', 'Manager', 'Reviewer'], 0)
587
        return item
1✔
588

589

590
BBBHomeFolderFactory = _BBBHomeFolderFactory()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc