Permalink
Browse files

Security APIs on pyramid.request.Request

The pyramid.security Authorization API function has_permission is made available on the request.
The pyramid.security Authentication API functions are now available as
properties (unauthenticated_userid, authenticated_userid, effective_principals)
and methods (remember_userid, forget_userid) on pyramid.request.Request.

Backwards compatibility:
    For each of the APIs moved to request method or property,
    the original API in the pyramid.security module proxies to the request.

Reworked tests to check module level b/c wrappers call through to mixins for each API.
Tests that check no reg on request now do the right thing.
Use a response callback to set the request headers for forget_userid and remember_userid.

Update docs.

Attempt to improve a documentation section referencing the pyramid.security.has_permission
function in docs/narr/resources.rst

Ensures backwards compatiblity for `pyramid.security.forget`
and `pyramid.security.remember`.
  • Loading branch information...
1 parent a4492a9 commit 3c2f95e8049bbd45b144d454daa68005361828b2 Matt Russell committed with mgrbyte Oct 24, 2013
View
8 CHANGES.txt
@@ -4,6 +4,14 @@ Unreleased
Features
--------
+- The :mod:``pyramid.security`` authentication API methods should now be
+ accessed via the request. The ``pyramid.security`` authoriztion API function
+ :meth:`has_permission` should now be accessed via the request.
+ The methods :meth:``pyramid.request.Request.forget_userid``,
+ meth:``pyramid.request.Request.remember_userid`` now automatically
+ set the headers on the response, as returned by the corrosponding
+ method of the current request's :term:``authentication policy``.
+
- Pyramid's console scripts (``pserve``, ``pviews``, etc) can now be run
directly, allowing custom arguments to be sent to the python interpreter
at runtime. For example::
View
2 CONTRIBUTORS.txt
@@ -224,3 +224,5 @@ Contributors
- Doug Hellmann, 2013/09/06
- Karl O. Pinc, 2013/09/27
+
+- Matthew Russell, 2013/10/14
View
12 docs/narr/resources.rst
@@ -201,7 +201,7 @@ location-aware resources. These APIs include (but are not limited to)
:func:`~pyramid.traversal.resource_path`,
:func:`~pyramid.traversal.resource_path_tuple`, or
:func:`~pyramid.traversal.traverse`, :func:`~pyramid.traversal.virtual_root`,
-and (usually) :func:`~pyramid.security.has_permission` and
+and (usually) :meth:`~pyramid.request.Request.has_permission` and
:func:`~pyramid.security.principals_allowed_by_permission`.
In general, since so much :app:`Pyramid` infrastructure depends on
@@ -695,10 +695,10 @@ The APIs provided by :ref:`location_module` are used against resources.
These can be used to walk down a resource tree, or conveniently locate one
resource "inside" another.
-Some APIs in :ref:`security_module` accept a resource object as a parameter.
-For example, the :func:`~pyramid.security.has_permission` API accepts a
+Some APIs on the :class:`pyramid.request.Request` accept a resource object as a parameter.
+For example, the :meth:`~pyramid.request.Request.has_permission` API accepts a
resource object as one of its arguments; the ACL is obtained from this
-resource or one of its ancestors. Other APIs in the :mod:`pyramid.security`
-module also accept :term:`context` as an argument, and a context is always a
-resource.
+resource or one of its ancestors. Other security related APIs on the
+:class:`pyramid.request.Request` class also accept :term:`context` as an argument,
+and a context is always a resource.
View
4 docs/narr/security.rst
@@ -550,7 +550,7 @@ also contain security debugging information in its body.
Debugging Imperative Authorization Failures
-------------------------------------------
-The :func:`pyramid.security.has_permission` API is used to check
+The :meth:`pyramid.request.Request.has_permission` API 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
@@ -563,7 +563,7 @@ one of :data:`pyramid.security.ACLAllowed`,
``msg`` attribute, which is a string indicating why the permission was
denied or allowed. Introspecting this information in the debugger or
via print statements when a call to
-:func:`~pyramid.security.has_permission` fails is often useful.
+:meth:`~pyramid.request.Request.has_permission` fails is often useful.
.. index::
single: authentication policy (creating)
View
2 docs/narr/testing.rst
@@ -229,7 +229,7 @@ function.
otherwise it would fail when run normally.
Without doing anything special during a unit test, the call to
-:func:`~pyramid.security.has_permission` in this view function will always
+:meth:`~pyramid.request.Request.has_permission` in this view function will always
return a ``True`` value. When a :app:`Pyramid` application starts normally,
it will populate a :term:`application registry` using :term:`configuration
declaration` calls made against a :term:`Configurator`. But if this
View
2 docs/narr/viewconfig.rst
@@ -435,7 +435,7 @@ configured view.
If specified, this value should be a :term:`principal` identifier or a
sequence of principal identifiers. If the
- :func:`pyramid.security.effective_principals` method indicates that every
+ :meth:`pyramid.request.Request.effective_principals` method indicates that every
principal named in the argument list is present in the current request, this
predicate will return True; otherwise it will return False. For example:
``effective_principals=pyramid.security.Authenticated`` or
View
4 docs/tutorials/wiki/authorization.rst
@@ -207,8 +207,8 @@ need to be added.)
:meth:`~pyramid.view.forbidden_view_config` will be used
to customize the default 403 Forbidden page.
-:meth:`~pyramid.security.remember` and
-:meth:`~pyramid.security.forget` help to create and
+:meth:`~pyramid.request.Request.remember_userid` and
+:meth:`~pyramid.request.Request.forget_userid` help to create and
expire an auth ticket cookie.
Now add the ``login`` and ``logout`` views:
View
4 docs/tutorials/wiki2/authorization.rst
@@ -230,8 +230,8 @@ head of ``tutorial/tutorial/views.py``:
:meth:`~pyramid.view.forbidden_view_config` will be used
to customize the default 403 Forbidden page.
-:meth:`~pyramid.security.remember` and
-:meth:`~pyramid.security.forget` help to create and
+:meth:`~pyramid.request.Request.remember_userid` and
+:meth:`~pyramid.request.Request.forget_userid` help to create and
expire an auth ticket cookie.
Now add the ``login`` and ``logout`` views:
View
2 pyramid/config/routes.py
@@ -237,7 +237,7 @@ def add_route(self,
If specified, this value should be a :term:`principal` identifier or
a sequence of principal identifiers. If the
- :func:`pyramid.security.effective_principals` method indicates that
+ :meth:`pyramid.request.Request.effective_principals` method indicates that
every principal named in the argument list is present in the current
request, this predicate will return True; otherwise it will return
False. For example:
View
10 pyramid/config/testing.py
@@ -47,14 +47,14 @@ def testing_securitypolicy(self, userid=None, groupids=(),
``groupids`` argument. The authentication policy will return
the userid identifier implied by the ``userid`` argument and
the group ids implied by the ``groupids`` argument when the
- :func:`pyramid.security.authenticated_userid` or
- :func:`pyramid.security.effective_principals` APIs are
+ :meth:`pyramid.request.Request.authenticated_userid` or
+ :meth:`pyramid.request.Request.effective_principals` APIs are
used.
This function is most useful when testing code that uses
- the APIs named :func:`pyramid.security.has_permission`,
- :func:`pyramid.security.authenticated_userid`,
- :func:`pyramid.security.effective_principals`, and
+ the APIs named :meth:`pyramid.request.Request.has_permission`,
+ :meth:`pyramid.request.Request.authenticated_userid`,
+ :meth:`pyramid.request.Request.effective_principals`, and
:func:`pyramid.security.principals_allowed_by_permission`.
.. versionadded:: 1.4
View
2 pyramid/config/views.py
@@ -1017,7 +1017,7 @@ def myview(request):
If specified, this value should be a :term:`principal` identifier or
a sequence of principal identifiers. If the
- :func:`pyramid.security.effective_principals` method indicates that
+ :meth:`pyramid.request.Request.effective_principals` method indicates that
every principal named in the argument list is present in the current
request, this predicate will return True; otherwise it will return
False. For example:
View
10 pyramid/request.py
@@ -21,6 +21,7 @@
from pyramid.decorator import reify
from pyramid.i18n import LocalizerRequestMixin
from pyramid.response import Response
+from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
@@ -136,8 +137,13 @@ def _process_finished_callbacks(self):
callback(self)
@implementer(IRequest)
-class Request(BaseRequest, URLMethodsMixin, CallbackMethodsMixin,
- InstancePropertyMixin, LocalizerRequestMixin):
+class Request(BaseRequest,
+ URLMethodsMixin,
+ CallbackMethodsMixin,
+ InstancePropertyMixin,
+ LocalizerRequestMixin,
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin):
"""
A subclass of the :term:`WebOb` Request class. An instance of
this class is created by the :term:`router` and is provided to a
View
291 pyramid/security.py
@@ -30,79 +30,64 @@ def __eq__(self, other):
NO_PERMISSION_REQUIRED = '__no_permission_required__'
-def has_permission(permission, context, request):
- """ Provided a permission (a string or unicode object), a context
- (a :term:`resource` instance) and a request object, return an
- instance of :data:`pyramid.security.Allowed` if the permission
- is granted in this context to the user implied by the
- request. Return an instance of :mod:`pyramid.security.Denied`
- if this permission is not granted in this context to this user.
- This function delegates to the current authentication and
- authorization policies. Return
- :data:`pyramid.security.Allowed` unconditionally if no
- authentication policy has been configured in this application."""
+def _get_registry(request):
try:
reg = request.registry
except AttributeError:
reg = get_current_registry() # b/c
- authn_policy = reg.queryUtility(IAuthenticationPolicy)
- if authn_policy is None:
- return Allowed('No authentication policy in use.')
+ return reg
+
+# b/c
+def has_permission(permission, context, request):
+ """ Backwards compatible wrapper.
- authz_policy = reg.queryUtility(IAuthorizationPolicy)
- if authz_policy is None:
- raise ValueError('Authentication policy registered without '
- 'authorization policy') # should never happen
- principals = authn_policy.effective_principals(request)
- return authz_policy.permits(context, principals, permission)
+ Delegates to the :meth:``pyramid.request.Request.has_permission`` method.
+ """
+ return request.has_permission(permission, context)
+# b/c
def authenticated_userid(request):
- """ Return the userid of the currently authenticated user or
- ``None`` if there is no :term:`authentication policy` in effect or
- there is no currently authenticated user."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
+ """ Backwards compatible wrapper.
- policy = reg.queryUtility(IAuthenticationPolicy)
- if policy is None:
- return None
- return policy.authenticated_userid(request)
+ Delegates to the
+ :meth:``pyramid.request.Request.authenticated_userid`` method.
+ """
+ return request.authenticated_userid
+# b/c
def unauthenticated_userid(request):
- """ Return an object which represents the *claimed* (not verified) user
- id of the credentials present in the request. ``None`` if there is no
- :term:`authentication policy` in effect or there is no user data
- associated with the current request. This differs from
- :func:`~pyramid.security.authenticated_userid`, because the effective
- authentication policy will not ensure that a record associated with the
- userid exists in persistent storage."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
+ """ Backwards compatible wrapper.
- policy = reg.queryUtility(IAuthenticationPolicy)
- if policy is None:
- return None
- return policy.unauthenticated_userid(request)
+ Delegates to the
+ :meth:``pyramid.request.Request.unauthenticated_userid`` method.
+ """
+ return request.unauthenticated_userid
+# b/c
def effective_principals(request):
- """ Return the list of 'effective' :term:`principal` identifiers
- for the ``request``. This will include the userid of the
- currently authenticated user if a user is currently
- authenticated. If no :term:`authentication policy` is in effect,
- this will return an empty sequence."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
+ """ Backwards compatible wrapper.
+
+ Delegates to the
+ :meth:``pyramid.request.Request.effective_principals`` method.
+ """
+ return request.effective_principals
+
+# b/c
+def remember(request, principal, **kw):
+ """ Backwards compatible wrapper.
+
+ Delegates to the :meth:``pyramid.request.Request.remember_userid`` method.
+ """
+ return request._remember_userid(principal, **kw)
+
+# b/c
+def forget(request):
+ """ Backwards compatible wrapper.
+
+ Delegates to the :meth:``pyramid.request.Request.forget_userid`` method.
+ """
+ return request._forget_userid()
- policy = reg.queryUtility(IAuthenticationPolicy)
- if policy is None:
- return [Everyone]
- return policy.effective_principals(request)
def principals_allowed_by_permission(context, permission):
""" Provided a ``context`` (a resource object), and a ``permission``
@@ -140,10 +125,7 @@ def view_execution_permitted(context, request, name=''):
An exception is raised if no view is found.
"""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
+ reg = _get_registry(request)
provides = [IViewClassifier] + map_(providedBy, (request, context))
view = reg.adapters.lookup(provides, ISecuredView, name=name)
if view is None:
@@ -157,58 +139,6 @@ def view_execution_permitted(context, request, name=''):
(name, context))
return view.__permitted__(context, request)
-def remember(request, principal, **kw):
- """ Return a sequence of header tuples (e.g. ``[('Set-Cookie',
- 'foo=abc')]``) suitable for 'remembering' a set of credentials
- implied by the data passed as ``principal`` and ``*kw`` using the
- current :term:`authentication policy`. Common usage might look
- like so within the body of a view function (``response`` is
- assumed to be a :term:`WebOb` -style :term:`response` object
- computed previously by the view code)::
-
- from pyramid.security import remember
- headers = remember(request, 'chrism', password='123', max_age='86400')
- response.headerlist.extend(headers)
- return response
-
- If no :term:`authentication policy` is in use, this function will
- always return an empty sequence. If used, the composition and
- meaning of ``**kw`` must be agreed upon by the calling code and
- the effective authentication policy."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
- policy = reg.queryUtility(IAuthenticationPolicy)
- if policy is None:
- return []
- else:
- return policy.remember(request, principal, **kw)
-
-def forget(request):
- """ Return a sequence of header tuples (e.g. ``[('Set-Cookie',
- 'foo=abc')]``) suitable for 'forgetting' the set of credentials
- possessed by the currently authenticated user. A common usage
- might look like so within the body of a view function
- (``response`` is assumed to be an :term:`WebOb` -style
- :term:`response` object computed previously by the view code)::
-
- from pyramid.security import forget
- headers = forget(request)
- response.headerlist.extend(headers)
- return response
-
- If no :term:`authentication policy` is in use, this function will
- always return an empty sequence."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
- policy = reg.queryUtility(IAuthenticationPolicy)
- if policy is None:
- return []
- else:
- return policy.forget(request)
class PermitsResult(int):
def __new__(cls, s, *args):
@@ -294,3 +224,134 @@ class ACLAllowed(ACLPermitsResult):
summary is available as the ``msg`` attribute."""
boolval = 1
+class AuthenticationAPIMixin(object):
+
+ def _get_authentication_policy(self):
+ reg = _get_registry(self)
+ return reg.queryUtility(IAuthenticationPolicy)
+
+ @property
+ def authenticated_userid(self):
+ """ Return the userid of the currently authenticated user or
+ ``None`` if there is no :term:`authentication policy` in effect or
+ there is no currently authenticated user."""
+ policy = self._get_authentication_policy()
+ if policy is None:
+ return None
+ return policy.authenticated_userid(self)
+
+ @property
+ def unauthenticated_userid(self):
+ """ Return an object which represents the *claimed* (not verified) user
+ id of the credentials present in the request. ``None`` if there is no
+ :term:`authentication policy` in effect or there is no user data
+ associated with the current request. This differs from
+ :func:`~pyramid.security.authenticated_userid`, because the effective
+ authentication policy will not ensure that a record associated with the
+ userid exists in persistent storage."""
+ policy = self._get_authentication_policy()
+ if policy is None:
+ return None
+ return policy.unauthenticated_userid(self)
+
+ @property
+ def effective_principals(self):
+ """ Return the list of 'effective' :term:`principal` identifiers
+ for the ``request``. This will include the userid of the
+ currently authenticated user if a user is currently
+ authenticated. If no :term:`authentication policy` is in effect,
+ this will return an empty sequence."""
+ policy = self._get_authentication_policy()
+ if policy is None:
+ return [Everyone]
+ return policy.effective_principals(self)
+
+ # b/c
+ def _remember_userid(self, principal, **kw):
+ policy = self._get_authentication_policy()
+ if policy is None:
+ return
+ return policy.remember(self, principal, **kw)
+
+ def remember_userid(self, principal, **kw):
+ """ Sets a sequence of header tuples (e.g. ``[('Set-Cookie',
+ 'foo=abc')]``) on this request's response.
+ These headers are suitable for 'remembering' a set of credentials
+ implied by the data passed as ``principal`` and ``*kw`` using the
+ current :term:`authentication policy`. Common usage might look
+ like so within the body of a view function (``response`` is
+ assumed to be a :term:`WebOb` -style :term:`response` object
+ computed previously by the view code)::
+
+ .. code-block:: python
+
+ request.remember_userid('chrism', password='123', max_age='86400')
+
+ If no :term:`authentication policy` is in use, this function will
+ do nothing. If used, the composition and
+ meaning of ``**kw`` must be agreed upon by the calling code and
+ the effective authentication policy."""
+ headers = self._remember_userid(principal, **kw)
+ callback = lambda req, response: response.headerlist.extend(headers)
+ self.add_response_callback(callback)
+
+ # b/c
+ def _forget_userid(self):
+ policy = self._get_authentication_policy()
+ if policy is None:
+ return
+ return policy.forget(self)
+
+ def forget_userid(self):
+ """ Sets a sequence of header tuples (e.g. ``[('Set-Cookie',
+ 'foo=abc')]``) suitable for 'forgetting' the set of credentials
+ possessed by the currently authenticated user on the response.
+ A common usage might look like so within the body of a view function
+ (``response`` is assumed to be an :term:`WebOb` -style
+ :term:`response` object computed previously by the view code)::
+
+ .. code-block:: python
+
+ request.forget_userid()
+
+ If no :term:`authentication policy` is in use, this function will
+ be a noop."""
+ headers = self._forget_userid()
+ callback = lambda req, response: response.headerlist.extend(headers)
+ self.add_response_callback(callback)
+
+class AuthorizationAPIMixin(object):
+
+ def has_permission(self, permission, context=None):
+ """ Given a permission and an optional context,
+ returns an instance of :data:`pyramid.security.Allowed if the
+ permission is granted to this request with the provided context,
+ or the context already associated with the request. Otherwise,
+ returns an instance of :data:`pyramid.security.Denied`.
+ This method delegates to the current authentication and
+ authorization policies. Returns :data:`pyramid.security.Allowed`
+ unconditionally if no authentication policy has been registered
+ for this request.
+
+ .. versionchanged:: 1.5a3
+ If context is None, then attempt to use the context attribute
+ of self, if not set then the AttributeError is propergated.
+
+ :param permission: Does this request have the given permission?
+ :type permission: unicode, str
+ :param context: Typically a resource of a regsitered type.
+ :type context: object
+ :returns: `pyramid.security.PermitsResult`
+ """
+ if context is None:
+ context = self.context
+ reg = _get_registry(self)
+ authn_policy = reg.queryUtility(IAuthenticationPolicy)
+ if authn_policy is None:
+ return Allowed('No authentication policy in use.')
+ authz_policy = reg.queryUtility(IAuthorizationPolicy)
+ if authz_policy is None:
+ raise ValueError('Authentication policy registered without '
+ 'authorization policy') # should never happen
+ principals = authn_policy.effective_principals(self)
+ return authz_policy.permits(context, principals, permission)
View
11 pyramid/testing.py
@@ -27,6 +27,8 @@
from pyramid.security import (
Authenticated,
Everyone,
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin,
)
from pyramid.threadlocal import (
@@ -280,10 +282,13 @@ def get_csrf_token(self):
token = self.new_csrf_token()
return token
-
@implementer(IRequest)
-class DummyRequest(URLMethodsMixin, CallbackMethodsMixin, InstancePropertyMixin,
- LocalizerRequestMixin):
+class DummyRequest(URLMethodsMixin,
+ CallbackMethodsMixin,
+ InstancePropertyMixin,
+ LocalizerRequestMixin,
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin):
""" A DummyRequest object (incompletely) imitates a :term:`request` object.
The ``params``, ``environ``, ``headers``, ``path``, and
View
33 pyramid/tests/test_config/test_testing.py
@@ -1,6 +1,7 @@
import unittest
from pyramid.compat import text_
+from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin
from pyramid.tests.test_config import IDummy
class TestingConfiguratorMixinTests(unittest.TestCase):
@@ -24,28 +25,31 @@ def test_testing_securitypolicy(self):
self.assertEqual(ut.permissive, False)
def test_testing_securitypolicy_remember_result(self):
- from pyramid.security import remember
config = self._makeOne(autocommit=True)
pol = config.testing_securitypolicy(
'user', ('group1', 'group2'),
- permissive=False, remember_result=True)
+ permissive=False,
+ remember_result=[('X-Pyramid-Test', True)])
request = DummyRequest()
request.registry = config.registry
- val = remember(request, 'fred')
+ request.remember_userid('fred')
self.assertEqual(pol.remembered, 'fred')
+ val = dict(request.response.headerlist).get('X-Pyramid-Test')
self.assertEqual(val, True)
def test_testing_securitypolicy_forget_result(self):
- from pyramid.security import forget
config = self._makeOne(autocommit=True)
pol = config.testing_securitypolicy(
'user', ('group1', 'group2'),
- permissive=False, forget_result=True)
+ permissive=False,
+ forget_result=[('X-Pyramid-Test', True)])
request = DummyRequest()
request.registry = config.registry
- val = forget(request)
+ request.response = DummyResponse()
+ request.forget_userid()
self.assertEqual(pol.forgotten, True)
- self.assertEqual(val, True)
+ val = dict(request.response.headerlist).get('X-Pyramid-Test')
+ self.assertTrue(val)
def test_testing_resources(self):
from pyramid.traversal import find_resource
@@ -196,7 +200,15 @@ def test_testing_add_template(self):
class DummyEvent:
pass
-class DummyRequest:
+class DummyResponse(object):
+ def __init__(self):
+ self.headers = []
+
+ @property
+ def headerlist(self):
+ return self.headers
+
+class DummyRequest(AuthenticationAPIMixin, AuthorizationAPIMixin):
subpath = ()
matchdict = None
def __init__(self, environ=None):
@@ -205,4 +217,7 @@ def __init__(self, environ=None):
self.environ = environ
self.params = {}
self.cookies = {}
-
+ self.response = DummyResponse()
+
+ def add_response_callback(self, callback):
+ callback(self, self.response)
View
12 pyramid/tests/test_request.py
@@ -6,9 +6,10 @@
text_,
bytes_,
native_,
- iteritems_,
- iterkeys_,
- itervalues_,
+ )
+from pyramid.security import (
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin,
)
class TestRequest(unittest.TestCase):
@@ -53,6 +54,11 @@ def test_ResponseClass_is_pyramid_Response(self):
cls = self._getTargetClass()
self.assertEqual(cls.ResponseClass, Response)
+ def test_implements_security_apis(self):
+ apis = (AuthenticationAPIMixin, AuthorizationAPIMixin)
+ r = self._makeOne()
+ self.assertTrue(isinstance(r, apis))
+
def test_charset_defaults_to_utf8(self):
r = self._makeOne({'PATH_INFO':'/'})
self.assertEqual(r.charset, 'UTF-8')
View
369 pyramid/tests/test_security.py
@@ -1,7 +1,8 @@
import unittest
-from pyramid.testing import cleanUp
+from pyramid.testing import cleanUp, DummyRequest
+_TEST_HEADER = 'X-Pyramid-Test'
class TestAllPermissionsList(unittest.TestCase):
def setUp(self):
@@ -103,13 +104,38 @@ def test_it(self):
self.assertTrue('<ACLDenied instance at ' in repr(denied))
self.assertTrue("with msg %r>" % msg in repr(denied))
-class TestViewExecutionPermitted(unittest.TestCase):
+class TestPrincipalsAllowedByPermission(unittest.TestCase):
def setUp(self):
cleanUp()
def tearDown(self):
cleanUp()
+ def _callFUT(self, *arg):
+ from pyramid.security import principals_allowed_by_permission
+ return principals_allowed_by_permission(*arg)
+
+ def test_no_authorization_policy(self):
+ from pyramid.security import Everyone
+ context = DummyContext()
+ result = self._callFUT(context, 'view')
+ self.assertEqual(result, [Everyone])
+
+ def test_with_authorization_policy(self):
+ from pyramid.threadlocal import get_current_registry
+ registry = get_current_registry()
+ _registerAuthorizationPolicy(registry, 'yo')
+ context = DummyContext()
+ result = self._callFUT(context, 'view')
+ self.assertEqual(result, 'yo')
+
+class TestViewExecutionPermitted(unittest.TestCase):
+ def setUp(self):
+ cleanUp()
+
+ def tearDown(self):
+ cleanUp()
+
def _callFUT(self, *arg, **kw):
from pyramid.security import view_execution_permitted
return view_execution_permitted(*arg, **kw)
@@ -174,229 +200,258 @@ class IContext(Interface):
request = DummyRequest({})
directlyProvides(request, IRequest)
result = self._callFUT(context, request, '')
- self.assertTrue(result is True)
+ self.assertTrue(result)
-class TestHasPermission(unittest.TestCase):
+class AuthenticationAPIMixinTest(object):
def setUp(self):
cleanUp()
-
- def tearDown(self):
- cleanUp()
-
- def _callFUT(self, *arg):
- from pyramid.security import has_permission
- return has_permission(*arg)
-
- def test_no_authentication_policy(self):
- request = _makeRequest()
- result = self._callFUT('view', None, request)
- self.assertEqual(result, True)
- self.assertEqual(result.msg, 'No authentication policy in use.')
-
- def test_authentication_policy_no_authorization_policy(self):
- request = _makeRequest()
- _registerAuthenticationPolicy(request.registry, None)
- self.assertRaises(ValueError, self._callFUT, 'view', None, request)
-
- def test_authn_and_authz_policies_registered(self):
- request = _makeRequest()
- _registerAuthenticationPolicy(request.registry, None)
- _registerAuthorizationPolicy(request.registry, 'yo')
- self.assertEqual(self._callFUT('view', None, request), 'yo')
- def test_no_registry_on_request(self):
- from pyramid.threadlocal import get_current_registry
- request = DummyRequest({})
- registry = get_current_registry()
- _registerAuthenticationPolicy(registry, None)
- _registerAuthorizationPolicy(registry, 'yo')
- self.assertEqual(self._callFUT('view', None, request), 'yo')
-
-class TestAuthenticatedUserId(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
def tearDown(self):
cleanUp()
- def _callFUT(self, request):
+ def _makeOne(self):
+ from pyramid.registry import Registry
+ from pyramid.security import AuthenticationAPIMixin
+ request = DummyRequest(environ={})
+ self.assertTrue(isinstance(request, AuthenticationAPIMixin))
+ request.registry = Registry()
+ request.context = object()
+ return request
+
+ def _makeFakeOne(self):
+ class FakeRequest(DummyRequest):
+ @property
+ def authenticated_userid(req):
+ return 'authenticated_userid'
+
+ @property
+ def unauthenticated_userid(req):
+ return 'unauthenticated_userid'
+
+ @property
+ def effective_principals(req):
+ return 'effective_principals'
+
+ def _forget_userid(req):
+ return [('X-Pyramid-Test', 'forget_userid')]
+
+ def _remember_userid(req, principal, **kw):
+ return [('X-Pyramid-Test', 'remember_userid')]
+
+ return FakeRequest({})
+
+class TestAuthenticatedUserId(AuthenticationAPIMixinTest, unittest.TestCase):
+ def test_backward_compat_delegates_to_mixin(self):
+ request = self._makeFakeOne()
from pyramid.security import authenticated_userid
- return authenticated_userid(request)
+ self.assertEqual(authenticated_userid(request), 'authenticated_userid')
def test_no_authentication_policy(self):
- request = _makeRequest()
- result = self._callFUT(request)
- self.assertEqual(result, None)
+ request = self._makeOne()
+ self.assertEqual(request.authenticated_userid, None)
def test_with_authentication_policy(self):
- request = _makeRequest()
+ request = self._makeOne()
_registerAuthenticationPolicy(request.registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.authenticated_userid, 'yo')
def test_with_authentication_policy_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
- request = DummyRequest({})
registry = get_current_registry()
+ request = self._makeOne()
+ del request.registry
_registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.authenticated_userid, 'yo')
-class TestUnauthenticatedUserId(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
-
- def _callFUT(self, request):
+class TestUnAuthenticatedUserId(AuthenticationAPIMixinTest, unittest.TestCase):
+ def test_backward_compat_delegates_to_mixin(self):
+ request = self._makeFakeOne()
from pyramid.security import unauthenticated_userid
- return unauthenticated_userid(request)
+ self.assertEqual(unauthenticated_userid(request),
+ 'unauthenticated_userid')
def test_no_authentication_policy(self):
- request = _makeRequest()
- result = self._callFUT(request)
- self.assertEqual(result, None)
+ request = self._makeOne()
+ self.assertEqual(request.unauthenticated_userid, None)
def test_with_authentication_policy(self):
- request = _makeRequest()
+ request = self._makeOne()
_registerAuthenticationPolicy(request.registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.unauthenticated_userid, 'yo')
def test_with_authentication_policy_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
- request = DummyRequest({})
registry = get_current_registry()
+ request = self._makeOne()
+ del request.registry
_registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.unauthenticated_userid, 'yo')
-class TestEffectivePrincipals(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
-
- def _callFUT(self, request):
+class TestEffectivePrincipals(AuthenticationAPIMixinTest, unittest.TestCase):
+ def test_backward_compat_delegates_to_mixin(self):
+ request = self._makeFakeOne()
from pyramid.security import effective_principals
- return effective_principals(request)
+ self.assertEqual(effective_principals(request), 'effective_principals')
def test_no_authentication_policy(self):
from pyramid.security import Everyone
- request = _makeRequest()
- result = self._callFUT(request)
- self.assertEqual(result, [Everyone])
+ request = self._makeOne()
+ self.assertEqual(request.effective_principals, [Everyone])
def test_with_authentication_policy(self):
- request = _makeRequest()
+ request = self._makeOne()
_registerAuthenticationPolicy(request.registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.effective_principals, 'yo')
def test_with_authentication_policy_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
registry = get_current_registry()
- request = DummyRequest({})
+ request = self._makeOne()
+ del request.registry
_registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.effective_principals, 'yo')
-class TestPrincipalsAllowedByPermission(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
+class ResponseCallbackTestMixin(AuthenticationAPIMixinTest):
- def _callFUT(self, *arg):
- from pyramid.security import principals_allowed_by_permission
- return principals_allowed_by_permission(*arg)
+ def assert_response_headers_set(self, request):
+ request._process_response_callbacks(request.response)
+ headers = request.response.headerlist
+ self.assertTrue((_TEST_HEADER, self.principal) in headers, msg=headers)
- def test_no_authorization_policy(self):
- from pyramid.security import Everyone
- context = DummyContext()
- result = self._callFUT(context, 'view')
- self.assertEqual(result, [Everyone])
+class TestRememberUserId(ResponseCallbackTestMixin, unittest.TestCase):
+ principal = 'the4th'
- def test_with_authorization_policy(self):
+ def test_backward_compat_delegates_to_mixin(self):
+ request = self._makeFakeOne()
+ from pyramid.security import remember
+ self.assertEqual(remember(request, 'matt'),
+ [('X-Pyramid-Test', 'remember_userid')])
+
+ def test_with_no_authentication_policy(self):
+ request = self._makeOne()
+ headers_before = request.response.headers
+ request.remember_userid(self.principal)
+ self.assertEqual(headers_before, request.response.headers)
+
+ def test_with_authentication_policy(self):
+ request = self._makeOne()
+ _registerAuthenticationPolicy(request.registry, self.principal)
+ request.remember_userid(self.principal)
+ self.assert_response_headers_set(request)
+
+ def test_with_authentication_policy_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
registry = get_current_registry()
- _registerAuthorizationPolicy(registry, 'yo')
- context = DummyContext()
- result = self._callFUT(context, 'view')
- self.assertEqual(result, 'yo')
+ request = self._makeOne()
+ del request.registry
+ _registerAuthenticationPolicy(registry, self.principal)
+ request.remember_userid(self.principal)
+ self.assert_response_headers_set(request)
-class TestRemember(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
+class TestForgetUserId(ResponseCallbackTestMixin, unittest.TestCase):
+ principal = 'me-not'
- def _callFUT(self, *arg):
- from pyramid.security import remember
- return remember(*arg)
+ def _makeOne(self):
+ request = super(TestForgetUserId, self)._makeOne()
+ request.response.headers.add(_TEST_HEADER, self.principal)
+ return request
- def test_no_authentication_policy(self):
- request = _makeRequest()
- result = self._callFUT(request, 'me')
- self.assertEqual(result, [])
+ def test_backward_compat_delegates_to_mixin(self):
+ request = self._makeFakeOne()
+ from pyramid.security import forget
+ self.assertEqual(forget(request),
+ [('X-Pyramid-Test', 'forget_userid')])
+
+ def test_with_no_authentication_policy(self):
+ request = self._makeOne()
+ headers_before = request.response.headers
+ request.forget_userid()
+ self.assertEqual(headers_before, request.response.headers)
def test_with_authentication_policy(self):
- request = _makeRequest()
- registry = request.registry
- _registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request, 'me')
- self.assertEqual(result, 'yo')
+ request = self._makeOne()
+ policy = _registerAuthenticationPolicy(request.registry, self.principal)
+ policy._header_remembered = (_TEST_HEADER, self.principal)
+ request.forget_userid()
+ self.assert_response_headers_set(request)
def test_with_authentication_policy_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
registry = get_current_registry()
- request = DummyRequest({})
- _registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request, 'me')
- self.assertEqual(result, 'yo')
-
-class TestForget(unittest.TestCase):
+ request = self._makeOne()
+ del request.registry
+ policy = _registerAuthenticationPolicy(registry, self.principal)
+ policy._header_remembered = (_TEST_HEADER, self.principal)
+ request.forget_userid()
+ self.assert_response_headers_set(request)
+
+class TestHasPermission(unittest.TestCase):
def setUp(self):
cleanUp()
def tearDown(self):
cleanUp()
- def _callFUT(self, *arg):
- from pyramid.security import forget
- return forget(*arg)
+ def _makeOne(self):
+ from pyramid.security import AuthorizationAPIMixin
+ from pyramid.registry import Registry
+ mixin = AuthorizationAPIMixin()
+ mixin.registry = Registry()
+ mixin.context = object()
+ return mixin
+
+ def test_delegates_to_mixin(self):
+ mixin = self._makeOne()
+ from pyramid.security import has_permission
+ self.called_has_permission = False
+
+ def mocked_has_permission(*args, **kw):
+ self.called_has_permission = True
+
+ mixin.has_permission = mocked_has_permission
+ has_permission('view', object(), mixin)
+ self.assertTrue(self.called_has_permission)
def test_no_authentication_policy(self):
- request = _makeRequest()
- result = self._callFUT(request)
- self.assertEqual(result, [])
+ request = self._makeOne()
+ result = request.has_permission('view')
+ self.assertTrue(result)
+ self.assertEqual(result.msg, 'No authentication policy in use.')
- def test_with_authentication_policy(self):
- request = _makeRequest()
- _registerAuthenticationPolicy(request.registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ def test_with_no_authorization_policy(self):
+ request = self._makeOne()
+ _registerAuthenticationPolicy(request.registry, None)
+ self.assertRaises(ValueError,
+ request.has_permission, 'view', context=None)
- def test_with_authentication_policy_no_reg_on_request(self):
+ def test_with_authn_and_authz_policies_registered(self):
+ request = self._makeOne()
+ _registerAuthenticationPolicy(request.registry, None)
+ _registerAuthorizationPolicy(request.registry, 'yo')
+ self.assertEqual(request.has_permission('view', context=None), 'yo')
+
+ def test_with_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
registry = get_current_registry()
- request = DummyRequest({})
- _registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ request = self._makeOne()
+ del request.registry
+ _registerAuthenticationPolicy(registry, None)
+ _registerAuthorizationPolicy(registry, 'yo')
+ self.assertEqual(request.has_permission('view'), 'yo')
+
+ def test_with_no_context_passed(self):
+ request = self._makeOne()
+ self.assertTrue(request.has_permission('view'))
+
+ def test_with_no_context_passed_or_on_request(self):
+ request = self._makeOne()
+ del request.context
+ self.assertRaises(AttributeError, request.has_permission, 'view')
class DummyContext:
def __init__(self, *arg, **kw):
self.__dict__.update(kw)
-class DummyRequest:
- def __init__(self, environ):
- self.environ = environ
-
class DummyAuthenticationPolicy:
def __init__(self, result):
self.result = result
@@ -411,10 +466,12 @@ def authenticated_userid(self, request):
return self.result
def remember(self, request, principal, **kw):
- return self.result
+ headers = [(_TEST_HEADER, principal)]
+ self._header_remembered = headers[0]
+ return headers
def forget(self, request):
- return self.result
+ return [self._header_remembered]
class DummyAuthorizationPolicy:
def __init__(self, result):
@@ -437,11 +494,3 @@ def _registerAuthorizationPolicy(reg, result):
policy = DummyAuthorizationPolicy(result)
reg.registerUtility(policy, IAuthorizationPolicy)
return policy
-
-def _makeRequest():
- from pyramid.registry import Registry
- request = DummyRequest({})
- request.registry = Registry()
- return request
-
-

0 comments on commit 3c2f95e

Please sign in to comment.