summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/glossary.rst9
-rw-r--r--docs/narr/introspector.rst4
-rw-r--r--docs/narr/viewconfig.rst31
-rw-r--r--docs/whatsnew-1.4.rst6
-rw-r--r--pyramid/config/predicates.py23
-rw-r--r--pyramid/config/views.py31
-rw-r--r--pyramid/session.py17
-rw-r--r--pyramid/tests/test_config/test_predicates.py43
-rw-r--r--pyramid/tests/test_session.py11
-rw-r--r--pyramid/view.py2
10 files changed, 164 insertions, 13 deletions
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 2b006da20..96dd826d1 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -818,9 +818,12 @@ Glossary
application.
session factory
- A callable, which, when called with a single argument named
- ``request`` (a :term:`request` object), returns a
- :term:`session` object.
+ A callable, which, when called with a single argument named ``request``
+ (a :term:`request` object), returns a :term:`session` object. See
+ :ref:`using_the_default_session_factory`,
+ :ref:`using_alternate_session_factories` and
+ :meth:`pyramid.config.Configurator.set_session_factory` for more
+ information.
Mako
`Mako <http://www.makotemplates.org/>`_ is a template language language
diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst
index 6bfaf11c0..b88f3f0c8 100644
--- a/docs/narr/introspector.rst
+++ b/docs/narr/introspector.rst
@@ -393,6 +393,10 @@ introspectables in categories not described here.
The ``match_param`` argument passed to ``add_view``.
+ ``csrf_token``
+
+ The ``csrf_token`` argument passed to ``add_view``.
+
``callable``
The (resolved) ``view`` argument passed to ``add_view``. Represents the
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 23b4fde68..f65435cc6 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -394,6 +394,28 @@ configured view.
consideration when deciding whether or not to invoke the associated view
callable.
+``check_csrf``
+ If specified, this value should be one of ``None``, ``True``, ``False``, or
+ a string representing the 'check name'. If the value is ``True`` or a
+ string, CSRF checking will be performed. If the value is ``False`` or
+ ``None``, CSRF checking will not be performed.
+
+ If the value provided is a string, that string will be used as the 'check
+ name'. If the value provided is ``True``, ``csrf_token`` will be used as
+ the check name.
+
+ If CSRF checking is performed, the checked value will be the value of
+ ``request.params[check_name]``. This value will be compared against the
+ value of ``request.session.get_csrf_token()``, and the check will pass if
+ these two values are the same. If the check passes, the associated view
+ will be permitted to execute. If the check fails, the associated view
+ will not be permitted to execute.
+
+ Note that using this feature requires a :term:`session factory` to have
+ been configured.
+
+ .. versionadded:: 1.4a2
+
``custom_predicates``
If ``custom_predicates`` is specified, it must be a sequence of references
to custom predicate callables. Use custom predicates when no set of
@@ -407,6 +429,15 @@ configured view.
If ``custom_predicates`` is not specified, no custom predicates are
used.
+``predicates``
+ Pass a key/value pair here to use a third-party predicate registered via
+ :meth:`pyramid.config.Configurator.add_view_predicate`. More than one
+ key/value pair can be used at the same time. See
+ :ref:`view_and_route_predicates` for more information about third-party
+ predicates.
+
+ .. versionadded:: 1.4a1
+
.. index::
single: view_config decorator
diff --git a/docs/whatsnew-1.4.rst b/docs/whatsnew-1.4.rst
index 76320f6e6..86bfc7c0a 100644
--- a/docs/whatsnew-1.4.rst
+++ b/docs/whatsnew-1.4.rst
@@ -156,6 +156,12 @@ Minor Feature Additions
- A new :func:`pyramid.session.check_csrf_token` convenience API function was
added.
+- A ``check_csrf`` view predicate was added. For example, you can now do
+ ``config.add_view(someview, check_csrf=True)``. When the predicate is
+ checked, if the ``csrf_token`` value in ``request.params`` matches the csrf
+ token in the request's session, the view will be permitted to execute.
+ Otherwise, it will not be permitted to execute.
+
Backwards Incompatibilities
---------------------------
diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py
index 9e0ee28c1..77b55d9b3 100644
--- a/pyramid/config/predicates.py
+++ b/pyramid/config/predicates.py
@@ -13,6 +13,8 @@ from pyramid.urldispatch import _compile_route
from pyramid.util import object_description
+from pyramid.session import check_csrf_token
+
from .util import as_sorted_tuple
class XHRPredicate(object):
@@ -226,3 +228,24 @@ class TraversePredicate(object):
# injects ``traverse`` into the matchdict. As a result, we just
# return True.
return True
+
+class CheckCSRFTokenPredicate(object):
+
+ check_csrf_token = staticmethod(check_csrf_token) # testing
+
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'check_csrf = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ val = self.val
+ if val:
+ if val is True:
+ val = 'csrf_token'
+ return self.check_csrf_token(request, val, raises=False)
+ return True
+
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 36896a17e..9ace96c1d 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -662,6 +662,7 @@ class ViewsConfiguratorMixin(object):
mapper=None,
http_cache=None,
match_param=None,
+ check_csrf=None,
**predicates):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
@@ -989,6 +990,29 @@ class ViewsConfiguratorMixin(object):
variable. If the regex matches, this predicate will be
``True``.
+ check_csrf
+
+ If specified, this value should be one of ``None``, ``True``,
+ ``False``, or a string representing the 'check name'. If the value
+ is ``True`` or a string, CSRF checking will be performed. If the
+ value is ``False`` or ``None``, CSRF checking will not be performed.
+
+ If the value provided is a string, that string will be used as the
+ 'check name'. If the value provided is ``True``, ``csrf_token`` will
+ be used as the check name.
+
+ If CSRF checking is performed, the checked value will be the value
+ of ``request.params[check_name]``. This value will be compared
+ against the value of ``request.session.get_csrf_token()``, and the
+ check will pass if these two values are the same. If the check
+ passes, the associated view will be permitted to execute. If the
+ check fails, the associated view will not be permitted to execute.
+
+ Note that using this feature requires a :term:`session factory` to
+ have been configured.
+
+ .. versionadded:: 1.4a2
+
custom_predicates
This value should be a sequence of references to custom
@@ -1007,7 +1031,9 @@ class ViewsConfiguratorMixin(object):
:meth:`pyramid.config.Configurator.add_view_predicate`. More than
one key/value pair can be used at the same time. See
:ref:`view_and_route_predicates` for more information about
- third-party predicates. This argument is new as of Pyramid 1.4.
+ third-party predicates.
+
+ .. versionadded: 1.4a1
"""
view = self.maybe_dotted(view)
@@ -1061,6 +1087,7 @@ class ViewsConfiguratorMixin(object):
containment=containment,
request_type=request_type,
match_param=match_param,
+ check_csrf=check_csrf,
custom=predvalseq(custom_predicates),
)
)
@@ -1098,6 +1125,7 @@ class ViewsConfiguratorMixin(object):
header=header,
path_info=path_info,
match_param=match_param,
+ check_csrf=check_csrf,
callable=view,
mapper=mapper,
decorator=decorator,
@@ -1340,6 +1368,7 @@ class ViewsConfiguratorMixin(object):
('containment', p.ContainmentPredicate),
('request_type', p.RequestTypePredicate),
('match_param', p.MatchParamPredicate),
+ ('check_csrf', p.CheckCSRFTokenPredicate),
('custom', p.CustomPredicate),
):
self.add_view_predicate(name, factory)
diff --git a/pyramid/session.py b/pyramid/session.py
index 3f700564d..3b2834693 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -81,19 +81,26 @@ def signed_deserialize(serialized, secret, hmac=hmac):
return pickle.loads(pickled)
-def check_csrf_token(request, token='csrf_token'):
+def check_csrf_token(request, token='csrf_token', raises=True):
""" Check the CSRF token in the request's session against the value in
``request.params.get(token)``. If ``token`` is not supplied, the string
value ``csrf_token`` will be used as the token value. If the value in
``request.params.get(token)`` doesn't match the value supplied by
- ``request.session.get_csrf_token()``, this function will raise an
- :exc:`pyramid.httpexceptions.HTTPBadRequest` exception. If the CSRF
- check is successful, this function will return ``True``.
+ ``request.session.get_csrf_token()``, and ``raises`` is ``True``, this
+ function will raise an :exc:`pyramid.httpexceptions.HTTPBadRequest`
+ exception. If the check does succeed and ``raises`` is ``False``, this
+ function will return ``False``. If the CSRF check is successful, this
+ function will return ``True`` unconditionally.
+
+ Note that using this function requires that a :term:`session factory` is
+ configured.
.. versionadded:: 1.4a2
"""
if request.params.get(token) != request.session.get_csrf_token():
- raise HTTPBadRequest('incorrect CSRF token')
+ if raises:
+ raise HTTPBadRequest('incorrect CSRF token')
+ return False
return True
def UnencryptedCookieSessionFactoryConfig(
diff --git a/pyramid/tests/test_config/test_predicates.py b/pyramid/tests/test_config/test_predicates.py
index e33a31458..005b1b27a 100644
--- a/pyramid/tests/test_config/test_predicates.py
+++ b/pyramid/tests/test_config/test_predicates.py
@@ -256,6 +256,49 @@ class TestTraversePredicate(unittest.TestCase):
inst = self._makeOne('/abc')
self.assertEqual(inst.phash(), '')
+class Test_CheckCSRFTokenPredicate(unittest.TestCase):
+ def _makeOne(self, val, config):
+ from pyramid.config.predicates import CheckCSRFTokenPredicate
+ return CheckCSRFTokenPredicate(val, config)
+
+ def test_text(self):
+ inst = self._makeOne(True, None)
+ self.assertEqual(inst.text(), 'check_csrf = True')
+
+ def test_phash(self):
+ inst = self._makeOne(True, None)
+ self.assertEqual(inst.phash(), 'check_csrf = True')
+
+ def test_it_call_val_True(self):
+ inst = self._makeOne(True, None)
+ request = Dummy()
+ def check_csrf_token(req, val, raises=True):
+ self.assertEqual(req, request)
+ self.assertEqual(val, 'csrf_token')
+ self.assertEqual(raises, False)
+ return True
+ inst.check_csrf_token = check_csrf_token
+ result = inst(None, request)
+ self.assertEqual(result, True)
+
+ def test_it_call_val_str(self):
+ inst = self._makeOne('abc', None)
+ request = Dummy()
+ def check_csrf_token(req, val, raises=True):
+ self.assertEqual(req, request)
+ self.assertEqual(val, 'abc')
+ self.assertEqual(raises, False)
+ return True
+ inst.check_csrf_token = check_csrf_token
+ result = inst(None, request)
+ self.assertEqual(result, True)
+
+ def test_it_call_val_False(self):
+ inst = self._makeOne(False, None)
+ request = Dummy()
+ result = inst(None, request)
+ self.assertEqual(result, True)
+
class predicate(object):
def __repr__(self):
return 'predicate'
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index 21cf16b12..b3e0e20c4 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -356,9 +356,9 @@ class Test_signed_deserialize(unittest.TestCase):
self.assertRaises(ValueError, self._callFUT, serialized, 'secret')
class Test_check_csrf_token(unittest.TestCase):
- def _callFUT(self, request, token):
+ def _callFUT(self, request, token, raises=True):
from ..session import check_csrf_token
- return check_csrf_token(request, token)
+ return check_csrf_token(request, token, raises=raises)
def test_success(self):
request = testing.DummyRequest()
@@ -371,11 +371,16 @@ class Test_check_csrf_token(unittest.TestCase):
request.params['csrf_token'] = request.session.get_csrf_token()
self.assertEqual(check_csrf_token(request), True)
- def test_failure(self):
+ def test_failure_raises(self):
from pyramid.httpexceptions import HTTPBadRequest
request = testing.DummyRequest()
self.assertRaises(HTTPBadRequest, self._callFUT, request, 'csrf_token')
+ def test_failure_no_raises(self):
+ request = testing.DummyRequest()
+ result = self._callFUT(request, 'csrf_token', raises=False)
+ self.assertEqual(result, False)
+
class DummySessionFactory(dict):
_dirty = False
_cookie_name = 'session'
diff --git a/pyramid/view.py b/pyramid/view.py
index 12a2efde6..76f466b83 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -170,7 +170,7 @@ class view_config(object):
``request_type``, ``route_name``, ``request_method``, ``request_param``,
``containment``, ``xhr``, ``accept``, ``header``, ``path_info``,
``custom_predicates``, ``decorator``, ``mapper``, ``http_cache``,
- ``match_param``, and ``predicates``.
+ ``match_param``, ``csrf_token``, and ``predicates``.
The meanings of these arguments are the same as the arguments passed to
:meth:`pyramid.config.Configurator.add_view`. If any argument is left