Michael Merickel
2016-04-18 00ee409f54d3038640d1cc721a1b6e7bf46583ef
replace pyramid.require_default_csrf setting with config.set_default_csrf_options
11 files modified
454 ■■■■■ changed files
docs/api/config.rst 2 ●●●●● patch | view | raw | blame | history
docs/narr/extconfig.rst 1 ●●●● patch | view | raw | blame | history
docs/narr/introspector.rst 25 ●●●●● patch | view | raw | blame | history
docs/narr/sessions.rst 66 ●●●● patch | view | raw | blame | history
pyramid/config/security.py 57 ●●●●● patch | view | raw | blame | history
pyramid/config/views.py 39 ●●●●● patch | view | raw | blame | history
pyramid/interfaces.py 10 ●●●●● patch | view | raw | blame | history
pyramid/session.py 7 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_security.py 21 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_viewderivers.py 168 ●●●●● patch | view | raw | blame | history
pyramid/viewderivers.py 58 ●●●●● patch | view | raw | blame | history
docs/api/config.rst
@@ -35,6 +35,7 @@
     .. automethod:: set_authentication_policy
     .. automethod:: set_authorization_policy
     .. automethod:: set_default_csrf_options
     .. automethod:: set_default_permission
     .. automethod:: add_permission
@@ -65,6 +66,7 @@
     .. automethod:: add_traverser
     .. automethod:: add_tween
     .. automethod:: add_route_predicate
     .. automethod:: add_subscriber_predicate
     .. automethod:: add_view_predicate
     .. automethod:: add_view_deriver
     .. automethod:: set_request_factory
docs/narr/extconfig.rst
@@ -261,6 +261,7 @@
- :meth:`pyramid.config.Configurator.add_view_predicate`
- :meth:`pyramid.config.Configurator.add_view_deriver`
- :meth:`pyramid.config.Configurator.set_authorization_policy`
- :meth:`pyramid.config.Configurator.set_default_csrf_options`
- :meth:`pyramid.config.Configurator.set_default_permission`
- :meth:`pyramid.config.Configurator.set_view_mapper`
docs/narr/introspector.rst
@@ -337,6 +337,31 @@
    The permission name passed to ``set_default_permission``.
``default csrf options``
  There will be one and only one introspectable in the ``default csrf options``
  category. It represents a call to the
  :meth:`pyramid.config.Configurator.set_default_csrf_options` method. It
  will have the following data.
  ``require_csrf``
    The default value for ``require_csrf`` if left unspecified on calls to
    :meth:`pyramid.config.Configurator.add_view`.
  ``token``
    The name of the token searched in ``request.POST`` to find a valid CSRF
    token.
  ``header``
    The name of the request header searched to find a valid CSRF token.
  ``safe_methods``
    The list of HTTP methods considered safe and exempt from CSRF checks.
``views``
  Each introspectable in the ``views`` category represents a call to
docs/narr/sessions.rst
@@ -396,13 +396,13 @@
.. code-block:: python
    from pyramid.session import check_csrf_token
   from pyramid.session import check_csrf_token
    def myview(request):
        # Require CSRF Token
        check_csrf_token(request)
   def myview(request):
       # Require CSRF Token
       check_csrf_token(request)
        # ...
       # ...
.. _auto_csrf_checking:
@@ -414,41 +414,45 @@
:app:`Pyramid` supports automatically checking CSRF tokens on requests with an
unsafe method as defined by RFC2616. Any other request may be checked manually.
This feature can be turned on globally for an application using the
``pyramid.require_default_csrf`` setting.
:meth:`pyramid.config.Configurator.set_default_csrf_options` directive.
For example:
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 request.
.. code-block:: python
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.
   from pyramid.config import Configurator
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.POST`` which is the submitted form body.
   config = Configurator()
   config.set_default_csrf_options(require_csrf=True)
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.
CSRF checking may be explicitly enabled or disabled on a per-view basis using
the ``require_csrf`` view option. A value of ``True`` or ``False`` will
override the default set by ``set_default_csrf_options``. For example:
In addition to token based CSRF checks, the automatic CSRF checking will also
check the referrer of the request to ensure that it matches one of the trusted
origins. By default the only trusted origin is the current host, however
additional origins may be configured by setting
.. code-block:: python
   @view_config(route_name='hello', require_csrf=False)
   def myview(request):
       # ...
When CSRF checking is active, the token and header used to find the
supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively,
unless otherwise overridden by ``set_default_csrf_options``. The token is
checked against the value in ``request.POST`` which is the submitted form body.
If this value is not present, then the header will be checked.
In addition to token based CSRF checks, if the request is using HTTPS then the
automatic CSRF checking will also check the referrer of the request to ensure
that it matches one of the trusted origins. By default the only trusted origin
is the current host, however additional origins may be configured by setting
``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they
are non standard). If a host in the list of domains starts with a ``.`` then
that will allow all subdomains as well as the domain without the ``.``.
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.
If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or
:class:`pyramid.exceptions.BadCSRFOrigin` 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pyramid/config/security.py
@@ -1,11 +1,15 @@
from zope.interface import implementer
from pyramid.interfaces import (
    IAuthorizationPolicy,
    IAuthenticationPolicy,
    IDefaultCSRFOptions,
    IDefaultPermission,
    PHASE1_CONFIG,
    PHASE2_CONFIG,
    )
from pyramid.config.util import as_sorted_tuple
from pyramid.exceptions import ConfigurationError
from pyramid.util import action_method
@@ -138,7 +142,6 @@
        self.action(IDefaultPermission, register, order=PHASE1_CONFIG,
                    introspectables=(intr, perm_intr,))
    def add_permission(self, permission_name):
        """
        A configurator directive which registers a free-standing
@@ -159,3 +162,55 @@
        intr['value'] = permission_name
        self.action(None, introspectables=(intr,))
    @action_method
    def set_default_csrf_options(
        self,
        require_csrf=True,
        token='csrf_token',
        header='X-CSRF-Token',
        safe_methods=('GET', 'HEAD', 'OPTIONS', 'TRACE'),
    ):
        """
        Set the default CSRF options used by subsequent view registrations.
        ``require_csrf`` controls whether CSRF checks will be automatically
        enabled on each view in the application. This value is used as the
        fallback when ``require_csrf`` is left at the default of ``None`` on
        :meth:`pyramid.config.Configurator.add_view`.
        ``token`` is the name of the CSRF token used in the body of the
        request, accessed via ``request.POST[token]``. Default: ``csrf_token``.
        ``header`` is the name of the header containing the CSRF token,
        accessed via ``request.headers[header]``. Default: ``X-CSRF-Token``.
        If ``token`` or ``header`` are set to ``None`` they will not be used
        for checking CSRF tokens.
        ``safe_methods`` is an iterable of HTTP methods which are expected to
        not contain side-effects as defined by RFC2616. Safe methods will
        never be automatically checked for CSRF tokens.
        Default: ``('GET', 'HEAD', 'OPTIONS', TRACE')``.
        """
        options = DefaultCSRFOptions(require_csrf, token, header, safe_methods)
        def register():
            self.registry.registerUtility(options, IDefaultCSRFOptions)
        intr = self.introspectable('default csrf view options',
                                   None,
                                   options,
                                   'default csrf view options')
        intr['require_csrf'] = require_csrf
        intr['token'] = token
        intr['header'] = header
        intr['safe_methods'] = as_sorted_tuple(safe_methods)
        self.action(IDefaultCSRFOptions, register, order=PHASE1_CONFIG,
                    introspectables=(intr,))
@implementer(IDefaultCSRFOptions)
class DefaultCSRFOptions(object):
    def __init__(self, require_csrf, token, header, safe_methods):
        self.require_csrf = require_csrf
        self.token = token
        self.header = header
        self.safe_methods = frozenset(safe_methods)
pyramid/config/views.py
@@ -371,24 +371,24 @@
          .. 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.
          A boolean option or ``None``. Default: ``None``.
          If this option is set to ``True`` then CSRF checks will be enabled
          for requests to this view. The required token or header default to
          ``csrf_token`` and ``X-CSRF-Token``, respectively.
          CSRF checks only affect "unsafe" methods as defined by RFC2616. By
          default, these methods are anything except
          ``GET``, ``HEAD``, ``OPTIONS``, and ``TRACE``.
          The defaults here may be overridden by
          :meth:`pyramid.config.Configurator.set_default_csrf_options`.
          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.
          regardless of the default ``require_csrf`` setting passed
          to ``set_default_csrf_options``.
          See :ref:`auto_csrf_checking` for more information.
@@ -1229,7 +1229,6 @@
        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),
@@ -1246,6 +1245,16 @@
            )
            last = name
        # leave the csrf_view loosely coupled to the rest of the pipeline
        # by ensuring nothing in the default pipeline depends on the order
        # of the csrf_view
        self.add_view_deriver(
            d.csrf_view,
            'csrf_view',
            under='secured_view',
            over='owrapped_view',
        )
    def derive_view(self, view, attr=None, renderer=None):
        """
        Create a :term:`view callable` using the function, instance,
pyramid/interfaces.py
@@ -916,6 +916,16 @@
    for all view configurations which do not explicitly declare their
    own."""
class IDefaultCSRFOptions(Interface):
    """ An object representing the default CSRF settings to be used for
    all view configurations which do not explicitly declare their own."""
    require_csrf = Attribute(
        'Boolean attribute. If ``True``, then CSRF checks will be enabled by '
        'default for the view unless overridden.')
    token = Attribute('The key to be matched in the body of the request.')
    header = Attribute('The header to be matched with the CSRF token.')
    safe_methods = Attribute('A set of safe methods that skip CSRF checks.')
class ISessionFactory(Interface):
    """ An interface representing a factory which accepts a request object and
    returns an ISession object """
pyramid/session.py
@@ -109,7 +109,6 @@
    return pickle.loads(pickled)
def check_csrf_origin(request, trusted_origins=None, raises=True):
    """
    Check the Origin of the request to see if it is a cross site request or
@@ -233,16 +232,18 @@
       considered valid. It must be passed in either the request body or
       a header.
    """
    supplied_token = ""
    # If this is a POST/PUT/etc request, then we'll check the body to see if it
    # has a token. We explicitly use request.POST here because CSRF tokens
    # should never appear in an URL as doing so is a security issue. We also
    # explicitly check for request.POST here as we do not support sending form
    # encoded data over anything but a request.POST.
    supplied_token = request.POST.get(token, "")
    if token is not None:
        supplied_token = request.POST.get(token, "")
    # If we were unable to locate a CSRF token in a request body, then we'll
    # check to see if there are any headers that have a value for us.
    if supplied_token == "":
    if supplied_token == "" and header is not None:
        supplied_token = request.headers.get(header, "")
    expected_token = request.session.get_csrf_token()
pyramid/tests/test_config/test_security.py
@@ -98,3 +98,24 @@
        intr = D['introspectable']
        self.assertEqual(intr['value'], 'perm')
    def test_set_default_csrf_options(self):
        from pyramid.interfaces import IDefaultCSRFOptions
        config = self._makeOne(autocommit=True)
        config.set_default_csrf_options()
        result = config.registry.getUtility(IDefaultCSRFOptions)
        self.assertEqual(result.require_csrf, True)
        self.assertEqual(result.token, 'csrf_token')
        self.assertEqual(result.header, 'X-CSRF-Token')
        self.assertEqual(list(sorted(result.safe_methods)),
                         ['GET', 'HEAD', 'OPTIONS', 'TRACE'])
    def test_changing_set_default_csrf_options(self):
        from pyramid.interfaces import IDefaultCSRFOptions
        config = self._makeOne(autocommit=True)
        config.set_default_csrf_options(
            require_csrf=False, token='DUMMY', header=None, safe_methods=('PUT',))
        result = config.registry.getUtility(IDefaultCSRFOptions)
        self.assertEqual(result.require_csrf, False)
        self.assertEqual(result.token, 'DUMMY')
        self.assertEqual(result.header, None)
        self.assertEqual(list(sorted(result.safe_methods)), ['PUT'])
pyramid/tests/test_viewderivers.py
@@ -1090,45 +1090,52 @@
        self.assertRaises(ConfigurationError, self.config._derive_view, 
            view, http_cache=(None,))
    def test_csrf_view_requires_bool_or_str_in_require_csrf(self):
        def view(request): pass
        try:
            self.config._derive_view(view, require_csrf=object())
        except ConfigurationError as ex:
            self.assertEqual(
                'View option "require_csrf" must be a string or boolean value',
                ex.args[0])
        else: # pragma: no cover
            raise AssertionError
    def test_csrf_view_ignores_GET(self):
        response = DummyResponse()
        def inner_view(request):
            return response
        request = self._makeRequest()
        request.method = 'GET'
        view = self.config._derive_view(inner_view, require_csrf=True)
        result = view(None, request)
        self.assertTrue(result is response)
    def test_csrf_view_requires_bool_or_str_in_config_setting(self):
        def view(request): pass
        self.config.add_settings({'pyramid.require_default_csrf': object()})
        try:
            self.config._derive_view(view)
        except ConfigurationError as ex:
            self.assertEqual(
                'Config setting "pyramid.require_default_csrf" must be a '
                'string or boolean value',
                ex.args[0])
        else: # pragma: no cover
            raise AssertionError
    def test_csrf_view_fails_with_bad_POST_header(self):
        from pyramid.exceptions import BadCSRFToken
        def inner_view(request): pass
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.headers = {'X-CSRF-Token': 'bar'}
        view = self.config._derive_view(inner_view, require_csrf=True)
        self.assertRaises(BadCSRFToken, lambda: view(None, request))
    def test_csrf_view_requires_header(self):
    def test_csrf_view_passes_with_good_POST_header(self):
        response = DummyResponse()
        def inner_view(request):
            return response
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'POST'
        request.POST = {}
        request.session = DummySession({'csrf_token': 'foo'})
        request.headers = {'X-CSRF-Token': 'foo'}
        view = self.config._derive_view(inner_view, require_csrf=True)
        result = view(None, request)
        self.assertTrue(result is response)
    def test_csrf_view_requires_param(self):
    def test_csrf_view_fails_with_bad_POST_token(self):
        from pyramid.exceptions import BadCSRFToken
        def inner_view(request): pass
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.POST = {'csrf_token': 'bar'}
        view = self.config._derive_view(inner_view, require_csrf=True)
        self.assertRaises(BadCSRFToken, lambda: view(None, request))
    def test_csrf_view_passes_with_good_POST_token(self):
        response = DummyResponse()
        def inner_view(request):
            return response
@@ -1136,8 +1143,8 @@
        request.scheme = "http"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.POST = {'DUMMY': 'foo'}
        view = self.config._derive_view(inner_view, require_csrf='DUMMY')
        request.POST = {'csrf_token': 'foo'}
        view = self.config._derive_view(inner_view, require_csrf=True)
        result = view(None, request)
        self.assertTrue(result is response)
@@ -1152,43 +1159,10 @@
        request.referrer = "https://example.com/login/"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.POST = {'DUMMY': 'foo'}
        view = self.config._derive_view(inner_view, require_csrf='DUMMY')
        result = view(None, request)
        self.assertTrue(result is response)
    def test_csrf_view_ignores_GET(self):
        response = DummyResponse()
        def inner_view(request):
            return response
        request = self._makeRequest()
        request.method = 'GET'
        request.POST = {'csrf_token': 'foo'}
        view = self.config._derive_view(inner_view, require_csrf=True)
        result = view(None, request)
        self.assertTrue(result is response)
    def test_csrf_view_fails_on_bad_POST_param(self):
        from pyramid.exceptions import BadCSRFToken
        def inner_view(request): pass
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.POST = {'DUMMY': 'bar'}
        view = self.config._derive_view(inner_view, require_csrf='DUMMY')
        self.assertRaises(BadCSRFToken, lambda: view(None, request))
    def test_csrf_view_fails_on_bad_POST_header(self):
        from pyramid.exceptions import BadCSRFToken
        def inner_view(request): pass
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'POST'
        request.POST = {}
        request.session = DummySession({'csrf_token': 'foo'})
        request.headers = {'X-CSRF-Token': 'bar'}
        view = self.config._derive_view(inner_view, require_csrf='DUMMY')
        self.assertRaises(BadCSRFToken, lambda: view(None, request))
    def test_csrf_view_fails_on_bad_PUT_header(self):
        from pyramid.exceptions import BadCSRFToken
@@ -1196,10 +1170,9 @@
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'PUT'
        request.POST = {}
        request.session = DummySession({'csrf_token': 'foo'})
        request.headers = {'X-CSRF-Token': 'bar'}
        view = self.config._derive_view(inner_view, require_csrf='DUMMY')
        view = self.config._derive_view(inner_view, require_csrf=True)
        self.assertRaises(BadCSRFToken, lambda: view(None, request))
    def test_csrf_view_fails_on_bad_referrer(self):
@@ -1212,7 +1185,7 @@
        request.domain = "example.com"
        request.referrer = "https://not-example.com/evil/"
        request.registry.settings = {}
        view = self.config._derive_view(inner_view, require_csrf='DUMMY')
        view = self.config._derive_view(inner_view, require_csrf=True)
        self.assertRaises(BadCSRFOrigin, lambda: view(None, request))
    def test_csrf_view_fails_on_bad_origin(self):
@@ -1225,24 +1198,21 @@
        request.domain = "example.com"
        request.headers = {"Origin": "https://not-example.com/evil/"}
        request.registry.settings = {}
        view = self.config._derive_view(inner_view, require_csrf='DUMMY')
        view = self.config._derive_view(inner_view, require_csrf=True)
        self.assertRaises(BadCSRFOrigin, lambda: view(None, request))
    def test_csrf_view_uses_config_setting_truthy(self):
        response = DummyResponse()
        def inner_view(request):
            return response
    def test_csrf_view_enabled_by_default(self):
        from pyramid.exceptions import BadCSRFToken
        def inner_view(request): pass
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.POST = {'csrf_token': 'foo'}
        self.config.add_settings({'pyramid.require_default_csrf': 'yes'})
        self.config.set_default_csrf_options(require_csrf=True)
        view = self.config._derive_view(inner_view)
        result = view(None, request)
        self.assertTrue(result is response)
        self.assertRaises(BadCSRFToken, lambda: view(None, request))
    def test_csrf_view_uses_config_setting_with_custom_token(self):
    def test_csrf_view_uses_custom_csrf_token(self):
        response = DummyResponse()
        def inner_view(request):
            return response
@@ -1251,20 +1221,35 @@
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.POST = {'DUMMY': 'foo'}
        self.config.add_settings({'pyramid.require_default_csrf': 'DUMMY'})
        self.config.set_default_csrf_options(require_csrf=True, token='DUMMY')
        view = self.config._derive_view(inner_view)
        result = view(None, request)
        self.assertTrue(result is response)
    def test_csrf_view_uses_config_setting_falsey(self):
    def test_csrf_view_uses_custom_csrf_header(self):
        response = DummyResponse()
        def inner_view(request):
            return response
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.params['csrf_token'] = 'foo'
        self.config.add_settings({'pyramid.require_default_csrf': 'no'})
        request.headers = {'DUMMY': 'foo'}
        self.config.set_default_csrf_options(require_csrf=True, header='DUMMY')
        view = self.config._derive_view(inner_view)
        result = view(None, request)
        self.assertTrue(result is response)
    def test_csrf_view_uses_custom_methods(self):
        response = DummyResponse()
        def inner_view(request):
            return response
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'PUT'
        request.session = DummySession({'csrf_token': 'foo'})
        self.config.set_default_csrf_options(
            require_csrf=True, safe_methods=['PUT'])
        view = self.config._derive_view(inner_view)
        result = view(None, request)
        self.assertTrue(result is response)
@@ -1277,23 +1262,9 @@
        request.scheme = "http"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.POST = {'DUMMY': 'foo'}
        self.config.add_settings({'pyramid.require_default_csrf': 'yes'})
        view = self.config._derive_view(inner_view, require_csrf='DUMMY')
        result = view(None, request)
        self.assertTrue(result is response)
    def test_csrf_view_uses_config_setting_when_view_option_is_true(self):
        response = DummyResponse()
        def inner_view(request):
            return response
        request = self._makeRequest()
        request.scheme = "http"
        request.method = 'POST'
        request.session = DummySession({'csrf_token': 'foo'})
        request.POST = {'DUMMY': 'foo'}
        self.config.add_settings({'pyramid.require_default_csrf': 'DUMMY'})
        view = self.config._derive_view(inner_view, require_csrf=True)
        request.POST = {'csrf_token': 'bar'}
        self.config.set_default_csrf_options(require_csrf=True)
        view = self.config._derive_view(inner_view, require_csrf=False)
        result = view(None, request)
        self.assertTrue(result is response)
@@ -1303,7 +1274,7 @@
            raise ValueError
        def excview(request):
            return 'hello'
        self.config.add_settings({'pyramid.require_default_csrf': 'yes'})
        self.config.set_default_csrf_options(require_csrf=True)
        self.config.set_session_factory(
            lambda request: DummySession({'csrf_token': 'foo'}))
        self.config.add_view(view, name='foo', require_csrf=False)
@@ -1320,7 +1291,7 @@
        def view(request):
            raise ValueError
        def excview(request): pass
        self.config.add_settings({'pyramid.require_default_csrf': 'yes'})
        self.config.set_default_csrf_options(require_csrf=True)
        self.config.set_session_factory(
            lambda request: DummySession({'csrf_token': 'foo'}))
        self.config.add_view(view, name='foo', require_csrf=False)
@@ -1342,7 +1313,7 @@
            raise ValueError
        def excview(request):
            return 'hello'
        self.config.add_settings({'pyramid.require_default_csrf': 'yes'})
        self.config.set_default_csrf_options(require_csrf=True)
        self.config.set_session_factory(
            lambda request: DummySession({'csrf_token': 'foo'}))
        self.config.add_view(view, name='foo', require_csrf=False)
@@ -1675,6 +1646,7 @@
            environ = {}
        self.environ = environ
        self.params = {}
        self.POST = {}
        self.cookies = {}
        self.headers = {}
        self.response = DummyResponse()
pyramid/viewderivers.py
@@ -15,6 +15,7 @@
from pyramid.interfaces import (
    IAuthenticationPolicy,
    IAuthorizationPolicy,
    IDefaultCSRFOptions,
    IDebugLogger,
    IResponse,
    IViewMapper,
@@ -22,7 +23,6 @@
    )
from pyramid.compat import (
    string_types,
    is_bound_method,
    is_unbound_method,
    )
@@ -38,10 +38,6 @@
    PredicateMismatch,
    )
from pyramid.httpexceptions import HTTPForbidden
from pyramid.settings import (
    falsey,
    truthy,
)
from pyramid.util import object_description
from pyramid.view import render_view_to_response
from pyramid import renderers
@@ -464,40 +460,30 @@
decorated_view.options = ('decorator',)
def _parse_csrf_setting(val, error_source):
    if val:
        if isinstance(val, string_types):
            if val.lower() in truthy:
                val = True
            elif val.lower() in falsey:
                val = False
        elif not isinstance(val, bool):
            raise ConfigurationError(
                '{0} must be a string or boolean value'
                .format(error_source))
    return val
SAFE_REQUEST_METHODS = frozenset(["GET", "HEAD", "OPTIONS", "TRACE"])
def csrf_view(view, info):
    default_val = _parse_csrf_setting(
        info.settings.get('pyramid.require_default_csrf'),
        'Config setting "pyramid.require_default_csrf"')
    explicit_val = _parse_csrf_setting(
        info.options.get('require_csrf'),
        'View option "require_csrf"')
    resolved_val = explicit_val
    if (explicit_val is True and default_val) or explicit_val is None:
        resolved_val = default_val
    if resolved_val is True:
        resolved_val = 'csrf_token'
    explicit_val = info.options.get('require_csrf')
    defaults = info.registry.queryUtility(IDefaultCSRFOptions)
    if defaults is None:
        default_val = False
        token = 'csrf_token'
        header = 'X-CSRF-Token'
        safe_methods = frozenset(["GET", "HEAD", "OPTIONS", "TRACE"])
    else:
        default_val = defaults.require_csrf
        token = defaults.token
        header = defaults.header
        safe_methods = defaults.safe_methods
    enabled = (
        explicit_val is True or
        (explicit_val is not False and default_val)
    )
    # disable if both header and token are disabled
    enabled = enabled and (token or header)
    wrapped_view = view
    if resolved_val:
    if enabled:
        def csrf_view(context, request):
            # Assume that anything not defined as 'safe' by RFC2616 needs
            # protection
            if (
                request.method not in SAFE_REQUEST_METHODS and
                request.method not in safe_methods and
                (
                    # skip exception views unless value is explicitly defined
                    getattr(request, 'exception', None) is None or
@@ -505,7 +491,7 @@
                )
            ):
                check_csrf_origin(request, raises=True)
                check_csrf_token(request, resolved_val, raises=True)
                check_csrf_token(request, token, header, raises=True)
            return view(context, request)
        wrapped_view = csrf_view
    return wrapped_view