summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2008-09-21 00:44:37 +0000
committerChris McDonough <chrism@agendaless.com>2008-09-21 00:44:37 +0000
commit64ea2e288d7e6f47075269994319b9331dd09391 (patch)
tree54b43683deffe41d40e83e3a7a160c7559c857fe
parent3a99b193e9324c516227e2358bf41c2b6bc5a537 (diff)
downloadpyramid-64ea2e288d7e6f47075269994319b9331dd09391.tar.gz
pyramid-64ea2e288d7e6f47075269994319b9331dd09391.tar.bz2
pyramid-64ea2e288d7e6f47075269994319b9331dd09391.zip
- Add ``principals_allowed_by_permission`` API to security module.
-rw-r--r--CHANGES.txt4
-rw-r--r--docs/api/security.rst2
-rw-r--r--docs/tutorials/cmf/index.rst3
-rw-r--r--repoze/bfg/interfaces.py11
-rw-r--r--repoze/bfg/security.py50
-rw-r--r--repoze/bfg/tests/test_security.py48
6 files changed, 104 insertions, 14 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index a1ac46fd6..4378fba85 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -9,6 +9,10 @@ Next release
- Routes URL dispatch did not have access to the WSGI environment,
so conditions such as method=GET did not work.
+ Features
+
+ - Add ``principals_allowed_by_permission`` API to security module.
+
0.3.7 (09/09/2008)
Features
diff --git a/docs/api/security.rst b/docs/api/security.rst
index c50921100..58a405633 100644
--- a/docs/api/security.rst
+++ b/docs/api/security.rst
@@ -11,6 +11,8 @@
.. autofunction:: has_permission
+ .. autofunction:: principals_allowed_by_permission
+
.. attribute:: Everyone
The special principal id named 'Everyone'. This principal id is
diff --git a/docs/tutorials/cmf/index.rst b/docs/tutorials/cmf/index.rst
index 64e7f2ea3..76ba6ae0f 100644
--- a/docs/tutorials/cmf/index.rst
+++ b/docs/tutorials/cmf/index.rst
@@ -9,6 +9,8 @@ application to :mod:`repoze.bfg`.
Missing:
+ templates.rst
+ forms.rst
cataloging.rst
workflow.rst
skins.rst
@@ -18,7 +20,6 @@ Missing:
syndication.rst
dublincore.rst
-
.. toctree::
:maxdepth: 2
diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py
index 6c28c75a7..7647facb4 100644
--- a/repoze/bfg/interfaces.py
+++ b/repoze/bfg/interfaces.py
@@ -45,8 +45,8 @@ class ISecurityPolicy(Interface):
using authentication data """
def permits(context, request, permission):
""" Returns True if the combination of the authorization
- information in the context and the authentication data in
- the request allow the action implied by the permission """
+ information in the context and the authentication data in
+ the request allow the action implied by the permission """
def authenticated_userid(request):
""" Return the userid of the currently authenticated user or
@@ -57,6 +57,13 @@ class ISecurityPolicy(Interface):
This must include the userid of the currently authenticated
user if a user is currently authenticated."""
+ def principals_allowed_by_permission(context, permission):
+ """ Return a sequence of principal identifiers allowed by the
+ ``permission`` in the model implied by ``context``. This
+ method may not be supported by a given security policy
+ implementation, in which case, it should raise a
+ ``NotImplementedError`` exception."""
+
class NoAuthorizationInformation(Exception):
pass
diff --git a/repoze/bfg/security.py b/repoze/bfg/security.py
index 226cf8664..6c1f262f1 100644
--- a/repoze/bfg/security.py
+++ b/repoze/bfg/security.py
@@ -32,35 +32,48 @@ def has_permission(permission, context, request):
return policy.permits(context, request, permission)
def authenticated_userid(request):
- """ Return the userid of the currently authenticated user or None
- if there is no security policy in effect or there is no currently
- authenticated user """
+ """ Return the userid of the currently authenticated user or
+ ``None`` if there is no security policy in effect or there is no
+ currently authenticated user"""
policy = queryUtility(ISecurityPolicy)
if policy is None:
return None
return policy.authenticated_userid(request)
def effective_principals(request):
- """ Return the list of 'effective' principals for the request.
- This will include the userid of the currently authenticated user
- if a user is currently authenticated. If no security policy is in
- effect, this will return an empty sequence."""
+ """ Return the list of 'effective' principal identifiers for the
+ request. This will include the userid of the currently
+ authenticated user if a user is currently authenticated. If no
+ security policy is in effect, this will return an empty sequence."""
policy = queryUtility(ISecurityPolicy)
if policy is None:
return []
return policy.effective_principals(request)
+def principals_allowed_by_permission(context, permission):
+ """ Provided a context (a model object), and a permission (a
+ string or unicode object), return a sequence of principal ids that
+ possess the permission in the context. If no security policy is
+ in effect, this will return a sequence with the single value
+ representing ``Everyone`` (the special principal identifier
+ representing all principals). Note that even if a security policy
+ *is* in effect, some security policies may not implement the
+ required machinery for this function; those will cause a
+ ``NotImplementedError`` exception to be raised when this function
+ is invoked."""
+ policy = queryUtility(ISecurityPolicy)
+ if policy is None:
+ return [Everyone]
+ return policy.principals_allowed_by_permission(context, permission)
+
class ACLAuthorizer(object):
def __init__(self, context, logger=None):
self.context = context
self.logger = logger
- def get_acl(self, default=None):
- return getattr(self.context, '__acl__', default)
-
def permits(self, permission, *principals):
- acl = self.get_acl()
+ acl = getattr(self.context, '__acl__', None)
if acl is None:
raise NoAuthorizationInformation('%s item has no __acl__' % acl)
@@ -120,6 +133,21 @@ class ACLSecurityPolicy(object):
return effective_principals
+ def principals_allowed_by_permission(self, context, permission):
+ for location in LocationIterator(context):
+ acl = getattr(location, '__acl__', None)
+ if acl is not None:
+ allowed = {}
+ for ace in acl:
+ ace_action, ace_principal, ace_permissions = ace
+ if ace_action == Allow:
+ ace_permissions = flatten(ace_permissions)
+ for ace_permission in ace_permissions:
+ if ace_permission == permission:
+ allowed[ace_principal] = True
+ return sorted(allowed.keys())
+ return []
+
DEBUG_LOG_KEY = 'BFG_SECURITY_DEBUG'
def debug_logger(logger):
diff --git a/repoze/bfg/tests/test_security.py b/repoze/bfg/tests/test_security.py
index 9797ebce5..98750c728 100644
--- a/repoze/bfg/tests/test_security.py
+++ b/repoze/bfg/tests/test_security.py
@@ -304,6 +304,38 @@ class TestACLSecurityPolicy(unittest.TestCase, PlacelessSetup):
self.assertEqual(authorizer_factory.permission, 'view')
self.assertEqual(authorizer_factory.context, context)
+ def test_principals_allowed_by_permission_direct(self):
+ from repoze.bfg.security import Allow
+ context = DummyContext()
+ acl = [ (Allow, 'chrism', ('read', 'write')),
+ (Allow, 'other', ('read',)) ]
+ context.__acl__ = acl
+ logger = DummyLogger()
+ policy = self._makeOne(logger, lambda *arg: None)
+ result = policy.principals_allowed_by_permission(context, 'read')
+ self.assertEqual(result, ['chrism', 'other'])
+
+ def test_principals_allowed_by_permission_acquired(self):
+ from repoze.bfg.security import Allow
+ context = DummyContext()
+ acl = [ (Allow, 'chrism', ('read', 'write')),
+ (Allow, 'other', ('read',)) ]
+ context.__acl__ = acl
+ context.__parent__ = None
+ context.__name__ = 'context'
+ inter = DummyContext()
+ inter.__name__ = None
+ inter.__parent__ = context
+ logger = DummyLogger()
+ policy = self._makeOne(logger, lambda *arg: None)
+ result = policy.principals_allowed_by_permission(inter, 'read')
+ self.assertEqual(result, ['chrism', 'other'])
+
+ def test_principals_allowed_by_permission_no_acls(self):
+ logger = DummyLogger()
+ policy = self._makeOne(logger, lambda *arg: None)
+ result = policy.principals_allowed_by_permission(None, 'read')
+ self.assertEqual(result, [])
class TestRemoteUserACLSecurityPolicy(unittest.TestCase, PlacelessSetup):
def _getTargetClass(self):
@@ -434,6 +466,19 @@ class TestAPIFunctions(unittest.TestCase, PlacelessSetup):
request = DummyRequest({})
self.assertEqual(effective_principals(request), [])
+ def test_principals_allowed_by_permission_not_registered(self):
+ from repoze.bfg.security import principals_allowed_by_permission
+ from repoze.bfg.security import Everyone
+ self.assertEqual(principals_allowed_by_permission(None, None),
+ [Everyone])
+
+ def test_principals_allowed_by_permission_registered(self):
+ secpol = DummySecurityPolicy(False)
+ self._registerSecurityPolicy(secpol)
+ from repoze.bfg.security import principals_allowed_by_permission
+ self.assertEqual(principals_allowed_by_permission(None, None),
+ ['fred', 'bob'])
+
class TestViewPermission(unittest.TestCase):
def _getTargetClass(self):
from repoze.bfg.security import ViewPermission
@@ -491,6 +536,9 @@ class DummySecurityPolicy:
def effective_principals(self, request):
return ['fred', 'bob']
+ def principals_allowed_by_permission(self, context, permission):
+ return ['fred', 'bob']
+
class DummyLogger:
def __init__(self):
self.messages = []