From 363b54c97d90e37e732a6ec2cc53addae861dfaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Wed, 1 Jul 2020 18:33:12 -0400 Subject: [PATCH] add tests and docs --- CHANGES.rst | 6 ++++- docs/narr/viewconfig.rst | 6 ++++- src/pyramid/config/routes.py | 7 ++++- src/pyramid/config/views.py | 6 ++++- src/pyramid/interfaces.py | 9 ++++--- src/pyramid/predicates.py | 2 +- tests/test_config/test_predicates.py | 23 +++++++++++++--- tests/test_config/test_routes.py | 12 +++++++++ tests/test_config/test_views.py | 40 ++++++++++++++++++++++++++++ tests/test_security.py | 23 ++++++++++++++++ 10 files changed, 122 insertions(+), 12 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6b6e1ebbb7..9f8b715779 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,10 +22,14 @@ Features - ``pyramid.config.Configurator.set_security_policy``. - ``pyramid.interfaces.ISecurityPolicy`` - ``pyramid.request.Request.authenticated_identity``. + - ``pyramid.request.Request.authenticated_userid`` + - ``pyramid.request.Request.is_authenticated`` - ``pyramid.authentication.SessionAuthenticationHelper`` - ``pyramid.authorization.ACLHelper`` + - config predicate ``is_authenticated=True/False`` - See https://github.com/Pylons/pyramid/pull/3465 + See https://github.com/Pylons/pyramid/pull/3465 and + https://github.com/Pylons/pyramid/pull/3598 - Changed the default ``serializer`` on ``pyramid.session.SignedCookieSessionFactory`` to use diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index c60b7b50d1..e6ab61f5da 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -496,7 +496,11 @@ configured view. ``is_authenticated`` - XXX doc doc + This value, if specified, should be either ``True`` or ``False``. If it is + specified and is ``True``, the request must be for an authenticated user, + as determined by the :term:`security policy` in use. If it is specified and + ``False``, the associated view callable will match only if the request does + not have an authenticated user. .. versionadded:: 2.0 diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index 0fbfcca0c3..feb28c7a78 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -270,7 +270,12 @@ def add_route( is_authenticated - XXX doc doc + This value, if specified, should be either ``True`` or ``False``. + If it is specified and is ``True``, the route will only match if + the request has an authenticated user, as determined by the + :term:`security policy` in use. If it is specified and ``False``, + the route will only match if the request does not have an + authenticated user. .. versionadded:: 2.0 diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 87f2cbcd70..4a5723a143 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -714,7 +714,11 @@ def wrapper(context, request): is_authenticated - XXX doc doc + This value, if specified, should be either ``True`` or ``False``. + If it is specified and is ``True``, the request must be for an + authenticated user, as determined by the :term:`security policy` in + use. If it is specified and ``False``, the associated view callable + will match only if the request does not have an authenticated user. ..versionadded:: 2.0 diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 85539c2f2f..a44ad54bac 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -114,11 +114,13 @@ def app_iter_range(start, stop): serves up only the given start:stop range. """ authenticated_identity = Attribute( - """XXX Doc doc""" + """An object representing the authenticated user, as determined by + the security policy in use. The object's class and meaning is defined + by the security policy. Will be None for unauthenticated requests.""" ) authenticated_userid = Attribute( - """XXX Doc doc""" + """A string to identify the authenticated user, or None.""" ) body = Attribute( @@ -242,7 +244,8 @@ def encode_content(encoding='gzip', lazy=False): headers = Attribute(""" The headers in a dictionary-like object """) is_authenticated = Attribute( - """XXX doc doc""" + """A boolean indicated whether the request has an authenticated + user (determined by the security policy in use).""" ) last_modified = Attribute( diff --git a/src/pyramid/predicates.py b/src/pyramid/predicates.py index fe8bc228c8..f04086e5fd 100644 --- a/src/pyramid/predicates.py +++ b/src/pyramid/predicates.py @@ -286,7 +286,7 @@ def text(self): phash = text def __call__(self, context, request): - return request.is_authenticated == self.val + return bool(request.is_authenticated) is self.val class EffectivePrincipalsPredicate: diff --git a/tests/test_config/test_predicates.py b/tests/test_config/test_predicates.py index 6797e71bcf..7e2f32786a 100644 --- a/tests/test_config/test_predicates.py +++ b/tests/test_config/test_predicates.py @@ -454,13 +454,28 @@ def test_header_multiple_mixed_fails(self): self.assertFalse(predicates[0](Dummy(), request)) def test_is_authenticated_true_matches(self): - ... + _, predicates, _ = self._callFUT(is_authenticated=True) + request = DummyRequest() + request.is_authenticated = True + self.assertTrue(predicates[0](Dummy(), request)) + def test_is_authenticated_true_fails(self): - ... + _, predicates, _ = self._callFUT(is_authenticated=True) + request = DummyRequest() + request.is_authenticated = False + self.assertFalse(predicates[0](Dummy(), request)) + def test_is_authenticated_false_matches(self): - ... + _, predicates, _ = self._callFUT(is_authenticated=False) + request = DummyRequest() + request.is_authenticated = False + self.assertTrue(predicates[0](Dummy(), request)) + def test_is_authenticated_false_fails(self): - ... + _, predicates, _ = self._callFUT(is_authenticated=False) + request = DummyRequest() + request.is_authenticated = True + self.assertFalse(predicates[0](Dummy(), request)) def test_unknown_predicate(self): from pyramid.exceptions import ConfigurationError diff --git a/tests/test_config/test_routes.py b/tests/test_config/test_routes.py index 8227784ec3..605f618577 100644 --- a/tests/test_config/test_routes.py +++ b/tests/test_config/test_routes.py @@ -184,6 +184,18 @@ def test_add_route_with_request_param(self): request.params = {} self.assertEqual(predicate(None, request), False) + def test_add_route_with_is_authenticated(self): + config = self._makeOne(autocommit=True) + config.add_route('name', 'path', is_authenticated=True) + route = self._assertRoute(config, 'name', 'path', 1) + predicate = route.predicates[0] + request = self._makeRequest(config) + request.is_authenticated = True + self.assertEqual(predicate(None, request), True) + request = self._makeRequest(config) + request.is_authenticated = False + self.assertEqual(predicate(None, request), False) + def test_add_route_with_custom_predicates(self): import warnings diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py index 2a55ad45dc..09714d82ee 100644 --- a/tests/test_config/test_views.py +++ b/tests/test_config/test_views.py @@ -1742,6 +1742,46 @@ def test_add_view_with_xhr_false(self): request.is_xhr = False self._assertNotFound(wrapper, None, request) + def test_add_view_with_is_authenticated_true_matches(self): + from pyramid.renderers import null_renderer as nr + + view = lambda *arg: 'OK' + config = self._makeOne(autocommit=True) + config.add_view(view=view, is_authenticated=True, renderer=nr) + wrapper = self._getViewCallable(config) + request = self._makeRequest(config) + request.is_authenticated = True + self.assertEqual(wrapper(None, request), 'OK') + + def test_add_view_with_is_authenticated_true_no_match(self): + view = lambda *arg: 'OK' + config = self._makeOne(autocommit=True) + config.add_view(view=view, is_authenticated=True) + wrapper = self._getViewCallable(config) + request = self._makeRequest(config) + request.is_authenticated = False + self._assertNotFound(wrapper, None, request) + + def test_add_view_with_is_authenticated_false_matches(self): + from pyramid.renderers import null_renderer as nr + + view = lambda *arg: 'OK' + config = self._makeOne(autocommit=True) + config.add_view(view=view, is_authenticated=False, renderer=nr) + wrapper = self._getViewCallable(config) + request = self._makeRequest(config) + request.is_authenticated = False + self.assertEqual(wrapper(None, request), 'OK') + + def test_add_view_with_is_authenticated_false_no_match(self): + view = lambda *arg: 'OK' + config = self._makeOne(autocommit=True) + config.add_view(view=view, is_authenticated=False) + wrapper = self._getViewCallable(config) + request = self._makeRequest(config) + request.is_authenticated = True + self._assertNotFound(wrapper, None, request) + def test_add_view_with_header_badregex(self): view = lambda *arg: 'OK' config = self._makeOne() diff --git a/tests/test_security.py b/tests/test_security.py index bf29081005..72598f5706 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -393,6 +393,29 @@ def test_security_policy_trumps_authentication_policy(self): self.assertEqual(request.unauthenticated_userid, 'wat') +class TestIsAuthenticated(unittest.TestCase): + def setUp(self): + testing.setUp() + + def tearDown(self): + testing.tearDown() + + def test_no_security_policy(self): + request = _makeRequest() + self.assertIs(request.is_authenticated, False) + + def test_with_security_policy(self): + request = _makeRequest() + _registerSecurityPolicy(request.registry, '123') + self.assertIs(request.is_authenticated, True) + + def test_with_legacy_security_policy(self): + request = _makeRequest() + _registerAuthenticationPolicy(request.registry, 'yo') + _registerLegacySecurityPolicy(request.registry) + self.assertEqual(request.authenticated_userid, 'yo') + + class TestEffectivePrincipals(unittest.TestCase): def setUp(self): testing.setUp()