Skip to content

Commit

Permalink
Merge branch 'master' into 1.4-branch
Browse files Browse the repository at this point in the history
  • Loading branch information
mcdonc committed Sep 19, 2012
2 parents 68c25d3 + 643a834 commit 68c6020
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 13 deletions.
9 changes: 6 additions & 3 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/narr/introspector.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions docs/narr/viewconfig.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
6 changes: 6 additions & 0 deletions docs/whatsnew-1.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------------

Expand Down
23 changes: 23 additions & 0 deletions pyramid/config/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

from pyramid.util import object_description

from pyramid.session import check_csrf_token

from .util import as_sorted_tuple

class XHRPredicate(object):
Expand Down Expand Up @@ -226,3 +228,24 @@ def __call__(self, context, request):
# 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

31 changes: 30 additions & 1 deletion pyramid/config/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ def add_view(
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
Expand Down Expand Up @@ -989,6 +990,29 @@ def add_view(
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
Expand All @@ -1007,7 +1031,9 @@ def add_view(
: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)
Expand Down Expand Up @@ -1061,6 +1087,7 @@ def view(context, request):
containment=containment,
request_type=request_type,
match_param=match_param,
check_csrf=check_csrf,
custom=predvalseq(custom_predicates),
)
)
Expand Down Expand Up @@ -1098,6 +1125,7 @@ def discrim_func():
header=header,
path_info=path_info,
match_param=match_param,
check_csrf=check_csrf,
callable=view,
mapper=mapper,
decorator=decorator,
Expand Down Expand Up @@ -1340,6 +1368,7 @@ def add_default_view_predicates(self):
('containment', p.ContainmentPredicate),
('request_type', p.RequestTypePredicate),
('match_param', p.MatchParamPredicate),
('check_csrf', p.CheckCSRFTokenPredicate),
('custom', p.CustomPredicate),
):
self.add_view_predicate(name, factory)
Expand Down
17 changes: 12 additions & 5 deletions pyramid/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
43 changes: 43 additions & 0 deletions pyramid/tests/test_config/test_predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,49 @@ def test_phash(self):
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'
Expand Down
11 changes: 8 additions & 3 deletions pyramid/tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,9 @@ def test_it_bad_encoding(self):
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()
Expand All @@ -371,11 +371,16 @@ def test_success_default_token(self):
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'
Expand Down
2 changes: 1 addition & 1 deletion pyramid/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def my_view(context, request):
``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
Expand Down

0 comments on commit 68c6020

Please sign in to comment.