Switch to side-by-side view

--- a/Allura/allura/lib/security.py
+++ b/Allura/allura/lib/security.py
@@ -297,6 +297,71 @@
         return result
     return TruthyCallable(predicate)
 
+def all_allowed(obj, user_or_role=None, project=None):
+    '''
+    List all the permission names that a given user or named role
+    is allowed for a given object.  This list reflects the permissions
+    for which has_access() would return True for the user (or a user
+    in the given named role, e.g. Developer).
+
+    Example:
+
+        Given a tracker with the following ACL (pseudo-code):
+            [
+                ACE.allow(ProjectRole.by_name('Developer'), 'create'),
+                ACE.allow(ProjectRole.by_name('Member'), 'post'),
+                ACE.allow(ProjectRole.by_name('*anonymous'), 'read'),
+            ]
+
+        And user1 is in the Member group, then all_allowed(tracker, user1)
+        will return:
+
+            set(['post', 'read'])
+
+        And all_allowed(tracker, ProjectRole.by_name('Developer')) will return:
+
+            set(['create', 'post', 'read'])
+    '''
+    from allura import model as M
+    anon = M.ProjectRole.anonymous(project)
+    auth = M.ProjectRole.authenticated(project)
+    if user_or_role is None:
+        user_or_role = c.user
+    if user_or_role is None:
+        user_or_role = anon
+    if isinstance(user_or_role, M.User):
+        user_or_role = M.ProjectRole.by_user(user_or_role, project)
+        if user_or_role is None:
+            user_or_role = auth  # user is not member of project, treat as auth
+    roles = [user_or_role]
+    if user_or_role == anon:
+        pass  # anon inherits nothing
+    elif user_or_role == auth:
+        roles += [anon]  # auth inherits from anon
+    else:
+        roles += [auth, anon]  # named group or user inherits from auth + anon
+    role_ids = RoleCache(Credentials.get(), roles).reaching_ids  # match rules applicable to us
+    perms = set()
+    denied = defaultdict(set)
+    while obj:  # traverse parent contexts
+        for role_id in role_ids:
+            for ace in obj.acl:
+                if ace.permission in denied[role_id]:
+                    # don't consider permissions that were denied for this role
+                    continue
+                if M.ACE.match(ace, role_id, ace.permission):
+                    if ace.access == M.ACE.ALLOW:
+                        perms.add(ace.permission)
+                    else:
+                        # explicit DENY overrides any ALLOW for this permission
+                        # for this role_id in this ACL or parent(s) (but an ALLOW
+                        # for a different role could still grant this permission)
+                        denied[role_id].add(ace.permission)
+        obj = obj.parent_security_context()
+    if M.ALL_PERMISSIONS in perms:
+        return set([M.ALL_PERMISSIONS])
+    return perms
+
 def require(predicate, message=None):
     '''
     Example: require(has_access(c.app, 'read'))