Michael Merickel
2018-10-15 2b024920847481592b1a13d4006d2a9fa8881d72
commit | author | age
3b7334 1 from zope.interface import implementer
a1a9fb 2
b60bdb 3 from pyramid.interfaces import IAuthorizationPolicy
57d4ef 4
b60bdb 5 from pyramid.location import lineage
0c1c39 6
52ca12 7 from pyramid.compat import is_nonstr_iter
CM 8
0c1c39 9 from pyramid.security import (
CM 10     ACLAllowed,
11     ACLDenied,
12     Allow,
13     Deny,
14     Everyone,
15     )
a1a9fb 16
3b7334 17 @implementer(IAuthorizationPolicy)
a1a9fb 18 class ACLAuthorizationPolicy(object):
6f4da2 19     """ An :term:`authorization policy` which consults an :term:`ACL`
CM 20     object attached to a :term:`context` to determine authorization
c5f24b 21     information about a :term:`principal` or multiple principals.
8b1f6e 22     If the context is part of a :term:`lineage`, the context's parents
CM 23     are consulted for ACL information too.  The following is true
24     about this security policy.
a1a9fb 25
6f4da2 26     - When checking whether the 'current' user is permitted (via the
CM 27       ``permits`` method), the security policy consults the
28       ``context`` for an ACL first.  If no ACL exists on the context,
29       or one does exist but the ACL does not explicitly allow or deny
30       access for any of the effective principals, consult the
31       context's parent ACL, and so on, until the lineage is exhausted
32       or we determine that the policy permits or denies.
a1a9fb 33
c81aad 34       During this processing, if any :data:`pyramid.security.Deny`
714438 35       ACE is found matching any principal in ``principals``, stop
CM 36       processing by returning an
c81aad 37       :class:`pyramid.security.ACLDenied` instance (equals
714438 38       ``False``) immediately.  If any
c81aad 39       :data:`pyramid.security.Allow` ACE is found matching any
714438 40       principal, stop processing by returning an
c81aad 41       :class:`pyramid.security.ACLAllowed` instance (equals
714438 42       ``True``) immediately.  If we exhaust the context's
CM 43       :term:`lineage`, and no ACE has explicitly permitted or denied
44       access, return an instance of
c81aad 45       :class:`pyramid.security.ACLDenied` (equals ``False``).
a1a9fb 46
CM 47     - When computing principals allowed by a permission via the
c81aad 48       :func:`pyramid.security.principals_allowed_by_permission`
eaa4c5 49       method, we compute the set of principals that are explicitly
CM 50       granted the ``permission`` in the provided ``context``.  We do
51       this by walking 'up' the object graph *from the root* to the
52       context.  During this walking process, if we find an explicit
c81aad 53       :data:`pyramid.security.Allow` ACE for a principal that
714438 54       matches the ``permission``, the principal is included in the
CM 55       allow list.  However, if later in the walking process that
c81aad 56       principal is mentioned in any :data:`pyramid.security.Deny`
714438 57       ACE for the permission, the principal is removed from the allow
c81aad 58       list.  If a :data:`pyramid.security.Deny` to the principal
CM 59       :data:`pyramid.security.Everyone` is encountered during the
714438 60       walking process that matches the ``permission``, the allow list
CM 61       is cleared for all principals encountered in previous ACLs.  The
6f4da2 62       walking process ends after we've processed the any ACL directly
CM 63       attached to ``context``; a set of principals is returned.
d2973d 64
CM 65     Objects of this class implement the
66     :class:`pyramid.interfaces.IAuthorizationPolicy` interface.
a1a9fb 67     """
CM 68
69     def permits(self, context, principals, permission):
714438 70         """ Return an instance of
c81aad 71         :class:`pyramid.security.ACLAllowed` instance if the policy
714438 72         permits access, return an instance of
c81aad 73         :class:`pyramid.security.ACLDenied` if not."""
e0162e 74
3e2f12 75         acl = '<No ACL found on any object in resource lineage>'
25c64c 76
a1a9fb 77         for location in lineage(context):
CM 78             try:
79                 acl = location.__acl__
80             except AttributeError:
81                 continue
82
2d9314 83             if acl and callable(acl):
MM 84                 acl = acl()
85
a1a9fb 86             for ace in acl:
CM 87                 ace_action, ace_principal, ace_permissions = ace
88                 if ace_principal in principals:
52ca12 89                     if not is_nonstr_iter(ace_permissions):
a1a9fb 90                         ace_permissions = [ace_permissions]
CM 91                     if permission in ace_permissions:
92                         if ace_action == Allow:
93                             return ACLAllowed(ace, acl, permission,
94                                               principals, location)
95                         else:
96                             return ACLDenied(ace, acl, permission,
97                                              principals, location)
98
e0162e 99         # default deny (if no ACL in lineage at all, or if none of the
CM 100         # principals were mentioned in any ACE we found)
101         return ACLDenied(
102             '<default deny>',
103             acl,
104             permission,
105             principals,
106             context)
a1a9fb 107
CM 108     def principals_allowed_by_permission(self, context, permission):
109         """ Return the set of principals explicitly granted the
110         permission named ``permission`` according to the ACL directly
8b1f6e 111         attached to the ``context`` as well as inherited ACLs based on
714438 112         the :term:`lineage`."""
a1a9fb 113         allowed = set()
CM 114
115         for location in reversed(list(lineage(context))):
116             # NB: we're walking *up* the object graph from the root
117             try:
118                 acl = location.__acl__
119             except AttributeError:
120                 continue
121
122             allowed_here = set()
123             denied_here = set()
25c64c 124
678f49 125             if acl and callable(acl):
CM 126                 acl = acl()
127
a1a9fb 128             for ace_action, ace_principal, ace_permissions in acl:
52ca12 129                 if not is_nonstr_iter(ace_permissions):
a1a9fb 130                     ace_permissions = [ace_permissions]
729c91 131                 if (ace_action == Allow) and (permission in ace_permissions):
25c64c 132                     if ace_principal not in denied_here:
a1a9fb 133                         allowed_here.add(ace_principal)
729c91 134                 if (ace_action == Deny) and (permission in ace_permissions):
CM 135                         denied_here.add(ace_principal)
136                         if ace_principal == Everyone:
137                             # clear the entire allowed set, as we've hit a
138                             # deny of Everyone ala (Deny, Everyone, ALL)
139                             allowed = set()
140                             break
141                         elif ace_principal in allowed:
142                             allowed.remove(ace_principal)
a1a9fb 143
CM 144             allowed.update(allowed_here)
145
146         return allowed