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 |