diff --git a/src/pretix/base/views/errors.py b/src/pretix/base/views/errors.py index 7b76973b9..dbc92e20a 100644 --- a/src/pretix/base/views/errors.py +++ b/src/pretix/base/views/errors.py @@ -27,11 +27,11 @@ from django.template import TemplateDoesNotExist, loader from django.template.loader import get_template from django.utils.functional import Promise from django.utils.translation import gettext as _ -from django.views.decorators.csrf import requires_csrf_token from sentry_sdk import last_event_id from pretix.base.i18n import language from pretix.base.middleware import get_language_from_request +from pretix.multidomain.middlewares import requires_csrf_token def csrf_failure(request, reason=""): diff --git a/src/pretix/helpers/cookies.py b/src/pretix/helpers/cookies.py index 4288ce71a..ede9392cb 100644 --- a/src/pretix/helpers/cookies.py +++ b/src/pretix/helpers/cookies.py @@ -22,7 +22,7 @@ import itertools import re from http.cookies import Morsel -import logging + from django.conf import settings diff --git a/src/pretix/multidomain/middlewares.py b/src/pretix/multidomain/middlewares.py index 80e080d5e..44bab7d71 100644 --- a/src/pretix/multidomain/middlewares.py +++ b/src/pretix/multidomain/middlewares.py @@ -51,6 +51,7 @@ from django.middleware.csrf import ( from django.shortcuts import render from django.urls import set_urlconf 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.http import http_date from django_scopes import scopes_disabled @@ -216,7 +217,8 @@ class SessionMiddleware(BaseSessionMiddleware): request.session.save() if is_secure and settings.SESSION_COOKIE_NAME in request.COOKIES: # remove legacy cookie # response.delete_cookie does not work as we might have set a partitioned cookie - thoroughly_delete_cookie(response, + thoroughly_delete_cookie( + response, settings.SESSION_COOKIE_NAME, path=settings.SESSION_COOKIE_PATH, secure=is_secure, @@ -278,12 +280,13 @@ class CsrfViewMiddleware(BaseCsrfMiddleware): # remove legacy cookie if request.is_secure() and settings.CSRF_COOKIE_NAME in request.COOKIES: - thoroughly_delete_cookie(response, - settings.CSRF_COOKIE_NAME, - path=settings.CSRF_COOKIE_PATH, - secure=request.is_secure(), - httponly=settings.CSRF_COOKIE_HTTPONLY - ) + thoroughly_delete_cookie( + response, + settings.CSRF_COOKIE_NAME, + path=settings.CSRF_COOKIE_PATH, + secure=request.is_secure(), + httponly=settings.CSRF_COOKIE_HTTPONLY + ) handle_duplicated_csrftoken(request, response) @@ -299,6 +302,19 @@ class CsrfViewMiddleware(BaseCsrfMiddleware): # Content varies with the CSRF cookie, so set the Vary header. 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 + ): + logger.warning("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 equivalent instead.") + + return super().process_response(request, response) + def handle_duplicated_csrftoken(request, response): # Due to a Safari bug, in some browser, two csrftoken cookies can exist: @@ -317,4 +333,16 @@ def handle_duplicated_csrftoken(request, response): secure=request.is_secure(), path=settings.CSRF_COOKIE_PATH, httponly=settings.CSRF_COOKIE_HTTPONLY - ) \ No newline at end of file + ) + + +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) diff --git a/src/pretix/presale/views/customer.py b/src/pretix/presale/views/customer.py index 9cafb72b5..71a3c2012 100644 --- a/src/pretix/presale/views/customer.py +++ b/src/pretix/presale/views/customer.py @@ -44,7 +44,6 @@ from django.utils.http import url_has_allowed_host_and_scheme from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ 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.generic import FormView, ListView, View @@ -101,7 +100,6 @@ class LoginView(RedirectBackMixin, FormView): redirect_authenticated_user = True @method_decorator(sensitive_post_parameters()) - @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): if not request.organizer.settings.customer_accounts: @@ -213,7 +211,6 @@ class RegistrationView(RedirectBackMixin, FormView): redirect_authenticated_user = True @method_decorator(sensitive_post_parameters()) - @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): if not request.organizer.settings.customer_accounts: @@ -257,7 +254,6 @@ class SetPasswordView(FormView): template_name = 'pretixpresale/organizers/customer_setpassword.html' @method_decorator(sensitive_post_parameters()) - @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): if not request.organizer.settings.customer_accounts: @@ -301,7 +297,6 @@ class ResetPasswordView(FormView): template_name = 'pretixpresale/organizers/customer_resetpw.html' @method_decorator(sensitive_post_parameters()) - @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): if not request.organizer.settings.customer_accounts: @@ -527,7 +522,6 @@ class ChangePasswordView(CustomerAccountBaseMixin, FormView): form_class = ChangePasswordForm @method_decorator(sensitive_post_parameters()) - @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): if not request.organizer.settings.customer_accounts: @@ -561,7 +555,6 @@ class ChangeInformationView(CustomerAccountBaseMixin, FormView): form_class = ChangeInfoForm @method_decorator(sensitive_post_parameters()) - @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): if not request.organizer.settings.customer_accounts: @@ -669,7 +662,6 @@ class SSOLoginView(RedirectBackMixin, View): redirect_authenticated_user = True @method_decorator(sensitive_post_parameters()) - @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): if not request.organizer.settings.customer_accounts: @@ -732,7 +724,6 @@ class SSOLoginReturnView(RedirectBackMixin, View): redirect_authenticated_user = True @method_decorator(sensitive_post_parameters()) - @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): if not request.organizer.settings.customer_accounts: