Fixed #32800 -- Changed CsrfViewMiddleware not to mask the CSRF secret.
This also adds CSRF_COOKIE_MASKED transitional setting helpful in migrating multiple instance of the same project to Django 4.1+. Thanks Florian Apolloner and Shai Berger for reviews. Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
parent
05e29da421
commit
5d80843ebc
|
@ -34,6 +34,11 @@ USE_L10N_DEPRECATED_MSG = (
|
||||||
'display numbers and dates using the format of the current locale.'
|
'display numbers and dates using the format of the current locale.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CSRF_COOKIE_MASKED_DEPRECATED_MSG = (
|
||||||
|
'The CSRF_COOKIE_MASKED transitional setting is deprecated. Support for '
|
||||||
|
'it will be removed in Django 5.0.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SettingsReference(str):
|
class SettingsReference(str):
|
||||||
"""
|
"""
|
||||||
|
@ -206,6 +211,9 @@ class Settings:
|
||||||
if self.is_overridden('USE_DEPRECATED_PYTZ'):
|
if self.is_overridden('USE_DEPRECATED_PYTZ'):
|
||||||
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
|
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||||
|
|
||||||
|
if self.is_overridden('CSRF_COOKIE_MASKED'):
|
||||||
|
warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||||
|
|
||||||
if hasattr(time, 'tzset') and self.TIME_ZONE:
|
if hasattr(time, 'tzset') and self.TIME_ZONE:
|
||||||
# When we can, attempt to validate the timezone. If we can't find
|
# When we can, attempt to validate the timezone. If we can't find
|
||||||
# this file, no check happens and it's harmless.
|
# this file, no check happens and it's harmless.
|
||||||
|
@ -254,6 +262,8 @@ class UserSettingsHolder:
|
||||||
self._deleted.discard(name)
|
self._deleted.discard(name)
|
||||||
if name == 'USE_L10N':
|
if name == 'USE_L10N':
|
||||||
warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
|
warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||||
|
if name == 'CSRF_COOKIE_MASKED':
|
||||||
|
warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
if name == 'USE_DEPRECATED_PYTZ':
|
if name == 'USE_DEPRECATED_PYTZ':
|
||||||
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
|
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||||
|
|
|
@ -557,6 +557,10 @@ CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
|
||||||
CSRF_TRUSTED_ORIGINS = []
|
CSRF_TRUSTED_ORIGINS = []
|
||||||
CSRF_USE_SESSIONS = False
|
CSRF_USE_SESSIONS = False
|
||||||
|
|
||||||
|
# Whether to mask CSRF cookie value. It's a transitional setting helpful in
|
||||||
|
# migrating multiple instance of the same project to Django 4.1+.
|
||||||
|
CSRF_COOKIE_MASKED = False
|
||||||
|
|
||||||
############
|
############
|
||||||
# MESSAGES #
|
# MESSAGES #
|
||||||
############
|
############
|
||||||
|
|
|
@ -83,7 +83,12 @@ def _add_new_csrf_cookie(request):
|
||||||
"""Generate a new random CSRF_COOKIE value, and add it to request.META."""
|
"""Generate a new random CSRF_COOKIE value, and add it to request.META."""
|
||||||
csrf_secret = _get_new_csrf_string()
|
csrf_secret = _get_new_csrf_string()
|
||||||
request.META.update({
|
request.META.update({
|
||||||
'CSRF_COOKIE': _mask_cipher_secret(csrf_secret),
|
# RemovedInDjango50Warning: when the deprecation ends, replace
|
||||||
|
# with: 'CSRF_COOKIE': csrf_secret
|
||||||
|
'CSRF_COOKIE': (
|
||||||
|
_mask_cipher_secret(csrf_secret)
|
||||||
|
if settings.CSRF_COOKIE_MASKED else csrf_secret
|
||||||
|
),
|
||||||
'CSRF_COOKIE_NEEDS_UPDATE': True,
|
'CSRF_COOKIE_NEEDS_UPDATE': True,
|
||||||
})
|
})
|
||||||
return csrf_secret
|
return csrf_secret
|
||||||
|
@ -100,7 +105,7 @@ def get_token(request):
|
||||||
function lazily, as is done by the csrf context processor.
|
function lazily, as is done by the csrf context processor.
|
||||||
"""
|
"""
|
||||||
if 'CSRF_COOKIE' in request.META:
|
if 'CSRF_COOKIE' in request.META:
|
||||||
csrf_secret = _unmask_cipher_token(request.META["CSRF_COOKIE"])
|
csrf_secret = request.META['CSRF_COOKIE']
|
||||||
# Since the cookie is being used, flag to send the cookie in
|
# Since the cookie is being used, flag to send the cookie in
|
||||||
# process_response() (even if the client already has it) in order to
|
# process_response() (even if the client already has it) in order to
|
||||||
# renew the expiry timer.
|
# renew the expiry timer.
|
||||||
|
@ -124,29 +129,33 @@ class InvalidTokenFormat(Exception):
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_token(token):
|
def _sanitize_token(token):
|
||||||
|
"""
|
||||||
|
Raise an InvalidTokenFormat error if the token has an invalid length or
|
||||||
|
characters that aren't allowed. The token argument can be a CSRF cookie
|
||||||
|
secret or non-cookie CSRF token, and either masked or unmasked.
|
||||||
|
"""
|
||||||
if len(token) not in (CSRF_TOKEN_LENGTH, CSRF_SECRET_LENGTH):
|
if len(token) not in (CSRF_TOKEN_LENGTH, CSRF_SECRET_LENGTH):
|
||||||
raise InvalidTokenFormat(REASON_INCORRECT_LENGTH)
|
raise InvalidTokenFormat(REASON_INCORRECT_LENGTH)
|
||||||
# Make sure all characters are in CSRF_ALLOWED_CHARS.
|
# Make sure all characters are in CSRF_ALLOWED_CHARS.
|
||||||
if invalid_token_chars_re.search(token):
|
if invalid_token_chars_re.search(token):
|
||||||
raise InvalidTokenFormat(REASON_INVALID_CHARACTERS)
|
raise InvalidTokenFormat(REASON_INVALID_CHARACTERS)
|
||||||
if len(token) == CSRF_SECRET_LENGTH:
|
|
||||||
# Older Django versions set cookies to values of CSRF_SECRET_LENGTH
|
|
||||||
# alphanumeric characters. For backwards compatibility, accept
|
|
||||||
# such values as unmasked secrets.
|
|
||||||
# It's easier to mask here and be consistent later, rather than add
|
|
||||||
# different code paths in the checks, although that might be a tad more
|
|
||||||
# efficient.
|
|
||||||
return _mask_cipher_secret(token)
|
|
||||||
return token
|
|
||||||
|
|
||||||
|
|
||||||
def _does_token_match(request_csrf_token, csrf_token):
|
def _does_token_match(request_csrf_token, csrf_secret):
|
||||||
# Assume both arguments are sanitized -- that is, strings of
|
"""
|
||||||
# length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.
|
Return whether the given CSRF token matches the given CSRF secret, after
|
||||||
return constant_time_compare(
|
unmasking the token if necessary.
|
||||||
_unmask_cipher_token(request_csrf_token),
|
|
||||||
_unmask_cipher_token(csrf_token),
|
This function assumes that the request_csrf_token argument has been
|
||||||
)
|
validated to have the correct length (CSRF_SECRET_LENGTH or
|
||||||
|
CSRF_TOKEN_LENGTH characters) and allowed characters, and that if it has
|
||||||
|
length CSRF_TOKEN_LENGTH, it is a masked secret.
|
||||||
|
"""
|
||||||
|
# Only unmask tokens that are exactly CSRF_TOKEN_LENGTH characters long.
|
||||||
|
if len(request_csrf_token) == CSRF_TOKEN_LENGTH:
|
||||||
|
request_csrf_token = _unmask_cipher_token(request_csrf_token)
|
||||||
|
assert len(request_csrf_token) == CSRF_SECRET_LENGTH
|
||||||
|
return constant_time_compare(request_csrf_token, csrf_secret)
|
||||||
|
|
||||||
|
|
||||||
class RejectRequest(Exception):
|
class RejectRequest(Exception):
|
||||||
|
@ -206,10 +215,17 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _get_token(self, request):
|
def _get_secret(self, request):
|
||||||
|
"""
|
||||||
|
Return the CSRF secret originally associated with the request, or None
|
||||||
|
if it didn't have one.
|
||||||
|
|
||||||
|
If the CSRF_USE_SESSIONS setting is false, raises InvalidTokenFormat if
|
||||||
|
the request's secret has invalid characters or an invalid length.
|
||||||
|
"""
|
||||||
if settings.CSRF_USE_SESSIONS:
|
if settings.CSRF_USE_SESSIONS:
|
||||||
try:
|
try:
|
||||||
return request.session.get(CSRF_SESSION_KEY)
|
csrf_secret = request.session.get(CSRF_SESSION_KEY)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
'CSRF_USE_SESSIONS is enabled, but request.session is not '
|
'CSRF_USE_SESSIONS is enabled, but request.session is not '
|
||||||
|
@ -218,18 +234,18 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME]
|
csrf_secret = request.COOKIES[settings.CSRF_COOKIE_NAME]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
csrf_secret = None
|
||||||
|
else:
|
||||||
# This can raise InvalidTokenFormat.
|
# This can raise InvalidTokenFormat.
|
||||||
csrf_token = _sanitize_token(cookie_token)
|
_sanitize_token(csrf_secret)
|
||||||
|
if csrf_secret is None:
|
||||||
if csrf_token != cookie_token:
|
return None
|
||||||
# Then the cookie token had length CSRF_SECRET_LENGTH, so flag
|
# Django versions before 4.0 masked the secret before storing.
|
||||||
# to replace it with the masked version.
|
if len(csrf_secret) == CSRF_TOKEN_LENGTH:
|
||||||
request.META['CSRF_COOKIE_NEEDS_UPDATE'] = True
|
csrf_secret = _unmask_cipher_token(csrf_secret)
|
||||||
return csrf_token
|
return csrf_secret
|
||||||
|
|
||||||
def _set_csrf_cookie(self, request, response):
|
def _set_csrf_cookie(self, request, response):
|
||||||
if settings.CSRF_USE_SESSIONS:
|
if settings.CSRF_USE_SESSIONS:
|
||||||
|
@ -328,15 +344,15 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
return f'CSRF token from {token_source} {reason}.'
|
return f'CSRF token from {token_source} {reason}.'
|
||||||
|
|
||||||
def _check_token(self, request):
|
def _check_token(self, request):
|
||||||
# Access csrf_token via self._get_token() as rotate_token() may have
|
# Access csrf_secret via self._get_secret() as rotate_token() may have
|
||||||
# been called by an authentication middleware during the
|
# been called by an authentication middleware during the
|
||||||
# process_request() phase.
|
# process_request() phase.
|
||||||
try:
|
try:
|
||||||
csrf_token = self._get_token(request)
|
csrf_secret = self._get_secret(request)
|
||||||
except InvalidTokenFormat as exc:
|
except InvalidTokenFormat as exc:
|
||||||
raise RejectRequest(f'CSRF cookie {exc.reason}.')
|
raise RejectRequest(f'CSRF cookie {exc.reason}.')
|
||||||
|
|
||||||
if csrf_token is None:
|
if csrf_secret is None:
|
||||||
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
|
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
|
||||||
# and in this way we can avoid all CSRF attacks, including login
|
# and in this way we can avoid all CSRF attacks, including login
|
||||||
# CSRF.
|
# CSRF.
|
||||||
|
@ -358,6 +374,10 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
# Fall back to X-CSRFToken, to make things easier for AJAX, and
|
# Fall back to X-CSRFToken, to make things easier for AJAX, and
|
||||||
# possible for PUT/DELETE.
|
# possible for PUT/DELETE.
|
||||||
try:
|
try:
|
||||||
|
# This can have length CSRF_SECRET_LENGTH or CSRF_TOKEN_LENGTH,
|
||||||
|
# depending on whether the client obtained the token from
|
||||||
|
# the DOM or the cookie (and if the cookie, whether the cookie
|
||||||
|
# was masked or unmasked).
|
||||||
request_csrf_token = request.META[settings.CSRF_HEADER_NAME]
|
request_csrf_token = request.META[settings.CSRF_HEADER_NAME]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise RejectRequest(REASON_CSRF_TOKEN_MISSING)
|
raise RejectRequest(REASON_CSRF_TOKEN_MISSING)
|
||||||
|
@ -366,24 +386,27 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
token_source = 'POST'
|
token_source = 'POST'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request_csrf_token = _sanitize_token(request_csrf_token)
|
_sanitize_token(request_csrf_token)
|
||||||
except InvalidTokenFormat as exc:
|
except InvalidTokenFormat as exc:
|
||||||
reason = self._bad_token_message(exc.reason, token_source)
|
reason = self._bad_token_message(exc.reason, token_source)
|
||||||
raise RejectRequest(reason)
|
raise RejectRequest(reason)
|
||||||
|
|
||||||
if not _does_token_match(request_csrf_token, csrf_token):
|
if not _does_token_match(request_csrf_token, csrf_secret):
|
||||||
reason = self._bad_token_message('incorrect', token_source)
|
reason = self._bad_token_message('incorrect', token_source)
|
||||||
raise RejectRequest(reason)
|
raise RejectRequest(reason)
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
try:
|
try:
|
||||||
csrf_token = self._get_token(request)
|
csrf_secret = self._get_secret(request)
|
||||||
except InvalidTokenFormat:
|
except InvalidTokenFormat:
|
||||||
_add_new_csrf_cookie(request)
|
_add_new_csrf_cookie(request)
|
||||||
else:
|
else:
|
||||||
if csrf_token is not None:
|
if csrf_secret is not None:
|
||||||
# Use same token next time.
|
# Use the same secret next time. If the secret was originally
|
||||||
request.META['CSRF_COOKIE'] = csrf_token
|
# masked, this also causes it to be replaced with the unmasked
|
||||||
|
# form, but only in cases where the secret is already getting
|
||||||
|
# saved anyways.
|
||||||
|
request.META['CSRF_COOKIE'] = csrf_secret
|
||||||
|
|
||||||
def process_view(self, request, callback, callback_args, callback_kwargs):
|
def process_view(self, request, callback, callback_args, callback_kwargs):
|
||||||
if getattr(request, 'csrf_processing_done', False):
|
if getattr(request, 'csrf_processing_done', False):
|
||||||
|
|
|
@ -67,6 +67,8 @@ details on these changes.
|
||||||
|
|
||||||
* The ``SitemapIndexItem.__str__()`` method will be removed.
|
* The ``SitemapIndexItem.__str__()`` method will be removed.
|
||||||
|
|
||||||
|
* The ``CSRF_COOKIE_MASKED`` transitional setting will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-4.1:
|
.. _deprecation-removed-in-4.1:
|
||||||
|
|
||||||
4.1
|
4.1
|
||||||
|
|
|
@ -110,11 +110,12 @@ The above code could be simplified by using the `JavaScript Cookie library
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The CSRF token is also present in the DOM, but only if explicitly included
|
The CSRF token is also present in the DOM in a masked form, but only if
|
||||||
using :ttag:`csrf_token` in a template. The cookie contains the canonical
|
explicitly included using :ttag:`csrf_token` in a template. The cookie
|
||||||
token; the ``CsrfViewMiddleware`` will prefer the cookie to the token in
|
contains the canonical, unmasked token. The
|
||||||
the DOM. Regardless, you're guaranteed to have the cookie if the token is
|
:class:`~django.middleware.csrf.CsrfViewMiddleware` will accept either.
|
||||||
present in the DOM, so you should use the cookie!
|
However, in order to protect against `BREACH`_ attacks, it's recommended to
|
||||||
|
use a masked token.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
@ -231,25 +232,21 @@ How it works
|
||||||
|
|
||||||
The CSRF protection is based on the following things:
|
The CSRF protection is based on the following things:
|
||||||
|
|
||||||
#. A CSRF cookie that is based on a random secret value, which other sites
|
#. A CSRF cookie that is a random secret value, which other sites will not have
|
||||||
will not have access to.
|
access to.
|
||||||
|
|
||||||
This cookie is set by ``CsrfViewMiddleware``. It is sent with every
|
``CsrfViewMiddleware`` sends this cookie with the response whenever
|
||||||
response that has called ``django.middleware.csrf.get_token()`` (the
|
``django.middleware.csrf.get_token()`` is called. It can also send it in
|
||||||
function used internally to retrieve the CSRF token), if it wasn't already
|
other cases. For security reasons, the value of the secret is changed each
|
||||||
set on the request.
|
time a user logs in.
|
||||||
|
|
||||||
In order to protect against `BREACH`_ attacks, the token is not simply the
|
#. A hidden form field with the name 'csrfmiddlewaretoken', present in all
|
||||||
secret; a random mask is prepended to the secret and used to scramble it.
|
outgoing POST forms.
|
||||||
|
|
||||||
For security reasons, the value of the secret is changed each time a
|
In order to protect against `BREACH`_ attacks, the value of this field is
|
||||||
user logs in.
|
not simply the secret. It is scrambled differently with each response using
|
||||||
|
a mask. The mask is generated randomly on every call to ``get_token()``, so
|
||||||
#. A hidden form field with the name 'csrfmiddlewaretoken' present in all
|
the form field value is different each time.
|
||||||
outgoing POST forms. The value of this field is, again, the value of the
|
|
||||||
secret, with a mask which is both added to it and used to scramble it. The
|
|
||||||
mask is regenerated on every call to ``get_token()`` so that the form field
|
|
||||||
value is changed in every such response.
|
|
||||||
|
|
||||||
This part is done by the template tag.
|
This part is done by the template tag.
|
||||||
|
|
||||||
|
@ -294,6 +291,10 @@ The CSRF protection is based on the following things:
|
||||||
|
|
||||||
``Origin`` checking was added, as described above.
|
``Origin`` checking was added, as described above.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.1
|
||||||
|
|
||||||
|
In older versions, the CSRF cookie value was masked.
|
||||||
|
|
||||||
This ensures that only forms that have originated from trusted domains can be
|
This ensures that only forms that have originated from trusted domains can be
|
||||||
used to POST data back.
|
used to POST data back.
|
||||||
|
|
||||||
|
|
|
@ -347,6 +347,22 @@ form input <acquiring-csrf-token-from-html>` instead of :ref:`from the cookie
|
||||||
|
|
||||||
See :setting:`SESSION_COOKIE_HTTPONLY` for details on ``HttpOnly``.
|
See :setting:`SESSION_COOKIE_HTTPONLY` for details on ``HttpOnly``.
|
||||||
|
|
||||||
|
.. setting:: CSRF_COOKIE_MASKED
|
||||||
|
|
||||||
|
``CSRF_COOKIE_MASKED``
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
Whether to mask the CSRF cookie. See
|
||||||
|
:ref:`release notes <csrf-cookie-masked-usage>` for usage details.
|
||||||
|
|
||||||
|
.. deprecated:: 4.1
|
||||||
|
|
||||||
|
This transitional setting is deprecated and will be removed in Django 5.0.
|
||||||
|
|
||||||
.. setting:: CSRF_COOKIE_NAME
|
.. setting:: CSRF_COOKIE_NAME
|
||||||
|
|
||||||
``CSRF_COOKIE_NAME``
|
``CSRF_COOKIE_NAME``
|
||||||
|
|
|
@ -26,6 +26,25 @@ officially support the latest release of each series.
|
||||||
What's new in Django 4.1
|
What's new in Django 4.1
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
.. _csrf-cookie-masked-usage:
|
||||||
|
|
||||||
|
``CSRF_COOKIE_MASKED`` setting
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
The new :setting:`CSRF_COOKIE_MASKED` transitional setting allows specifying
|
||||||
|
whether to mask the CSRF cookie.
|
||||||
|
|
||||||
|
:class:`~django.middleware.csrf.CsrfViewMiddleware` no longer masks the CSRF
|
||||||
|
cookie like it does the CSRF token in the DOM. If you are upgrading multiple
|
||||||
|
instances of the same project to Django 4.1, you should set
|
||||||
|
:setting:`CSRF_COOKIE_MASKED` to ``True`` during the transition, in
|
||||||
|
order to allow compatibility with the older versions of Django. Once the
|
||||||
|
transition to 4.1 is complete you can stop overriding
|
||||||
|
:setting:`CSRF_COOKIE_MASKED`.
|
||||||
|
|
||||||
|
This setting is deprecated as of this release and will be removed in Django
|
||||||
|
5.0.
|
||||||
|
|
||||||
Minor features
|
Minor features
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
@ -270,6 +289,13 @@ Miscellaneous
|
||||||
* The Django test runner now returns a non-zero error code for unexpected
|
* The Django test runner now returns a non-zero error code for unexpected
|
||||||
successes from tests marked with :py:func:`unittest.expectedFailure`.
|
successes from tests marked with :py:func:`unittest.expectedFailure`.
|
||||||
|
|
||||||
|
* :class:`~django.middleware.csrf.CsrfViewMiddleware` no longer masks the CSRF
|
||||||
|
cookie like it does the CSRF token in the DOM.
|
||||||
|
|
||||||
|
* :class:`~django.middleware.csrf.CsrfViewMiddleware` now uses
|
||||||
|
``request.META['CSRF_COOKIE']`` for storing the unmasked CSRF secret rather
|
||||||
|
than a masked version. This is an undocumented, private API.
|
||||||
|
|
||||||
.. _deprecated-features-4.1:
|
.. _deprecated-features-4.1:
|
||||||
|
|
||||||
Features deprecated in 4.1
|
Features deprecated in 4.1
|
||||||
|
@ -283,6 +309,8 @@ Miscellaneous
|
||||||
:ref:`context variables <sitemap-index-context-variables>`, expecting a list
|
:ref:`context variables <sitemap-index-context-variables>`, expecting a list
|
||||||
of objects with ``location`` and optional ``lastmod`` attributes.
|
of objects with ``location`` and optional ``lastmod`` attributes.
|
||||||
|
|
||||||
|
* ``CSRF_COOKIE_MASKED`` transitional setting is deprecated.
|
||||||
|
|
||||||
Features removed in 4.1
|
Features removed in 4.1
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ class TestContextProcessor(CsrfFunctionTestMixin, SimpleTestCase):
|
||||||
|
|
||||||
def test_force_token_to_string(self):
|
def test_force_token_to_string(self):
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
test_token = '1bcdefghij2bcdefghij3bcdefghij4bcdefghij5bcdefghij6bcdefghijABCD'
|
test_secret = 32 * 'a'
|
||||||
request.META['CSRF_COOKIE'] = test_token
|
request.META['CSRF_COOKIE'] = test_secret
|
||||||
token = csrf(request).get('csrf_token')
|
token = csrf(request).get('csrf_token')
|
||||||
self.assertMaskedSecretCorrect(token, 'lcccccccX2kcccccccY2jcccccccssIC')
|
self.assertMaskedSecretCorrect(token, test_secret)
|
||||||
|
|
|
@ -12,6 +12,8 @@ from django.middleware.csrf import (
|
||||||
_unmask_cipher_token, get_token, rotate_token,
|
_unmask_cipher_token, get_token, rotate_token,
|
||||||
)
|
)
|
||||||
from django.test import SimpleTestCase, override_settings
|
from django.test import SimpleTestCase, override_settings
|
||||||
|
from django.test.utils import ignore_warnings
|
||||||
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
from django.views.decorators.csrf import csrf_exempt, requires_csrf_token
|
from django.views.decorators.csrf import csrf_exempt, requires_csrf_token
|
||||||
|
|
||||||
from .views import (
|
from .views import (
|
||||||
|
@ -76,13 +78,12 @@ class CsrfFunctionTests(CsrfFunctionTestMixin, SimpleTestCase):
|
||||||
|
|
||||||
def test_get_token_csrf_cookie_set(self):
|
def test_get_token_csrf_cookie_set(self):
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.META['CSRF_COOKIE'] = MASKED_TEST_SECRET1
|
request.META['CSRF_COOKIE'] = TEST_SECRET
|
||||||
self.assertNotIn('CSRF_COOKIE_NEEDS_UPDATE', request.META)
|
self.assertNotIn('CSRF_COOKIE_NEEDS_UPDATE', request.META)
|
||||||
token = get_token(request)
|
token = get_token(request)
|
||||||
self.assertNotEqual(token, MASKED_TEST_SECRET1)
|
|
||||||
self.assertMaskedSecretCorrect(token, TEST_SECRET)
|
self.assertMaskedSecretCorrect(token, TEST_SECRET)
|
||||||
# The existing cookie is preserved.
|
# The existing cookie is preserved.
|
||||||
self.assertEqual(request.META['CSRF_COOKIE'], MASKED_TEST_SECRET1)
|
self.assertEqual(request.META['CSRF_COOKIE'], TEST_SECRET)
|
||||||
self.assertIs(request.META['CSRF_COOKIE_NEEDS_UPDATE'], True)
|
self.assertIs(request.META['CSRF_COOKIE_NEEDS_UPDATE'], True)
|
||||||
|
|
||||||
def test_get_token_csrf_cookie_not_set(self):
|
def test_get_token_csrf_cookie_not_set(self):
|
||||||
|
@ -91,38 +92,32 @@ class CsrfFunctionTests(CsrfFunctionTestMixin, SimpleTestCase):
|
||||||
self.assertNotIn('CSRF_COOKIE_NEEDS_UPDATE', request.META)
|
self.assertNotIn('CSRF_COOKIE_NEEDS_UPDATE', request.META)
|
||||||
token = get_token(request)
|
token = get_token(request)
|
||||||
cookie = request.META['CSRF_COOKIE']
|
cookie = request.META['CSRF_COOKIE']
|
||||||
self.assertEqual(len(cookie), CSRF_TOKEN_LENGTH)
|
self.assertMaskedSecretCorrect(token, cookie)
|
||||||
unmasked_cookie = _unmask_cipher_token(cookie)
|
|
||||||
self.assertMaskedSecretCorrect(token, unmasked_cookie)
|
|
||||||
self.assertIs(request.META['CSRF_COOKIE_NEEDS_UPDATE'], True)
|
self.assertIs(request.META['CSRF_COOKIE_NEEDS_UPDATE'], True)
|
||||||
|
|
||||||
def test_rotate_token(self):
|
def test_rotate_token(self):
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.META['CSRF_COOKIE'] = MASKED_TEST_SECRET1
|
request.META['CSRF_COOKIE'] = TEST_SECRET
|
||||||
self.assertNotIn('CSRF_COOKIE_NEEDS_UPDATE', request.META)
|
self.assertNotIn('CSRF_COOKIE_NEEDS_UPDATE', request.META)
|
||||||
rotate_token(request)
|
rotate_token(request)
|
||||||
# The underlying secret was changed.
|
# The underlying secret was changed.
|
||||||
cookie = request.META['CSRF_COOKIE']
|
cookie = request.META['CSRF_COOKIE']
|
||||||
self.assertEqual(len(cookie), CSRF_TOKEN_LENGTH)
|
self.assertEqual(len(cookie), CSRF_SECRET_LENGTH)
|
||||||
unmasked_cookie = _unmask_cipher_token(cookie)
|
self.assertNotEqual(cookie, TEST_SECRET)
|
||||||
self.assertNotEqual(unmasked_cookie, TEST_SECRET)
|
|
||||||
self.assertIs(request.META['CSRF_COOKIE_NEEDS_UPDATE'], True)
|
self.assertIs(request.META['CSRF_COOKIE_NEEDS_UPDATE'], True)
|
||||||
|
|
||||||
def test_sanitize_token_masked(self):
|
def test_sanitize_token_valid(self):
|
||||||
# Tokens of length CSRF_TOKEN_LENGTH are preserved.
|
|
||||||
cases = [
|
cases = [
|
||||||
(MASKED_TEST_SECRET1, MASKED_TEST_SECRET1),
|
# A token of length CSRF_SECRET_LENGTH.
|
||||||
(64 * 'a', 64 * 'a'),
|
TEST_SECRET,
|
||||||
|
# A token of length CSRF_TOKEN_LENGTH.
|
||||||
|
MASKED_TEST_SECRET1,
|
||||||
|
64 * 'a',
|
||||||
]
|
]
|
||||||
for token, expected in cases:
|
for token in cases:
|
||||||
with self.subTest(token=token):
|
with self.subTest(token=token):
|
||||||
actual = _sanitize_token(token)
|
actual = _sanitize_token(token)
|
||||||
self.assertEqual(actual, expected)
|
self.assertIsNone(actual)
|
||||||
|
|
||||||
def test_sanitize_token_unmasked(self):
|
|
||||||
# A token of length CSRF_SECRET_LENGTH is masked.
|
|
||||||
actual = _sanitize_token(TEST_SECRET)
|
|
||||||
self.assertMaskedSecretCorrect(actual, TEST_SECRET)
|
|
||||||
|
|
||||||
def test_sanitize_token_invalid(self):
|
def test_sanitize_token_invalid(self):
|
||||||
cases = [
|
cases = [
|
||||||
|
@ -136,14 +131,26 @@ class CsrfFunctionTests(CsrfFunctionTestMixin, SimpleTestCase):
|
||||||
|
|
||||||
def test_does_token_match(self):
|
def test_does_token_match(self):
|
||||||
cases = [
|
cases = [
|
||||||
((MASKED_TEST_SECRET1, MASKED_TEST_SECRET2), True),
|
# Masked tokens match.
|
||||||
((MASKED_TEST_SECRET1, 64 * 'a'), False),
|
((MASKED_TEST_SECRET1, TEST_SECRET), True),
|
||||||
|
((MASKED_TEST_SECRET2, TEST_SECRET), True),
|
||||||
|
((64 * 'a', _unmask_cipher_token(64 * 'a')), True),
|
||||||
|
# Unmasked tokens match.
|
||||||
|
((TEST_SECRET, TEST_SECRET), True),
|
||||||
|
((32 * 'a', 32 * 'a'), True),
|
||||||
|
# Incorrect tokens don't match.
|
||||||
|
((32 * 'a', TEST_SECRET), False),
|
||||||
|
((64 * 'a', TEST_SECRET), False),
|
||||||
]
|
]
|
||||||
for (token1, token2), expected in cases:
|
for (token, secret), expected in cases:
|
||||||
with self.subTest(token1=token1, token2=token2):
|
with self.subTest(token=token, secret=secret):
|
||||||
actual = _does_token_match(token1, token2)
|
actual = _does_token_match(token, secret)
|
||||||
self.assertIs(actual, expected)
|
self.assertIs(actual, expected)
|
||||||
|
|
||||||
|
def test_does_token_match_wrong_token_length(self):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
_does_token_match(16 * 'a', TEST_SECRET)
|
||||||
|
|
||||||
|
|
||||||
class TestingSessionStore(SessionStore):
|
class TestingSessionStore(SessionStore):
|
||||||
"""
|
"""
|
||||||
|
@ -215,14 +222,6 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('This method must be implemented by a subclass.')
|
raise NotImplementedError('This method must be implemented by a subclass.')
|
||||||
|
|
||||||
def assertCookiesSet(self, req, resp, expected_secrets):
|
|
||||||
"""
|
|
||||||
Assert that set_cookie() was called with the given sequence of secrets.
|
|
||||||
"""
|
|
||||||
cookies_set = self._get_cookies_set(req, resp)
|
|
||||||
secrets_set = [_unmask_cipher_token(cookie) for cookie in cookies_set]
|
|
||||||
self.assertEqual(secrets_set, expected_secrets)
|
|
||||||
|
|
||||||
def _get_request(self, method=None, cookie=None, request_class=None):
|
def _get_request(self, method=None, cookie=None, request_class=None):
|
||||||
if method is None:
|
if method is None:
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
@ -280,11 +279,9 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
# This method depends on _unmask_cipher_token() being correct.
|
# This method depends on _unmask_cipher_token() being correct.
|
||||||
def _check_token_present(self, response, csrf_token=None):
|
def _check_token_present(self, response, csrf_secret=None):
|
||||||
if csrf_token is None:
|
if csrf_secret is None:
|
||||||
csrf_secret = TEST_SECRET
|
csrf_secret = TEST_SECRET
|
||||||
else:
|
|
||||||
csrf_secret = _unmask_cipher_token(csrf_token)
|
|
||||||
text = str(response.content, response.charset)
|
text = str(response.content, response.charset)
|
||||||
match = re.search('name="csrfmiddlewaretoken" value="(.*?)"', text)
|
match = re.search('name="csrfmiddlewaretoken" value="(.*?)"', text)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
|
@ -482,10 +479,12 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
|
||||||
req = self._get_POST_request_with_token()
|
req = self._get_POST_request_with_token()
|
||||||
resp = sandwiched_rotate_token_view(req)
|
resp = sandwiched_rotate_token_view(req)
|
||||||
self.assertContains(resp, 'OK')
|
self.assertContains(resp, 'OK')
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
actual_secret = self._read_csrf_cookie(req, resp)
|
||||||
actual_secret = _unmask_cipher_token(csrf_cookie)
|
|
||||||
# set_cookie() was called a second time with a different secret.
|
# set_cookie() was called a second time with a different secret.
|
||||||
self.assertCookiesSet(req, resp, [TEST_SECRET, actual_secret])
|
cookies_set = self._get_cookies_set(req, resp)
|
||||||
|
# Only compare the last two to exclude a spurious entry that's present
|
||||||
|
# when CsrfViewMiddlewareUseSessionsTests is running.
|
||||||
|
self.assertEqual(cookies_set[-2:], [TEST_SECRET, actual_secret])
|
||||||
self.assertNotEqual(actual_secret, TEST_SECRET)
|
self.assertNotEqual(actual_secret, TEST_SECRET)
|
||||||
|
|
||||||
# Tests for the template tag method
|
# Tests for the template tag method
|
||||||
|
@ -498,7 +497,8 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
|
||||||
|
|
||||||
token = get_token(req)
|
token = get_token(req)
|
||||||
self.assertIsNotNone(token)
|
self.assertIsNotNone(token)
|
||||||
self._check_token_present(resp, token)
|
csrf_secret = _unmask_cipher_token(token)
|
||||||
|
self._check_token_present(resp, csrf_secret)
|
||||||
|
|
||||||
def test_token_node_empty_csrf_cookie(self):
|
def test_token_node_empty_csrf_cookie(self):
|
||||||
"""
|
"""
|
||||||
|
@ -511,7 +511,8 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
|
||||||
|
|
||||||
token = get_token(req)
|
token = get_token(req)
|
||||||
self.assertIsNotNone(token)
|
self.assertIsNotNone(token)
|
||||||
self._check_token_present(resp, token)
|
csrf_secret = _unmask_cipher_token(token)
|
||||||
|
self._check_token_present(resp, csrf_secret)
|
||||||
|
|
||||||
def test_token_node_with_csrf_cookie(self):
|
def test_token_node_with_csrf_cookie(self):
|
||||||
"""
|
"""
|
||||||
|
@ -568,7 +569,7 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
|
||||||
resp = mw(req)
|
resp = mw(req)
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
csrf_cookie, self._csrf_id_cookie,
|
csrf_cookie, TEST_SECRET,
|
||||||
'CSRF cookie was changed on an accepted request',
|
'CSRF cookie was changed on an accepted request',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1108,7 +1109,7 @@ class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
||||||
mw.process_view(req, token_view, (), {})
|
mw.process_view(req, token_view, (), {})
|
||||||
resp = mw(req)
|
resp = mw(req)
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
self.assertEqual(len(csrf_cookie), CSRF_TOKEN_LENGTH)
|
self.assertEqual(len(csrf_cookie), CSRF_SECRET_LENGTH)
|
||||||
|
|
||||||
def test_process_view_token_invalid_chars(self):
|
def test_process_view_token_invalid_chars(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1121,7 +1122,7 @@ class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
||||||
mw.process_view(req, token_view, (), {})
|
mw.process_view(req, token_view, (), {})
|
||||||
resp = mw(req)
|
resp = mw(req)
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
self.assertEqual(len(csrf_cookie), CSRF_TOKEN_LENGTH)
|
self.assertEqual(len(csrf_cookie), CSRF_SECRET_LENGTH)
|
||||||
self.assertNotEqual(csrf_cookie, token)
|
self.assertNotEqual(csrf_cookie, token)
|
||||||
|
|
||||||
def test_masked_unmasked_combinations(self):
|
def test_masked_unmasked_combinations(self):
|
||||||
|
@ -1151,20 +1152,19 @@ class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
||||||
resp = mw.process_view(req, token_view, (), {})
|
resp = mw.process_view(req, token_view, (), {})
|
||||||
self.assertIsNone(resp)
|
self.assertIsNone(resp)
|
||||||
|
|
||||||
def test_cookie_reset_only_once(self):
|
def test_set_cookie_called_only_once(self):
|
||||||
"""
|
"""
|
||||||
A CSRF cookie that needs to be reset is reset only once when the view
|
set_cookie() is called only once when the view is decorated with both
|
||||||
is decorated with both ensure_csrf_cookie and csrf_protect.
|
ensure_csrf_cookie and csrf_protect.
|
||||||
"""
|
"""
|
||||||
# Pass an unmasked cookie to trigger a cookie reset.
|
req = self._get_POST_request_with_token()
|
||||||
req = self._get_POST_request_with_token(cookie=TEST_SECRET)
|
|
||||||
resp = ensured_and_protected_view(req)
|
resp = ensured_and_protected_view(req)
|
||||||
self.assertContains(resp, 'OK')
|
self.assertContains(resp, 'OK')
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
actual_secret = _unmask_cipher_token(csrf_cookie)
|
self.assertEqual(csrf_cookie, TEST_SECRET)
|
||||||
self.assertEqual(actual_secret, TEST_SECRET)
|
|
||||||
# set_cookie() was called only once and with the expected secret.
|
# set_cookie() was called only once and with the expected secret.
|
||||||
self.assertCookiesSet(req, resp, [TEST_SECRET])
|
cookies_set = self._get_cookies_set(req, resp)
|
||||||
|
self.assertEqual(cookies_set, [TEST_SECRET])
|
||||||
|
|
||||||
def test_invalid_cookie_replaced_on_GET(self):
|
def test_invalid_cookie_replaced_on_GET(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1175,28 +1175,28 @@ class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
||||||
self.assertContains(resp, 'OK')
|
self.assertContains(resp, 'OK')
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
self.assertTrue(csrf_cookie, msg='No CSRF cookie was sent.')
|
self.assertTrue(csrf_cookie, msg='No CSRF cookie was sent.')
|
||||||
self.assertEqual(len(csrf_cookie), CSRF_TOKEN_LENGTH)
|
self.assertEqual(len(csrf_cookie), CSRF_SECRET_LENGTH)
|
||||||
|
|
||||||
def test_unmasked_secret_replaced_on_GET(self):
|
def test_valid_secret_not_replaced_on_GET(self):
|
||||||
"""An unmasked CSRF cookie is replaced during a GET request."""
|
|
||||||
req = self._get_request(cookie=TEST_SECRET)
|
|
||||||
resp = protected_view(req)
|
|
||||||
self.assertContains(resp, 'OK')
|
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
|
||||||
self.assertTrue(csrf_cookie, msg='No CSRF cookie was sent.')
|
|
||||||
self.assertMaskedSecretCorrect(csrf_cookie, TEST_SECRET)
|
|
||||||
|
|
||||||
def test_masked_secret_not_replaced_on_GET(self):
|
|
||||||
"""A masked CSRF cookie is not replaced during a GET request."""
|
|
||||||
req = self._get_request(cookie=MASKED_TEST_SECRET1)
|
|
||||||
resp = protected_view(req)
|
|
||||||
self.assertContains(resp, 'OK')
|
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
|
||||||
self.assertFalse(csrf_cookie, msg='A CSRF cookie was sent.')
|
|
||||||
|
|
||||||
def test_masked_secret_accepted_and_not_replaced(self):
|
|
||||||
"""
|
"""
|
||||||
The csrf cookie is left unchanged if originally masked.
|
Masked and unmasked CSRF cookies are not replaced during a GET request.
|
||||||
|
"""
|
||||||
|
cases = [
|
||||||
|
TEST_SECRET,
|
||||||
|
MASKED_TEST_SECRET1,
|
||||||
|
]
|
||||||
|
for cookie in cases:
|
||||||
|
with self.subTest(cookie=cookie):
|
||||||
|
req = self._get_request(cookie=cookie)
|
||||||
|
resp = protected_view(req)
|
||||||
|
self.assertContains(resp, 'OK')
|
||||||
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
|
self.assertFalse(csrf_cookie, msg='A CSRF cookie was sent.')
|
||||||
|
|
||||||
|
def test_masked_secret_accepted_and_replaced(self):
|
||||||
|
"""
|
||||||
|
For a view that uses the csrf_token, the csrf cookie is replaced with
|
||||||
|
the unmasked version if originally masked.
|
||||||
"""
|
"""
|
||||||
req = self._get_POST_request_with_token(cookie=MASKED_TEST_SECRET1)
|
req = self._get_POST_request_with_token(cookie=MASKED_TEST_SECRET1)
|
||||||
mw = CsrfViewMiddleware(token_view)
|
mw = CsrfViewMiddleware(token_view)
|
||||||
|
@ -1205,12 +1205,12 @@ class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
||||||
self.assertIsNone(resp)
|
self.assertIsNone(resp)
|
||||||
resp = mw(req)
|
resp = mw(req)
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
self.assertEqual(csrf_cookie, MASKED_TEST_SECRET1)
|
self.assertEqual(csrf_cookie, TEST_SECRET)
|
||||||
self._check_token_present(resp, csrf_cookie)
|
self._check_token_present(resp, csrf_cookie)
|
||||||
|
|
||||||
def test_bare_secret_accepted_and_replaced(self):
|
def test_bare_secret_accepted_and_not_replaced(self):
|
||||||
"""
|
"""
|
||||||
The csrf cookie is reset (masked) if originally not masked.
|
The csrf cookie is left unchanged if originally not masked.
|
||||||
"""
|
"""
|
||||||
req = self._get_POST_request_with_token(cookie=TEST_SECRET)
|
req = self._get_POST_request_with_token(cookie=TEST_SECRET)
|
||||||
mw = CsrfViewMiddleware(token_view)
|
mw = CsrfViewMiddleware(token_view)
|
||||||
|
@ -1219,8 +1219,7 @@ class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
||||||
self.assertIsNone(resp)
|
self.assertIsNone(resp)
|
||||||
resp = mw(req)
|
resp = mw(req)
|
||||||
csrf_cookie = self._read_csrf_cookie(req, resp)
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
# This also checks that csrf_cookie now has length CSRF_TOKEN_LENGTH.
|
self.assertEqual(csrf_cookie, TEST_SECRET)
|
||||||
self.assertMaskedSecretCorrect(csrf_cookie, TEST_SECRET)
|
|
||||||
self._check_token_present(resp, csrf_cookie)
|
self._check_token_present(resp, csrf_cookie)
|
||||||
|
|
||||||
@override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com', USE_X_FORWARDED_PORT=True)
|
@override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com', USE_X_FORWARDED_PORT=True)
|
||||||
|
@ -1407,3 +1406,31 @@ class CsrfInErrorHandlingViewsTests(CsrfFunctionTestMixin, SimpleTestCase):
|
||||||
token2 = response.content.decode('ascii')
|
token2 = response.content.decode('ascii')
|
||||||
secret2 = _unmask_cipher_token(token2)
|
secret2 = _unmask_cipher_token(token2)
|
||||||
self.assertMaskedSecretCorrect(token1, secret2)
|
self.assertMaskedSecretCorrect(token1, secret2)
|
||||||
|
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango50Warning)
|
||||||
|
class CsrfCookieMaskedTests(CsrfFunctionTestMixin, SimpleTestCase):
|
||||||
|
@override_settings(CSRF_COOKIE_MASKED=True)
|
||||||
|
def test_get_token_csrf_cookie_not_set(self):
|
||||||
|
request = HttpRequest()
|
||||||
|
self.assertNotIn('CSRF_COOKIE', request.META)
|
||||||
|
self.assertNotIn('CSRF_COOKIE_NEEDS_UPDATE', request.META)
|
||||||
|
token = get_token(request)
|
||||||
|
cookie = request.META['CSRF_COOKIE']
|
||||||
|
self.assertEqual(len(cookie), CSRF_TOKEN_LENGTH)
|
||||||
|
unmasked_cookie = _unmask_cipher_token(cookie)
|
||||||
|
self.assertMaskedSecretCorrect(token, unmasked_cookie)
|
||||||
|
self.assertIs(request.META['CSRF_COOKIE_NEEDS_UPDATE'], True)
|
||||||
|
|
||||||
|
@override_settings(CSRF_COOKIE_MASKED=True)
|
||||||
|
def test_rotate_token(self):
|
||||||
|
request = HttpRequest()
|
||||||
|
request.META['CSRF_COOKIE'] = MASKED_TEST_SECRET1
|
||||||
|
self.assertNotIn('CSRF_COOKIE_NEEDS_UPDATE', request.META)
|
||||||
|
rotate_token(request)
|
||||||
|
# The underlying secret was changed.
|
||||||
|
cookie = request.META['CSRF_COOKIE']
|
||||||
|
self.assertEqual(len(cookie), CSRF_TOKEN_LENGTH)
|
||||||
|
unmasked_cookie = _unmask_cipher_token(cookie)
|
||||||
|
self.assertNotEqual(unmasked_cookie, TEST_SECRET)
|
||||||
|
self.assertIs(request.META['CSRF_COOKIE_NEEDS_UPDATE'], True)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import sys
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
from django.conf import CSRF_COOKIE_MASKED_DEPRECATED_MSG, Settings, settings
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
|
|
||||||
|
|
||||||
|
class CsrfCookieMaskedDeprecationTests(SimpleTestCase):
|
||||||
|
msg = CSRF_COOKIE_MASKED_DEPRECATED_MSG
|
||||||
|
|
||||||
|
def test_override_settings_warning(self):
|
||||||
|
with self.assertRaisesMessage(RemovedInDjango50Warning, self.msg):
|
||||||
|
with self.settings(CSRF_COOKIE_MASKED=True):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_settings_init_warning(self):
|
||||||
|
settings_module = ModuleType('fake_settings_module')
|
||||||
|
settings_module.USE_TZ = False
|
||||||
|
settings_module.CSRF_COOKIE_MASKED = True
|
||||||
|
sys.modules['fake_settings_module'] = settings_module
|
||||||
|
try:
|
||||||
|
with self.assertRaisesMessage(RemovedInDjango50Warning, self.msg):
|
||||||
|
Settings('fake_settings_module')
|
||||||
|
finally:
|
||||||
|
del sys.modules['fake_settings_module']
|
||||||
|
|
||||||
|
def test_access(self):
|
||||||
|
# Warning is not raised on access.
|
||||||
|
self.assertEqual(settings.CSRF_COOKIE_MASKED, False)
|
Loading…
Reference in New Issue