From 7aba820aca98a3db77d405b616c9a2d39562f076 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 2 Aug 2021 14:38:04 -0400 Subject: [PATCH] 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. --- tests/csrf_tests/tests.py | 56 +++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index 61da1367ac..10171adb2a 100644 --- a/tests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -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):