Compare commits

...
4 Commits
Author SHA1 Message Date
Raphael Michel f346acb0d2 API: Only require transmission fields depending on method logic (#6765)
* API: Do not mark all invoice transmission fields as required

* Fix test
2026-11-13 14:34:41 +01:00
Phin Wolkwitz 0478559fd7 Accounting report: Add annotation and full daterange… (Z#23245563) (#6742)
Add an annotation and full daterange in accounting reports if "all time" is selected
2026-11-13 12:33:22 +01:00
Raphael Michel 137059dd4e Customer account login: Add rate limit (Z#23252382) (#6760)
* Customer account login: Add rate limit

* Apply suggestion from @raphaelm
2026-11-11 10:29:51 +01:00
Raphael Michel bd0b55bdd3 Reusable media API: Limit data exposed on cross-organizer requests (Z#23253153) (#6761) 2026-11-11 10:29:02 +01:00
7 changed files with 61 additions and 21 deletions

No files matched your search

+2
View File
@@ -197,6 +197,8 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
'linked_giftcard',
'info',
'notes',
# When adding fields, check if they should be hidden in ReusableMediaViewSet
# for cross-organizer lookups
)
+1
View File
@@ -117,6 +117,7 @@ class TransmissionInfoSerializer(serializers.Serializer):
super().__init__(*args, **kwargs)
for k, v in transmission_type.invoice_address_form_fields.items():
self.fields[k] = form_field_to_serializer_field(v)
self.fields[k].required = False # invoice_address_form_fields_required is checked by parent serializer
class InvoiceAddressSerializer(I18nAwareModelSerializer):
+11 -2
View File
@@ -183,9 +183,18 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
type=s.validated_data["type"],
identifier=s.validated_data["identifier"],
)
m.customer = None # not relevant for cross-organizer
s = self.get_serializer(m)
return Response({"result": s.data})
data = s.data
# Remove fields that are not relevant for cross-organizer usage
data["customer"] = None
data["linked_orderposition"] = None
data["linked_orderpositions"] = []
data["info"] = {}
data["notes"] = None
data["claim_token"] = None
data["label"] = None
return Response({"result": data})
except ReusableMedium.DoesNotExist:
mt = MEDIA_TYPES.get(s.validated_data["type"])
if mt:
+28 -17
View File
@@ -27,6 +27,7 @@ from decimal import Decimal
from django import forms
from django.db.models import F, Sum
from django.db.models.aggregates import Min
from django.db.models.functions import Coalesce
from django.utils.formats import date_format
from django.utils.html import escape
@@ -41,7 +42,8 @@ from reportlab.platypus import (
from pretix.base.exporter import BaseExporter
from pretix.base.models import (
GiftCardTransaction, OrderFee, OrderPayment, OrderRefund, Transaction,
GiftCardTransaction, Order, OrderFee, OrderPayment, OrderRefund,
Transaction,
)
from pretix.base.templatetags.money import money_filter, tax_rate_format
from pretix.base.timeframes import (
@@ -116,25 +118,34 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
dt_start, df_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(
now(), form_data["date_range"], self.timezone
)
if dt_start:
filters.append(
_("Begin")
+ ": "
+ date_format(
else:
dt_start = Order.objects.filter(
event__in=self.events,
).aggregate(min_datetime=Min('datetime'))['min_datetime']
df_end = now()
if dt_start:
filters.append(
"{label}: {value}{hint}".format(
label=_("Begin"),
value=date_format(
dt_start.astimezone(self.timezone), "SHORT_DATETIME_FORMAT"
)
),
hint="" if form_data["date_range"] else " ({})".format(_("first order received"))
)
if df_end:
filters.append(
_("End")
+ ": "
+ date_format(
(df_end - datetime.timedelta.resolution).astimezone(
self.timezone
),
"SHORT_DATETIME_FORMAT",
)
)
if df_end:
filters.append(
_("End")
+ ": "
+ date_format(
(df_end - datetime.timedelta.resolution).astimezone(
self.timezone
),
"SHORT_DATETIME_FORMAT",
)
)
if not form_data["no_testmode"]:
filters.append(_("Report includes test orders which may be deleted later!"))
+7
View File
@@ -75,6 +75,7 @@ class AuthenticationForm(forms.Form):
'unverified': _("You have not yet activated your account and set a password. Please click the link in the "
"email we sent you. In case you cannot find it, click \"Forgot your password?\" to receive "
"a new email."),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
}
def __init__(self, request=None, *args, **kwargs):
@@ -93,6 +94,12 @@ class AuthenticationForm(forms.Form):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if rate_limit("customer_login", include_ip_from_request=self.request, max_num=10, expire_time=300):
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if email is not None and password:
try:
u = self.request.organizer.customers.get(email=email.lower(), provider__isnull=True)
-1
View File
@@ -333,7 +333,6 @@ def test_order_update_transmission_validation(token_client, organizer, event, or
assert resp.status_code == 400
assert resp.data == {"invoice_address": {"transmission_info": {
"transmission_it_sdi_pec": ["Enter a valid email address.", "Enter a valid email address."],
"transmission_it_sdi_recipient_code": ["This field is required."]
}}}
resp = token_client.patch(
+12 -1
View File
@@ -696,7 +696,7 @@ def test_medium_autocreate_giftcard(token_client, organizer, team):
@pytest.mark.django_db
def test_medium_lookup_cross_organizer(token_client, organizer, organizer2, org2_event, medium2, giftcard2):
def test_medium_lookup_cross_organizer(token_client, organizer, customer, organizer2, org2_event, medium2, giftcard2):
with scopes_disabled():
o = Order.objects.create(
code='FOO', event=org2_event, email='dummy@dummy.test',
@@ -708,7 +708,12 @@ def test_medium_lookup_cross_organizer(token_client, organizer, organizer2, org2
personalized=True)
op = o.positions.create(item=ticket, price=Decimal("14"))
medium2.linked_orderpositions.add(op)
medium2.customer = customer
medium2.linked_giftcard = giftcard2
medium2.claim_token = "1234"
medium2.info = {"foo": "bar"}
medium2.notes = "Internal notes"
medium2.label = "Max Muster"
medium2.save()
resp = token_client.post(
@@ -755,3 +760,9 @@ def test_medium_lookup_cross_organizer(token_client, organizer, organizer2, org2
assert resp.data["result"]["organizer"] == "partner"
assert resp.data["result"]["linked_giftcard"] is not None
assert resp.data["result"]["linked_orderposition"] is None
assert resp.data["result"]["linked_orderpositions"] == []
assert resp.data["result"]["customer"] is None
assert resp.data["result"]["info"] == {}
assert resp.data["result"]["notes"] is None
assert resp.data["result"]["claim_token"] is None
assert resp.data["result"]["label"] is None