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