Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

require_csrf to replace check_csrf #2413

Merged
merged 5 commits into from
Apr 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,14 @@ Glossary
Examples of built-in derivers including view mapper, the permission
checker, and applying a renderer to a dictionary returned from the view.

truthy string
A string represeting a value of ``True``. Acceptable values are
``t``, ``true``, ``y``, ``yes``, ``on`` and ``1``.

falsey string
A string represeting a value of ``False``. Acceptable values are
``f``, ``false``, ``n``, ``no``, ``off`` and ``0``.

pip
The `Python Packaging Authority's <https://www.pypa.io/>`_ recommended
tool for installing Python packages.
Expand Down
42 changes: 6 additions & 36 deletions docs/narr/hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,12 @@ the user-defined :term:`view callable`:
This element will also output useful debugging information when
``pyramid.debug_authorization`` is enabled.

``csrf_view``

Used to check the CSRF token provided in the request. This element is a
no-op if both the ``require_csrf`` view option and the
``pyramid.require_default_csrf`` setting are disabled.

``owrapped_view``

Invokes the wrapped view defined by the ``wrapper`` option.
Expand Down Expand Up @@ -1656,42 +1662,6 @@ View derivers are unique in that they have access to most of the options
passed to :meth:`pyramid.config.Configurator.add_view` in order to decide what
to do, and they have a chance to affect every view in the application.

Let's look at one more example which will protect views by requiring a CSRF
token unless ``disable_csrf=True`` is passed to the view:

.. code-block:: python
:linenos:

from pyramid.response import Response
from pyramid.session import check_csrf_token

def require_csrf_view(view, info):
wrapper_view = view
if not info.options.get('disable_csrf', False):
def wrapper_view(context, request):
if request.method == 'POST':
check_csrf_token(request)
return view(context, request)
return wrapper_view

require_csrf_view.options = ('disable_csrf',)

config.add_view_deriver(require_csrf_view)

def protected_view(request):
return Response('protected')

def unprotected_view(request):
return Response('unprotected')

config.add_view(protected_view, name='safe')
config.add_view(unprotected_view, name='unsafe', disable_csrf=True)

Navigating to ``/safe`` with a POST request will then fail when the call to
:func:`pyramid.session.check_csrf_token` raises a
:class:`pyramid.exceptions.BadCSRFToken` exception. However, ``/unsafe`` will
not error.

Ordering View Derivers
~~~~~~~~~~~~~~~~~~~~~~

Expand Down
70 changes: 56 additions & 14 deletions docs/narr/sessions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,21 @@ Or include it as a header in a jQuery AJAX request:
The handler for the URL that receives the request should then require that the
correct CSRF token is supplied.

.. index::
single: session.new_csrf_token

Using the ``session.new_csrf_token`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To explicitly create a new CSRF token, use the ``session.new_csrf_token()``
method. This differs only from ``session.get_csrf_token()`` inasmuch as it
clears any existing CSRF token, creates a new CSRF token, sets the token into
the session, and returns the token.

.. code-block:: python

token = request.session.new_csrf_token()

Checking CSRF Tokens Manually
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand All @@ -389,12 +404,51 @@ header named ``X-CSRF-Token``.

# ...

.. index::
single: session.new_csrf_token
.. _auto_csrf_checking:

Checking CSRF Tokens Automatically
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 1.7

:app:`Pyramid` supports automatically checking CSRF tokens on POST requests.
Any other request may be checked manually. This feature can be turned on
globally for an application using the ``pyramid.require_default_csrf`` setting.

If the ``pyramid.required_default_csrf`` setting is a :term:`truthy string` or
``True`` then the default CSRF token parameter will be ``csrf_token``. If a
different token is desired, it may be passed as the value. Finally, a
:term:`falsey string` or ``False`` will turn off automatic CSRF checking
globally on every POST request.

No matter what, CSRF checking may be explicitly enabled or disabled on a
per-view basis using the ``require_csrf`` view option. This option is of the
same format as the ``pyramid.require_default_csrf`` setting, accepting strings
or boolean values.

If ``require_csrf`` is ``True`` but does not explicitly define a token to
check, then the token name is pulled from whatever was set in the
``pyramid.require_default_csrf`` setting. Finally, if that setting does not
explicitly define a token, then ``csrf_token`` is the token required. This token
name will be required in ``request.params`` which is a combination of the
query string and a submitted form body.

It is always possible to pass the token in the ``X-CSRF-Token`` header as well.
There is currently no way to define an alternate name for this header without
performing CSRF checking manually.

If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` exception
will be raised. This exception may be caught and handled by an
:term:`exception view` but, by default, will result in a ``400 Bad Request``
response being sent to the client.

Checking CSRF Tokens with a View Predicate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. deprecated:: 1.7
Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead
to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised.

A convenient way to require a valid CSRF token for a particular view is to
include ``check_csrf=True`` as a view predicate. See
:meth:`pyramid.config.Configurator.add_view`.
Expand All @@ -410,15 +464,3 @@ include ``check_csrf=True`` as a view predicate. See
predicate system, when it doesn't find a view, raises ``HTTPNotFound``
instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different
from calling :func:`pyramid.session.check_csrf_token`.

Using the ``session.new_csrf_token`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To explicitly create a new CSRF token, use the ``session.new_csrf_token()``
method. This differs only from ``session.get_csrf_token()`` inasmuch as it
clears any existing CSRF token, creates a new CSRF token, sets the token into
the session, and returns the token.

.. code-block:: python

token = request.session.new_csrf_token()
26 changes: 26 additions & 0 deletions docs/narr/viewconfig.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,32 @@ Non-Predicate Arguments
only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with
the first element of ``None``, i.e., ``(None, {'public':True})``.


``require_csrf``

CSRF checks only affect POST requests. Any other request methods will pass
untouched. This option is used in combination with the
``pyramid.require_default_csrf`` setting to control which request parameters
are checked for CSRF tokens.

This feature requires a configured :term:`session factory`.

If this option is set to ``True`` then CSRF checks will be enabled for POST
requests to this view. The required token will be whatever was specified by
the ``pyramid.require_default_csrf`` setting, or will fallback to
``csrf_token``.

If this option is set to a string then CSRF checks will be enabled and it
will be used as the required token regardless of the
``pyramid.require_default_csrf`` setting.

If this option is set to ``False`` then CSRF checks will be disabled
regardless of the ``pyramid.require_default_csrf`` setting.

See :ref:`auto_csrf_checking` for more information.

.. versionadded:: 1.7

``wrapper``
The :term:`view name` of a different :term:`view configuration` which will
receive the response body of this view as the ``request.wrapped_body``
Expand Down
6 changes: 5 additions & 1 deletion pyramid/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ def __init__(self, d=None, _environ_=os.environ, **kw):
config_prevent_cachebust)
eff_prevent_cachebust = asbool(eget('PYRAMID_PREVENT_CACHEBUST',
config_prevent_cachebust))
require_default_csrf = self.get('pyramid.require_default_csrf')
eff_require_default_csrf = require_default_csrf

update = {
'debug_authorization': eff_debug_all or eff_debug_auth,
Expand All @@ -134,6 +136,7 @@ def __init__(self, d=None, _environ_=os.environ, **kw):
'default_locale_name':eff_locale_name,
'prevent_http_cache':eff_prevent_http_cache,
'prevent_cachebust':eff_prevent_cachebust,
'require_default_csrf':eff_require_default_csrf,

'pyramid.debug_authorization': eff_debug_all or eff_debug_auth,
'pyramid.debug_notfound': eff_debug_all or eff_debug_notfound,
Expand All @@ -145,7 +148,8 @@ def __init__(self, d=None, _environ_=os.environ, **kw):
'pyramid.default_locale_name':eff_locale_name,
'pyramid.prevent_http_cache':eff_prevent_http_cache,
'pyramid.prevent_cachebust':eff_prevent_cachebust,
}
'pyramid.require_default_csrf':eff_require_default_csrf,
}

self.update(update)

Expand Down
51 changes: 49 additions & 2 deletions pyramid/config/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def add_view(
http_cache=None,
match_param=None,
check_csrf=None,
require_csrf=None,
**view_options):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
Expand Down Expand Up @@ -366,6 +367,31 @@ def add_view(
before returning the response from the view. This effectively
disables any HTTP caching done by ``http_cache`` for that response.

require_csrf

.. versionadded:: 1.7

CSRF checks only affect POST requests. Any other request methods
will pass untouched. This option is used in combination with the
``pyramid.require_default_csrf`` setting to control which
request parameters are checked for CSRF tokens.

This feature requires a configured :term:`session factory`.

If this option is set to ``True`` then CSRF checks will be enabled
for POST requests to this view. The required token will be whatever
was specified by the ``pyramid.require_default_csrf`` setting, or
will fallback to ``csrf_token``.

If this option is set to a string then CSRF checks will be enabled
and it will be used as the required token regardless of the
``pyramid.require_default_csrf`` setting.

If this option is set to ``False`` then CSRF checks will be disabled
regardless of the ``pyramid.require_default_csrf`` setting.

See :ref:`auto_csrf_checking` for more information.

wrapper

The :term:`view name` of a different :term:`view
Expand Down Expand Up @@ -587,6 +613,11 @@ def wrapper(context, request):

check_csrf

.. deprecated:: 1.7
Use the ``require_csrf`` option or see :ref:`auto_csrf_checking`
instead to have :class:`pyramid.exceptions.BadCSRFToken`
exceptions raised.

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
Expand Down Expand Up @@ -682,7 +713,18 @@ def wrapper(context, request):
'Predicate" in the "Hooks" chapter of the documentation '
'for more information.'),
DeprecationWarning,
stacklevel=4
stacklevel=4,
)

if check_csrf is not None:
warnings.warn(
('The "check_csrf" argument to Configurator.add_view is '
'deprecated as of Pyramid 1.7. Use the "require_csrf" option '
'instead or see "Checking CSRF Tokens Automatically" in the '
'"Sessions" chapter of the documentation for more '
'information.'),
DeprecationWarning,
stacklevel=4,
)

view = self.maybe_dotted(view)
Expand Down Expand Up @@ -805,6 +847,8 @@ def discrim_func():
path_info=path_info,
match_param=match_param,
check_csrf=check_csrf,
http_cache=http_cache,
require_csrf=require_csrf,
callable=view,
mapper=mapper,
decorator=decorator,
Expand Down Expand Up @@ -860,6 +904,7 @@ def register(permission=permission, renderer=renderer):
decorator=decorator,
mapper=mapper,
http_cache=http_cache,
require_csrf=require_csrf,
extra_options=ovals,
)
derived_view.__discriminator__ = lambda *arg: discriminator
Expand Down Expand Up @@ -1184,6 +1229,7 @@ def add_default_view_derivers(self):
d = pyramid.viewderivers
derivers = [
('secured_view', d.secured_view),
('csrf_view', d.csrf_view),
('owrapped_view', d.owrapped_view),
('http_cached_view', d.http_cached_view),
('decorated_view', d.decorated_view),
Expand Down Expand Up @@ -1284,7 +1330,7 @@ def _derive_view(self, view, permission=None, predicates=(),
viewname=None, accept=None, order=MAX_ORDER,
phash=DEFAULT_PHASH, decorator=None,
mapper=None, http_cache=None, context=None,
extra_options=None):
require_csrf=None, extra_options=None):
view = self.maybe_dotted(view)
mapper = self.maybe_dotted(mapper)
if isinstance(renderer, string_types):
Expand All @@ -1311,6 +1357,7 @@ def _derive_view(self, view, permission=None, predicates=(),
mapper=mapper,
decorator=decorator,
http_cache=http_cache,
require_csrf=require_csrf,
)
if extra_options:
options.update(extra_options)
Expand Down
3 changes: 3 additions & 0 deletions pyramid/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ def check_csrf_token(request,
Note that using this function requires that a :term:`session factory` is
configured.

See :ref:`auto_csrf_checking` for information about how to secure your
application automatically against CSRF attacks.

.. versionadded:: 1.4a2
"""
supplied_token = request.params.get(token, request.headers.get(header, ""))
Expand Down
7 changes: 3 additions & 4 deletions pyramid/settings.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from pyramid.compat import string_types

truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1'))
falsey = frozenset(('f', 'false', 'n', 'no', 'off', '0'))

def asbool(s):
""" Return the boolean value ``True`` if the case-lowered value of string
input ``s`` is any of ``t``, ``true``, ``y``, ``on``, or ``1``, otherwise
return the boolean value ``False``. If ``s`` is the value ``None``,
return ``False``. If ``s`` is already one of the boolean values ``True``
or ``False``, return it."""
input ``s`` is a :term:`truthy string`. If ``s`` is already one of the
boolean values ``True`` or ``False``, return it."""
if s is None:
return False
if isinstance(s, bool):
Expand Down
Loading