Refs #32800 -- Improved CsrfViewMiddlewareTestMixin._check_token_present().

This changes CsrfViewMiddlewareTestMixin._check_token_present() to give more
detailed information if the check fails, and in particular why it failed. It
also moves CsrfFunctionTests.assertMaskedSecretCorrect() to a separate
CsrfFunctionTestMixin so the helper can be used in CsrfViewMiddlewareTestMixin.
This commit is contained in:
Chris Jerdonek 2021-08-02 14:38:04 -04:00 committed by Carlton Gibson
parent f10553ec93
commit 7aba820aca
1 changed files with 33 additions and 23 deletions

View File

@ -5,10 +5,11 @@ from django.contrib.sessions.backends.cache import SessionStore
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpRequest, HttpResponse, UnreadablePostError
from django.middleware.csrf import (
CSRF_ALLOWED_CHARS, CSRF_SESSION_KEY, CSRF_TOKEN_LENGTH, REASON_BAD_ORIGIN,
REASON_CSRF_TOKEN_MISSING, REASON_NO_CSRF_COOKIE, CsrfViewMiddleware,
InvalidTokenFormat, RejectRequest, _does_token_match, _mask_cipher_secret,
_sanitize_token, _unmask_cipher_token, get_token, rotate_token,
CSRF_ALLOWED_CHARS, CSRF_SECRET_LENGTH, CSRF_SESSION_KEY,
CSRF_TOKEN_LENGTH, REASON_BAD_ORIGIN, REASON_CSRF_TOKEN_MISSING,
REASON_NO_CSRF_COOKIE, CsrfViewMiddleware, InvalidTokenFormat,
RejectRequest, _does_token_match, _mask_cipher_secret, _sanitize_token,
_unmask_cipher_token, get_token, rotate_token,
)
from django.test import SimpleTestCase, override_settings
from django.views.decorators.csrf import csrf_exempt, requires_csrf_token
@ -26,7 +27,22 @@ MASKED_TEST_SECRET1 = '1bcdefghij2bcdefghij3bcdefghij4bcdefghij5bcdefghij6bcdefg
MASKED_TEST_SECRET2 = '2JgchWvM1tpxT2lfz9aydoXW9yT1DN3NdLiejYxOOlzzV4nhBbYqmqZYbAV3V5Bf'
class CsrfFunctionTests(SimpleTestCase):
class CsrfFunctionTestMixin:
# This method depends on _unmask_cipher_token() being correct.
def assertMaskedSecretCorrect(self, masked_secret, secret):
"""Test that a string is a valid masked version of a secret."""
self.assertEqual(len(masked_secret), CSRF_TOKEN_LENGTH)
self.assertEqual(len(secret), CSRF_SECRET_LENGTH)
self.assertTrue(
set(masked_secret).issubset(set(CSRF_ALLOWED_CHARS)),
msg=f'invalid characters in {masked_secret!r}',
)
actual = _unmask_cipher_token(masked_secret)
self.assertEqual(actual, secret)
class CsrfFunctionTests(CsrfFunctionTestMixin, SimpleTestCase):
def test_unmask_cipher_token(self):
cases = [
@ -47,17 +63,6 @@ class CsrfFunctionTests(SimpleTestCase):
actual = _unmask_cipher_token(masked_secret)
self.assertEqual(actual, secret)
# This method depends on _unmask_cipher_token() being correct.
def assertMaskedSecretCorrect(self, masked_secret, secret):
"""Test that a string is a valid masked version of a secret."""
self.assertEqual(len(masked_secret), CSRF_TOKEN_LENGTH)
self.assertTrue(
set(masked_secret).issubset(set(CSRF_ALLOWED_CHARS)),
msg=f'invalid characters in {masked_secret!r}',
)
actual = _unmask_cipher_token(masked_secret)
self.assertEqual(actual, secret)
def test_mask_cipher_secret(self):
cases = [
32 * 'a',
@ -186,7 +191,7 @@ class PostErrorRequest(TestingHttpRequest):
POST = property(_get_post, _set_post)
class CsrfViewMiddlewareTestMixin:
class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
"""
Shared methods and tests for session-based and cookie-based tokens.
"""
@ -274,14 +279,19 @@ class CsrfViewMiddlewareTestMixin:
request_class=request_class,
)
def _check_token_present(self, response, csrf_id=None):
# This method depends on _unmask_cipher_token() being correct.
def _check_token_present(self, response, csrf_token=None):
if csrf_token is None:
csrf_secret = TEST_SECRET
else:
csrf_secret = _unmask_cipher_token(csrf_token)
text = str(response.content, response.charset)
match = re.search('name="csrfmiddlewaretoken" value="(.*?)"', text)
csrf_token = csrf_id or self._csrf_id_token
self.assertTrue(
match and _does_token_match(csrf_token, match[1]),
"Could not find csrfmiddlewaretoken to match %s" % csrf_token
match, f'Could not find a csrfmiddlewaretoken value in: {text}',
)
csrf_token = match[1]
self.assertMaskedSecretCorrect(csrf_token, csrf_secret)
def test_process_response_get_token_not_used(self):
"""
@ -543,7 +553,7 @@ class CsrfViewMiddlewareTestMixin:
mw.process_view(req, token_view, (), {})
resp = mw(req)
csrf_cookie = self._read_csrf_cookie(req, resp)
self._check_token_present(resp, csrf_id=csrf_cookie)
self._check_token_present(resp, csrf_cookie)
def test_cookie_not_reset_on_accepted_request(self):
"""
@ -1179,7 +1189,7 @@ class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
resp = mw(req)
csrf_cookie = self._read_csrf_cookie(req, resp)
self.assertEqual(len(csrf_cookie), CSRF_TOKEN_LENGTH)
self._check_token_present(resp, csrf_id=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)
def test_https_good_referer_behind_proxy(self):