cross site request forgery middleware this module provides a middleware that implements protection against request forgeries from other sites this matches if any character is not in csrfallowedchars 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 noncookie token return the view to be used for csrf rejections return getcallablesettings csrffailureview def getnewcsrfstring return getrandomstringcsrfsecretlength allowedcharscsrfallowedchars def maskciphersecretsecret mask getnewcsrfstring chars csrfallowedchars pairs zipchars indexx for x in secret chars indexx for x in mask cipher joincharsx y lenchars for x y in pairs return mask cipher def unmaskciphertokentoken mask token csrfsecretlength token tokencsrfsecretlength chars csrfallowedchars pairs zipchars indexx for x in token chars indexx for x in mask return joincharsx y for x y in pairs note negative values are ok def addnewcsrfcookierequest return the csrf token required for a post form the token is an alphanumeric value a new token is created if one is not already set a side effect of calling this function is to make the csrfprotect decorator and the csrfviewmiddleware add a csrf cookie and a vary cookie header to the outgoing response for this reason you may need to use this function lazily as is done by the csrf context processor since the cookie is being used flag to send the cookie in processresponse even if the client already has it in order to renew the expiry timer change the csrf token in use for a request should be done on login for security purposes 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 noncookie csrf token and either masked or unmasked make sure all characters are in csrfallowedchars return whether the given csrf token matches the given csrf secret after unmasking the token if necessary this function assumes that the requestcsrftoken argument has been validated to have the correct length csrfsecretlength or csrftokenlength characters and allowed characters and that if it has length csrftokenlength it is a masked secret only unmask tokens that are exactly csrftokenlength characters long require a present and correct csrfmiddlewaretoken for post requests that have a csrf cookie and set an outgoing csrf cookie this middleware should be used in conjunction with the csrftoken template tag a mapping of allowed schemes to list of allowed netlocs where all subdomains of the netloc are allowed the accept and reject methods currently only exist for the sake of the requirescsrftoken decorator avoid checking the request twice by adding a custom attribute to request this will be relevant when both decorator and middleware are used return the csrf secret originally associated with the request or none if it didn t have one if the csrfusesessions setting is false raises invalidtokenformat if the request s secret has invalid characters or an invalid length this can raise invalidtokenformat django versions before 4 0 masked the secret before storing set the vary header since content varies with the csrf cookie make sure we have a valid url for referer ensure that our referer is also secure allow matching the configured cookie domain if no cookie domain is configured allow matching the current host port exactly if it s permitted by allowedhosts request gethost includes the port assume it is a settings csrfheadername value access csrfsecret via self getsecret as rotatetoken may have been called by an authentication middleware during the processrequest phase no csrf cookie for post requests we insist on a csrf cookie and in this way we can avoid all csrf attacks including login csrf check noncookie token for match handle a broken connection before we ve completed reading the post data processview shouldn t raise any exceptions so we ll ignore and serve the user a 403 assuming they re still listening which they probably aren t because of the error fall back to xcsrftoken to make things easier for ajax and possible for putdelete this can have length csrfsecretlength or csrftokenlength 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 use the same secret next time if the secret was originally masked this also causes it to be replaced with the unmasked form but only in cases where the secret is already getting saved anyways wait until request metacsrfcookie has been manipulated before bailing out so that gettoken still works assume that anything not defined as safe by rfc 9110 needs protection 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 before any branches that call the reject method reject the request if the origin header doesn t match an allowed value if the origin header wasn t provided reject https requests if the referer header doesn t match an allowed value suppose user visits http example com an active network attacker maninthemiddle mitm sends a post form that targets https example comdetonatebomb and submits it via javascript the attacker will need to provide a csrf cookie and token but that s no problem for a mitm and the sessionindependent secret we re using so the mitm can circumvent the csrf protection this is true for any http connection but anyone using https expects better for this reason for https example com we need additional protection that treats http example com as completely untrusted under https barth et al found that the referer header is missing for samedomain requests in only about 0 2 of cases or less so we can use strict referer checking unset the flag to prevent setcsrfcookie from being unnecessarily called again in processresponse by other instances of csrfviewmiddleware this can happen e g when both a decorator and middleware are used however csrfcookieneedsupdate is still respected in subsequent calls e g in case rotatetoken is called in processresponse later by custom middleware but before those subsequent calls this matches if any character is not in csrf_allowed_chars 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 return the view to be used for csrf rejections given a secret assumed to be a string of csrf_allowed_chars generate a token by adding a mask and applying it to the secret given a token assumed to be a string of csrf_allowed_chars of length csrf_token_length and that its first half is a mask use it to decrypt the second half to produce the original secret note negative values are ok generate a new random csrf_cookie value and add it to request meta return the csrf token required for a post form the token is an alphanumeric value a new token is created if one is not already set a side effect of calling this function is to make the csrf_protect decorator and the csrfviewmiddleware add a csrf cookie and a vary cookie header to the outgoing response for this reason you may need to use this function lazily as is done by the csrf context processor 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 change the csrf token in use for a request should be done on login for security purposes 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 make sure all characters are in csrf_allowed_chars return whether the given csrf token matches the given csrf secret after unmasking the token if necessary 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 require a present and correct csrfmiddlewaretoken for post requests that have a csrf cookie and set an outgoing csrf cookie this middleware should be used in conjunction with the csrf_token template tag a mapping of allowed schemes to list of allowed netlocs where all subdomains of the netloc are allowed the _accept and _reject methods currently only exist for the sake of the requires_csrf_token decorator avoid checking the request twice by adding a custom attribute to request this will be relevant when both decorator and middleware are used 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 this can raise invalidtokenformat django versions before 4 0 masked the secret before storing set the vary header since content varies with the csrf cookie make sure we have a valid url for referer ensure that our referer is also secure allow matching the configured cookie domain if no cookie domain is configured allow matching the current host port exactly if it s permitted by allowed_hosts request get_host includes the port assume it is a settings csrf_header_name value access csrf_secret via self _get_secret as rotate_token may have been called by an authentication middleware during the process_request phase no csrf cookie for post requests we insist on a csrf cookie and in this way we can avoid all csrf attacks including login csrf check non cookie token for match handle a broken connection before we ve completed reading the post data process_view shouldn t raise any exceptions so we ll ignore and serve the user a 403 assuming they re still listening which they probably aren t because of the error fall back to x csrftoken to make things easier for ajax and possible for put delete 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 use the same secret next time if the secret was originally masked this also causes it to be replaced with the unmasked form but only in cases where the secret is already getting saved anyways wait until request meta csrf_cookie has been manipulated before bailing out so that get_token still works assume that anything not defined as safe by rfc 9110 needs protection 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 before any branches that call the _reject method reject the request if the origin header doesn t match an allowed value if the origin header wasn t provided reject https requests if the referer header doesn t match an allowed value suppose user visits http example com an active network attacker man in the middle mitm sends a post form that targets https example com detonate bomb and submits it via javascript the attacker will need to provide a csrf cookie and token but that s no problem for a mitm and the session independent secret we re using so the mitm can circumvent the csrf protection this is true for any http connection but anyone using https expects better for this reason for https example com we need additional protection that treats http example com as completely untrusted under https barth et al found that the referer header is missing for same domain requests in only about 0 2 of cases or less so we can use strict referer checking unset the flag to prevent _set_csrf_cookie from being unnecessarily called again in process_response by other instances of csrfviewmiddleware this can happen e g when both a decorator and middleware are used however 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
import logging
import string
from collections import defaultdict
from urllib.parse import urlparse

from django.conf import settings
from django.core.exceptions import DisallowedHost, ImproperlyConfigured
from django.http import HttpHeaders, UnreadablePostError
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
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import cached_property
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")

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_MALFORMED_REFERER = "Referer checking failed - Referer is malformed."
REASON_INSECURE_REFERER = (
    "Referer checking failed - Referer is insecure while host is secure."
)



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"


def _get_failure_view():
    
    return get_callable(settings.CSRF_FAILURE_VIEW)


def _get_new_csrf_string():
    return get_random_string(CSRF_SECRET_LENGTH, allowed_chars=CSRF_ALLOWED_CHARS)


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)
    return mask + cipher


def _unmask_cipher_token(token):
    
    mask = token[:CSRF_SECRET_LENGTH]
    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)  


def _add_new_csrf_cookie(request):
    
    csrf_secret = _get_new_csrf_string()
    request.META.update(
        {
            "CSRF_COOKIE": csrf_secret,
            "CSRF_COOKIE_NEEDS_UPDATE": True,
        }
    )
    return csrf_secret


def get_token(request):
    
    if "CSRF_COOKIE" in request.META:
        csrf_secret = request.META["CSRF_COOKIE"]
        
        
        
        request.META["CSRF_COOKIE_NEEDS_UPDATE"] = True
    else:
        csrf_secret = _add_new_csrf_cookie(request)
    return _mask_cipher_secret(csrf_secret)


def rotate_token(request):
    
    _add_new_csrf_cookie(request)


class InvalidTokenFormat(Exception):
    def __init__(self, reason):
        self.reason = reason


def _check_token_format(token):
    
    if len(token) not in (CSRF_TOKEN_LENGTH, CSRF_SECRET_LENGTH):
        raise InvalidTokenFormat(REASON_INCORRECT_LENGTH)
    
    if invalid_token_chars_re.search(token):
        raise InvalidTokenFormat(REASON_INVALID_CHARACTERS)


def _does_token_match(request_csrf_token, csrf_secret):
    
    
    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):
    def __init__(self, reason):
        self.reason = reason


class CsrfViewMiddleware(MiddlewareMixin):
    

    @cached_property
    def csrf_trusted_origins_hosts(self):
        return [
            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}

    @cached_property
    def allowed_origin_subdomains(self):
        
        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("*"))
        return allowed_origin_subdomains

    
    
    def _accept(self, request):
        
        
        
        request.csrf_processing_done = True
        return None

    def _reject(self, request, reason):
        response = _get_failure_view()(request, reason=reason)
        log_response(
            "Forbidden (%s): %s",
            reason,
            request.path,
            response=response,
            request=request,
            logger=logger,
        )
        return response

    def _get_secret(self, request):
        
        if settings.CSRF_USE_SESSIONS:
            try:
                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."
                )
        else:
            try:
                csrf_secret = request.COOKIES[settings.CSRF_COOKIE_NAME]
            except KeyError:
                csrf_secret = None
            else:
                
                _check_token_format(csrf_secret)
        if csrf_secret is None:
            return None
        
        if len(csrf_secret) == CSRF_TOKEN_LENGTH:
            csrf_secret = _unmask_cipher_token(csrf_secret)
        return csrf_secret

    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"]
        else:
            response.set_cookie(
                settings.CSRF_COOKIE_NAME,
                request.META["CSRF_COOKIE"],
                max_age=settings.CSRF_COOKIE_AGE,
                domain=settings.CSRF_COOKIE_DOMAIN,
                path=settings.CSRF_COOKIE_PATH,
                secure=settings.CSRF_COOKIE_SECURE,
                httponly=settings.CSRF_COOKIE_HTTPONLY,
                samesite=settings.CSRF_COOKIE_SAMESITE,
            )
            
            patch_vary_headers(response, ("Cookie",))

    def _origin_verified(self, request):
        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_host,
            )
            if request_origin == good_origin:
                return True
        if request_origin in self.allowed_origins_exact:
            return True
        try:
            parsed_origin = urlparse(request_origin)
        except ValueError:
            return False
        request_scheme = parsed_origin.scheme
        request_netloc = parsed_origin.netloc
        return any(
            is_same_domain(request_netloc, host)
            for host in self.allowed_origin_subdomains.get(request_scheme, ())
        )

    def _check_referer(self, request):
        referer = request.META.get("HTTP_REFERER")
        if referer is None:
            raise RejectRequest(REASON_NO_REFERER)

        try:
            referer = urlparse(referer)
        except ValueError:
            raise RejectRequest(REASON_MALFORMED_REFERER)

        
        if "" in (referer.scheme, referer.netloc):
            raise RejectRequest(REASON_MALFORMED_REFERER)

        
        if referer.scheme != "https":
            raise RejectRequest(REASON_INSECURE_REFERER)

        if any(
            is_same_domain(referer.netloc, host)
            for host in self.csrf_trusted_origins_hosts
        ):
            return
        
        good_referer = (
            settings.SESSION_COOKIE_DOMAIN
            if settings.CSRF_USE_SESSIONS
            else settings.CSRF_COOKIE_DOMAIN
        )
        if good_referer is None:
            
            
            try:
                
                good_referer = request.get_host()
            except DisallowedHost:
                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 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":
            
            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):
        
        
        
        try:
            csrf_secret = self._get_secret(request)
        except InvalidTokenFormat as exc:
            raise RejectRequest(f"CSRF cookie {exc.reason}.")

        if csrf_secret is None:
            
            
            
            raise RejectRequest(REASON_NO_CSRF_COOKIE)

        
        request_csrf_token = ""
        if request.method == "POST":
            try:
                request_csrf_token = request.POST.get("csrfmiddlewaretoken", "")
            except UnreadablePostError:
                
                
                
                
                pass

        if request_csrf_token == "":
            
            
            try:
                
                
                
                
                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:
            _check_token_format(request_csrf_token)
        except InvalidTokenFormat as exc:
            reason = self._bad_token_message(exc.reason, token_source)
            raise RejectRequest(reason)

        if not _does_token_match(request_csrf_token, csrf_secret):
            reason = self._bad_token_message("incorrect", token_source)
            raise RejectRequest(reason)

    def process_request(self, request):
        try:
            csrf_secret = self._get_secret(request)
        except InvalidTokenFormat:
            _add_new_csrf_cookie(request)
        else:
            if csrf_secret is not None:
                
                
                
                
                request.META["CSRF_COOKIE"] = csrf_secret

    def process_view(self, request, callback, callback_args, callback_kwargs):
        if getattr(request, "csrf_processing_done", False):
            return None

        
        
        if getattr(callback, "csrf_exempt", False):
            return None

        
        if request.method in ("GET", "HEAD", "OPTIONS", "TRACE"):
            return self._accept(request)

        if getattr(request, "_dont_enforce_csrf_checks", False):
            
            
            
            
            return self._accept(request)

        
        
        if "HTTP_ORIGIN" in request.META:
            if not self._origin_verified(request):
                return self._reject(
                    request, REASON_BAD_ORIGIN % request.META["HTTP_ORIGIN"]
                )
        elif request.is_secure():
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            try:
                self._check_referer(request)
            except RejectRequest as exc:
                return self._reject(request, exc.reason)

        try:
            self._check_token(request)
        except RejectRequest as exc:
            return self._reject(request, exc.reason)

        return self._accept(request)

    def process_response(self, request, response):
        if request.META.get("CSRF_COOKIE_NEEDS_UPDATE"):
            self._set_csrf_cookie(request, response)
            
            
            
            
            
            
            
            request.META["CSRF_COOKIE_NEEDS_UPDATE"] = False

        return response