summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2008-11-02 17:27:33 +0000
committerChris McDonough <chrism@agendaless.com>2008-11-02 17:27:33 +0000
commit17ce5747ea36df10ec78e0af7140b55f691f5016 (patch)
tree10c3a5ca6b460c59ecd72d29a4e2db587ce550e8
parent2fc5d11826931435cfb42e2f334391c783f31f1d (diff)
downloadpyramid-17ce5747ea36df10ec78e0af7140b55f691f5016.tar.gz
pyramid-17ce5747ea36df10ec78e0af7140b55f691f5016.tar.bz2
pyramid-17ce5747ea36df10ec78e0af7140b55f691f5016.zip
Features
- The ``BFG_DEBUG_AUTHORIZATION`` envvar and the ``debug_authorization`` config file value now only imply debugging of view-invoked security checks. Previously, information was printed for every call to ``has_permission`` as well, which made output confusing. To debug ``has_permission`` checks and other manual permission checks, use the debugger and print statements in your own code. - Authorization debugging info is now only present in the HTTP response body oif ``debug_authorization`` is true. - The format of authorization debug messages was improved. - A new ``BFG_DEBUG_NOTFOUND`` envvar was added and a symmetric ``debug_notfound`` config file value was added. When either is true, and a NotFound response is returned by the BFG router (because a view could not be found), debugging information is printed to stderr. When this value is set true, the body of HTTPNotFound responses will also contain the same debugging information. - ``Allowed`` and ``Denied`` responses from the security machinery are now specialized into two types: ACL types, and non-ACL types. The ACL-related responses are instances of ``repoze.bfg.security.ACLAllowed`` and ``repoze.bfg.security.ACLDenied``. The non-ACL-related responses are ``repoze.bfg.security.Allowed`` and ``repoze.bfg.security.Denied``. The allowed-type responses continue to evaluate equal to things that themselves evaluate equal to the ``True`` boolean, while the denied-type responses continue to evaluate equal to things that themselves evaluate equal to the ``False`` boolean. The only difference between the two types is the information attached to them for debugging purposes. - Added a new ``BFG_DEBUG_ALL`` envvar and a symmetric ``debug_all`` config file value. When either is true, all other debug-related flags are set true unconditionally (e.g. ``debug_notfound`` and ``debug_authorization``). Documentation - Added info about debug flag changes. - Added a section to the security chapter named "Debugging Imperative Authorization Failures" (for e.g. ``has_permssion``).
-rw-r--r--CHANGES.txt47
-rw-r--r--docs/api/security.rst7
-rw-r--r--docs/narr/environment.rst18
-rw-r--r--docs/narr/security.rst32
-rw-r--r--docs/narr/traversal.rst16
-rw-r--r--repoze/bfg/registry.py11
-rw-r--r--repoze/bfg/router.py44
-rw-r--r--repoze/bfg/security.py119
-rw-r--r--repoze/bfg/tests/test_registry.py38
-rw-r--r--repoze/bfg/tests/test_router.py165
-rw-r--r--repoze/bfg/tests/test_security.py205
-rw-r--r--repoze/bfg/tests/test_view.py64
-rw-r--r--repoze/bfg/view.py34
13 files changed, 628 insertions, 172 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 50e96a2ba..376040aa0 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -8,6 +8,53 @@ Next release
it as necessary rather than inventing their own logger, for
convenience.
+ - The ``BFG_DEBUG_AUTHORIZATION`` envvar and the
+ ``debug_authorization`` config file value now only imply debugging
+ of view-invoked security checks. Previously, information was
+ printed for every call to ``has_permission`` as well, which made
+ output confusing. To debug ``has_permission`` checks and other
+ manual permission checks, use the debugger and print statements in
+ your own code.
+
+ - Authorization debugging info is now only present in the HTTP
+ response body oif ``debug_authorization`` is true.
+
+ - The format of authorization debug messages was improved.
+
+ - A new ``BFG_DEBUG_NOTFOUND`` envvar was added and a symmetric
+ ``debug_notfound`` config file value was added. When either is
+ true, and a NotFound response is returned by the BFG router
+ (because a view could not be found), debugging information is
+ printed to stderr. When this value is set true, the body of
+ HTTPNotFound responses will also contain the same debugging
+ information.
+
+ - ``Allowed`` and ``Denied`` responses from the security machinery
+ are now specialized into two types: ACL types, and non-ACL types.
+ The ACL-related responses are instances of
+ ``repoze.bfg.security.ACLAllowed`` and
+ ``repoze.bfg.security.ACLDenied``. The non-ACL-related responses
+ are ``repoze.bfg.security.Allowed`` and
+ ``repoze.bfg.security.Denied``. The allowed-type responses
+ continue to evaluate equal to things that themselves evaluate
+ equal to the ``True`` boolean, while the denied-type responses
+ continue to evaluate equal to things that themselves evaluate
+ equal to the ``False`` boolean. The only difference between the
+ two types is the information attached to them for debugging
+ purposes.
+
+ - Added a new ``BFG_DEBUG_ALL`` envvar and a symmetric ``debug_all``
+ config file value. When either is true, all other debug-related
+ flags are set true unconditionally (e.g. ``debug_notfound`` and
+ ``debug_authorization``).
+
+ Documentation
+
+ - Added info about debug flag changes.
+
+ - Added a section to the security chapter named "Debugging
+ Imperative Authorization Failures" (for e.g. ``has_permssion``).
+
Bug Fixes
- Change default paster template generator to use ``Paste#http``
diff --git a/docs/api/security.rst b/docs/api/security.rst
index 58a405633..c7088656e 100644
--- a/docs/api/security.rst
+++ b/docs/api/security.rst
@@ -38,9 +38,14 @@
'george', 'read')`` that means deny access. A sequence of ACEs
makes up an ACL. It is a string, and it's actual value is "Deny".
+ .. autoclass:: ACLDenied
+ :members:
+
+ .. autoclass:: ACLAllowed
+ :members:
+
.. autoclass:: Denied
:members:
.. autoclass:: Allowed
:members:
-
diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst
index 9ace43048..3fe3457c0 100644
--- a/docs/narr/environment.rst
+++ b/docs/narr/environment.rst
@@ -27,11 +27,18 @@ application-specific configuration settings.
| | | See also: |
| | | :ref:`reload_templates_section` |
+-----------------------------+--------------------------+-------------------------------------+
-| ``BFG_DEBUG_AUTHORIZATION`` | ``debug_authorization`` | Print authorization failure/success|
-| | | messages to stderr when true |
+| ``BFG_DEBUG_AUTHORIZATION`` | ``debug_authorization`` | Print view authorization failure & |
+| | | success info to stderr when true |
| | | See also: |
| | | :ref:`debug_authorization_section` |
+-----------------------------+--------------------------+-------------------------------------+
+| ``BFG_DEBUG_NOTFOUND`` | ``debug_notfound`` | Print view-related NotFound debug |
+| | | messages to stderr when true |
+| | | See also: |
+| | | :ref:`debug_notfound_section` |
++-----------------------------+--------------------------+-------------------------------------+
+| ``BFG_DEBUG_ALL`` | ``debug_all`` | Turns all debug_* settings on. |
++-----------------------------+--------------------------+-------------------------------------+
Examples
--------
@@ -58,3 +65,10 @@ If you started your application this way, your :mod:`repoze.bfg`
application would behave in the same manner as if you had placed the
respective settings in the ``[app:main]`` section of your
application's ``.ini`` file.
+
+If you want to turn all ``debug`` settings (every debug setting that
+starts with ``debug_``). on in one fell swoop, you can use
+``BFG_DEBUG_ALL=1`` as an environment variable setting or you may use
+``debug_all=true`` in the config file. Note that this does not effect
+settings that do not start with ``debug_*`` such as
+``reload_templates``.
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index b57ad2958..041fff89d 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -166,19 +166,20 @@ location-awareness.
.. _debug_authorization_section:
-Debugging Authorization Failures
---------------------------------
+Debugging View Authorization Failures
+-------------------------------------
-If your application in your judgment is allowing or denying access
-inappropriately, start your application under a shell using the
+If your application in your judgment is allowing or denying view
+access inappropriately, start your application under a shell using the
``BFG_DEBUG_AUTHORIZATION`` environment variable set to ``1``. For
example::
$ BFG_DEBUG_AUTHORIZATION=1 bin/paster serve myproject.ini
-When any authorization takes place, a message will be logged to the
-console (to stderr) about what ACE in which ACL permitted or denied
-the authorization based on authentication information.
+When any authorization takes place during a top-level view rendering,
+a message will be logged to the console (to stderr) about what ACE in
+which ACL permitted or denied the authorization based on
+authentication information.
This behavior can also be turned on in the application ``.ini`` file
by setting the ``debug_authorization`` key to ``true`` within the
@@ -188,3 +189,20 @@ application's configuration section, e.g.::
use = egg:MyProject#app
debug_authorization = true
+With this debug flag turned on, the response sent to the browser will
+also contain security debugging information in its body.
+
+Debugging Imperative Authorization Failures
+-------------------------------------------
+
+The ``has_permission`` API (see :ref:`security_module`) is used to
+check security within view functions imperatively. It returns
+instances of objects that are effectively booleans. But these objects
+are not raw ``True`` or ``False`` objects, and have information
+attached to them about why the permission was allowed or denied. The
+object will be one of ``ACLAllowed``, ``ACLDenied``, ``Allowed``, and
+``Denied``, documented in :ref:`security_module`. At very minimum
+these objects will have a ``msg`` attribute, which is a string
+indicating why permission was denied or allowed. Introspecting this
+information in the debugger or via print statements when a
+``has_permission`` fails is often useful.
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
index d57cf8f02..6ed2dabd1 100644
--- a/docs/narr/traversal.rst
+++ b/docs/narr/traversal.rst
@@ -129,11 +129,25 @@ code to execute:
and the context. If a view function is found, it is called with
the context and the request. It returns a response, which is fed
back upstream. If a view is not found, a generic WSGI
- ``NotFound`` application is constructed.
+ ``NotFound`` application is constructed and returned.
In either case, the result is returned upstream via the :term:`WSGI`
protocol.
+.. _debug_notfound_section:
+
+NotFound Errors
+---------------
+
+It's useful to be able to debug ``NotFound`` errors when they occur
+unexpectedly due to an application registry misconfiguration. To
+debug these errors, use the ``BFG_DEBUG_NOTFOUND`` environment
+variable or the ``debug_notfound`` configuration file setting.
+Details of why a view was not found will be printed to stderr, and the
+browser representation of the error will include the same information.
+See :ref:`environment_chapter` for more information about how and
+where to set these values.
+
A Traversal Example
-------------------
diff --git a/repoze/bfg/registry.py b/repoze/bfg/registry.py
index 8fcebaf13..eccfa27e0 100644
--- a/repoze/bfg/registry.py
+++ b/repoze/bfg/registry.py
@@ -91,15 +91,22 @@ def asbool(s):
def get_options(kw, environ=os.environ):
# environ is passed in for unit tests
eget = environ.get
+ config_debug_all = kw.get('debug_all', '')
+ effective_debug_all = asbool(eget('BFG_DEBUG_ALL',
+ config_debug_all))
config_debug_auth = kw.get('debug_authorization', '')
effective_debug_auth = asbool(eget('BFG_DEBUG_AUTHORIZATION',
config_debug_auth))
+ config_debug_notfound = kw.get('debug_notfound', '')
+ effective_debug_notfound = asbool(eget('BFG_DEBUG_NOTFOUND',
+ config_debug_notfound))
config_reload_templates = kw.get('reload_templates')
effective_reload_templates = asbool(eget('BFG_RELOAD_TEMPLATES',
config_reload_templates))
return {
- 'debug_authorization': effective_debug_auth,
- 'reload_templates':effective_reload_templates,
+ 'debug_authorization': effective_debug_all or effective_debug_auth,
+ 'debug_notfound': effective_debug_all or effective_debug_notfound,
+ 'reload_templates': effective_reload_templates,
}
from zope.testing.cleanup import addCleanUp
diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py
index 28cb319cb..d51d07dda 100644
--- a/repoze/bfg/router.py
+++ b/repoze/bfg/router.py
@@ -1,4 +1,7 @@
+from cgi import escape
+
from zope.component import getAdapter
+from zope.component import queryUtility
from zope.component.event import dispatch
from zope.interface import directlyProvides
from zope.interface import implements
@@ -11,9 +14,11 @@ from repoze.bfg.events import NewRequest
from repoze.bfg.events import NewResponse
from repoze.bfg.events import WSGIApplicationCreatedEvent
+from repoze.bfg.interfaces import ILogger
from repoze.bfg.interfaces import ITraverserFactory
from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IRouter
+from repoze.bfg.interfaces import ISettings
from repoze.bfg.registry import registry_manager
from repoze.bfg.registry import makeRegistry
@@ -21,6 +26,7 @@ from repoze.bfg.registry import makeRegistry
from repoze.bfg.security import Unauthorized
from repoze.bfg.view import render_view_to_response
+from repoze.bfg.view import view_execution_permitted
_marker = ()
@@ -41,22 +47,46 @@ class Router(object):
dispatch(NewRequest(request))
root = self.root_policy(environ)
traverser = getAdapter(root, ITraverserFactory)
+ settings = queryUtility(ISettings)
context, name, subpath = traverser(environ)
request.context = context
request.view_name = name
request.subpath = subpath
- try:
- response = render_view_to_response(context, request, name,
- secure=True)
- except Unauthorized, why:
- app = HTTPUnauthorized()
- app.explanation = str(why)
+ permitted = view_execution_permitted(context, request, name)
+ debug_authorization = settings and settings.debug_authorization
+
+ if debug_authorization:
+ logger = queryUtility(ILogger, 'repoze.bfg.debug')
+ logger and logger.debug(
+ 'debug_authorization of url %s (view name %r against context '
+ '%r): %s' % (request.url, name, context, permitted.msg)
+ )
+ if not permitted:
+ if debug_authorization:
+ msg = permitted.msg
+ else:
+ msg = 'Unauthorized: failed security policy check'
+ app = HTTPUnauthorized(escape(msg))
return app(environ, start_response)
+
+ response = render_view_to_response(context, request, name,
+ secure=False)
if response is None:
- app = HTTPNotFound(request.url)
+ debug_notfound = settings and settings.debug_notfound
+ if debug_notfound:
+ logger = queryUtility(ILogger, 'repoze.bfg.debug')
+ msg = (
+ 'debug_notfound of url %s; path_info: %r, context: %r, '
+ 'view_name: %r, subpath: %r' % (
+ request.url, request.path_info, context, name, subpath)
+ )
+ logger and logger.debug(msg)
+ else:
+ msg = request.url
+ app = HTTPNotFound(escape(msg))
return app(environ, start_response)
dispatch(NewResponse(response))
diff --git a/repoze/bfg/security.py b/repoze/bfg/security.py
index f1149cc7a..21b7f98d3 100644
--- a/repoze/bfg/security.py
+++ b/repoze/bfg/security.py
@@ -7,7 +7,6 @@ from repoze.bfg.interfaces import ISecurityPolicy
from repoze.bfg.interfaces import IViewPermission
from repoze.bfg.interfaces import IViewPermissionFactory
from repoze.bfg.interfaces import NoAuthorizationInformation
-from repoze.bfg.interfaces import ILogger
Everyone = 'system.Everyone'
Authenticated = 'system.Authenticated'
@@ -65,14 +64,14 @@ def principals_allowed_by_permission(context, permission):
class ACLAuthorizer(object):
- def __init__(self, context, logger):
+ def __init__(self, context):
self.context = context
- self.logger = logger
def permits(self, permission, *principals):
acl = getattr(self.context, '__acl__', None)
if acl is None:
- raise NoAuthorizationInformation('%s item has no __acl__' % acl)
+ raise NoAuthorizationInformation('%s item has no __acl__' %
+ self.context)
for ace in acl:
ace_action, ace_principal, ace_permissions = ace
@@ -80,18 +79,14 @@ class ACLAuthorizer(object):
if ace_principal == principal:
permissions = flatten(ace_permissions)
if permission in permissions:
- action = ace_action
- if action == Allow:
- result = Allowed(ace, acl, permission, principals,
+ if ace_action == Allow:
+ return ACLAllowed(ace, acl, permission, principals,
+ self.context)
+ else:
+ return ACLDenied(ace, acl, permission, principals,
self.context)
- self.logger and self.logger.debug(str(result))
- return result
- result = Denied(ace, acl, permission, principals,
- self.context)
- self.logger and self.logger.debug(str(result))
- return result
- result = Denied(None, acl, permission, principals, self.context)
- self.logger and self.logger.debug(str(result))
+ # default deny
+ result = ACLDenied(None, acl, permission, principals, self.context)
return result
class ACLSecurityPolicy(object):
@@ -102,18 +97,18 @@ class ACLSecurityPolicy(object):
self.get_principals = get_principals
def permits(self, context, request, permission):
- """ Return ``Allowed`` if the policy permits access,
- ``Denied`` if not."""
- logger = queryUtility(ILogger, name='repoze.bfg.debug')
+ """ Return ``ACLAllowed`` if the policy permits access,
+ ``ACLDenied`` if not. """
principals = self.effective_principals(request)
for location in lineage(context):
- authorizer = self.authorizer_factory(location, logger)
+ authorizer = self.authorizer_factory(location)
try:
return authorizer.permits(permission, *principals)
+
except NoAuthorizationInformation:
continue
- return False
+ return Denied(None, None, permission, principals, self.context)
def authenticated_userid(self, request):
principals = self.get_principals(request)
@@ -202,26 +197,22 @@ def RepozeWhoIdentityACLSecurityPolicy():
return ACLSecurityPolicy(get_who_principals)
class PermitsResult:
- def __init__(self, ace, acl, permission, principals, context):
- self.acl = acl
- self.ace = ace
- self.permission = permission
- self.principals = principals
- self.context_repr = repr(context)
-
def __str__(self):
- msg = '%s: %r via ace %r in acl %r or principals %r in context %s'
- msg = msg % (self.__class__.__name__,
- self.permission, self.ace, self.acl, self.principals,
- self.context_repr)
- return msg
+ return self.msg
+
+ def __repr__(self):
+ return '<%s instance at %s with msg %r>' % (self.__class__.__name__,
+ id(self),
+ self.msg)
class Denied(PermitsResult):
- """ An instance of ``Denied`` is returned by an ACL denial. It
- evaluates equal to all boolean false types. It also has
- attributes which indicate which acl, ace, permission, principals,
- and context were involved in the request. Its __str__ method
- prints a summary of these attributes for debugging purposes."""
+ """ An instance of ``Denied`` is returned when a security policy
+ or other ``repoze.bfg`` code denies an action unlrelated to an ACL
+ check. It evaluates equal to all boolean false types. It has an
+ attribute named ``msg`` describing the circumstances for the deny."""
+ def __init__(self, msg):
+ self.msg = msg
+
def __nonzero__(self):
return False
@@ -229,17 +220,55 @@ class Denied(PermitsResult):
return bool(other) is False
class Allowed(PermitsResult):
- """ An instance of ``Allowed`` is returned by an ACL allow. It
- evaluates equal to all boolean true types. It also has attributes
- which indicate which acl, ace, permission, principals, and context
- were involved in the request. Its __str__ method prints a summary
- of these attributes for debugging purposes."""
+ """ An instance of ``Allowed`` is returned when a security policy
+ or other ``repoze.bfg`` code allows an action unrelated to an ACL
+ check. It evaluates equal to all boolean true types. It has an
+ attribute named ``msg`` describing the circumstances for the
+ allow."""
+ def __init__(self, msg):
+ self.msg = msg
+
def __nonzero__(self):
return True
def __eq__(self, other):
return bool(other) is True
+class ACLPermitsResult:
+ def __init__(self, ace, acl, permission, principals, context):
+ self.permission = permission
+ self.ace = ace
+ self.acl = acl
+ self.principals = principals
+ self.context = context
+ msg = ('%s permission %r via ACE %r in ACL %r on context %r for '
+ 'principals %r')
+ msg = msg % (self.__class__.__name__,
+ self.permission,
+ self.ace,
+ self.acl,
+ self.context,
+ self.principals)
+ self.msg = msg
+
+class ACLDenied(ACLPermitsResult, Denied):
+ """ An instance of ``ACLDenied`` represents that a security check
+ made explicitly against ACL was denied. It evaluates equal to all
+ boolean false types. It also has attributes which indicate which
+ acl, ace, permission, principals, and context were involved in the
+ request. Its __str__ method prints a summary of these attributes
+ for debugging purposes. The same summary is available as he
+ ``msg`` attribute."""
+
+class ACLAllowed(ACLPermitsResult, Allowed):
+ """ An instance of ``ACLDenied`` represents that a security check
+ made explicitly against ACL was allowed. It evaluates equal to
+ all boolean true types. It also has attributes which indicate
+ which acl, ace, permission, principals, and context were involved
+ in the request. Its __str__ method prints a summary of these
+ attributes for debugging purposes. The same summary is available
+ as he ``msg`` attribute."""
+
def flatten(x):
"""flatten(sequence) -> list
@@ -269,8 +298,9 @@ class ViewPermission(object):
self.request = request
self.permission_name = permission_name
- def __call__(self, security_policy):
- return security_policy.permits(self.context, self.request,
+ def __call__(self, security_policy, debug_info=None):
+ return security_policy.permits(self.context,
+ self.request,
self.permission_name)
def __repr__(self):
@@ -289,5 +319,6 @@ class ViewPermissionFactory(object):
class Unauthorized(Exception):
pass
+
diff --git a/repoze/bfg/tests/test_registry.py b/repoze/bfg/tests/test_registry.py
index 86977e941..73bdc40fe 100644
--- a/repoze/bfg/tests/test_registry.py
+++ b/repoze/bfg/tests/test_registry.py
@@ -79,6 +79,44 @@ class TestGetOptions(unittest.TestCase):
{'BFG_DEBUG_AUTHORIZATION':'1'})
self.assertEqual(result['debug_authorization'], True)
+ def test_debug_notfound(self):
+ get_options = self._getFUT()
+ result = get_options({})
+ self.assertEqual(result['debug_notfound'], False)
+ result = get_options({'debug_notfound':'false'})
+ self.assertEqual(result['debug_notfound'], False)
+ result = get_options({'debug_notfound':'t'})
+ self.assertEqual(result['debug_notfound'], True)
+ result = get_options({'debug_notfound':'1'})
+ self.assertEqual(result['debug_notfound'], True)
+ result = get_options({}, {'BFG_DEBUG_NOTFOUND':'1'})
+ self.assertEqual(result['debug_notfound'], True)
+ result = get_options({'debug_notfound':'false'},
+ {'BFG_DEBUG_NOTFOUND':'1'})
+ self.assertEqual(result['debug_notfound'], True)
+
+ def test_debug_all(self):
+ get_options = self._getFUT()
+ result = get_options({})
+ self.assertEqual(result['debug_notfound'], False)
+ self.assertEqual(result['debug_authorization'], False)
+ result = get_options({'debug_all':'false'})
+ self.assertEqual(result['debug_notfound'], False)
+ self.assertEqual(result['debug_authorization'], False)
+ result = get_options({'debug_all':'t'})
+ self.assertEqual(result['debug_notfound'], True)
+ self.assertEqual(result['debug_authorization'], True)
+ result = get_options({'debug_all':'1'})
+ self.assertEqual(result['debug_notfound'], True)
+ self.assertEqual(result['debug_authorization'], True)
+ result = get_options({}, {'BFG_DEBUG_ALL':'1'})
+ self.assertEqual(result['debug_notfound'], True)
+ self.assertEqual(result['debug_authorization'], True)
+ result = get_options({'debug_all':'false'},
+ {'BFG_DEBUG_ALL':'1'})
+ self.assertEqual(result['debug_notfound'], True)
+ self.assertEqual(result['debug_authorization'], True)
+
class TestThreadLocalRegistryManager(unittest.TestCase, PlacelessSetup):
def setUp(self):
PlacelessSetup.setUp(self)
diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py
index a1c2b0cfb..c61694fd1 100644
--- a/repoze/bfg/tests/test_router.py
+++ b/repoze/bfg/tests/test_router.py
@@ -9,6 +9,33 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
def tearDown(self):
PlacelessSetup.tearDown(self)
+ def _registerLogger(self):
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ from repoze.bfg.interfaces import ILogger
+ class Logger:
+ def __init__(self):
+ self.messages = []
+ def info(self, msg):
+ self.messages.append(msg)
+ debug = info
+ logger = Logger()
+ gsm.registerUtility(logger, ILogger, name='repoze.bfg.debug')
+ return logger
+
+ def _registerSettings(self, **kw):
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ from repoze.bfg.interfaces import ISettings
+ class Settings:
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+ defaultkw = {'debug_authorization':False, 'debug_notfound':False}
+ defaultkw.update(kw)
+ settings = Settings(**defaultkw)
+ gsm.registerUtility(settings, ISettings)
+
def _registerTraverserFactory(self, app, name, *for_):
import zope.component
gsm = zope.component.getGlobalSiteManager()
@@ -56,12 +83,51 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
environ.update(extras)
return environ
- def test_call_no_view_registered(self):
+ def test_call_no_view_registered_no_isettings(self):
+ rootpolicy = make_rootpolicy(None)
+ environ = self._makeEnviron()
+ context = DummyContext()
+ traversalfactory = make_traversal_factory(context, '', [])
+ self._registerTraverserFactory(traversalfactory, '', None)
+ logger = self._registerLogger()
+ router = self._makeOne(rootpolicy, None)
+ start_response = DummyStartResponse()
+ result = router(environ, start_response)
+ headers = start_response.headers
+ self.assertEqual(len(headers), 2)
+ status = start_response.status
+ self.assertEqual(status, '404 Not Found')
+ self.failUnless('http://localhost:8080' in result[0], result)
+ self.failIf('debug_notfound' in result[0])
+ self.assertEqual(len(logger.messages), 0)
+
+ def test_call_no_view_registered_debug_notfound_false(self):
+ rootpolicy = make_rootpolicy(None)
+ environ = self._makeEnviron()
+ context = DummyContext()
+ traversalfactory = make_traversal_factory(context, '', [])
+ self._registerTraverserFactory(traversalfactory, '', None)
+ logger = self._registerLogger()
+ self._registerSettings(debug_notfound=False)
+ router = self._makeOne(rootpolicy, None)
+ start_response = DummyStartResponse()
+ result = router(environ, start_response)
+ headers = start_response.headers
+ self.assertEqual(len(headers), 2)
+ status = start_response.status
+ self.assertEqual(status, '404 Not Found')
+ self.failUnless('http://localhost:8080' in result[0], result)
+ self.failIf('debug_notfound' in result[0])
+ self.assertEqual(len(logger.messages), 0)
+
+ def test_call_no_view_registered_debug_notfound_true(self):
rootpolicy = make_rootpolicy(None)
environ = self._makeEnviron()
context = DummyContext()
traversalfactory = make_traversal_factory(context, '', [])
self._registerTraverserFactory(traversalfactory, '', None)
+ self._registerSettings(debug_notfound=True)
+ logger = self._registerLogger()
router = self._makeOne(rootpolicy, None)
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -69,7 +135,19 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
self.assertEqual(len(headers), 2)
status = start_response.status
self.assertEqual(status, '404 Not Found')
+ self.failUnless(
+ "debug_notfound of url http://localhost:8080; path_info: '', "
+ "context:" in result[0])
+ self.failUnless(
+ "view_name: '', subpath: []" in result[0])
self.failUnless('http://localhost:8080' in result[0], result)
+ self.assertEqual(len(logger.messages), 1)
+ message = logger.messages[0]
+ self.failUnless('of url http://localhost:8080' in message)
+ self.failUnless("path_info: ''" in message)
+ self.failUnless('DummyContext instance at' in message)
+ self.failUnless("view_name: ''" in message)
+ self.failUnless("subpath: []" in message)
def test_call_view_registered_nonspecific_default_path(self):
rootpolicy = make_rootpolicy(None)
@@ -207,7 +285,68 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
self.assertEqual(start_response.status, '200 OK')
self.assertEqual(permissionfactory.checked_with, secpol)
- def test_call_view_registered_security_policy_permission_fails(self):
+ def test_call_view_permission_fails_nosettings(self):
+ rootpolicy = make_rootpolicy(None)
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ class IContext(Interface):
+ pass
+ from repoze.bfg.interfaces import IRequest
+ context = DummyContext()
+ directlyProvides(context, IContext)
+ traversalfactory = make_traversal_factory(context, '', [''])
+ response = DummyResponse()
+ view = make_view(response)
+ secpol = DummySecurityPolicy()
+ from repoze.bfg.security import ACLDenied
+ permissionfactory = make_permission_factory(
+ ACLDenied('ace', 'acl', 'permission', ['principals'], context)
+ )
+ environ = self._makeEnviron()
+ self._registerTraverserFactory(traversalfactory, '', None)
+ self._registerView(view, '', IContext, IRequest)
+ self._registerSecurityPolicy(secpol)
+ self._registerPermission(permissionfactory, '', IContext, IRequest)
+ router = self._makeOne(rootpolicy, None)
+ start_response = DummyStartResponse()
+ result = router(environ, start_response)
+ self.assertEqual(start_response.status, '401 Unauthorized')
+ message = result[0]
+ self.failUnless('failed security policy check' in message)
+ self.assertEqual(permissionfactory.checked_with, secpol)
+
+ def test_call_view_permission_fails_no_debug_auth(self):
+ rootpolicy = make_rootpolicy(None)
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ class IContext(Interface):
+ pass
+ from repoze.bfg.interfaces import IRequest
+ context = DummyContext()
+ directlyProvides(context, IContext)
+ traversalfactory = make_traversal_factory(context, '', [''])
+ response = DummyResponse()
+ view = make_view(response)
+ secpol = DummySecurityPolicy()
+ from repoze.bfg.security import ACLDenied
+ permissionfactory = make_permission_factory(
+ ACLDenied('ace', 'acl', 'permission', ['principals'], context)
+ )
+ environ = self._makeEnviron()
+ self._registerTraverserFactory(traversalfactory, '', None)
+ self._registerView(view, '', IContext, IRequest)
+ self._registerSecurityPolicy(secpol)
+ self._registerPermission(permissionfactory, '', IContext, IRequest)
+ self._registerSettings(debug_authorization=False)
+ router = self._makeOne(rootpolicy, None)
+ start_response = DummyStartResponse()
+ result = router(environ, start_response)
+ self.assertEqual(start_response.status, '401 Unauthorized')
+ message = result[0]
+ self.failUnless('failed security policy check' in message)
+ self.assertEqual(permissionfactory.checked_with, secpol)
+
+ def test_call_view_permission_fails_with_debug_auth(self):
rootpolicy = make_rootpolicy(None)
from zope.interface import Interface
from zope.interface import directlyProvides
@@ -220,21 +359,37 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
response = DummyResponse()
view = make_view(response)
secpol = DummySecurityPolicy()
- from repoze.bfg.security import Denied
+ from repoze.bfg.security import ACLDenied
permissionfactory = make_permission_factory(
- Denied('ace', 'acl', 'permission', ['principals'], context)
+ ACLDenied('ace', 'acl', 'permission', ['principals'], context)
)
environ = self._makeEnviron()
self._registerTraverserFactory(traversalfactory, '', None)
self._registerView(view, '', IContext, IRequest)
self._registerSecurityPolicy(secpol)
self._registerPermission(permissionfactory, '', IContext, IRequest)
+ self._registerSettings(debug_authorization=True)
+ logger = self._registerLogger()
router = self._makeOne(rootpolicy, None)
start_response = DummyStartResponse()
result = router(environ, start_response)
self.assertEqual(start_response.status, '401 Unauthorized')
- self.failUnless('permission' in result[0])
+ message = result[0]
+ self.failUnless(
+ "ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' "
+ "on context" in message)
+ self.failUnless("for principals ['principals']" in message)
self.assertEqual(permissionfactory.checked_with, secpol)
+ self.assertEqual(len(logger.messages), 1)
+ logged = logger.messages[0]
+ self.failUnless(
+ "debug_authorization of url http://localhost:8080 (view name "
+ "'' against context" in logged)
+ self.failUnless(
+ "ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' on "
+ "context" in logged)
+ self.failUnless(
+ "for principals ['principals']" in logged)
def test_call_eventsends(self):
rootpolicy = make_rootpolicy(None)
diff --git a/repoze/bfg/tests/test_security.py b/repoze/bfg/tests/test_security.py
index a9a30ee6f..43dc38890 100644
--- a/repoze/bfg/tests/test_security.py
+++ b/repoze/bfg/tests/test_security.py
@@ -11,72 +11,85 @@ class TestACLAuthorizer(unittest.TestCase):
klass = self._getTargetClass()
return klass(*arg, **kw)
+ def test_deny_implicit(self):
+ context = DummyContext()
+ from repoze.bfg.security import Allow
+ ace = (Allow, 'somebodyelse', 'read')
+ acl = [ace]
+ context.__acl__ = acl
+ authorizer = self._makeOne(context)
+ principals = ['fred']
+ result = authorizer.permits('read', *principals)
+
+ def test_deny_explicit(self):
+ context = DummyContext()
+ from repoze.bfg.security import Deny
+ ace = (Deny, 'somebodyelse', 'read')
+ acl = [ace]
+ context.__acl__ = acl
+ authorizer = self._makeOne(context)
+ principals = ['somebodyelse']
+ result = authorizer.permits('read', *principals)
+
def test_permits_no_acl_raises(self):
context = DummyContext()
- logger = DummyLogger()
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
from repoze.bfg.interfaces import NoAuthorizationInformation
self.assertRaises(NoAuthorizationInformation,
authorizer.permits, (), None)
def test_permits_deny_implicit_empty_acl(self):
context = DummyContext()
- logger = DummyLogger()
context.__acl__ = []
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
result = authorizer.permits((), None)
self.assertEqual(result, False)
self.assertEqual(result.ace, None)
def test_permits_deny_no_principals_implicit(self):
context = DummyContext()
- logger = DummyLogger()
from repoze.bfg.security import Allow
from repoze.bfg.security import Everyone
acl = [(Allow, Everyone, 'view')]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
result = authorizer.permits(None)
self.assertEqual(result, False)
self.assertEqual(result.ace, None)
def test_permits_deny_oneacl_implicit(self):
context = DummyContext()
- logger = DummyLogger()
from repoze.bfg.security import Allow
acl = [(Allow, 'somebody', 'view')]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
result = authorizer.permits('view', 'somebodyelse')
self.assertEqual(result, False)
self.assertEqual(result.ace, None)
def test_permits_deny_twoacl_implicit(self):
context = DummyContext()
- logger = DummyLogger()
from repoze.bfg.security import Allow
acl = [(Allow, 'somebody', 'view'), (Allow, 'somebody', 'write')]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
result = authorizer.permits('view', 'somebodyelse')
self.assertEqual(result, False)
self.assertEqual(result.ace, None)
def test_permits_deny_oneacl_explcit(self):
context = DummyContext()
- logger = DummyLogger()
from repoze.bfg.security import Deny
ace = (Deny, 'somebody', 'view')
acl = [ace]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
result = authorizer.permits('view', 'somebody')
self.assertEqual(result, False)
self.assertEqual(result.ace, ace)
def test_permits_deny_oneacl_multiperm_explcit(self):
context = DummyContext()
- logger = DummyLogger()
acl = []
from repoze.bfg.security import Deny
from repoze.bfg.security import Allow
@@ -84,14 +97,13 @@ class TestACLAuthorizer(unittest.TestCase):
allow = (Allow, 'somebody', 'view')
acl = [deny, allow]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
result = authorizer.permits('view', 'somebody')
self.assertEqual(result, False)
self.assertEqual(result.ace, deny)
def test_permits_deny_twoacl_explicit(self):
context = DummyContext()
- logger = DummyLogger()
acl = []
from repoze.bfg.security import Deny
from repoze.bfg.security import Allow
@@ -99,34 +111,32 @@ class TestACLAuthorizer(unittest.TestCase):
deny = (Deny, 'somebody', 'view')
acl = [allow, deny]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
result = authorizer.permits('view', 'somebody')
self.assertEqual(result, False)
self.assertEqual(result.ace, deny)
def test_permits_allow_twoacl_explicit(self):
context = DummyContext()
- logger = DummyLogger()
from repoze.bfg.security import Deny
from repoze.bfg.security import Allow
allow = (Allow, 'somebody', 'read')
deny = (Deny, 'somebody', 'view')
acl = [allow, deny]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
result = authorizer.permits('read', 'somebody')
self.assertEqual(result, True)
self.assertEqual(result.ace, allow)
def test_permits_nested_principals_list_allow(self):
context = DummyContext()
- logger = DummyLogger()
acl = []
from repoze.bfg.security import Allow
ace = (Allow, 'larry', 'read')
acl = [ace]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
principals = (['fred', ['jim', ['bob', 'larry']]])
result = authorizer.permits('read', *principals)
self.assertEqual(result, True)
@@ -134,12 +144,11 @@ class TestACLAuthorizer(unittest.TestCase):
def test_permits_nested_principals_list_deny_explicit(self):
context = DummyContext()
- logger = DummyLogger()
from repoze.bfg.security import Deny
ace = (Deny, 'larry', 'read')
acl = [ace]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
principals = (['fred', ['jim', ['bob', 'larry']]])
result = authorizer.permits('read', *principals)
self.assertEqual(result, False)
@@ -147,12 +156,11 @@ class TestACLAuthorizer(unittest.TestCase):
def test_permits_nested_principals_list_deny_implicit(self):
context = DummyContext()
- logger = DummyLogger()
from repoze.bfg.security import Allow
ace = (Allow, 'somebodyelse', 'read')
acl = [ace]
context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
principals = (['fred', ['jim', ['bob', 'larry']]])
result = authorizer.permits('read', *principals)
self.assertEqual(result, False)
@@ -161,7 +169,6 @@ class TestACLAuthorizer(unittest.TestCase):
context = DummyContext()
context.__parent__ = None
context.__name__ = None
- logger = DummyLogger()
from repoze.bfg.security import Allow
ace = (Allow, 'fred', 'read')
acl = [ace]
@@ -169,46 +176,11 @@ class TestACLAuthorizer(unittest.TestCase):
context2 = DummyContext()
context2.__parent__ = context
context2.__name__ = 'myname'
- authorizer = self._makeOne(context, logger)
+ authorizer = self._makeOne(context)
principals = ['fred']
result = authorizer.permits('read', *principals)
self.assertEqual(result, True)
- def test_logging_deny_implicit(self):
- context = DummyContext()
- logger = DummyLogger()
- from repoze.bfg.security import Allow
- ace = (Allow, 'somebodyelse', 'read')
- acl = [ace]
- context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
- principals = ['fred']
- result = authorizer.permits('read', *principals)
- self.assertEqual(len(logger.messages), 1)
-
- def test_logging_deny_explicit(self):
- context = DummyContext()
- logger = DummyLogger()
- from repoze.bfg.security import Deny
- ace = (Deny, 'somebodyelse', 'read')
- acl = [ace]
- context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
- principals = ['somebodyelse']
- result = authorizer.permits('read', *principals)
- self.assertEqual(len(logger.messages), 1)
-
- def test_logging_allow(self):
- context = DummyContext()
- logger = DummyLogger()
- from repoze.bfg.security import Allow
- ace = (Allow, 'somebodyelse', 'read')
- acl = [ace]
- context.__acl__ = acl
- authorizer = self._makeOne(context, logger)
- principals = ['somebodyelse']
- result = authorizer.permits('read', *principals)
- self.assertEqual(len(logger.messages), 1)
class TestACLSecurityPolicy(unittest.TestCase, PlacelessSetup):
def _getTargetClass(self):
@@ -219,12 +191,6 @@ class TestACLSecurityPolicy(unittest.TestCase, PlacelessSetup):
klass = self._getTargetClass()
return klass(*arg, **kw)
- def _registerLogger(self, logger):
- import zope.component
- gsm = zope.component.getGlobalSiteManager()
- from repoze.bfg.interfaces import ILogger
- gsm.registerUtility(logger, ILogger, name='repoze.bfg.debug')
-
def setUp(self):
PlacelessSetup.setUp(self)
@@ -305,26 +271,6 @@ class TestACLSecurityPolicy(unittest.TestCase, PlacelessSetup):
self.assertEqual(authorizer_factory.permission, 'view')
self.assertEqual(authorizer_factory.context, context)
- def test_permits_with_logger(self):
- logger = DummyLogger()
- self._registerLogger(logger)
- context = DummyContext()
- request = DummyRequest({})
- policy = self._makeOne(lambda *arg: None)
- authorizer_factory = make_authorizer_factory(context)
- policy.authorizer_factory = authorizer_factory
- policy.permits(context, request, 'view')
- self.assertEqual(authorizer_factory.logger, logger)
-
- def test_permits_no_logger(self):
- context = DummyContext()
- request = DummyRequest({})
- policy = self._makeOne(lambda *arg: None)
- authorizer_factory = make_authorizer_factory(context)
- policy.authorizer_factory = authorizer_factory
- policy.permits(context, request, 'view')
- self.assertEqual(authorizer_factory.logger, None)
-
def test_principals_allowed_by_permission_direct(self):
from repoze.bfg.security import Allow
context = DummyContext()
@@ -526,6 +472,82 @@ class TestViewPermissionFactory(unittest.TestCase):
self.assertEqual(result.permission_name, 'repoze.view')
self.assertEqual(result.context, context)
self.assertEqual(result.request, request)
+
+class TestAllowed(unittest.TestCase):
+ def _getTargetClass(self):
+ from repoze.bfg.security import Allowed
+ return Allowed
+
+ def _makeOne(self, *arg, **kw):
+ klass = self._getTargetClass()
+ return klass(*arg, **kw)
+
+ def test_it(self):
+ allowed = self._makeOne('hello')
+ self.assertEqual(allowed.msg, 'hello')
+ self.assertEqual(allowed, True)
+ self.failUnless(allowed)
+ self.assertEqual(str(allowed), 'hello')
+ self.failUnless('<Allowed instance at ' in repr(allowed))
+ self.failUnless("with msg 'hello'>" in repr(allowed))
+
+class TestDenied(unittest.TestCase):
+ def _getTargetClass(self):
+ from repoze.bfg.security import Denied
+ return Denied
+
+ def _makeOne(self, *arg, **kw):
+ klass = self._getTargetClass()
+ return klass(*arg, **kw)
+
+ def test_it(self):
+ denied = self._makeOne('hello')
+ self.assertEqual(denied.msg, 'hello')
+ self.assertEqual(denied, False)
+ self.failIf(denied)
+ self.assertEqual(str(denied), 'hello')
+ self.failUnless('<Denied instance at ' in repr(denied))
+ self.failUnless("with msg 'hello'>" in repr(denied))
+
+class TestACLAllowed(unittest.TestCase):
+ def _getTargetClass(self):
+ from repoze.bfg.security import ACLAllowed
+ return ACLAllowed
+
+ def _makeOne(self, *arg, **kw):
+ klass = self._getTargetClass()
+ return klass(*arg, **kw)
+
+ def test_it(self):
+ msg = ("ACLAllowed permission 'permission' via ACE 'ace' in ACL 'acl' "
+ "on context 'ctx' for principals 'principals'")
+ allowed = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx')
+ self.failUnless(msg in allowed.msg)
+ self.assertEqual(allowed, True)
+ self.failUnless(allowed)
+ self.assertEqual(str(allowed), msg)
+ self.failUnless('<ACLAllowed instance at ' in repr(allowed))
+ self.failUnless("with msg %r>" % msg in repr(allowed))
+
+class TestACLDenied(unittest.TestCase):
+ def _getTargetClass(self):
+ from repoze.bfg.security import ACLDenied
+ return ACLDenied
+
+ def _makeOne(self, *arg, **kw):
+ klass = self._getTargetClass()
+ return klass(*arg, **kw)
+
+ def test_it(self):
+ msg = ("ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' "
+ "on context 'ctx' for principals 'principals'")
+ denied = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx')
+ self.failUnless(msg in denied.msg)
+ self.assertEqual(denied, False)
+ self.failIf(denied)
+ self.assertEqual(str(denied), msg)
+ self.failUnless('<ACLDenied instance at ' in repr(denied))
+ self.failUnless("with msg %r>" % msg in repr(denied))
class DummyContext:
pass
@@ -551,25 +573,18 @@ class DummySecurityPolicy:
def principals_allowed_by_permission(self, context, permission):
return ['fred', 'bob']
-class DummyLogger:
- def __init__(self):
- self.messages = []
- def debug(self, msg):
- self.messages.append(msg)
-
class make_authorizer_factory:
def __init__(self, expected_context, intermediates_raise=False):
self.expected_context = expected_context
self.intermediates_raise = intermediates_raise
- def __call__(self, context, logger):
+ def __call__(self, context):
authorizer = self
class Authorizer:
def permits(self, permission, *principals):
authorizer.permission = permission
authorizer.principals = principals
authorizer.context = context
- authorizer.logger = logger
result = authorizer.expected_context == context
if not result and authorizer.intermediates_raise:
from repoze.bfg.interfaces import NoAuthorizationInformation
diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py
index 3636692a8..46687e904 100644
--- a/repoze/bfg/tests/test_view.py
+++ b/repoze/bfg/tests/test_view.py
@@ -372,9 +372,73 @@ class TestIsResponse(unittest.TestCase):
f = self._getFUT()
self.assertEqual(f(response), False)
+class TestViewExecutionPermitted(unittest.TestCase, PlacelessSetup):
+ def setUp(self):
+ PlacelessSetup.setUp(self)
+
+ def tearDown(self):
+ PlacelessSetup.tearDown(self)
+
+ def _callFUT(self, *arg, **kw):
+ from repoze.bfg.view import view_execution_permitted
+ return view_execution_permitted(*arg, **kw)
+
+ def _registerSecurityPolicy(self, secpol):
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ from repoze.bfg.interfaces import ISecurityPolicy
+ gsm.registerUtility(secpol, ISecurityPolicy)
+
+ def _registerPermission(self, permission, name, *for_):
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ from repoze.bfg.interfaces import IViewPermission
+ gsm.registerAdapter(permission, for_, IViewPermission, name)
+
+ def test_no_secpol(self):
+ context = DummyContext()
+ request = DummyRequest()
+ result = self._callFUT(context, request, '')
+ msg = result.msg
+ self.failUnless("Allowed: view name '' in context" in msg)
+ self.failUnless('(no security policy in use)' in msg)
+ self.assertEqual(result, True)
+
+ def test_secpol_no_permission(self):
+ secpol = DummySecurityPolicy()
+ self._registerSecurityPolicy(secpol)
+ context = DummyContext()
+ request = DummyRequest()
+ result = self._callFUT(context, request, '')
+ msg = result.msg
+ self.failUnless("Allowed: view name '' in context" in msg)
+ self.failUnless("(no permission registered for name '')" in msg)
+ self.assertEqual(result, True)
+
+ def test_secpol_and_permission(self):
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ from repoze.bfg.interfaces import IRequest
+ class IContext(Interface):
+ pass
+ context = DummyContext()
+ directlyProvides(context, IContext)
+ permissionfactory = make_permission_factory(True)
+ self._registerPermission(permissionfactory, '', IContext,
+ IRequest)
+ secpol = DummySecurityPolicy()
+ self._registerSecurityPolicy(secpol)
+ request = DummyRequest()
+ directlyProvides(request, IRequest)
+ result = self._callFUT(context, request, '')
+ self.failUnless(result is True)
+
class DummyContext:
pass
+class DummyRequest:
+ pass
+
def make_view(response):
def view(context, request):
return response
diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py
index 0012019e7..ae4f304f0 100644
--- a/repoze/bfg/view.py
+++ b/repoze/bfg/view.py
@@ -4,10 +4,31 @@ from zope.component import queryUtility
from repoze.bfg.interfaces import ISecurityPolicy
from repoze.bfg.interfaces import IViewPermission
from repoze.bfg.interfaces import IView
+
from repoze.bfg.security import Unauthorized
+from repoze.bfg.security import Allowed
_marker = ()
+def view_execution_permitted(context, request, name=''):
+ """ If the view specified by ``context`` and ``name`` is protected
+ by a permission, return the result of checking the permission
+ associated with the view using the effective security policy and
+ the ``request``. If no security policy is in effect, or if the
+ view is not protected by a permission, return a True value. """
+ security_policy = queryUtility(ISecurityPolicy)
+ if security_policy:
+ permission = queryMultiAdapter((context, request), IViewPermission,
+ name=name)
+ if permission is None:
+ return Allowed(
+ 'Allowed: view name %r in context %r (no permission '
+ 'registered for name %r).' % (name, context, name)
+ )
+ return permission(security_policy)
+ return Allowed('Allowed: view name %r in context %r (no security policy '
+ 'in use).' % (name, context))
+
def render_view_to_response(context, request, name='', secure=True):
""" Render the view named ``name`` against the specified
``context`` and ``request`` to an object implementing
@@ -24,16 +45,13 @@ def render_view_to_response(context, request, name='', secure=True):
``args`` attribute explains why the view access was disallowed.
If ``secure`` is ``False``, no permission checking is done."""
if secure:
- security_policy = queryUtility(ISecurityPolicy)
- if security_policy:
- permission = queryMultiAdapter((context, request), IViewPermission,
- name=name)
- if permission is not None:
- result = permission(security_policy)
- if not result:
- raise Unauthorized(result)
+ permitted = view_execution_permitted(context, request, name)
+ if not permitted:
+ raise Unauthorized(permitted)
+
response = queryMultiAdapter((context, request), IView, name=name,
default=_marker)
+
if response is _marker:
return None