From 9e9fa9ac40bdd79fbce69f94a13d705e40f3d458 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 22 Mar 2016 01:02:05 -0500 Subject: add a csrf_view to the view pipeline supporting a require_csrf option --- docs/narr/hooks.rst | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'docs') diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 2c3782387..e7db97565 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1580,6 +1580,11 @@ There are several built-in view derivers that :app:`Pyramid` will automatically apply to any view. Below they are defined in order from furthest to closest to the user-defined :term:`view callable`: +``csrf_view`` + + Used to check the CSRF token provided in the request. This element is a + no-op if ``require_csrf`` is not defined. + ``secured_view`` Enforce the ``permission`` defined on the view. This element is a no-op if no @@ -1656,27 +1661,32 @@ 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: +Let's override the default CSRF checker to default to on instead of off and +only check ``POST`` requests: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.session import check_csrf_token + from pyramid.viewderivers import INGRESS - def require_csrf_view(view, info): + def csrf_view(view, info): + val = info.options.get('require_csrf', True) wrapper_view = view - if not info.options.get('disable_csrf', False): - def wrapper_view(context, request): + if val: + if val is True: + val = 'csrf_token' + def csrf_view(context, request): if request.method == 'POST': - check_csrf_token(request) + check_csrf_token(request, val, raises=True) return view(context, request) + wrapper_view = csrf_view return wrapper_view - require_csrf_view.options = ('disable_csrf',) + csrf_view.options = ('require_csrf',) - config.add_view_deriver(require_csrf_view) + config.add_view_deriver(csrf_view, 'csrf_view', over='secured_view', under=INGRESS) def protected_view(request): return Response('protected') @@ -1685,7 +1695,7 @@ token unless ``disable_csrf=True`` is passed to the view: return Response('unprotected') config.add_view(protected_view, name='safe') - config.add_view(unprotected_view, name='unsafe', disable_csrf=True) + config.add_view(unprotected_view, name='unsafe', require_csrf=False) Navigating to ``/safe`` with a POST request will then fail when the call to :func:`pyramid.session.check_csrf_token` raises a -- cgit v1.2.3 From 6b35eb6ca3b271e2943d37307c925c5733e082d9 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 10 Apr 2016 20:50:10 -0500 Subject: rewrite csrf checks to support a global setting to turn it on - only check csrf on POST - support "pyramid.require_default_csrf" setting - support "require_csrf=True" to fallback to the global setting to determine the token name --- docs/glossary.rst | 8 ++++++++ docs/narr/hooks.rst | 52 ++++++-------------------------------------------- docs/narr/sessions.rst | 42 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 48 deletions(-) (limited to 'docs') diff --git a/docs/glossary.rst b/docs/glossary.rst index 039665926..ef9c66b99 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -1098,3 +1098,11 @@ Glossary implementing the :class:`pyramid.interfaces.IViewDeriver` interface. 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``. diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index e7db97565..28d1e09d5 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1580,11 +1580,6 @@ There are several built-in view derivers that :app:`Pyramid` will automatically apply to any view. Below they are defined in order from furthest to closest to the user-defined :term:`view callable`: -``csrf_view`` - - Used to check the CSRF token provided in the request. This element is a - no-op if ``require_csrf`` is not defined. - ``secured_view`` Enforce the ``permission`` defined on the view. This element is a no-op if no @@ -1595,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. @@ -1661,47 +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 override the default CSRF checker to default to on instead of off and -only check ``POST`` requests: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.session import check_csrf_token - from pyramid.viewderivers import INGRESS - - def csrf_view(view, info): - val = info.options.get('require_csrf', True) - wrapper_view = view - if val: - if val is True: - val = 'csrf_token' - def csrf_view(context, request): - if request.method == 'POST': - check_csrf_token(request, val, raises=True) - return view(context, request) - wrapper_view = csrf_view - return wrapper_view - - csrf_view.options = ('require_csrf',) - - config.add_view_deriver(csrf_view, 'csrf_view', over='secured_view', under=INGRESS) - - 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', require_csrf=False) - -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 ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index db554a93b..3baed1cb8 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -389,8 +389,43 @@ 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`` +resposne being sent to the client. Checking CSRF Tokens with a View Predicate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -411,6 +446,9 @@ include ``check_csrf=True`` as a view predicate. See instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different from calling :func:`pyramid.session.check_csrf_token`. +.. index:: + single: session.new_csrf_token + Using the ``session.new_csrf_token`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 15b97dc81c8bcdc039f8f2293f85812f68a076da Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 10 Apr 2016 20:51:23 -0500 Subject: deprecate the check_csrf predicate --- docs/narr/sessions.rst | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'docs') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 3baed1cb8..4e8f6db88 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -430,6 +430,10 @@ resposne 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`. -- cgit v1.2.3 From 769da1215a0287f4161e58f36d8d4b7650154202 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 10 Apr 2016 21:14:22 -0500 Subject: cleanup some references in the docs --- docs/narr/sessions.rst | 32 ++++++++++++++++---------------- docs/narr/viewconfig.rst | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 16 deletions(-) (limited to 'docs') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 4e8f6db88..d66e86258 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -425,7 +440,7 @@ 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`` -resposne being sent to the client. +response being sent to the client. Checking CSRF Tokens with a View Predicate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -449,18 +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`. - -.. 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() diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 0bd52b6e2..e645185f5 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -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`` -- cgit v1.2.3