diff options
Diffstat (limited to 'django/middleware/csrf.py')
| -rw-r--r-- | django/middleware/csrf.py | 137 |
1 files changed, 74 insertions, 63 deletions
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index 6be68ebd76..94f580fa71 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -22,27 +22,29 @@ from django.utils.http import is_same_domain from django.utils.log import log_response from django.utils.regex_helper import _lazy_re_compile -logger = logging.getLogger('django.security.csrf') +logger = logging.getLogger("django.security.csrf") # This matches if any character is not in CSRF_ALLOWED_CHARS. -invalid_token_chars_re = _lazy_re_compile('[^a-zA-Z0-9]') +invalid_token_chars_re = _lazy_re_compile("[^a-zA-Z0-9]") REASON_BAD_ORIGIN = "Origin checking failed - %s does not match any trusted origins." 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_MISSING = 'CSRF token missing.' +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." +REASON_INSECURE_REFERER = ( + "Referer checking failed - Referer is insecure while host is secure." +) # The reason strings below are for passing to InvalidTokenFormat. They are # phrases without a subject because they can be in reference to either the CSRF # cookie or non-cookie token. -REASON_INCORRECT_LENGTH = 'has incorrect length' -REASON_INVALID_CHARACTERS = 'has invalid characters' +REASON_INCORRECT_LENGTH = "has incorrect length" +REASON_INVALID_CHARACTERS = "has invalid characters" CSRF_SECRET_LENGTH = 32 CSRF_TOKEN_LENGTH = 2 * CSRF_SECRET_LENGTH CSRF_ALLOWED_CHARS = string.ascii_letters + string.digits -CSRF_SESSION_KEY = '_csrftoken' +CSRF_SESSION_KEY = "_csrftoken" def _get_failure_view(): @@ -62,7 +64,7 @@ def _mask_cipher_secret(secret): mask = _get_new_csrf_string() chars = CSRF_ALLOWED_CHARS pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in mask)) - cipher = ''.join(chars[(x + y) % len(chars)] for x, y in pairs) + cipher = "".join(chars[(x + y) % len(chars)] for x, y in pairs) return mask + cipher @@ -76,21 +78,24 @@ def _unmask_cipher_token(token): token = token[CSRF_SECRET_LENGTH:] chars = CSRF_ALLOWED_CHARS pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in mask)) - return ''.join(chars[x - y] for x, y in pairs) # Note negative values are ok + return "".join(chars[x - y] for x, y in pairs) # Note negative values are ok def _add_new_csrf_cookie(request): """Generate a new random CSRF_COOKIE value, and add it to request.META.""" csrf_secret = _get_new_csrf_string() - request.META.update({ - # 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, - }) + request.META.update( + { + # 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, + } + ) return csrf_secret @@ -104,12 +109,12 @@ def get_token(request): header to the outgoing response. For this reason, you may need to use this function lazily, as is done by the csrf context processor. """ - if 'CSRF_COOKIE' in request.META: - csrf_secret = request.META['CSRF_COOKIE'] + if "CSRF_COOKIE" in request.META: + csrf_secret = request.META["CSRF_COOKIE"] # Since the cookie is being used, flag to send the cookie in # process_response() (even if the client already has it) in order to # renew the expiry timer. - request.META['CSRF_COOKIE_NEEDS_UPDATE'] = True + request.META["CSRF_COOKIE_NEEDS_UPDATE"] = True else: csrf_secret = _add_new_csrf_cookie(request) return _mask_cipher_secret(csrf_secret) @@ -171,19 +176,17 @@ class CsrfViewMiddleware(MiddlewareMixin): This middleware should be used in conjunction with the {% csrf_token %} template tag. """ + @cached_property def csrf_trusted_origins_hosts(self): return [ - urlparse(origin).netloc.lstrip('*') + urlparse(origin).netloc.lstrip("*") for origin in settings.CSRF_TRUSTED_ORIGINS ] @cached_property def allowed_origins_exact(self): - return { - origin for origin in settings.CSRF_TRUSTED_ORIGINS - if '*' not in origin - } + return {origin for origin in settings.CSRF_TRUSTED_ORIGINS if "*" not in origin} @cached_property def allowed_origin_subdomains(self): @@ -192,8 +195,12 @@ class CsrfViewMiddleware(MiddlewareMixin): subdomains of the netloc are allowed. """ allowed_origin_subdomains = defaultdict(list) - for parsed in (urlparse(origin) for origin in settings.CSRF_TRUSTED_ORIGINS if '*' in origin): - allowed_origin_subdomains[parsed.scheme].append(parsed.netloc.lstrip('*')) + for parsed in ( + urlparse(origin) + for origin in settings.CSRF_TRUSTED_ORIGINS + if "*" in origin + ): + allowed_origin_subdomains[parsed.scheme].append(parsed.netloc.lstrip("*")) return allowed_origin_subdomains # The _accept and _reject methods currently only exist for the sake of the @@ -208,7 +215,9 @@ class CsrfViewMiddleware(MiddlewareMixin): def _reject(self, request, reason): response = _get_failure_view()(request, reason=reason) log_response( - 'Forbidden (%s): %s', reason, request.path, + "Forbidden (%s): %s", + reason, + request.path, response=response, request=request, logger=logger, @@ -228,9 +237,9 @@ class CsrfViewMiddleware(MiddlewareMixin): csrf_secret = request.session.get(CSRF_SESSION_KEY) except AttributeError: raise ImproperlyConfigured( - 'CSRF_USE_SESSIONS is enabled, but request.session is not ' - 'set. SessionMiddleware must appear before CsrfViewMiddleware ' - 'in MIDDLEWARE.' + "CSRF_USE_SESSIONS is enabled, but request.session is not " + "set. SessionMiddleware must appear before CsrfViewMiddleware " + "in MIDDLEWARE." ) else: try: @@ -249,12 +258,12 @@ class CsrfViewMiddleware(MiddlewareMixin): def _set_csrf_cookie(self, request, response): if settings.CSRF_USE_SESSIONS: - if request.session.get(CSRF_SESSION_KEY) != request.META['CSRF_COOKIE']: - request.session[CSRF_SESSION_KEY] = request.META['CSRF_COOKIE'] + if request.session.get(CSRF_SESSION_KEY) != request.META["CSRF_COOKIE"]: + request.session[CSRF_SESSION_KEY] = request.META["CSRF_COOKIE"] else: response.set_cookie( settings.CSRF_COOKIE_NAME, - request.META['CSRF_COOKIE'], + request.META["CSRF_COOKIE"], max_age=settings.CSRF_COOKIE_AGE, domain=settings.CSRF_COOKIE_DOMAIN, path=settings.CSRF_COOKIE_PATH, @@ -263,17 +272,17 @@ class CsrfViewMiddleware(MiddlewareMixin): samesite=settings.CSRF_COOKIE_SAMESITE, ) # Set the Vary header since content varies with the CSRF cookie. - patch_vary_headers(response, ('Cookie',)) + patch_vary_headers(response, ("Cookie",)) def _origin_verified(self, request): - request_origin = request.META['HTTP_ORIGIN'] + request_origin = request.META["HTTP_ORIGIN"] try: good_host = request.get_host() except DisallowedHost: pass else: - good_origin = '%s://%s' % ( - 'https' if request.is_secure() else 'http', + good_origin = "%s://%s" % ( + "https" if request.is_secure() else "http", good_host, ) if request_origin == good_origin: @@ -292,7 +301,7 @@ class CsrfViewMiddleware(MiddlewareMixin): ) def _check_referer(self, request): - referer = request.META.get('HTTP_REFERER') + referer = request.META.get("HTTP_REFERER") if referer is None: raise RejectRequest(REASON_NO_REFERER) @@ -302,11 +311,11 @@ class CsrfViewMiddleware(MiddlewareMixin): raise RejectRequest(REASON_MALFORMED_REFERER) # Make sure we have a valid URL for Referer. - if '' in (referer.scheme, referer.netloc): + if "" in (referer.scheme, referer.netloc): raise RejectRequest(REASON_MALFORMED_REFERER) # Ensure that our Referer is also secure. - if referer.scheme != 'https': + if referer.scheme != "https": raise RejectRequest(REASON_INSECURE_REFERER) if any( @@ -330,18 +339,18 @@ class CsrfViewMiddleware(MiddlewareMixin): raise RejectRequest(REASON_BAD_REFERER % referer.geturl()) else: server_port = request.get_port() - if server_port not in ('443', '80'): - good_referer = '%s:%s' % (good_referer, server_port) + if server_port not in ("443", "80"): + good_referer = "%s:%s" % (good_referer, server_port) 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': + 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}.' + token_source = f"the {header_name!r} HTTP header" + return f"CSRF token from {token_source} {reason}." def _check_token(self, request): # Access csrf_secret via self._get_secret() as rotate_token() may have @@ -350,7 +359,7 @@ class CsrfViewMiddleware(MiddlewareMixin): try: csrf_secret = self._get_secret(request) except InvalidTokenFormat as exc: - raise RejectRequest(f'CSRF cookie {exc.reason}.') + raise RejectRequest(f"CSRF cookie {exc.reason}.") if csrf_secret is None: # No CSRF cookie. For POST requests, we insist on a CSRF cookie, @@ -359,10 +368,10 @@ class CsrfViewMiddleware(MiddlewareMixin): raise RejectRequest(REASON_NO_CSRF_COOKIE) # Check non-cookie token for match. - request_csrf_token = '' - if request.method == 'POST': + request_csrf_token = "" + if request.method == "POST": try: - request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') + request_csrf_token = request.POST.get("csrfmiddlewaretoken", "") except UnreadablePostError: # Handle a broken connection before we've completed reading the # POST data. process_view shouldn't raise any exceptions, so @@ -370,7 +379,7 @@ class CsrfViewMiddleware(MiddlewareMixin): # listening, which they probably aren't because of the error). pass - if request_csrf_token == '': + if request_csrf_token == "": # Fall back to X-CSRFToken, to make things easier for AJAX, and # possible for PUT/DELETE. try: @@ -383,7 +392,7 @@ class CsrfViewMiddleware(MiddlewareMixin): raise RejectRequest(REASON_CSRF_TOKEN_MISSING) token_source = settings.CSRF_HEADER_NAME else: - token_source = 'POST' + token_source = "POST" try: _check_token_format(request_csrf_token) @@ -392,7 +401,7 @@ class CsrfViewMiddleware(MiddlewareMixin): raise RejectRequest(reason) 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) def process_request(self, request): @@ -406,22 +415,22 @@ class CsrfViewMiddleware(MiddlewareMixin): # 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 + request.META["CSRF_COOKIE"] = csrf_secret def process_view(self, request, callback, callback_args, callback_kwargs): - if getattr(request, 'csrf_processing_done', False): + if getattr(request, "csrf_processing_done", False): return None # Wait until request.META["CSRF_COOKIE"] has been manipulated before # bailing out, so that get_token still works - if getattr(callback, 'csrf_exempt', False): + if getattr(callback, "csrf_exempt", False): return None # Assume that anything not defined as 'safe' by RFC7231 needs protection - if request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): + if request.method in ("GET", "HEAD", "OPTIONS", "TRACE"): return self._accept(request) - if getattr(request, '_dont_enforce_csrf_checks', False): + if getattr(request, "_dont_enforce_csrf_checks", False): # Mechanism to turn off CSRF checks for test suite. It comes after # the creation of CSRF cookies, so that everything else continues # to work exactly the same (e.g. cookies are sent, etc.), but @@ -430,9 +439,11 @@ class CsrfViewMiddleware(MiddlewareMixin): # Reject the request if the Origin header doesn't match an allowed # value. - if 'HTTP_ORIGIN' in request.META: + if "HTTP_ORIGIN" in request.META: if not self._origin_verified(request): - return self._reject(request, REASON_BAD_ORIGIN % request.META['HTTP_ORIGIN']) + return self._reject( + request, REASON_BAD_ORIGIN % request.META["HTTP_ORIGIN"] + ) elif request.is_secure(): # If the Origin header wasn't provided, reject HTTPS requests if # the Referer header doesn't match an allowed value. @@ -464,7 +475,7 @@ class CsrfViewMiddleware(MiddlewareMixin): return self._accept(request) def process_response(self, request, response): - if request.META.get('CSRF_COOKIE_NEEDS_UPDATE'): + if request.META.get("CSRF_COOKIE_NEEDS_UPDATE"): self._set_csrf_cookie(request, response) # Unset the flag to prevent _set_csrf_cookie() from being # unnecessarily called again in process_response() by other @@ -473,6 +484,6 @@ class CsrfViewMiddleware(MiddlewareMixin): # CSRF_COOKIE_NEEDS_UPDATE is still respected in subsequent calls # e.g. in case rotate_token() is called in process_response() later # by custom middleware but before those subsequent calls. - request.META['CSRF_COOKIE_NEEDS_UPDATE'] = False + request.META["CSRF_COOKIE_NEEDS_UPDATE"] = False return response |
