mirror of
https://github.com/pretix/pretix.git
synced 2026-07-02 04:41:55 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55f3bb3c1d | |||
| 5bb6a09549 | |||
| 23de795b3f | |||
| 958e318b4c | |||
| 1ca90892b3 | |||
| 3473fa738d | |||
| 6c7163406e | |||
| 49729d2c87 | |||
| e80b4b560b |
@@ -31,7 +31,9 @@ from pretix.api.serializers.order import OrderPositionSerializer
|
||||
from pretix.api.serializers.organizer import (
|
||||
CustomerSerializer, GiftCardSerializer,
|
||||
)
|
||||
from pretix.base.models import Order, OrderPosition, ReusableMedium
|
||||
from pretix.base.models import (
|
||||
Device, Order, OrderPosition, ReusableMedium, TeamAPIToken,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -80,8 +82,7 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
|
||||
if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'):
|
||||
# No additional permission check performed, documented limitation of the permission system
|
||||
# Would get to complex/unusable otherwise since the permission depends on the event
|
||||
# Permission Check performed in to_representation
|
||||
self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True)
|
||||
else:
|
||||
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
|
||||
@@ -117,6 +118,27 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
return data
|
||||
|
||||
def to_representation(self, instance):
|
||||
r = super().to_representation(instance)
|
||||
request = self.context.get('request')
|
||||
# late permission evaluations for checks that depend on the actual linked events
|
||||
expand_nested = self.context['request'].query_params.getlist('expand')
|
||||
perm_holder = request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user
|
||||
if 'linked_orderposition' in expand_nested:
|
||||
if instance.linked_orderposition is not None:
|
||||
event = instance.linked_orderposition.order.event
|
||||
if not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request):
|
||||
r['linked_orderposition'] = {'id': instance.linked_orderposition.id}
|
||||
|
||||
if 'linked_giftcard.owner_ticket' in expand_nested:
|
||||
gc = instance.linked_giftcard
|
||||
if gc is not None and gc.owner_ticket is not None:
|
||||
event = gc.owner_ticket.order.event
|
||||
if not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request):
|
||||
r['linked_giftcard']['owner_ticket'] = {'id': instance.linked_giftcard.owner_ticket.id}
|
||||
|
||||
return r
|
||||
|
||||
class Meta:
|
||||
model = ReusableMedium
|
||||
fields = (
|
||||
|
||||
@@ -286,6 +286,19 @@ class GiftCardSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
return data
|
||||
|
||||
def to_representation(self, instance):
|
||||
r = super().to_representation(instance)
|
||||
request = self.context.get('request')
|
||||
# late permission evaluations for checks that depend on the actual linked events
|
||||
if 'owner_ticket' in self.context['request'].query_params.getlist('expand'):
|
||||
owner_ticket = instance.owner_ticket
|
||||
if owner_ticket:
|
||||
event = owner_ticket.order.event
|
||||
perm_holder = request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user
|
||||
if not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request):
|
||||
r['owner_ticket'] = {'id': instance.owner_ticket.id}
|
||||
return r
|
||||
|
||||
class Meta:
|
||||
model = GiftCard
|
||||
fields = ('id', 'secret', 'issuance', 'value', 'currency', 'testmode', 'expires', 'conditions', 'owner_ticket',
|
||||
|
||||
@@ -251,7 +251,7 @@ def create_connection(address, timeout=socket.getdefaulttimeout(),
|
||||
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
|
||||
if not settings.get("MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS", False):
|
||||
if not getattr(settings, "MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS", False):
|
||||
ip_addr = ipaddress.ip_address(sa[0])
|
||||
if ip_addr.is_multicast:
|
||||
raise socket.error(f"Request to multicast address {sa[0]} blocked")
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-30 11:22+0000\n"
|
||||
"PO-Revision-Date: 2026-03-23 21:00+0000\n"
|
||||
"PO-Revision-Date: 2026-04-08 18:00+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ja/>\n"
|
||||
@@ -12939,7 +12939,7 @@ msgstr "企業名を必須にするには、請求先住所を必須にする必
|
||||
#: pretix/base/settings.py:4157
|
||||
#, python-brace-format
|
||||
msgid "VAT-ID is not supported for \"{}\"."
|
||||
msgstr ""
|
||||
msgstr "VAT-IDは「{}」に対してサポートされていません。"
|
||||
|
||||
#: pretix/base/settings.py:4164
|
||||
msgid "The last payment date cannot be before the end of presale."
|
||||
@@ -26796,8 +26796,6 @@ msgid "Add a two-factor authentication device"
|
||||
msgstr "2要素認証デバイスを追加してください"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:19
|
||||
#, fuzzy
|
||||
#| msgid "Smartphone with the Authenticator application"
|
||||
msgid "Smartphone with Authenticator app"
|
||||
msgstr "Authenticatorアプリを搭載したスマートフォン"
|
||||
|
||||
@@ -26806,18 +26804,20 @@ msgid ""
|
||||
"Use your smartphone with any Time-based One-Time-Password app like freeOTP, "
|
||||
"Google Authenticator or Proton Authenticator."
|
||||
msgstr ""
|
||||
"freeOTP、Google Authenticator、Proton Authenticator などの時間ベースの"
|
||||
"ワンタイムパスワードアプリをスマートフォンでご利用ください。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:30
|
||||
#, fuzzy
|
||||
#| msgid "WebAuthn-compatible hardware token (e.g. Yubikey)"
|
||||
msgid "WebAuthn-compatible hardware token"
|
||||
msgstr "WebAuthn対応のハードウェアトークン(例:Yubikey)"
|
||||
msgstr "WebAuthn対応のハードウェアトークン"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:32
|
||||
msgid ""
|
||||
"Use a hardware token like the Yubikey, or other biometric authentication "
|
||||
"like fingerprint or face recognition."
|
||||
msgstr ""
|
||||
"Yubikey などのハードウェアトークンや、指紋や顔認識などの生体認証を使用してく"
|
||||
"ださい。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/user/2fa_confirm_totp.html:8
|
||||
msgid "To set up this device, please follow the following steps:"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-30 11:22+0000\n"
|
||||
"PO-Revision-Date: 2026-04-01 17:00+0000\n"
|
||||
"PO-Revision-Date: 2026-04-08 18:00+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch (Belgium) <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix/nl_BE/>\n"
|
||||
@@ -31346,7 +31346,7 @@ msgstr "We zullen u een e-mail sturen zodra we uw betaling ontvangen hebben."
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:7
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/sepa_export.html:7
|
||||
msgid "Export bank transfer refunds"
|
||||
msgstr ""
|
||||
msgstr "Terugbetalingen per bankoverschrijving exporteren"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:9
|
||||
#, python-format
|
||||
@@ -31354,6 +31354,8 @@ msgid ""
|
||||
"<strong>%(num_new)s</strong> Bank transfer refunds have been placed and are "
|
||||
"not yet part of an export."
|
||||
msgstr ""
|
||||
"<strong>%(num_new)s</strong> terugbetalingen per bankoverschrijving zijn "
|
||||
"aangemaakt en nog niet geëxporteerd."
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:15
|
||||
msgid "In test mode, your exports will only contain test mode orders."
|
||||
@@ -31366,6 +31368,8 @@ msgid ""
|
||||
"If you want, you can now also create these exports for multiple events "
|
||||
"combined."
|
||||
msgstr ""
|
||||
"Als u dat wilt, kunt u deze exportbestanden nu ook voor meerdere evenementen "
|
||||
"tegelijk aanmaken."
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:22
|
||||
msgid "Go to organizer-level exports"
|
||||
@@ -31377,7 +31381,7 @@ msgstr "Nieuw exportbestand aanmaken"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:38
|
||||
msgid "Aggregate transactions to the same bank account"
|
||||
msgstr ""
|
||||
msgstr "Overschrijvingen naar hetzelfde rekeningnummer samenvoegen"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:43
|
||||
msgid ""
|
||||
|
||||
@@ -83,7 +83,7 @@ class AuthenticationForm(forms.Form):
|
||||
self.request = request
|
||||
self.customer_cache = None
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['password'].help_text = "<a href='{}'>{}</a>".format(
|
||||
self.fields['password'].help_text = "<a target='_blank' href='{}'>{}</a>".format(
|
||||
build_absolute_uri(False, 'presale:organizer.customer.resetpw', kwargs={
|
||||
'organizer': request.organizer.slug,
|
||||
}),
|
||||
|
||||
@@ -171,6 +171,35 @@ def test_giftcard_detail_expand(token_client, organizer, event, giftcard):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_giftcard_detail_expand_without_permissions(team, token_client, organizer, event, giftcard):
|
||||
with scopes_disabled():
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
giftcard.owner_ticket = op
|
||||
giftcard.save()
|
||||
|
||||
team.all_event_permissions = False
|
||||
team.save()
|
||||
|
||||
res = dict(TEST_GC_RES)
|
||||
res["id"] = giftcard.pk
|
||||
res["issuance"] = giftcard.issuance.isoformat().replace('+00:00', 'Z')
|
||||
resp = token_client.get('/api/v1/organizers/{}/giftcards/{}/?expand=owner_ticket'.format(organizer.slug, giftcard.pk))
|
||||
assert resp.status_code == 200
|
||||
|
||||
assert resp.data["owner_ticket"] == {
|
||||
"id": op.pk,
|
||||
}
|
||||
|
||||
|
||||
TEST_GIFTCARD_CREATE_PAYLOAD = {
|
||||
"secret": "DEFABC",
|
||||
"value": "12.00",
|
||||
|
||||
@@ -252,6 +252,76 @@ def test_medium_detail(token_client, organizer, event, medium, giftcard, custome
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_medium_detail_event_permission_missing(token_client, organizer, event, medium, giftcard, customer, team):
|
||||
team.all_organizer_permissions = False
|
||||
team.limit_organizer_permissions = {
|
||||
"organizer.reusablemedia:read": True,
|
||||
"organizer.customers:read": True,
|
||||
"organizer.giftcards:read": True,
|
||||
}
|
||||
team.all_event_permissions = False
|
||||
team.save()
|
||||
|
||||
with scopes_disabled():
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
|
||||
personalized=True)
|
||||
op = o.positions.create(item=ticket, price=Decimal("14"))
|
||||
medium.linked_orderposition = op
|
||||
medium.linked_giftcard = giftcard
|
||||
medium.customer = customer
|
||||
medium.save()
|
||||
giftcard.owner_ticket = op
|
||||
giftcard.save()
|
||||
|
||||
resp = token_client.get(
|
||||
'/api/v1/organizers/{}/reusablemedia/{}/?expand=linked_giftcard&expand='
|
||||
'linked_giftcard.owner_ticket&expand=linked_orderposition&expand=customer'.format(
|
||||
organizer.slug, medium.pk
|
||||
)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
assert resp.data["linked_orderposition"] == {
|
||||
"id": op.pk,
|
||||
}
|
||||
|
||||
assert resp.data["linked_giftcard"] == {
|
||||
"id": giftcard.pk,
|
||||
"secret": "ABCDEF",
|
||||
"issuance": giftcard.issuance.isoformat().replace("+00:00", "Z"),
|
||||
"value": "23.00",
|
||||
"currency": "EUR",
|
||||
"testmode": False,
|
||||
"expires": None,
|
||||
"conditions": None,
|
||||
"owner_ticket": {"id": op.pk},
|
||||
"issuer": "dummy",
|
||||
}
|
||||
|
||||
assert resp.data["customer"] == {
|
||||
"identifier": customer.identifier,
|
||||
"external_identifier": None,
|
||||
"email": "foo@example.org",
|
||||
"phone": None,
|
||||
"name": "Foo",
|
||||
"name_parts": {"_legacy": "Foo"},
|
||||
"is_active": True,
|
||||
"is_verified": False,
|
||||
"last_login": None,
|
||||
"date_joined": customer.date_joined.isoformat().replace("+00:00", "Z"),
|
||||
"locale": "en",
|
||||
"last_modified": customer.last_modified.isoformat().replace("+00:00", "Z"),
|
||||
"notes": None
|
||||
}
|
||||
|
||||
|
||||
TEST_MEDIUM_CREATE_PAYLOAD = {
|
||||
"type": "barcode",
|
||||
"identifier": "FOOBAR",
|
||||
|
||||
@@ -610,7 +610,7 @@ PRIVATE_IPS_RES = [
|
||||
|
||||
|
||||
@contextmanager
|
||||
def test_mail_connection(res, should_connect, use_ssl):
|
||||
def assert_mail_connection(res, should_connect, use_ssl):
|
||||
with (
|
||||
mock.patch('socket.socket') as mock_socket,
|
||||
mock.patch('socket.getaddrinfo', return_value=res),
|
||||
@@ -638,14 +638,14 @@ def test_mail_connection(res, should_connect, use_ssl):
|
||||
def test_private_smtp_ip(res, use_ssl, settings):
|
||||
settings.EMAIL_CUSTOM_SMTP_BACKEND = 'pretix.base.email.CheckPrivateNetworkSmtpBackend'
|
||||
settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = False
|
||||
with test_mail_connection(res=res, should_connect=False, use_ssl=use_ssl), pytest.raises(match="Request to .* blocked"):
|
||||
with assert_mail_connection(res=res, should_connect=False, use_ssl=use_ssl), pytest.raises(match="Request to .* blocked"):
|
||||
connection = djmail.get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
|
||||
host="localhost",
|
||||
use_ssl=use_ssl)
|
||||
connection.open()
|
||||
|
||||
settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = True
|
||||
with test_mail_connection(res=res, should_connect=True, use_ssl=use_ssl):
|
||||
with assert_mail_connection(res=res, should_connect=True, use_ssl=use_ssl):
|
||||
connection = djmail.get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
|
||||
host="localhost",
|
||||
use_ssl=use_ssl)
|
||||
@@ -662,7 +662,7 @@ def test_public_smtp_ip(use_ssl, allow_private, settings):
|
||||
settings.EMAIL_CUSTOM_SMTP_BACKEND = 'pretix.base.email.CheckPrivateNetworkSmtpBackend'
|
||||
settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = allow_private
|
||||
|
||||
with test_mail_connection(res=[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('8.8.8.8', 443))], should_connect=True, use_ssl=use_ssl):
|
||||
with assert_mail_connection(res=[(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('8.8.8.8', 443))], should_connect=True, use_ssl=use_ssl):
|
||||
connection = djmail.get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
|
||||
host="localhost",
|
||||
use_ssl=use_ssl)
|
||||
@@ -702,7 +702,7 @@ def test_send_mail_private_ip(res, use_ssl, allow_private_networks, env):
|
||||
m.refresh_from_db()
|
||||
return m
|
||||
|
||||
with test_mail_connection(res=res, should_connect=allow_private_networks, use_ssl=use_ssl):
|
||||
with assert_mail_connection(res=res, should_connect=allow_private_networks, use_ssl=use_ssl):
|
||||
m = send_mail()
|
||||
if allow_private_networks:
|
||||
assert m.status == OutgoingMail.STATUS_SENT
|
||||
|
||||
Reference in New Issue
Block a user