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

zopefoundation / AccessControl / 3742027669

pending completion
3742027669

push

github

Michael Howitz
Drop support for Python 2.7 up to 3.6.

934 of 1458 branches covered (64.06%)

Branch coverage included in aggregate %.

100 of 105 new or added lines in 26 files covered. (95.24%)

4982 of 5839 relevant lines covered (85.32%)

4.26 hits per line

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

67.47
/src/AccessControl/ImplPython.py
1
##############################################################################
2
#
3
# Copyright (c) 2003 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
# isort:skip_file
14
"""Python implementation of the access control machinery."""
5✔
15

16
from AccessControl.ZopeSecurityPolicy import getRoles  # XXX
5✔
17
from AccessControl.unauthorized import Unauthorized
5✔
18
from AccessControl.SimpleObjectPolicies import _noroles
5✔
19
from AccessControl.SimpleObjectPolicies import Containers
5✔
20
from AccessControl.SecurityManagement import getSecurityManager
5✔
21
from AccessControl.Permission import getPermissionIdentifier
5✔
22
from AccessControl.interfaces import ISecurityPolicy
5✔
23
from AccessControl.interfaces import ISecurityManager
5✔
24
import os
5✔
25
from logging import getLogger
5✔
26

27
from Acquisition import aq_acquire
5✔
28
from Acquisition import aq_base
5✔
29
from Acquisition import aq_inContextOf
5✔
30
from Acquisition import aq_inner
5✔
31
from Acquisition import aq_parent
5✔
32
from ExtensionClass import Base
5✔
33
from zope.interface import implementer
5✔
34

35
PURE_PYTHON = int(os.environ.get('PURE_PYTHON', '0'))
5✔
36
if PURE_PYTHON:
5✔
37
    # We need our own to not depend on the C implementation:
38
    _what_not_even_god_should_do = []
5✔
39
else:
40
    from AccessControl.cAccessControl import _what_not_even_god_should_do
5✔
41

42
from AccessControl.ZopeGuards import guarded_getitem  # NOQA
5✔
43

44

45
# AccessControl.ZopeSecurityPolicy
46
# --------------------------------
47
#
48
#   TODO:  implement this in cAccessControl, and have Implementation
49
#          do the indirection.
50
#
51

52

53
LOG = getLogger('ImplPython')
5✔
54

55
# AccessControl.PermissionRole
56
# ----------------------------
57

58
_default_roles = ('Manager',)
5✔
59

60
# If _embed_permission_in_roles is enabled, computed __roles__
61
# attributes will often include a special role that encodes the name
62
# of the permission from which the roles were derived.  This is useful
63
# for verbose security exceptions.
64
_embed_permission_in_roles = 0
5✔
65

66

67
def rolesForPermissionOn(perm, object, default=_default_roles, n=None):
5✔
68
    """Return the roles that have the given permission on the given object
69
    """
70
    n = n or getPermissionIdentifier(perm)
5✔
71
    r = None
5✔
72

73
    while True:
3✔
74
        if hasattr(object, n):
5✔
75
            roles = getattr(object, n)
5✔
76
            if roles is None:
5✔
77
                if _embed_permission_in_roles:
5!
78
                    return (('Anonymous',), n)
×
79
                return ('Anonymous',)
5✔
80

81
            t = type(roles)
5✔
82
            if t is tuple:
5✔
83
                # If we get a tuple, then we don't acquire
84
                if r is None:
5✔
85
                    if _embed_permission_in_roles:
5!
86
                        return roles + (n,)
×
87
                    return roles
5✔
88
                if _embed_permission_in_roles:
5!
89
                    return r + list(roles) + [n]
×
90
                return r + list(roles)
5✔
91

92
            if t is str:
5!
93
                # We found roles set to a name.  Start over
94
                # with the new permission name.  If the permission
95
                # name is '', then treat as private!
96
                if roles:
×
97
                    n = roles
×
98
                else:
99
                    return _what_not_even_god_should_do
×
100

101
            elif roles:
5!
102
                if r is None:
5!
103
                    r = list(roles)
5✔
104
                else:
105
                    r = r + list(roles)
×
106

107
        object = aq_inner(object)
5✔
108
        if object is None:
5✔
109
            break
5✔
110
        object = aq_parent(object)
5✔
111

112
    if r is None:
5!
113
        if _embed_permission_in_roles:
5!
114
            if default:
×
115
                if isinstance(default, tuple):
×
116
                    return default + (n,)
×
117
                else:
118
                    return default + [n]
×
119
            else:
120
                return [n]
×
121
        return default
5✔
122

123
    if _embed_permission_in_roles:
×
124
        return r + [n]
×
125
    return r
×
126

127

128
class PermissionRole(Base):
5✔
129
    """Implement permission-based roles.
130

131
    Under normal circumstances, our __of__ method will be
132
    called with an unwrapped object.  The result will then be called
133
    with a wrapped object, if the original object was wrapped.
134
    To deal with this, we have to create an intermediate object.
135

136
    """
137

138
    def __init__(self, name, default=('Manager',)):
5✔
139
        self.__name__ = name
5✔
140
        self._p = getPermissionIdentifier(name)
5✔
141
        self._d = self.__roles__ = default
5✔
142

143
    def __of__(self, parent):
5✔
144
        r = imPermissionRole()
5✔
145
        r._p = self._p
5✔
146
        r._pa = parent
5✔
147
        r._d = self._d
5✔
148
        p = getattr(parent, 'aq_inner', None)
5✔
149
        if p is not None:
5✔
150
            return r.__of__(p)
5✔
151
        else:
152
            return r
5✔
153

154
    def rolesForPermissionOn(self, value):
5✔
155
        return rolesForPermissionOn(None, value, self._d, self._p)
5✔
156

157

158
class imPermissionRole(Base):
5✔
159
    """Implement permission-based roles"""
160

161
    def __of__(self, value):
5✔
162
        return rolesForPermissionOn(None, value, self._d, self._p)
5✔
163
    rolesForPermissionOn = __of__
5✔
164

165
    # The following methods are needed in the unlikely case that an unwrapped
166
    # object is accessed:
167
    def __getitem__(self, i):
5✔
168
        try:
5✔
169
            v = self._v
5✔
170
        except:  # noqa: E722 do not use bare 'except'
5✔
171
            v = self._v = self.__of__(self._pa)
5✔
172
            del self._pa
5✔
173

174
        return v[i]
5✔
175

176
    def __len__(self):
5✔
177
        try:
5✔
178
            v = self._v
5✔
179
        except:  # noqa: E722 do not use bare 'except'
5✔
180
            v = self._v = self.__of__(self._pa)
5✔
181
            del self._pa
5✔
182

183
        return len(v)
5✔
184

185

186
@implementer(ISecurityPolicy)
5✔
187
class ZopeSecurityPolicy:
4✔
188

189
    def __init__(self, ownerous=1, authenticated=1, verbose=0):
5✔
190
        """Create a Zope security policy.
191

192
        Optional arguments may be provided:
193

194
        ownerous -- Untrusted users can create code
195
                    (e.g. Python scripts or templates),
196
                    so check that code owners can access resources.
197
                    The argument must have a truth value.
198
                    The default is true.
199

200
        authenticated -- Allow access to resources based on the
201
                    privaledges of the authenticated user.
202
                    The argument must have a truth value.
203
                    The default is true.
204

205
                    This (somewhat experimental) option can be set
206
                    to false on sites that allow only public
207
                    (unauthenticated) access. An anticipated
208
                    scenario is a ZEO configuration in which some
209
                    clients allow only public access and other
210
                    clients allow full management.
211

212
        verbose --  Include debugging information in Unauthorized
213
                    exceptions.  Not suitable for public sites.
214
        """
215
        self._ownerous = ownerous
5✔
216
        self._authenticated = authenticated
5✔
217
        self._verbose = verbose
5✔
218

219
    def validate(self, accessed, container, name, value, context,
5✔
220
                 roles=_noroles, getattr=getattr, _noroles=_noroles,
221
                 valid_aq_=('aq_parent', 'aq_inner', 'aq_explicit')):
222

223
        ############################################################
224
        # Provide special rules for the acquisition attributes
225
        if isinstance(name, str):
5✔
226
            if name.startswith('aq_') and name not in valid_aq_:
5✔
227
                if self._verbose:
5!
228
                    raiseVerbose(
×
229
                        'aq_* names (other than %s) are not allowed'
230
                        % ', '.join(valid_aq_),
231
                        accessed,
232
                        container,
233
                        name,
234
                        value,
235
                        context,
236
                    )
237
                raise Unauthorized(name, value)
5✔
238

239
        containerbase = aq_base(container)
5✔
240
        accessedbase = aq_base(accessed)
5✔
241
        if accessedbase is accessed:
5✔
242
            # accessed is not a wrapper, so assume that the
243
            # value could not have been acquired.
244
            accessedbase = container
5✔
245

246
        ############################################################
247
        # If roles weren't passed in, we'll try to get them from the object
248

249
        if roles is _noroles:
5!
250
            roles = getRoles(container, name, value, _noroles)
5✔
251

252
        ############################################################
253
        # We still might not have any roles
254

255
        if roles is _noroles:
5✔
256

257
            ############################################################
258
            # We have an object without roles and we didn't get a list
259
            # of roles passed in. Presumably, the value is some simple
260
            # object like a string or a list.  We'll try to get roles
261
            # from its container.
262
            if container is None:
5!
263
                # Either container or a list of roles is required
264
                # for ZopeSecurityPolicy to know whether access is
265
                # allowable.
266
                if self._verbose:
×
267
                    raiseVerbose(
×
268
                        'No container provided',
269
                        accessed, container, name, value, context)
270
                raise Unauthorized(name, value)
×
271

272
            roles = getattr(container, '__roles__', roles)
5✔
273
            if roles is _noroles:
5✔
274
                if containerbase is container:
5✔
275
                    # Container is not wrapped.
276
                    if containerbase is not accessedbase:
5!
277
                        if self._verbose:
×
278
                            raiseVerbose(
×
279
                                'Unable to find __roles__ in the container '
280
                                'and the container is not wrapped',
281
                                accessed,
282
                                container,
283
                                name,
284
                                value,
285
                                context,
286
                            )
287
                        raise Unauthorized(name, value)
×
288
                else:
289
                    # Try to acquire roles
290
                    try:
5✔
291
                        roles = aq_acquire(container, '__roles__')
5✔
292
                    except AttributeError:
5✔
293
                        if containerbase is not accessedbase:
5!
294
                            if self._verbose:
×
295
                                raiseVerbose(
×
296
                                    'Unable to find or acquire __roles__ '
297
                                    'from the container',
298
                                    accessed, container, name, value, context)
299
                            raise Unauthorized(name, value)
×
300

301
            # We need to make sure that we are allowed to
302
            # get unprotected attributes from the container. We are
303
            # allowed for certain simple containers and if the
304
            # container says we can. Simple containers
305
            # may also impose name restrictions.
306
            p = Containers(type(container), None)
5✔
307
            if p is None:
5✔
308
                p = getattr(container,
5✔
309
                            '__allow_access_to_unprotected_subobjects__',
310
                            None)
311

312
            if p is not None:
5✔
313
                if not isinstance(p, (bool, int)):
5✔
314
                    if isinstance(p, dict):
5✔
315

316
                        if isinstance(name, str):
5!
317
                            p = p.get(name)
5✔
318
                        else:
319
                            p = 1
×
320
                    else:
321
                        p = p(name, value)
5✔
322

323
            if not p:
5✔
324
                if self._verbose:
5!
325
                    raiseVerbose(
×
326
                        'The container has no security assertions',
327
                        accessed,
328
                        container,
329
                        name,
330
                        value,
331
                        context,
332
                    )
333
                raise Unauthorized(name, value)
5✔
334

335
            if roles is _noroles:
5✔
336
                return 1
5✔
337

338
            # We are going to need a security-aware object to pass
339
            # to allowed(). We'll use the container.
340
            value = container
5✔
341

342
        # Short-circuit tests if we can:
343
        try:
5✔
344
            if roles is None or 'Anonymous' in roles:
5✔
345
                return 1
5✔
346
        except TypeError:
×
347
            # 'roles' isn't a sequence
348
            LOG.error(
×
349
                "'%r' passed as roles"
350
                " during validation of '%s' is not a sequence." % (
351
                    roles,
352
                    name,
353
                ),
354
            )
355
            raise
×
356

357
        # Check executable security
358
        stack = context.stack
5✔
359
        if stack:
5✔
360
            eo = stack[-1]
5✔
361

362
            # If the executable had an owner, can it execute?
363
            if self._ownerous:
5!
364
                owner = eo.getOwner()
5✔
365
                if (owner is not None) and not owner.allowed(value, roles):
5✔
366
                    # We don't want someone to acquire if they can't
367
                    # get an unacquired!
368
                    if self._verbose:
5!
369
                        if len(roles) < 1:
×
370
                            raiseVerbose(
×
371
                                "The object is marked as private",
372
                                accessed,
373
                                container,
374
                                name,
375
                                value,
376
                                context,
377
                            )
378
                        elif userHasRolesButNotInContext(owner, value, roles):
×
379
                            raiseVerbose(
×
380
                                "The owner of the executing script is defined "
381
                                "outside the context of the object being "
382
                                "accessed",
383
                                accessed,
384
                                container,
385
                                name,
386
                                value,
387
                                context,
388
                                required_roles=roles,
389
                                eo_owner=owner,
390
                                eo=eo,
391
                            )
392
                        else:
393
                            raiseVerbose(
×
394
                                "The owner of the executing script does not "
395
                                "have the required permission",
396
                                accessed,
397
                                container,
398
                                name,
399
                                value,
400
                                context,
401
                                required_roles=roles,
402
                                eo_owner=owner,
403
                                eo=eo,
404
                                eo_owner_roles=getUserRolesInContext(owner,
405
                                                                     value),
406
                            )
407
                    raise Unauthorized(name, value)
5✔
408

409
            # Proxy roles, which are a lot safer now.
410
            proxy_roles = getattr(eo, '_proxy_roles', None)
5✔
411
            if proxy_roles:
5✔
412
                # Verify that the owner actually can state the proxy role
413
                # in the context of the accessed item; users in subfolders
414
                # should not be able to use proxy roles to access items
415
                # above their subfolder!
416
                owner = eo.getWrappedOwner()
5✔
417

418
                if owner is not None:
5✔
419
                    if container is not containerbase:
5!
420
                        # Unwrapped objects don't need checking
421
                        if not owner._check_context(container):
5✔
422
                            # container is higher up than the owner,
423
                            # deny access
424
                            if self._verbose:
5!
425
                                raiseVerbose(
×
426
                                    "The owner of the executing script is "
427
                                    "defined outside the context of the "
428
                                    "object being accessed.  The script has "
429
                                    "proxy roles, but they do not apply in "
430
                                    "this context.",
431
                                    accessed, container, name, value, context,
432
                                    required_roles=roles, eo_owner=owner,
433
                                    eo=eo)
434
                            raise Unauthorized(name, value)
5✔
435

436
                for r in proxy_roles:
5✔
437
                    if r in roles:
5✔
438
                        return 1
5✔
439

440
                # Proxy roles actually limit access!
441
                if self._verbose:
5!
442
                    if len(roles) < 1:
×
443
                        raiseVerbose(
×
444
                            "The object is marked as private",
445
                            accessed, container, name, value, context)
446
                    else:
447
                        raiseVerbose(
×
448
                            "The proxy roles set on the executing script "
449
                            "do not allow access",
450
                            accessed, container, name, value, context,
451
                            eo=eo, eo_proxy_roles=proxy_roles,
452
                            required_roles=roles)
453
                raise Unauthorized(name, value)
5✔
454

455
        try:
5✔
456
            if self._authenticated and context.user.allowed(value, roles):
5✔
457
                return 1
5✔
458
        except AttributeError:
×
459
            pass
×
460

461
        if self._verbose:
5!
462
            if len(roles) < 1:
×
463
                raiseVerbose(
×
464
                    "The object is marked as private",
465
                    accessed, container, name, value, context)
466
            elif not self._authenticated:
×
467
                raiseVerbose(
×
468
                    "Authenticated access is not allowed by this "
469
                    "security policy",
470
                    accessed, container, name, value, context)
471
            elif userHasRolesButNotInContext(context.user, value, roles):
×
472
                raiseVerbose(
×
473
                    "Your user account is defined outside "
474
                    "the context of the object being accessed",
475
                    accessed, container, name, value, context,
476
                    required_roles=roles, user=context.user)
477
            else:
478
                raiseVerbose(
×
479
                    "Your user account does not "
480
                    "have the required permission",
481
                    accessed, container, name, value, context,
482
                    required_roles=roles, user=context.user,
483
                    user_roles=getUserRolesInContext(context.user, value))
484
        raise Unauthorized(name, value)
5✔
485

486
    def checkPermission(self, permission, object, context):
5✔
487
        roles = rolesForPermissionOn(permission, object)
5✔
488
        if isinstance(roles, str):
5!
489
            roles = [roles]
×
490

491
        # check executable owner and proxy roles
492
        stack = context.stack
5✔
493
        if stack:
5✔
494
            eo = stack[-1]
5✔
495
            # If the executable had an owner, can it execute?
496
            if self._ownerous:
5✔
497
                owner = eo.getOwner()
5✔
498
                if (owner is not None) and not owner.allowed(object, roles):
5✔
499
                    # We don't want someone to acquire if they can't
500
                    # get an unacquired!
501
                    return 0
5✔
502
            proxy_roles = getattr(eo, '_proxy_roles', None)
5✔
503
            if proxy_roles:
5✔
504
                # Verify that the owner actually can state the proxy role
505
                # in the context of the accessed item; users in subfolders
506
                # should not be able to use proxy roles to access items
507
                # above their subfolder!
508
                owner = eo.getWrappedOwner()
5✔
509
                if owner is not None:
5!
510
                    if object is not aq_base(object):
5!
511
                        if not owner._check_context(object):
5✔
512
                            # object is higher up than the owner,
513
                            # deny access
514
                            return 0
5✔
515

516
                for r in proxy_roles:
5✔
517
                    if r in roles:
5✔
518
                        return 1
5✔
519
                return 0
5✔
520

521
        return context.user.allowed(object, roles)
5✔
522

523
# AccessControl.SecurityManager
524
# -----------------------------
525

526

527
# There is no corresponding control in the C implementation of the
528
# access control machinery (cAccessControl.c); this should probably go
529
# away in a future version.  If you're concerned about the size of
530
# security stack, you probably have bigger problems.
531
#
532
try:
5✔
533
    max_stack_size = int(os.environ.get('Z_MAX_STACK_SIZE', '100'))
5✔
534
except:  # noqa: E722 do not use bare 'except'
×
535
    max_stack_size = 100
×
536

537

538
def setDefaultBehaviors(ownerous, authenticated, verbose):
5✔
539
    global _defaultPolicy
540
    global _embed_permission_in_roles
541
    _defaultPolicy = ZopeSecurityPolicy(
5✔
542
        ownerous=ownerous,
543
        authenticated=authenticated,
544
        verbose=verbose)
545
    _embed_permission_in_roles = verbose
5✔
546

547

548
setDefaultBehaviors(True, True, False)
5✔
549

550

551
@implementer(ISecurityManager)
5✔
552
class SecurityManager:
4✔
553
    """A security manager provides methods for checking access and managing
554
    executable context and policies
555
    """
556
    __allow_access_to_unprotected_subobjects__ = {
5✔
557
        'validate': 1,
558
        'checkPermission': 1,
559
        'getUser': 1,
560
        'calledByExecutable': 1,
561
    }
562

563
    def __init__(self, thread_id, context):
5✔
564
        self._thread_id = thread_id
5✔
565
        self._context = context
5✔
566
        self._policy = _defaultPolicy
5✔
567

568
    def validate(self,
5✔
569
                 accessed=None,
570
                 container=None,
571
                 name=None,
572
                 value=None,
573
                 roles=_noroles,
574
                 ):
575
        """Validate access.
576

577
        Arguments:
578

579
        accessed -- the object that was being accessed
580

581
        container -- the object the value was found in
582

583
        name -- The name used to access the value
584

585
        value -- The value retrieved though the access.
586

587
        roles -- The roles of the object if already known.
588

589
        The arguments may be provided as keyword arguments. Some of these
590
        arguments may be ommitted, however, the policy may reject access
591
        in some cases when arguments are ommitted.  It is best to provide
592
        all the values possible.
593
        """
594
        policy = self._policy
5✔
595
        if roles is _noroles:
5✔
596
            return policy.validate(accessed, container, name, value,
5✔
597
                                   self._context)
598
        else:
599
            return policy.validate(accessed, container, name, value,
5✔
600
                                   self._context, roles)
601

602
    def DTMLValidate(self,
5✔
603
                     accessed=None,
604
                     container=None,
605
                     name=None,
606
                     value=None,
607
                     md=None,
608
                     ):
609
        """Validate access.
610
        * THIS EXISTS FOR DTML COMPATIBILITY *
611

612
        Arguments:
613

614
        accessed -- the object that was being accessed
615

616
        container -- the object the value was found in
617

618
        name -- The name used to access the value
619

620
        value -- The value retrieved though the access.
621

622
        md -- multidict for DTML (ignored)
623

624
        The arguments may be provided as keyword arguments. Some of these
625
        arguments may be ommitted, however, the policy may reject access
626
        in some cases when arguments are ommitted.  It is best to provide
627
        all the values possible.
628

629
        """
630
        policy = self._policy
5✔
631
        return policy.validate(accessed, container, name, value, self._context)
5✔
632

633
    def checkPermission(self, permission, object):
5✔
634
        """Check whether the security context allows the given permission on
635
        the given object.
636

637
        Arguments:
638

639
        permission -- A permission name
640

641
        object -- The object being accessed according to the permission
642
        """
643
        policy = self._policy
5✔
644
        return policy.checkPermission(permission, object, self._context)
5✔
645

646
    def addContext(self, anExecutableObject,
5✔
647
                   getattr=getattr):
648
        """Add an ExecutableObject to the current security
649
        context. Optionally, add a new SecurityPolicy as well.
650
        """
651
        stack = self._context.stack
5✔
652
        if len(stack) > max_stack_size:
5!
653
            raise SystemError('Excessive recursion')
×
654
        stack.append(anExecutableObject)
5✔
655
        p = getattr(anExecutableObject, '_customSecurityPolicy', None)
5✔
656
        if p is not None:
5✔
657
            p = p()
5✔
658
        else:
659
            p = _defaultPolicy
5✔
660
        self._policy = p
5✔
661

662
    def removeContext(self, anExecutableObject):
5✔
663
        """Remove an ExecutableObject, and optionally, a
664
        SecurityPolicy, from the current security context.
665
        """
666
        stack = self._context.stack
5✔
667
        if not stack:
5!
668
            return
×
669
        top = stack[-1]
5✔
670
        if top is anExecutableObject:
5✔
671
            del stack[-1]
5✔
672
        else:
673
            indexes = range(len(stack))
5✔
674
            for i in reversed(indexes):
5!
675
                top = stack[i]
5✔
676
                if top is anExecutableObject:
5✔
677
                    del stack[i:]
5✔
678
                    break
5✔
679
            else:
680
                return
×
681

682
        if stack:
5✔
683
            top = stack[-1]
5✔
684
            p = getattr(top, '_customSecurityPolicy', None)
5✔
685
            if p is not None:
5✔
686
                p = p()
5✔
687
            else:
688
                p = _defaultPolicy
5✔
689
            self._policy = p
5✔
690
        else:
691
            self._policy = _defaultPolicy
5✔
692

693
    def getUser(self):
5✔
694
        """Get the current authenticated user"""
695
        return self._context.user
5✔
696

697
    def calledByExecutable(self):
5✔
698
        """Return a boolean value indicating if this context was called
699
        by an executable"""
700
        return len(self._context.stack)
5✔
701

702

703
# AccessControl.ZopeGuards
704
# ------------------------
705

706

707
def aq_validate(inst, object, name, v, validate):
5✔
708
    return validate(inst, object, name, v)
5✔
709

710

711
_marker = object()
5✔
712

713

714
def guarded_getattr(inst, name, default=_marker):
5✔
715
    """Retrieves an attribute, checking security in the process.
716

717
    Raises Unauthorized if the attribute is found but the user is
718
    not allowed to access the attribute.
719
    """
720
    if name[:1] == '_':
5✔
721
        raise Unauthorized(name)
5✔
722

723
    # Try to get the attribute normally so that unusual
724
    # exceptions are caught early.
725
    try:
5✔
726
        v = getattr(inst, name)
5✔
727
    except AttributeError:
5✔
728
        if default is not _marker:
5!
729
            return default
×
730
        raise
5✔
731

732
    try:
5✔
733
        container = v.__self__
5✔
734
    except AttributeError:
5✔
735
        container = aq_parent(aq_inner(v)) or inst
5✔
736

737
    assertion = Containers(type(container))
5✔
738

739
    if isinstance(assertion, dict):
5✔
740
        # We got a table that lets us reason about individual
741
        # attrs
742
        assertion = assertion.get(name)
5✔
743
        if assertion:
5✔
744
            # There's an entry, but it may be a function.
745
            if callable(assertion):
5✔
746
                return assertion(inst, name)
5✔
747

748
            # Nope, it's boolean
749
            return v
5✔
750
        raise Unauthorized(name)
5✔
751

752
    if assertion:
5✔
753
        if callable(assertion):
5✔
754
            factory = assertion(name, v)
5✔
755
            if callable(factory):
5✔
756
                return factory(inst, name)
5✔
757
            assert factory == 1
5✔
758
        else:
759
            assert assertion == 1
5✔
760
        return v
5✔
761

762
    # See if we can get the value doing a filtered acquire.
763
    # aq_acquire will either return the same value as held by
764
    # v or it will return an Unauthorized raised by validate.
765
    validate = getSecurityManager().validate
5✔
766
    aq_acquire(inst, name, aq_validate, validate)
5✔
767

768
    return v
5✔
769

770

771
# Helpers for verbose authorization exceptions
772
# --------------------------------------------
773

774

775
def item_repr(ob):
5✔
776
    """Generates a repr without angle brackets (to avoid HTML quoting)"""
777
    return repr(ob).replace('<', '(').replace('>', ')')
×
778

779

780
def simplifyRoles(roles):
5✔
781
    """Sorts and removes duplicates from a role list."""
782
    return sorted(set(roles))
×
783

784

785
def raiseVerbose(msg, accessed, container, name, value, context,
5✔
786
                 required_roles=None,
787
                 user_roles=None,
788
                 user=None,
789
                 eo=None,
790
                 eo_owner=None,
791
                 eo_owner_roles=None,
792
                 eo_proxy_roles=None,
793
                 ):
794
    """Raises an Unauthorized error with a verbose explanation."""
795

NEW
796
    s = '{}.  Access to {} of {}'.format(
×
797
        msg, repr(name), item_repr(container))
798
    if aq_base(container) is not aq_base(accessed):
×
799
        s += ', acquired through %s,' % item_repr(accessed)
×
800
    info = [s + ' denied.']
×
801

802
    if user is not None:
×
803
        try:
×
804
            ufolder = '/'.join(aq_parent(aq_inner(user)).getPhysicalPath())
×
805
        except:  # noqa: E722 do not use bare 'except'
×
806
            ufolder = '(unknown)'
×
NEW
807
        info.append('Your user account, {}, exists at {}.'.format(
×
808
            str(user), ufolder))
809

810
    if required_roles is not None:
×
811
        p = None
×
812
        required_roles = list(required_roles)
×
813
        for r in required_roles:
×
814
            if r.startswith('_') and r.endswith('_Permission'):
×
815
                p = r[1:]
×
816
                required_roles.remove(r)
×
817
                break
×
818
        sr = simplifyRoles(required_roles)
×
819
        if p:
×
820
            # got a permission name
821
            info.append('Access requires %s, '
×
822
                        'granted to the following roles: %s.' %
823
                        (p, sr))
824
        else:
825
            # permission name unknown
826
            info.append('Access requires one of the following roles: %s.'
×
827
                        % sr)
828

829
    if user_roles is not None:
×
830
        info.append(
×
831
            'Your roles in this context are %s.' % simplifyRoles(user_roles))
832

833
    if eo is not None:
×
834
        s = 'The executing script is %s' % item_repr(eo)
×
835
        if eo_proxy_roles is not None:
×
836
            s += ', with proxy roles: %s' % simplifyRoles(eo_proxy_roles)
×
837
        if eo_owner is not None:
×
838
            s += ', owned by %s' % repr(eo_owner)
×
839
        if eo_owner_roles is not None:
×
840
            s += ', who has the roles %s' % simplifyRoles(eo_owner_roles)
×
841
        info.append(s + '.')
×
842

843
    text = ' '.join(info)
×
844
    LOG.debug('Unauthorized: %s' % text)
×
845
    raise Unauthorized(text)
×
846

847

848
def getUserRolesInContext(user, context):
5✔
849
    """Returns user roles for a context."""
850
    if hasattr(aq_base(user), 'getRolesInContext'):
×
851
        return user.getRolesInContext(context)
×
852
    else:
853
        return ()
×
854

855

856
def userHasRolesButNotInContext(user, object, object_roles):
5✔
857
    '''Returns 1 if the user has any of the listed roles but
858
    is not defined in a context which is not an ancestor of object.
859
    '''
860
    if object_roles is None or 'Anonymous' in object_roles:
×
861
        return 0
×
862
    usr_roles = getUserRolesInContext(user, object)
×
863
    for role in object_roles:
×
864
        if role in usr_roles:
×
865
            # User has the roles.
866
            return (not verifyAcquisitionContext(
×
867
                user, object, object_roles))
868
    return 0
×
869

870

871
def verifyAcquisitionContext(user, object, object_roles=None):
5✔
872
    """Mimics the relevant section of User.allowed().
873

874
    Returns true if the object is in the context of the user's user folder.
875
    """
876
    ufolder = aq_parent(user)
5✔
877
    ucontext = aq_parent(ufolder)
5✔
878
    if ucontext is not None:
5!
879
        if object is None:
5!
880
            # This is a strange rule, though
881
            # it doesn't cause any security holes. SDH
882
            return 1
×
883
        if getattr(object, '__self__', None) is not None:
5!
884
            # This is a method.  Grab its self.
885
            object = object.__self__
×
886
        if not aq_inContextOf(object, ucontext, 1):
5!
887
            if 'Shared' in object_roles:
×
888
                # Old role setting. Waaa
889
                object_roles = user._shared_roles(object)
×
890
                if 'Anonymous' in object_roles:
×
891
                    return 1
×
892
            return None
×
893
    # Note that if the user were not wrapped, it would
894
    # not be possible to determine the user's context
895
    # and this method would return 1.
896
    # However, as long as user folders always return
897
    # wrapped user objects, this is safe.
898
    return 1
5✔
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

© 2026 Coveralls, Inc