--- a/Allura/allura/lib/security.py
+++ b/Allura/allura/lib/security.py
@@ -1,12 +1,17 @@
"""
This module provides the security predicates used in decorating various models.
"""
+import logging
from collections import defaultdict
from pylons import c, request
from webob import exc
from itertools import chain
from ming.utils import LazyProperty
+
+from allura.lib import utils
+
+log = logging.getLogger(__name__)
class Credentials(object):
'''
@@ -171,68 +176,51 @@
def reaching_ids_set(self):
return set(self.reaching_ids)
-def has_neighborhood_access(access_type, neighborhood, user=None):
- '''
- :param str access_type: permission name
- :param Neighborhood neighborhood:
- :param User user: a specific user, else the current user
- :returns: _another function_ which returns True if has access
- '''
+def has_access(obj, permission, user=None, project=None):
from allura import model as M
- def result(user=user):
- if user is None: user = c.user
- acl = neighborhood.acl[access_type]
- anon = M.User.anonymous()
- if not acl: return user != anon
- for u in acl:
- if u == anon._id or u == user._id: return True
- return False
- return result
-
-def has_project_access(access_type, project=None, user=None):
- '''
- :param str access_type: permission name
- :param Project project: a specific project, else the current project
- :param User user: a specific user, else the current user
- :returns: _another function_ which returns True if has access. Neighborhood admin access will always result in True
- '''
- def result(project=project, user=user):
- if project is None: project = c.project
- if user is None: user = c.user
- assert user, 'c.user should always be at least M.User.anonymous()'
- cred = Credentials.get()
- for proj in project.parent_iter():
- acl = set(proj.acl.get(access_type, []))
- if cred.user_has_any_role(user._id, project.root_project._id, acl): return True
- if has_neighborhood_access('admin', project.neighborhood, user)():
- return True
- return False
- return result
-
-def has_artifact_access(access_type, obj=None, user=None, app=None):
- '''
- Check for artifact- or application-level access
-
- :param str access_type: permission name
- :param Artifact obj: if None, application access is checked
- :param Project project: a specific project, else the current project
- :param Application app: a specific user, else the current user
- :returns: _another function_ which returns True if has access. Neighborhood admin access will always result in True
- '''
- def result(user=user, app=app):
- if user is None: user = c.user
- if app is None: app = c.app
- project_id = app.project.root_project._id
- assert user, 'c.user should always be at least M.User.anonymous()'
- cred = Credentials.get()
- acl = set(app.config.acl.get(access_type, []))
- if obj is not None:
- acl |= set(obj.acl.get(access_type, []))
- if cred.user_has_any_role(user._id, project_id, acl): return True
- if has_neighborhood_access('admin', app.project.neighborhood, user)():
- return True
- return False
- return result
+ @utils.memoize_on_request(
+ 'has_access', obj, permission,
+ include_func_in_key=False)
+ def predicate(obj=obj, user=user, project=project, roles=None):
+ if roles is None:
+ if user is None: user = c.user
+ assert user, 'c.user should always be at least M.User.anonymous()'
+ cred = Credentials.get()
+ if project is None:
+ if isinstance(obj, M.Neighborhood):
+ project = M.Project.query.get(
+ neighborhood_id=obj._id,
+ shortname='--init--')
+ elif isinstance(obj, M.Project):
+ project = obj.root_project
+ else:
+ if project is None: project = c.project.root_project
+ roles = cred.user_roles(user_id=user._id, project_id=project._id).reaching_ids
+ chainable_roles = []
+ for rid in roles:
+ for ace in obj.acl:
+ if M.ACE.match(ace, rid, permission):
+ if ace.access == M.ACE.ALLOW:
+ # access is allowed
+ # log.info('%s: True', txt)
+ return True
+ else:
+ # access is denied for this role
+ break
+ else:
+ # access neither allowed or denied, may chain to parent context
+ chainable_roles.append(rid)
+ parent = obj.parent_security_context()
+ if parent and chainable_roles:
+ result = has_access(parent, permission, user=user, project=project)(
+ roles=tuple(chainable_roles))
+ elif not isinstance(obj, M.Neighborhood):
+ result = has_access(project.neighborhood, 'admin', user=user)()
+ else:
+ result = False
+ # log.info('%s: %s', txt, result)
+ return result
+ return predicate
def require(predicate, message=None):
'''
@@ -255,6 +243,10 @@
else:
raise exc.HTTPUnauthorized()
+def require_access(obj, permission, **kwargs):
+ predicate = has_access(obj, permission, **kwargs)
+ return require(predicate, message='%s access required' % permission.capitalize())
+
def require_authenticated():
'''
:raises: HTTPUnauthorized if current user is anonymous
@@ -262,3 +254,17 @@
from allura import model as M
if c.user == M.User.anonymous():
raise exc.HTTPUnauthorized()
+
+def simple_grant(acl, role_id, permission):
+ from allura.model.types import ACE
+ for ace in acl:
+ if ace.role_id == role_id and ace.permission == permission: return
+ acl.append(ACE.allow(role_id, permission))
+
+def simple_revoke(acl, role_id, permission):
+ remove = []
+ for i, ace in enumerate(acl):
+ if ace.role_id == role_id and ace.permission == permission:
+ remove.append(i)
+ for i in reversed(remove):
+ acl.pop(i)