Switch to unified view

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