Compare commits

...

7 Commits

Author SHA1 Message Date
luelista
a554a742be Apply suggestions from code review
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2026-05-22 16:38:38 +02:00
Mira Weller
50a5189626 Fix if condition 2026-05-22 16:37:46 +02:00
Mira Weller
6f74fe293d Add logging 2026-05-22 16:20:40 +02:00
Mira Weller
7554193e2a Remove duplicated csrftoken cookies
Due to a Safari bug, in some browser, two csrftoken cookies with different values
exist: one unpartitioned, one partitioned ("CHIPS"). This function generates an
additional Set-Cookie header to get rid of the unpartitioned one.

As Django usually only allows one Set-Cookie header per cookie name, we
need to manually create a cookie 'Morsel' for the deletion and store it
in the HttpResponse's cookie dictionary under a different name, so it is
not overwritten by the actual, correct Set-Cookie header. This works
because the code in django.core.handlers.wsgi/asgi, that generates the
actual Set-Cookie headers, only iterates over cookie.values(), ignoring
the keys.
2026-05-22 16:12:10 +02:00
luelista
18485f5d95 Only show "valid VAT ID" checkmark if VAT ID was entered (Z#23235033) (#6200) 2026-05-22 15:57:21 +02:00
Richard Schreiber
909ce5b27d Add NoUrlValidator to validators (#6208) 2026-05-22 13:51:28 +02:00
Raphael Michel
c7b82fdc97 Subevent update: Save SubEvent model before saving plugin forms (#6209) 2026-05-22 13:22:04 +02:00
4 changed files with 93 additions and 3 deletions

View File

@@ -24,10 +24,12 @@ import calendar
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rrulestr
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.core.validators import RegexValidator, validate_email
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
from pretix.base.templatetags.rich_text import URL_RE
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
#
@@ -113,6 +115,33 @@ def multimail_validate(val):
return s
class RegexValidatorInverseMatchAndParam(RegexValidator):
inverse_match = True
def __call__(self, value):
regex_matches = self.regex.search(str(value))
if regex_matches:
raise ValidationError(
self.message,
code=self.code,
params={
"value": value,
"match": regex_matches.group(0) if regex_matches else "",
}
)
class NoUrlValidator(RegexValidatorInverseMatchAndParam):
regex = URL_RE
def __init__(self, **kwargs):
if not kwargs.get("message"):
kwargs["message"] = _('You entered an URL, which is not allowed. Please remove %(match)s from your input.')
if not kwargs.get("code"):
kwargs["code"] = "contains_url"
super().__init__(**kwargs)
class RRuleValidator:
def __init__(self, enforce_simple=False):
self.enforce_simple = enforce_simple

View File

@@ -1078,7 +1078,7 @@
<dt>{% trans "VAT ID" %}</dt>
<dd>
{{ order.invoice_address.vat_id }}
{% if order.invoice_address.vat_id_validated %}
{% if order.invoice_address.vat_id and order.invoice_address.vat_id_validated %}
<span class="fa fa-check" data-toggle="tooltip" title="{% blocktrans trimmed %}Valid EU VAT ID{% endblocktrans %}"></span>
{% elif order.invoice_address.vat_id %}
<form class="form-inline helper-display-inline" method="post"

View File

@@ -531,6 +531,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
@transaction.atomic
def form_valid(self, form):
self.object = form.save()
self.save_formset(self.object)
self.save_cl_formset(self.object)
self.save_meta()
@@ -569,7 +570,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
f.subevent = self.object
f.save()
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk})
return super().form_valid(form)
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self) -> str:
return reverse('control:event.subevents', kwargs={

View File

@@ -32,7 +32,10 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import logging
import time
from datetime import datetime
from http.cookies import Morsel
from urllib.parse import urlparse
from django.conf import settings
@@ -58,6 +61,8 @@ from pretix.base.models import Event, Organizer
from pretix.helpers.cookies import set_cookie_without_samesite
from pretix.multidomain.models import KnownDomain
logger = logging.getLogger(__name__)
LOCAL_HOST_NAMES = ('testserver', 'localhost')
@@ -254,6 +259,9 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
if is_secure and settings.CSRF_COOKIE_NAME in request.COOKIES: # remove legacy cookie
response.delete_cookie(settings.CSRF_COOKIE_NAME)
response.delete_cookie(settings.CSRF_COOKIE_NAME, samesite="None")
handle_duplicated_csrftoken(request, response)
set_cookie_without_samesite(
request, response,
'__Host-' + settings.CSRF_COOKIE_NAME if is_secure else settings.CSRF_COOKIE_NAME,
@@ -265,3 +273,55 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
)
# Content varies with the CSRF cookie, so set the Vary header.
patch_vary_headers(response, ('Cookie',))
def handle_duplicated_csrftoken(request, response):
# Due to a Safari bug, in some browser, two csrftoken cookies with different values
# exist: one unpartitioned, one partitioned. This function generates an additional
# Set-Cookie header to get rid of the unpartitioned one.
cookie_name = '__Host-' + settings.CSRF_COOKIE_NAME
if request.scheme == 'https' and cookie_name in request.COOKIES:
values = get_all_values_of_cookie(request.headers.get('Cookie'), cookie_name)
if len(values) > 1:
logger.info('Trying to remove duplicated %s cookies: %r', cookie_name, values)
# Make sure the set_cookie_without_samesite below will add a new item in the dictionary, placing
# it below our deletion header.
response.cookies.pop(cookie_name, None)
# Add the deletion Set-Cookie header to the cookie dict under a wrong name, so it doesn't get
# overwritten by the set_cookie_without_samesite call below. This works because the code in
# django.core.handlers.wsgi/asgi, that generates the actual Set-Cookie headers, only iterates
# over cookie.values(), ignoring the keys.
response.cookies['___DELETECOOKIE___' + cookie_name] = make_delete_morsel(cookie_name)
def get_all_values_of_cookie(cookie_header, cookie_name):
# like django.http.cookie.parse_cookie, but returns all values of duplicated cookies instead of only the last
values = list()
if not cookie_header:
return values
for chunk in cookie_header.split(";"):
if "=" in chunk:
key, val = chunk.split("=", 1)
else:
# Assume an empty name per
# https://bugzilla.mozilla.org/show_bug.cgi?id=169091
key, val = "", chunk
key, val = key.strip(), val.strip()
if key == cookie_name:
values.append(val)
return values
def make_delete_morsel(name):
m = Morsel()
m.set(name, '', '')
m['expires'] = datetime.utcfromtimestamp(0).strftime("%a, %d %b %Y %H:%M:%S GMT")
m['samesite'] = 'None'
m['secure'] = True
m['path'] = settings.CSRF_COOKIE_PATH
m['httponly'] = settings.CSRF_COOKIE_HTTPONLY
return m