|
a/Allura/allura/lib/security.py |
|
b/Allura/allura/lib/security.py |
1 |
"""
|
1 |
"""
|
2 |
This module provides the security predicates used in decorating various models.
|
2 |
This module provides the security predicates used in decorating various models.
|
3 |
"""
|
3 |
"""
|
|
|
4 |
from collections import defaultdict
|
|
|
5 |
|
4 |
from pylons import c, request
|
6 |
from pylons import c, request
|
5 |
from webob import exc
|
7 |
from webob import exc
|
|
|
8 |
from itertools import chain
|
|
|
9 |
from ming.utils import LazyProperty
|
|
|
10 |
|
|
|
11 |
class Credentials(object):
|
|
|
12 |
|
|
|
13 |
def __init__(self):
|
|
|
14 |
self.clear()
|
|
|
15 |
|
|
|
16 |
@classmethod
|
|
|
17 |
def get(cls):
|
|
|
18 |
try:
|
|
|
19 |
return request.environ['allura.credentials']
|
|
|
20 |
except:
|
|
|
21 |
return cls()
|
|
|
22 |
|
|
|
23 |
def clear(self):
|
|
|
24 |
self.users = {}
|
|
|
25 |
self.projects = {}
|
|
|
26 |
|
|
|
27 |
def project_roles(self, project_id):
|
|
|
28 |
from allura import model as M
|
|
|
29 |
roles = self.projects.get(project_id)
|
|
|
30 |
if roles is None:
|
|
|
31 |
roles = self.projects[project_id] = RoleCache(
|
|
|
32 |
self, M.ProjectRole.query.find(dict(project_id=project_id)))
|
|
|
33 |
return roles
|
|
|
34 |
|
|
|
35 |
def user_roles(self, user_id, project_id=None):
|
|
|
36 |
from allura import model as M
|
|
|
37 |
roles = self.users.get((user_id, project_id))
|
|
|
38 |
if roles is None:
|
|
|
39 |
if project_id is None:
|
|
|
40 |
if user_id is None:
|
|
|
41 |
q = []
|
|
|
42 |
else:
|
|
|
43 |
q = M.ProjectRole.query.find(dict(user_id=user_id))
|
|
|
44 |
else:
|
|
|
45 |
if user_id is None:
|
|
|
46 |
q = M.ProjectRole.query.find(
|
|
|
47 |
dict(project_id=project_id,name='*anonymous'))
|
|
|
48 |
else:
|
|
|
49 |
q0 = M.ProjectRole.query.find(
|
|
|
50 |
dict(project_id=project_id,
|
|
|
51 |
name={'$in':['*anonymous', '*authenticated']}))
|
|
|
52 |
q1 = M.ProjectRole.query.find(
|
|
|
53 |
dict(project_id=project_id, user_id=user_id))
|
|
|
54 |
q = chain(q0, q1)
|
|
|
55 |
self.users[user_id, project_id] = roles = RoleCache(self, q)
|
|
|
56 |
return roles
|
|
|
57 |
|
|
|
58 |
def user_has_any_role(self, user_id, project_id, role_ids):
|
|
|
59 |
user_roles = self.user_roles(user_id=user_id, project_id=project_id)
|
|
|
60 |
return bool(set(role_ids) & user_roles.reaching_ids_set)
|
|
|
61 |
|
|
|
62 |
def users_with_named_role(self, project_id, name):
|
|
|
63 |
roles = self.project_roles(project_id)
|
|
|
64 |
return RoleCache(self, roles.find(name=name)).users_that_reach
|
|
|
65 |
|
|
|
66 |
def userids_with_named_role(self, project_id, name):
|
|
|
67 |
roles = self.project_roles(project_id)
|
|
|
68 |
return RoleCache(self, roles.find(name=name)).userids_that_reach
|
|
|
69 |
|
|
|
70 |
class RoleCache(object):
|
|
|
71 |
|
|
|
72 |
def __init__(self, cred, q):
|
|
|
73 |
self.cred = cred
|
|
|
74 |
self.q = q
|
|
|
75 |
|
|
|
76 |
def find(self, **kw):
|
|
|
77 |
tests = kw.items()
|
|
|
78 |
def _iter():
|
|
|
79 |
for r in self:
|
|
|
80 |
for k,v in tests:
|
|
|
81 |
val = getattr(r, k)
|
|
|
82 |
if callable(v):
|
|
|
83 |
if not v(val): break
|
|
|
84 |
elif v != val: break
|
|
|
85 |
else:
|
|
|
86 |
yield r
|
|
|
87 |
return RoleCache(self.cred, _iter())
|
|
|
88 |
|
|
|
89 |
def get(self, **kw):
|
|
|
90 |
for x in self.find(**kw): return x
|
|
|
91 |
return None
|
|
|
92 |
|
|
|
93 |
def __iter__(self):
|
|
|
94 |
return self.index.itervalues()
|
|
|
95 |
|
|
|
96 |
def __len__(self):
|
|
|
97 |
return len(self.index)
|
|
|
98 |
|
|
|
99 |
@LazyProperty
|
|
|
100 |
def index(self):
|
|
|
101 |
return dict((r._id, r) for r in self.q)
|
|
|
102 |
|
|
|
103 |
@LazyProperty
|
|
|
104 |
def named(self):
|
|
|
105 |
return RoleCache(self.cred, (
|
|
|
106 |
r for r in self
|
|
|
107 |
if r.name and not r.name.startswith('*')))
|
|
|
108 |
|
|
|
109 |
@LazyProperty
|
|
|
110 |
def reverse_index(self):
|
|
|
111 |
rev_index = defaultdict(list)
|
|
|
112 |
for r in self:
|
|
|
113 |
for rr_id in r.roles:
|
|
|
114 |
rev_index[rr_id].append(r)
|
|
|
115 |
return rev_index
|
|
|
116 |
|
|
|
117 |
@LazyProperty
|
|
|
118 |
def roles_that_reach(self):
|
|
|
119 |
def _iter():
|
|
|
120 |
visited = set()
|
|
|
121 |
to_visit = list(self)
|
|
|
122 |
while to_visit:
|
|
|
123 |
r = to_visit.pop(0)
|
|
|
124 |
if r in visited: continue
|
|
|
125 |
visited.add(r)
|
|
|
126 |
yield r
|
|
|
127 |
pr_rindex = self.cred.project_roles(r.project_id).reverse_index
|
|
|
128 |
to_visit += pr_rindex[r._id]
|
|
|
129 |
return RoleCache(self.cred, _iter())
|
|
|
130 |
|
|
|
131 |
@LazyProperty
|
|
|
132 |
def users_that_reach(self):
|
|
|
133 |
return [
|
|
|
134 |
r.user for r in self.roles_that_reach if r.user_id is not None ]
|
|
|
135 |
|
|
|
136 |
@LazyProperty
|
|
|
137 |
def userids_that_reach(self):
|
|
|
138 |
return [
|
|
|
139 |
r.user_id for r in self.roles_that_reach if r.user_id is not None ]
|
|
|
140 |
|
|
|
141 |
@LazyProperty
|
|
|
142 |
def reaching_roles(self):
|
|
|
143 |
def _iter():
|
|
|
144 |
to_visit = self.index.items()
|
|
|
145 |
visited = set()
|
|
|
146 |
while to_visit:
|
|
|
147 |
(rid, role) = to_visit.pop()
|
|
|
148 |
if rid in visited: continue
|
|
|
149 |
yield role
|
|
|
150 |
pr_index = self.cred.project_roles(role.project_id).index
|
|
|
151 |
to_visit += [
|
|
|
152 |
(i, pr_index[i]) for i in pr_index[rid].roles ]
|
|
|
153 |
return RoleCache(self.cred, _iter())
|
|
|
154 |
|
|
|
155 |
@LazyProperty
|
|
|
156 |
def reaching_ids(self):
|
|
|
157 |
return [ r._id for r in self.reaching_roles ]
|
|
|
158 |
|
|
|
159 |
@LazyProperty
|
|
|
160 |
def reaching_ids_set(self):
|
|
|
161 |
return set(self.reaching_ids)
|
6 |
|
162 |
|
7 |
def has_neighborhood_access(access_type, neighborhood, user=None):
|
163 |
def has_neighborhood_access(access_type, neighborhood, user=None):
|
8 |
from allura import model as M
|
164 |
from allura import model as M
|
9 |
def result(user=user):
|
165 |
def result(user=user):
|
10 |
if user is None: user = c.user
|
166 |
if user is None: user = c.user
|
|
... |
|
... |
14 |
for u in acl:
|
170 |
for u in acl:
|
15 |
if u == anon._id or u == user._id: return True
|
171 |
if u == anon._id or u == user._id: return True
|
16 |
return False
|
172 |
return False
|
17 |
return result
|
173 |
return result
|
18 |
|
174 |
|
19 |
def has_project_access(access_type, project=None, user=None,
|
175 |
def has_project_access(access_type, project=None, user=None):
|
20 |
user_roles=None):
|
|
|
21 |
def result(project=project, user=user, user_roles=user_roles):
|
176 |
def result(project=project, user=user):
|
22 |
if project is None: project = c.project
|
177 |
if project is None: project = c.project
|
23 |
if user is None: user = c.user
|
178 |
if user is None: user = c.user
|
24 |
if not project.database_configured: return False
|
|
|
25 |
assert user, 'c.user should always be at least M.User.anonymous()'
|
179 |
assert user, 'c.user should always be at least M.User.anonymous()'
|
26 |
if user_roles is None:
|
180 |
cred = Credentials.get()
|
27 |
user_roles = set(r._id for r in user.role_iter())
|
|
|
28 |
for proj in project.parent_iter():
|
181 |
for proj in project.parent_iter():
|
29 |
acl = set(proj.acl.get(access_type, []))
|
182 |
acl = set(proj.acl.get(access_type, []))
|
30 |
if acl & user_roles: return True
|
183 |
if cred.user_has_any_role(user._id, project.root_project._id, acl): return True
|
31 |
if has_neighborhood_access('admin', project.neighborhood, user)():
|
184 |
if has_neighborhood_access('admin', project.neighborhood, user)():
|
32 |
return True
|
185 |
return True
|
33 |
return False
|
186 |
return False
|
34 |
return result
|
187 |
return result
|
35 |
|
188 |
|
36 |
def has_artifact_access(access_type, obj=None, user=None, app=None,
|
189 |
def has_artifact_access(access_type, obj=None, user=None, app=None):
|
37 |
user_roles=None):
|
|
|
38 |
def result(user=user, app=app, user_roles=user_roles):
|
190 |
def result(user=user, app=app):
|
39 |
if user is None: user = c.user
|
191 |
if user is None: user = c.user
|
40 |
if app is None: app = c.app
|
192 |
if app is None: app = c.app
|
|
|
193 |
project_id = app.project.root_project._id
|
41 |
assert user, 'c.user should always be at least M.User.anonymous()'
|
194 |
assert user, 'c.user should always be at least M.User.anonymous()'
|
42 |
if user_roles is None:
|
195 |
cred = Credentials.get()
|
43 |
user_roles = set(r._id for r in user.role_iter())
|
|
|
44 |
acl = set(app.config.acl.get(access_type, []))
|
196 |
acl = set(app.config.acl.get(access_type, []))
|
45 |
if obj is not None:
|
197 |
if obj is not None:
|
46 |
acl |= set(obj.acl.get(access_type, []))
|
198 |
acl |= set(obj.acl.get(access_type, []))
|
47 |
if acl & user_roles: return True
|
199 |
if cred.user_has_any_role(user._id, project_id, acl): return True
|
48 |
if has_neighborhood_access('admin', app.project.neighborhood, user)():
|
200 |
if has_neighborhood_access('admin', app.project.neighborhood, user)():
|
49 |
return True
|
201 |
return True
|
50 |
return False
|
202 |
return False
|
51 |
return result
|
203 |
return result
|
52 |
|
204 |
|
|
... |
|
... |
66 |
def require_authenticated():
|
218 |
def require_authenticated():
|
67 |
from allura import model as M
|
219 |
from allura import model as M
|
68 |
if c.user == M.User.anonymous():
|
220 |
if c.user == M.User.anonymous():
|
69 |
raise exc.HTTPUnauthorized()
|
221 |
raise exc.HTTPUnauthorized()
|
70 |
|
222 |
|
71 |
def roles_with_project_access(access_type, project=None):
|
|
|
72 |
'''Return all ProjectRoles' _ids who directly or transitively have the given access
|
|
|
73 |
'''
|
|
|
74 |
from allura import model as M
|
|
|
75 |
if project is None: project = c.project
|
|
|
76 |
# Direct roles
|
|
|
77 |
result = set(project.acl.get(access_type, []))
|
|
|
78 |
roles = M.ProjectRole.query.find(project_id=project._id).all()
|
|
|
79 |
# Compute roles who can reach the direct roles
|
|
|
80 |
found = True
|
|
|
81 |
while found:
|
|
|
82 |
found = False
|
|
|
83 |
new_roles = []
|
|
|
84 |
for r in roles:
|
|
|
85 |
if r in result: continue
|
|
|
86 |
for rr_id in r.roles:
|
|
|
87 |
if rr_id in result:
|
|
|
88 |
result.add(r._id)
|
|
|
89 |
found = True
|
|
|
90 |
break
|
|
|
91 |
else:
|
|
|
92 |
new_roles.append(r)
|
|
|
93 |
roles =new_roles
|
|
|
94 |
return M.ProjectRole.query.find(dict(_id={'$in':list(result)})).all()
|
|
|
95 |
|
|
|