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