diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index ca32a040f4..c2a9470ab1 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -11,6 +11,7 @@ from urllib.parse import urlparse from django.conf import settings from django.core.exceptions import DisallowedHost, ImproperlyConfigured +from django.http.request import HttpHeaders from django.urls import get_callable from django.utils.cache import patch_vary_headers from django.utils.crypto import constant_time_compare, get_random_string @@ -28,7 +29,6 @@ REASON_BAD_ORIGIN = "Origin checking failed - %s does not match any trusted orig REASON_NO_REFERER = "Referer checking failed - no Referer." REASON_BAD_REFERER = "Referer checking failed - %s does not match any trusted origins." REASON_NO_CSRF_COOKIE = "CSRF cookie not set." -REASON_CSRF_TOKEN_INCORRECT = 'CSRF token incorrect.' REASON_CSRF_TOKEN_MISSING = 'CSRF token missing.' REASON_MALFORMED_REFERER = "Referer checking failed - Referer is malformed." REASON_INSECURE_REFERER = "Referer checking failed - Referer is insecure while host is secure." @@ -315,6 +315,13 @@ class CsrfViewMiddleware(MiddlewareMixin): if not is_same_domain(referer.netloc, good_referer): raise RejectRequest(REASON_BAD_REFERER % referer.geturl()) + def _bad_token_message(self, reason, token_source): + if token_source != 'POST': + # Assume it is a settings.CSRF_HEADER_NAME value. + header_name = HttpHeaders.parse_header_name(token_source) + token_source = f'the {header_name!r} HTTP header' + return f'CSRF token from {token_source} {reason}.' + def _check_token(self, request): # Access csrf_token via self._get_token() as rotate_token() may have # been called by an authentication middleware during the @@ -349,14 +356,19 @@ class CsrfViewMiddleware(MiddlewareMixin): request_csrf_token = request.META[settings.CSRF_HEADER_NAME] except KeyError: raise RejectRequest(REASON_CSRF_TOKEN_MISSING) + token_source = settings.CSRF_HEADER_NAME + else: + token_source = 'POST' try: request_csrf_token = _sanitize_token(request_csrf_token) except InvalidTokenFormat as exc: - raise RejectRequest(f'CSRF token {exc.reason}.') + reason = self._bad_token_message(exc.reason, token_source) + raise RejectRequest(reason) if not _compare_masked_tokens(request_csrf_token, csrf_token): - raise RejectRequest(REASON_CSRF_TOKEN_INCORRECT) + reason = self._bad_token_message('incorrect', token_source) + raise RejectRequest(reason) def process_request(self, request): try: diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index a028b56d08..9f9d380bb3 100644 --- a/tests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -147,12 +147,24 @@ class CsrfViewMiddlewareTestMixin: """ cases = [ (None, None, REASON_CSRF_TOKEN_MISSING), - (16 * 'a', None, 'CSRF token has incorrect length.'), - (64 * '*', None, 'CSRF token has invalid characters.'), - (64 * 'a', None, 'CSRF token incorrect.'), - (None, 16 * 'a', 'CSRF token has incorrect length.'), - (None, 64 * '*', 'CSRF token has invalid characters.'), - (None, 64 * 'a', 'CSRF token incorrect.'), + (16 * 'a', None, 'CSRF token from POST has incorrect length.'), + (64 * '*', None, 'CSRF token from POST has invalid characters.'), + (64 * 'a', None, 'CSRF token from POST incorrect.'), + ( + None, + 16 * 'a', + "CSRF token from the 'X-Csrftoken' HTTP header has incorrect length.", + ), + ( + None, + 64 * '*', + "CSRF token from the 'X-Csrftoken' HTTP header has invalid characters.", + ), + ( + None, + 64 * 'a', + "CSRF token from the 'X-Csrftoken' HTTP header incorrect.", + ), ] for post_token, meta_token, expected in cases: with self.subTest(post_token=post_token, meta_token=meta_token): @@ -168,7 +180,10 @@ class CsrfViewMiddlewareTestMixin: If a CSRF cookie is present and an invalid token is passed via a custom CSRF_HEADER_NAME, the middleware rejects the incoming request. """ - expected = 'CSRF token has incorrect length.' + expected = ( + "CSRF token from the 'X-Csrftoken-Customized' HTTP header has " + "incorrect length." + ) self._check_bad_or_missing_token( expected, meta_token=16 * 'a',