From 64ea2e288d7e6f47075269994319b9331dd09391 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Sep 2008 00:44:37 +0000 Subject: - Add ``principals_allowed_by_permission`` API to security module. --- CHANGES.txt | 4 ++++ docs/api/security.rst | 2 ++ docs/tutorials/cmf/index.rst | 3 ++- repoze/bfg/interfaces.py | 11 +++++++-- repoze/bfg/security.py | 50 ++++++++++++++++++++++++++++++--------- repoze/bfg/tests/test_security.py | 48 +++++++++++++++++++++++++++++++++++++ 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 = [] -- cgit v1.2.3