diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 2bce9aa7d8..ed972aa632 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -558,6 +558,7 @@ CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' # Settings for CSRF cookie. CSRF_COOKIE_NAME = 'csrftoken' +CSRF_COOKIE_AGE = 60 * 60 * 24 * 7 * 52 CSRF_COOKIE_DOMAIN = None CSRF_COOKIE_PATH = '/' CSRF_COOKIE_SECURE = False diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index 54019fb4c5..eb7fe7d8cb 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -196,7 +196,7 @@ class CsrfViewMiddleware(object): # the expiry timer. response.set_cookie(settings.CSRF_COOKIE_NAME, request.META["CSRF_COOKIE"], - max_age=60 * 60 * 24 * 7 * 52, + max_age=settings.CSRF_COOKIE_AGE, domain=settings.CSRF_COOKIE_DOMAIN, path=settings.CSRF_COOKIE_PATH, secure=settings.CSRF_COOKIE_SECURE, diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index 5c73ba3242..4c37f7448c 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -491,6 +491,7 @@ Settings A number of settings can be used to control Django's CSRF behavior: +* :setting:`CSRF_COOKIE_AGE` * :setting:`CSRF_COOKIE_DOMAIN` * :setting:`CSRF_COOKIE_HTTPONLY` * :setting:`CSRF_COOKIE_NAME` diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index f01ce3c7fd..9d133b73eb 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -324,6 +324,28 @@ See :doc:`/topics/cache`. .. _settings-csrf: +.. setting:: CSRF_COOKIE_AGE + +CSRF_COOKIE_AGE +--------------- + +.. versionadded:: 1.7 + +Default: ``31449600`` (1 year, in seconds) + +The age of CSRF cookies, in seconds. + +The reason for setting a long-lived expiration time is to avoid problems in +the case of a user closing a browser or bookmarking a page and then loading +that page from a browser cache. Without persistent cookies, the form submission +would fail in this case. + +Some browsers (specifically Internet Explorer) can disallow the use of +persistent cookies or can have the indexes to the cookie jar corrupted on disk, +thereby causing CSRF protection checks to fail (and sometimes intermittently). +Change this setting to ``None`` to use session-based CSRF cookies, which +keep the cookies in-memory instead of on persistent storage. + .. setting:: CSRF_COOKIE_DOMAIN CSRF_COOKIE_DOMAIN diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 359bc4d217..fd1f44eb0d 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -446,6 +446,12 @@ Cache "non-expiring" by default. Previously, it was only possible to pass ``timeout=None` to the cache backend's ``set()`` method. +Cross Site Request Forgery +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* The :setting:`CSRF_COOKIE_AGE` setting facilitates the use of session-based + CSRF cookies. + Email ^^^^^ diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index 4c9153f3a5..6199ca84bd 100644 --- a/tests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -384,3 +384,47 @@ class CsrfViewMiddlewareTest(TestCase): finally: logger.removeHandler(test_handler) logger.setLevel(old_log_level) + + def test_csrf_cookie_age(self): + """ + Test to verify CSRF cookie age can be set using + settings.CSRF_COOKIE_AGE. + """ + req = self._get_GET_no_csrf_cookie_request() + + MAX_AGE = 123 + with self.settings(CSRF_COOKIE_NAME='csrfcookie', + CSRF_COOKIE_DOMAIN='.example.com', + CSRF_COOKIE_AGE=MAX_AGE, + CSRF_COOKIE_PATH='/test/', + CSRF_COOKIE_SECURE=True, + CSRF_COOKIE_HTTPONLY=True): + # token_view calls get_token() indirectly + CsrfViewMiddleware().process_view(req, token_view, (), {}) + resp = token_view(req) + + resp2 = CsrfViewMiddleware().process_response(req, resp) + max_age = resp2.cookies.get('csrfcookie').get('max-age') + self.assertEqual(max_age, MAX_AGE) + + def test_csrf_cookie_age_none(self): + """ + Test to verify CSRF cookie age does not have max age set and therefore + uses session-based cookies. + """ + req = self._get_GET_no_csrf_cookie_request() + + MAX_AGE = None + with self.settings(CSRF_COOKIE_NAME='csrfcookie', + CSRF_COOKIE_DOMAIN='.example.com', + CSRF_COOKIE_AGE=MAX_AGE, + CSRF_COOKIE_PATH='/test/', + CSRF_COOKIE_SECURE=True, + CSRF_COOKIE_HTTPONLY=True): + # token_view calls get_token() indirectly + CsrfViewMiddleware().process_view(req, token_view, (), {}) + resp = token_view(req) + + resp2 = CsrfViewMiddleware().process_response(req, resp) + max_age = resp2.cookies.get('csrfcookie').get('max-age') + self.assertEqual(max_age, '')