Fix customer views using wrong csrf middleware

This lead to persistent csrf validation errors if the token from the cookie expired, which could only be solved by clearing cookies.
This commit is contained in:
Kara Engelhardt
2026-03-25 13:27:57 +01:00
parent 77eb6eb23b
commit 31a1e35071
3 changed files with 32 additions and 7 deletions

View File

@@ -27,11 +27,11 @@ from django.template import TemplateDoesNotExist, loader
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.functional import Promise from django.utils.functional import Promise
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.decorators.csrf import requires_csrf_token
from sentry_sdk import last_event_id from sentry_sdk import last_event_id
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.middleware import get_language_from_request from pretix.base.middleware import get_language_from_request
from pretix.multidomain.middlewares import requires_csrf_token
def csrf_failure(request, reason=""): def csrf_failure(request, reason=""):

View File

@@ -50,6 +50,7 @@ from django.middleware.csrf import (
from django.shortcuts import render from django.shortcuts import render
from django.urls import set_urlconf from django.urls import set_urlconf
from django.utils.cache import patch_vary_headers from django.utils.cache import patch_vary_headers
from django.utils.decorators import decorator_from_middleware
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from django.utils.http import http_date from django.utils.http import http_date
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
@@ -270,26 +271,50 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
if request.session.get(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"] request.session[CSRF_SESSION_KEY] = request.META["CSRF_COOKIE"]
else: else:
is_secure = request.scheme == 'https'
# Set the CSRF cookie even if it's already set, so we renew # Set the CSRF cookie even if it's already set, so we renew
# the expiry timer. # the expiry timer.
if is_secure and settings.CSRF_COOKIE_NAME in request.COOKIES: # remove legacy cookie if request.is_secure() and settings.CSRF_COOKIE_NAME in request.COOKIES: # remove legacy cookie
# response.delete_cookie does not work as we might have set a partitioned cookie # response.delete_cookie does not work as we might have set a partitioned cookie
delete_cookie_without_samesite( delete_cookie_without_samesite(
request, response, request, response,
settings.CSRF_COOKIE_NAME, settings.CSRF_COOKIE_NAME,
path=settings.CSRF_COOKIE_PATH, path=settings.CSRF_COOKIE_PATH,
secure=is_secure, secure=request.is_secure(),
httponly=settings.CSRF_COOKIE_HTTPONLY httponly=settings.CSRF_COOKIE_HTTPONLY
) )
set_cookie_without_samesite( set_cookie_without_samesite(
request, response, request, response,
'__Host-' + settings.CSRF_COOKIE_NAME if is_secure else settings.CSRF_COOKIE_NAME, '__Host-' + settings.CSRF_COOKIE_NAME if request.is_secure() else settings.CSRF_COOKIE_NAME,
request.META["CSRF_COOKIE"], request.META["CSRF_COOKIE"],
max_age=settings.CSRF_COOKIE_AGE, max_age=settings.CSRF_COOKIE_AGE,
path=settings.CSRF_COOKIE_PATH, path=settings.CSRF_COOKIE_PATH,
secure=is_secure, secure=request.is_secure(),
httponly=settings.CSRF_COOKIE_HTTPONLY httponly=settings.CSRF_COOKIE_HTTPONLY
) )
# Content varies with the CSRF cookie, so set the Vary header. # Content varies with the CSRF cookie, so set the Vary header.
patch_vary_headers(response, ('Cookie',)) patch_vary_headers(response, ('Cookie',))
def process_response(self, request, response):
if (
not settings.CSRF_USE_SESSIONS
and request.is_secure()
and settings.CSRF_COOKIE_NAME in response.cookies
and response.cookies[settings.CSRF_COOKIE_NAME].value
):
raise ValueError("Usage of djangos CsrfViewMiddleware detected (legacy cookie found in response). "
"This may be caused by using csrf_project or requires_csrf_token from django.views.decorators.csrf. "
"Use the pretix.multidomain.middlewares equivalents instead.")
return super().process_response(request, response)
csrf_protect = decorator_from_middleware(CsrfViewMiddleware)
class _EnsureCsrfToken(CsrfViewMiddleware):
# Behave like CsrfViewMiddleware but don't reject requests or log warnings.
def _reject(self, request, reason):
return None
requires_csrf_token = decorator_from_middleware(_EnsureCsrfToken)

View File

@@ -42,7 +42,6 @@ from django.utils.functional import cached_property
from django.utils.http import url_has_allowed_host_and_scheme from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, ListView, View from django.views.generic import FormView, ListView, View
@@ -55,6 +54,7 @@ from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import customer_created, customer_signed_in from pretix.base.signals import customer_created, customer_signed_in
from pretix.helpers.compat import CompatDeleteView from pretix.helpers.compat import CompatDeleteView
from pretix.helpers.http import redirect_to_url from pretix.helpers.http import redirect_to_url
from pretix.multidomain.middlewares import csrf_protect
from pretix.multidomain.models import KnownDomain from pretix.multidomain.models import KnownDomain
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
from pretix.presale.forms.customer import ( from pretix.presale.forms.customer import (