Compare commits

..
8 changed files with 83 additions and 88 deletions
+4 -4
View File
@@ -43,7 +43,7 @@ dependencies = [
"django-countries==8.2.*",
"django-filter==25.1",
"django-formset-js-improved==0.5.0.5",
"django-formtools==2.6.1",
"django-formtools==2.5.1",
"django-hierarkey==2.0.*,>=2.0.1",
"django-hijack==3.7.*",
"django-i18nfield==1.11.*",
@@ -56,7 +56,7 @@ dependencies = [
"django-redis==6.0.*",
"django-scopes==2.0.*",
"django-statici18n==2.7.*",
"djangorestframework==3.17.*",
"djangorestframework==3.16.*",
"dnspython==2.8.*",
"drf_ujson2==1.7.*",
"geoip2==5.*",
@@ -91,9 +91,9 @@ dependencies = [
"pyuca",
"qrcode==8.2",
"redis==7.4.*",
"reportlab==4.5.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.60.*",
"sentry-sdk==2.59.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
+19 -33
View File
@@ -35,7 +35,6 @@
import copy
import json
import logging
import re
from datetime import timedelta
from decimal import Decimal
from io import BytesIO
@@ -48,7 +47,9 @@ from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.validators import MaxValueValidator, MinValueValidator
from django.core.validators import (
MaxValueValidator, MinValueValidator, RegexValidator,
)
from django.db.models import QuerySet
from django.forms import Select, widgets
from django.forms.widgets import FILE_INPUT_CONTRADICTION
@@ -219,6 +220,20 @@ class NamePartsFormField(forms.MultiValueField):
defaults = {
'widget': self.widget,
'max_length': kwargs.pop('max_length', None),
'validators': [
RegexValidator(
# The following characters should never appear in a name anywhere of
# the world. However, they commonly appear in inputs generated by spam
# bots.
r'^[^$€/%§{}<>~]*$',
message=_('Please do not use special characters in names.')
),
RegexValidator(
URL_RE,
inverse_match=True,
message=_('Please do not use special characters in names.')
)
]
}
self.max_length = defaults['max_length']
self.scheme_name = kwargs.pop('scheme')
@@ -240,6 +255,7 @@ class NamePartsFormField(forms.MultiValueField):
if fname == 'title' and self.scheme_titles:
d = dict(defaults)
d.pop('max_length', None)
d.pop('validators', None)
field = forms.ChoiceField(
**d,
choices=[('', '')] + [(d, d) for d in self.scheme_titles[1]]
@@ -248,6 +264,7 @@ class NamePartsFormField(forms.MultiValueField):
elif fname == 'salutation':
d = dict(defaults)
d.pop('max_length', None)
d.pop('validators', None)
field = forms.ChoiceField(
**d,
choices=[
@@ -279,37 +296,6 @@ class NamePartsFormField(forms.MultiValueField):
if sum(len(v) for v in value.values() if v) > (self.max_length or 250):
raise forms.ValidationError(_('Please enter a shorter name.'), code='max_length')
for fname, label, size in self.scheme['fields']:
if fname == 'salutation' or (fname == 'title' and self.scheme_titles):
continue
v = value.get(fname)
if not v:
continue
special_chars = re.findall('[$€/%§{}<>~]', v)
if special_chars:
raise forms.ValidationError(
_('The field "%(label)s" may not contain special characters such as "%(chars)s".'),
code='name_special_chars',
params={
"label": label,
"chars": "".join(special_chars),
},
)
# URL_RE checks for valid domain names, including one special TLD med, which can be part of a title
if ".med" in v:
v = v.replace(".med", ". med")
value[fname] = v
url_matched = URL_RE.search(v)
if url_matched:
raise forms.ValidationError(
_('The field "%(label)s" may not contain an URL (%(url)s).'),
code='url_in_title',
params={
"label": label,
"url": url_matched.group(0),
}
)
if value.get("salutation") == "empty":
value["salutation"] = ""
+1 -1
View File
@@ -405,7 +405,7 @@ def process_banktransfers(self, job: int, data: list) -> None:
# We need to sort prefixes by length with long ones first. In case we have an event with slug
# "CONF" and one with slug "CONF2022", we want CONF2022 to match first, to avoid the parser
# thinking "2022" is already the order code.
"|".join(sorted([re.escape(p).replace("\\-", r"[\- ]*") for p in prefixes], key=lambda p: len(p), reverse=True)),
"|".join([re.escape(p).replace("\\-", r"[\- ]*") for p in sorted(prefixes, key=lambda p: len(p), reverse=True)]),
min(code_len_agg['min'] or 1, inr_len_agg['min'] or 1),
max(code_len_agg['max'] or 5, inr_len_agg['max'] or 5)
)
+1 -35
View File
@@ -19,18 +19,12 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import re
import typing
import weakref
from celery.exceptions import Retry
from sentry_sdk import Hub
from sentry_sdk.integrations import django as djangosentry
from sentry_sdk.scrubber import EventScrubber
from sentry_sdk.utils import AnnotatedValue, capture_internal_exceptions
if typing.TYPE_CHECKING:
from sentry_sdk._types import Event
from sentry_sdk.utils import capture_internal_exceptions
def _make_event_processor(weak_request, integration):
@@ -102,31 +96,3 @@ def setup_custom_filters():
hub = Hub.current
with hub.configure_scope() as scope:
scope.add_event_processor(ignore_retry)
class PretixEventScrubber(EventScrubber):
secret_re = re.compile(
'.*(pretixsecret|ramiiosecret|PRIVATE KEY|://[^/@:]*:[^/@:]+@).*',
re.IGNORECASE
)
def scrub_dict(self, d: object) -> None:
if not isinstance(d, dict):
return
super().scrub_dict(d)
self._scrub_dict_for_known_secrets(d)
def _scrub_dict_for_known_secrets(self, d: dict):
for k, v in d.items():
if isinstance(v, str) and self.secret_re.match(v):
d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
def scrub_exception(self, event: "Event") -> None:
with capture_internal_exceptions():
if "exception" in event:
self.scrub_dict(event["exception"])
def scrub_event(self, event: "Event") -> None:
super().scrub_event(event)
self.scrub_exception(event)
+3 -3
View File
@@ -704,9 +704,9 @@ if config.has_option('sentry', 'dsn') and not any(c in sys.argv for c in ('shell
from sentry_sdk.integrations.logging import (
LoggingIntegration, ignore_logger,
)
from sentry_sdk.scrubber import DEFAULT_DENYLIST
from sentry_sdk.scrubber import EventScrubber, DEFAULT_DENYLIST
from .sentry import PretixSentryIntegration, PretixEventScrubber, setup_custom_filters
from .sentry import PretixSentryIntegration, setup_custom_filters
SENTRY_TOKEN = config.get('sentry', 'traces_sample_token', fallback='')
pretix_denylist = DEFAULT_DENYLIST + [
@@ -739,7 +739,7 @@ if config.has_option('sentry', 'dsn') and not any(c in sys.argv for c in ('shell
traces_sampler=traces_sampler,
environment=urlparse(SITE_URL).netloc,
release=__version__,
event_scrubber=PretixEventScrubber(denylist=pretix_denylist, recursive=True),
event_scrubber=EventScrubber(denylist=pretix_denylist, recursive=True),
send_default_pii=False,
propagate_traces=False, # see https://github.com/getsentry/sentry-python/issues/1717
)
+1 -11
View File
@@ -53,17 +53,7 @@ function async_task_on_success(data) {
// hide waitingDialog when using browser's history back
waitingDialog.hide();
});
if (async_task_is_download && window.self !== window.top) {
// if in an iframe, force to download an async_task_is_download
// e.g. pretix-reseller embeds order-page in iframe, which would cause ticket-PDFs to be displayed inline
var a = document.createElement("a");
a.href = data.redirect;
a.download = "";
a.target = "_blank";
a.click();
} else {
location.href = data.redirect;
}
location.href = data.redirect;
}
$(this).trigger('pretix:async-task-success', data);
}
+1 -1
View File
@@ -43,7 +43,7 @@ RES_RULE = {
"limit_products": [],
"limit_variations": [],
"all_payment_methods": True,
"limit_payment_methods": [],
"limit_payment_methods": set(),
}
@@ -834,3 +834,56 @@ def test_ambigious_date_with_region(env, job):
with scopes_disabled():
assert env[2].payments.last().info_data["date"] == "2016-05-03"
@pytest.mark.django_db
def test_event_name_prefix_contains_dash(env, orga_job):
event, user, o1, o2 = env
slugs = ['dummy-2', 'dummy2345']
for slug in slugs:
event = Event.objects.create(
organizer=event.organizer, name=slug.upper(), slug=slug,
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal'
)
with scopes_disabled():
o1.event = Event.objects.get(slug="dummy2345")
o1.save()
process_banktransfers(orga_job, [{
'payer': 'Karla Kundin',
'reference': f'DUMMY2345-{o1.code}',
'date': '2016-01-26',
'amount': '23.00'
}])
with scopes_disabled():
job = BankImportJob.objects.last()
t = job.transactions.last()
assert t.state == BankTransaction.STATE_VALID
@pytest.mark.xfail
@pytest.mark.django_db
def test_event_name_prefix_multiple_dashes(env, orga_job):
# We decided to ignore the case of multiple events where the event slugs only differ in dashes
# for now. If we change that, switch this test case from xfail to regular test.
event, user, o1, o2 = env
slugs = ['dummy-2', 'dummy--2', 'dummy2345']
for slug in slugs:
event = Event.objects.create(
organizer=event.organizer, name=slug.upper(), slug=slug,
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal'
)
with scopes_disabled():
o1.event = Event.objects.get(slug="dummy-2")
o1.save()
process_banktransfers(orga_job, [{
'payer': 'Karla Kundin',
'reference': f'DUMMY2-{o1.code}',
'date': '2016-01-26',
'amount': '23.00'
}])
with scopes_disabled():
job = BankImportJob.objects.last()
t = job.transactions.last()
assert t.state == BankTransaction.STATE_VALID