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 |