mirror of
https://github.com/pretix/pretix.git
synced 2026-10-28 23:39:52 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb3bb25442 | ||
|
|
d4f205fbf0 | ||
|
|
6b6fc7f045 | ||
|
|
0a460cd154 |
No files matched your search
@@ -19,4 +19,4 @@
|
||||
# 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/>.
|
||||
#
|
||||
__version__ = "2026.8.0"
|
||||
__version__ = "2026.8.1"
|
||||
@@ -19,12 +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/>.
|
||||
#
|
||||
def get_session_key_for_api_auth(user, auth):
|
||||
def get_session_key_for_api_auth(user, auth, prefix='api-upload'):
|
||||
if user.is_authenticated:
|
||||
return f'api-upload-User-{user.pk}'
|
||||
return f'{prefix}-User-{user.pk}'
|
||||
else:
|
||||
return f'api-upload-{str(type(auth))}-{auth.pk}'
|
||||
return f'{prefix}-{str(type(auth))}-{auth.pk}'
|
||||
|
||||
|
||||
def get_session_key_for_api_request(request):
|
||||
return get_session_key_for_api_auth(request.user, request.auth)
|
||||
def get_session_key_for_api_request(request, prefix='api-upload'):
|
||||
return get_session_key_for_api_auth(request.user, request.auth, prefix)
|
||||
@@ -1240,6 +1240,9 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
return None
|
||||
if pp not in self.context['event'].get_payment_providers():
|
||||
raise ValidationError('The given payment provider is not known.')
|
||||
if pp == "giftcard":
|
||||
raise serializers.ValidationError("Cannot create giftcard payment. Use `use_gift_cards` instead")
|
||||
|
||||
return pp
|
||||
|
||||
def validate_payment_info(self, info):
|
||||
@@ -1960,10 +1963,15 @@ class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
|
||||
model = OrderPayment
|
||||
fields = ('state', 'amount', 'payment_date', 'provider', 'info')
|
||||
|
||||
def validate_provider(self, value):
|
||||
if value == "giftcard":
|
||||
raise serializers.ValidationError("Cannot create giftcard payment")
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
order = OrderPayment(order=self.context['order'], **validated_data)
|
||||
order.save()
|
||||
return order
|
||||
payment = OrderPayment(order=self.context['order'], **validated_data)
|
||||
payment.save()
|
||||
return payment
|
||||
|
||||
|
||||
class OrderRefundCreateSerializer(I18nAwareModelSerializer):
|
||||
@@ -1975,6 +1983,12 @@ class OrderRefundCreateSerializer(I18nAwareModelSerializer):
|
||||
model = OrderRefund
|
||||
fields = ('state', 'source', 'amount', 'payment', 'execution_date', 'provider', 'info', 'comment')
|
||||
|
||||
def validate_provider(self, value):
|
||||
if value == "giftcard":
|
||||
raise serializers.ValidationError("Cannot create giftcard payment")
|
||||
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
pid = validated_data.pop('payment', None)
|
||||
if pid:
|
||||
|
||||
@@ -41,10 +41,9 @@ from pretix.api.serializers.settings import SettingsSerializer
|
||||
from pretix.base.auth import get_auth_backends
|
||||
from pretix.base.i18n import get_language_without_region
|
||||
from pretix.base.models import (
|
||||
Customer, Device, EventMetaProperty, GiftCard, GiftCardAcceptance,
|
||||
GiftCardTransaction, Membership, MembershipType, OrderPosition, Organizer,
|
||||
ReusableMedium, SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite,
|
||||
User,
|
||||
Customer, Device, EventMetaProperty, GiftCard, GiftCardTransaction,
|
||||
Membership, MembershipType, OrderPosition, Organizer, ReusableMedium,
|
||||
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
|
||||
)
|
||||
from pretix.base.models.seating import SeatingPlanLayoutValidator
|
||||
from pretix.base.permissions import (
|
||||
@@ -270,15 +269,7 @@ class GiftCardSerializer(I18nAwareModelSerializer):
|
||||
data = super().validate(data)
|
||||
if 'secret' in data:
|
||||
s = data['secret']
|
||||
qs = GiftCard.objects.filter(
|
||||
secret=s
|
||||
).filter(
|
||||
Q(issuer=self.context["organizer"]) |
|
||||
Q(issuer__in=GiftCardAcceptance.objects.filter(
|
||||
acceptor=self.context["organizer"],
|
||||
active=True,
|
||||
).values_list('issuer', flat=True))
|
||||
)
|
||||
qs = self.context["organizer"].accepted_gift_cards.filter(secret=s)
|
||||
if self.instance:
|
||||
qs = qs.exclude(pk=self.instance.pk)
|
||||
if qs.exists():
|
||||
|
||||
@@ -75,7 +75,7 @@ class ExportersMixin:
|
||||
|
||||
@action(detail=True, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
|
||||
def download(self, *args, **kwargs):
|
||||
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
|
||||
cf = get_object_or_404(CachedFile, id=kwargs['cfid'], web_download=True)
|
||||
if not cf.allowed_for_session(self.request, "exporters-api"):
|
||||
return Response(
|
||||
{'status': 'failed', 'message': 'Unknown file ID or export failed'},
|
||||
|
||||
@@ -39,7 +39,7 @@ from pretix.api.serializers.media import (
|
||||
)
|
||||
from pretix.base.media import MEDIA_TYPES
|
||||
from pretix.base.models import (
|
||||
Checkin, GiftCard, GiftCardAcceptance, GiftCardTransaction, OrderPosition,
|
||||
Checkin, GiftCardAcceptance, GiftCardTransaction, OrderPosition,
|
||||
ReusableMedium,
|
||||
)
|
||||
from pretix.base.models.orders import PrintLog
|
||||
@@ -88,7 +88,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
|
||||
),
|
||||
Prefetch(
|
||||
'linked_giftcard',
|
||||
queryset=GiftCard.objects.annotate(
|
||||
queryset=self.request.organizer.accepted_gift_cards.annotate(
|
||||
cached_value=Coalesce(Subquery(s), Decimal('0.00'))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -33,6 +33,7 @@ from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from pretix.api.auth.utils import get_session_key_for_api_request
|
||||
from pretix.api.serializers.shredders import (
|
||||
JobRunSerializer, ShredderSerializer,
|
||||
)
|
||||
@@ -68,8 +69,14 @@ class ShreddersMixin:
|
||||
cf = get_object_or_404(
|
||||
CachedFile,
|
||||
id=kwargs['cfid'],
|
||||
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
|
||||
)
|
||||
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
|
||||
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
|
||||
# also be harmless because all files with this key are expired one day after deployment of this fix
|
||||
# and no new files with this key are created
|
||||
pass
|
||||
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
|
||||
raise Http404()
|
||||
if cf.file:
|
||||
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
|
||||
@@ -118,10 +125,16 @@ class ShreddersMixin:
|
||||
)
|
||||
|
||||
try:
|
||||
CachedFile.objects.get(
|
||||
cf = CachedFile.objects.get(
|
||||
id=kwargs['cfid'],
|
||||
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
|
||||
)
|
||||
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
|
||||
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
|
||||
# also be harmless because all files with this key are expired one day after deployment of this fix
|
||||
# and no new files with this key are created
|
||||
pass
|
||||
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
|
||||
raise CachedFile.DoesNotExist()
|
||||
except CachedFile.DoesNotExist:
|
||||
return Response(
|
||||
{'status': 'gone', 'message': 'May have succeeded or timed out'},
|
||||
@@ -140,8 +153,14 @@ class ShreddersMixin:
|
||||
cf = get_object_or_404(
|
||||
CachedFile,
|
||||
id=kwargs['cfid'],
|
||||
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
|
||||
)
|
||||
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
|
||||
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
|
||||
# also be harmless because all files with this key are expired one day after deployment of this fix
|
||||
# and no new files with this key are created
|
||||
pass
|
||||
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
|
||||
raise Http404()
|
||||
if cf.file:
|
||||
async_result = self.do_shred(cf)
|
||||
url_kwargs = {
|
||||
@@ -184,7 +203,7 @@ class ShreddersMixin:
|
||||
cf = CachedFile(web_download=False)
|
||||
cf.date = now()
|
||||
cf.expires = now() + timedelta(hours=2)
|
||||
cf.session_key = f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
|
||||
cf.session_key = get_session_key_for_api_request(self.request, prefix='api-shredder')
|
||||
cf.save()
|
||||
d = serializer.data
|
||||
for k, v in d.items():
|
||||
@@ -224,7 +243,7 @@ class EventShreddersViewSet(ShreddersMixin, viewsets.ViewSet):
|
||||
return export.apply_async(args=(
|
||||
self.request.event.id,
|
||||
list(shredders),
|
||||
f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}',
|
||||
get_session_key_for_api_request(self.request, prefix='api-shredder'),
|
||||
str(cf.pk)
|
||||
))
|
||||
|
||||
|
||||
@@ -55,9 +55,10 @@ from openpyxl.comments import Comment
|
||||
from openpyxl.styles import Font, PatternFill
|
||||
|
||||
from pretix.base.models import (
|
||||
Checkin, GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order,
|
||||
Checkin, GiftCardTransaction, Invoice, InvoiceAddress, Order,
|
||||
OrderPosition, Question,
|
||||
)
|
||||
from pretix.base.models.giftcards import GiftCard
|
||||
from pretix.base.models.orders import (
|
||||
OrderFee, OrderPayment, OrderRefund, Transaction,
|
||||
)
|
||||
@@ -1326,12 +1327,12 @@ class GiftcardRedemptionListExporter(ListExporter):
|
||||
order__event__in=self.events,
|
||||
provider='giftcard',
|
||||
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED),
|
||||
).order_by('created')
|
||||
).order_by('created').select_related("order__event__organizer")
|
||||
refunds = OrderRefund.objects.filter(
|
||||
order__event__in=self.events,
|
||||
provider='giftcard',
|
||||
state=OrderRefund.REFUND_STATE_DONE
|
||||
).order_by('created')
|
||||
).order_by('created').select_related("order__event__organizer")
|
||||
|
||||
objs = sorted(list(payments) + list(refunds), key=lambda o: (o.order.code, o.created))
|
||||
|
||||
@@ -1348,9 +1349,9 @@ class GiftcardRedemptionListExporter(ListExporter):
|
||||
obj.order.code,
|
||||
obj.full_id,
|
||||
obj.created.astimezone(tz).date().strftime('%Y-%m-%d'),
|
||||
gc.secret,
|
||||
gc.secret if gc.accepted_by(obj.order.event.organizer) else '?',
|
||||
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
|
||||
gc.issuer
|
||||
gc.issuer.slug
|
||||
]
|
||||
yield row
|
||||
|
||||
|
||||
+35
-29
@@ -1527,42 +1527,37 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
})
|
||||
|
||||
def refund_control_render(self, request, refund) -> str:
|
||||
from .models import GiftCard
|
||||
|
||||
if 'gift_card' in refund.info_data:
|
||||
gc = GiftCard.objects.get(pk=refund.info_data.get('gift_card'))
|
||||
template = get_template('pretixcontrol/giftcards/payment.html')
|
||||
|
||||
ctx = {
|
||||
'request': request,
|
||||
'event': self.event,
|
||||
'gc': gc,
|
||||
}
|
||||
try:
|
||||
gc = self.event.organizer.accepted_gift_cards.get(pk=refund.info_data.get('gift_card'))
|
||||
ctx['gc'] = gc
|
||||
except GiftCard.DoesNotExist:
|
||||
pass
|
||||
return template.render(ctx)
|
||||
|
||||
def payment_control_render(self, request, payment) -> str:
|
||||
from .models import GiftCard
|
||||
template = get_template('pretixcontrol/giftcards/payment.html')
|
||||
ctx = {
|
||||
'request': request,
|
||||
'event': self.event,
|
||||
**({'error': payment.info_data[
|
||||
'error']} if 'error' in payment.info_data else {}),
|
||||
**({'gift_card_secret': payment.info_data[
|
||||
'gift_card_secret']} if 'gift_card_secret' in payment.info_data else {})
|
||||
}
|
||||
|
||||
if any(key in payment.info_data for key in ('gift_card', 'error')):
|
||||
template = get_template('pretixcontrol/giftcards/payment.html')
|
||||
ctx = {
|
||||
'request': request,
|
||||
'event': self.event,
|
||||
**({'error': payment.info_data[
|
||||
'error']} if 'error' in payment.info_data else {}),
|
||||
**({'gift_card_secret': payment.info_data[
|
||||
'gift_card_secret']} if 'gift_card_secret' in payment.info_data else {})
|
||||
}
|
||||
try:
|
||||
ctx['gc'] = self.event.organizer.accepted_gift_cards.get(pk=payment.info_data.get('gift_card'))
|
||||
except GiftCard.DoesNotExist:
|
||||
pass
|
||||
|
||||
try:
|
||||
gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card'))
|
||||
ctx = {
|
||||
'gc': gc,
|
||||
}
|
||||
except GiftCard.DoesNotExist:
|
||||
pass
|
||||
finally:
|
||||
return template.render(ctx)
|
||||
if any(key in ctx for key in ('gc', 'gift_card_secret', 'error')):
|
||||
return template.render(ctx)
|
||||
|
||||
def payment_control_render_short(self, payment: OrderPayment) -> str:
|
||||
d = payment.info_data
|
||||
@@ -1572,10 +1567,9 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
d = refund.info_data
|
||||
return d.get('gift_card_secret', d.get('gift_card_code', self.public_name))
|
||||
|
||||
def api_payment_details(self, payment: OrderPayment):
|
||||
from .models import GiftCard
|
||||
def api_payment_details(self, payment: OrderPayment | OrderRefund):
|
||||
try:
|
||||
gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card'))
|
||||
gc = self.event.organizer.accepted_gift_cards.get(pk=payment.info_data.get('gift_card'))
|
||||
except GiftCard.DoesNotExist:
|
||||
return {
|
||||
**({'error': payment.info_data[
|
||||
@@ -1698,7 +1692,19 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
@transaction.atomic()
|
||||
def execute_refund(self, refund: OrderRefund):
|
||||
from .models import GiftCard
|
||||
gc = GiftCard.objects.get(pk=refund.info_data.get('gift_card') or refund.payment.info_data.get('gift_card'))
|
||||
try:
|
||||
gc = self.event.organizer.accepted_gift_cards.get(
|
||||
pk=refund.info_data.get('gift_card') or refund.payment.info_data.get('gift_card'),
|
||||
currency=self.event.currency
|
||||
)
|
||||
except GiftCard.DoesNotExist:
|
||||
raise PaymentException(_("Gift card not found."))
|
||||
|
||||
if gc.testmode != refund.order.testmode:
|
||||
raise PaymentException(_("Only test gift card can be used in test mode and vice versa."))
|
||||
if gc.expires and gc.expires < time_machine_now():
|
||||
raise PaymentException(_("This gift card is no longer valid."))
|
||||
|
||||
trans = gc.transactions.create(
|
||||
value=refund.amount,
|
||||
order=refund.order,
|
||||
|
||||
@@ -969,7 +969,7 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
|
||||
gift_cards = [] # for backwards compatibility
|
||||
for p in payment_requests:
|
||||
if p['provider'] == 'giftcard':
|
||||
gift_cards.append(GiftCard.objects.get(pk=p['info_data']['gift_card']))
|
||||
gift_cards.extend(event.organizer.accepted_gift_cards.filter(pk=p['info_data']['gift_card']))
|
||||
|
||||
for recv, resp in order_fee_calculation.send(sender=event, invoice_address=address, total=total, payment_requests=payment_requests,
|
||||
meta_info=meta_info, positions=positions, gift_cards=gift_cards):
|
||||
|
||||
@@ -172,7 +172,7 @@ class CachedFileInput(forms.ClearableFileInput):
|
||||
from ...base.models import CachedFile
|
||||
v = super().value_from_datadict(data, files, name)
|
||||
if v is None and data.get(name + '-cachedfile'): # An explicit "[x] clear" would be False, not None
|
||||
v = CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
|
||||
v = CachedFile.objects.filter(id=data[name + '-cachedfile'], web_download=True).first()
|
||||
if not v.allowed_for_session(self.request):
|
||||
v = None
|
||||
return v
|
||||
|
||||
@@ -38,7 +38,6 @@ from urllib.parse import urlparse
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.forms import formset_factory, inlineformset_factory
|
||||
from django.forms.utils import ErrorDict
|
||||
from django.urls import reverse
|
||||
@@ -70,9 +69,9 @@ from pretix.base.forms.widgets import (
|
||||
SplitDateTimePickerWidget, format_placeholders_help_text,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Customer, Device, Event, EventMetaProperty, Gate, GiftCard,
|
||||
GiftCardAcceptance, Membership, MembershipType, OrderPosition, Organizer,
|
||||
ReusableMedium, SalesChannel, Team,
|
||||
Customer, Device, Event, EventMetaProperty, Gate, GiftCard, Membership,
|
||||
MembershipType, OrderPosition, Organizer, ReusableMedium, SalesChannel,
|
||||
Team,
|
||||
)
|
||||
from pretix.base.models.customers import CustomerSSOClient, CustomerSSOProvider
|
||||
from pretix.base.models.organizer import OrganizerFooterLink, TeamQuerySet
|
||||
@@ -894,15 +893,7 @@ class GiftCardCreateForm(forms.ModelForm):
|
||||
|
||||
def clean_secret(self):
|
||||
s = self.cleaned_data['secret']
|
||||
if GiftCard.objects.filter(
|
||||
secret__iexact=s
|
||||
).filter(
|
||||
Q(issuer=self.organizer) |
|
||||
Q(issuer__in=GiftCardAcceptance.objects.filter(
|
||||
acceptor=self.organizer,
|
||||
active=True,
|
||||
).values_list('issuer', flat=True))
|
||||
).exists():
|
||||
if self.organizer.accepted_gift_cards.filter(secret__iexact=s).exists():
|
||||
raise ValidationError(
|
||||
_('A gift card with the same secret already exists in your or an affiliated organizer account.')
|
||||
)
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
{% load i18n %}
|
||||
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Gift card code" %}</dt>
|
||||
<dd>
|
||||
{% if gc %}
|
||||
<a href="{% url "control:organizer.giftcard" organizer=gc.issuer.slug giftcard=gc.pk %}">
|
||||
{{ gc.secret }}
|
||||
</a>
|
||||
{% if gc.issuer.slug != request.organizer %}
|
||||
<span class="text-muted">
|
||||
<br>
|
||||
<span class="fa fa-group"></span> {{ gc.issuer }}
|
||||
</span>
|
||||
{% if gc or gift_card_secret %}
|
||||
<dt>{% trans "Gift card code" %}</dt>
|
||||
<dd>
|
||||
{% if gc %}
|
||||
<a href="{% url "control:organizer.giftcard" organizer=gc.issuer.slug giftcard=gc.pk %}">
|
||||
{{ gc.secret }}
|
||||
</a>
|
||||
{% if gc.issuer.slug != request.organizer %}
|
||||
<span class="text-muted">
|
||||
<br>
|
||||
<span class="fa fa-group"></span> {{ gc.issuer }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% elif gift_card_secret %}
|
||||
{{ gift_card_secret }}
|
||||
{% endif %}
|
||||
{% elif gift_card_secret %}
|
||||
{{ gift_card_secret }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if gc %}
|
||||
<dt>{% trans "Issuer" %}</dt>
|
||||
<dd>{{ gc.issuer }}</dd>
|
||||
@@ -25,4 +27,4 @@
|
||||
<dt>{% trans "Error" %}</dt>
|
||||
<dd>{{ error }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</dl>
|
||||
@@ -250,6 +250,8 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
if request.POST.get("background", "").strip():
|
||||
try:
|
||||
cf = CachedFile.objects.get(id=request.POST.get("background"), web_download=True)
|
||||
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
|
||||
cf = None
|
||||
except CachedFile.DoesNotExist:
|
||||
pass
|
||||
|
||||
@@ -305,7 +307,7 @@ class FontsCSSView(TemplateView):
|
||||
|
||||
class PdfView(TemplateView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
|
||||
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf", web_download=True)
|
||||
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
|
||||
raise PermissionDenied()
|
||||
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
|
||||
|
||||
@@ -228,7 +228,7 @@ class TicketRendererViewSet(viewsets.ViewSet):
|
||||
|
||||
@action(detail=False, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
|
||||
def download(self, *args, **kwargs):
|
||||
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
|
||||
cf = get_object_or_404(CachedFile, id=kwargs['cfid'], web_download=True)
|
||||
if not cf.allowed_for_session(self.request, "ticketoutputpdf-api"):
|
||||
return Response(
|
||||
{'status': 'failed', 'message': 'Unknown file ID or export failed'},
|
||||
@@ -269,7 +269,7 @@ class TicketRendererViewSet(viewsets.ViewSet):
|
||||
})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
cf = CachedFile(web_download=False)
|
||||
cf = CachedFile(web_download=True)
|
||||
cf.bind_to_session(self.request, "ticketoutputpdf-api")
|
||||
cf.date = now()
|
||||
cf.expires = now() + timedelta(hours=24)
|
||||
|
||||
@@ -453,7 +453,7 @@ class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
|
||||
if not self.request.event.vouchers.filter(code__iexact=code):
|
||||
try:
|
||||
gc = self.request.event.organizer.accepted_gift_cards.get(secret=code)
|
||||
gc = self.request.organizer.accepted_gift_cards.get(secret=code)
|
||||
gcp = GiftCardPayment(self.request.event)
|
||||
if not gcp.is_enabled or not gcp.is_allowed(self.request, Decimal("1.00")):
|
||||
raise ValidationError(error_messages['voucher_invalid'])
|
||||
|
||||
@@ -235,6 +235,11 @@ class AuthorizeView(View):
|
||||
if response_type not in RESPONSE_TYPES_SUPPORTED:
|
||||
return self._final_error("unsupported_response_type", "response_type unsupported")
|
||||
|
||||
if client.authorization_grant_type == "authorization-code" and response_type != "code":
|
||||
# with authorization_grant_type=implicit, response_type=code is still allowed, otherwise "hybrid flow" would be impossible
|
||||
return self._final_error("unsupported_response_type", "For clients with grant type set "
|
||||
"to authorization code, using implicit or hybrid flows is not allowed")
|
||||
|
||||
if response_type != "code" and response_mode == "query":
|
||||
return self._final_error("invalid_request", "response_mode query must not be used with implicit or hybrid flow")
|
||||
elif not response_mode:
|
||||
|
||||
@@ -375,7 +375,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
|
||||
ctx['user_cancel_allowed'] = self.order.user_cancel_allowed
|
||||
for r in ctx['refunds']:
|
||||
if r.provider == 'giftcard':
|
||||
gc = GiftCard.objects.get(pk=r.info_data.get('gift_card'))
|
||||
gc = self.request.organizer.accepted_gift_cards.get(pk=r.info_data.get('gift_card'))
|
||||
r.giftcard = gc
|
||||
|
||||
return ctx
|
||||
|
||||
@@ -35,7 +35,9 @@ from tests.plugins.stripe.test_checkout import apple_domain_create
|
||||
from tests.plugins.stripe.test_provider import MockedCharge
|
||||
|
||||
from pretix.base.models import InvoiceAddress, Order, OrderPosition, Team
|
||||
from pretix.base.models.giftcards import GiftCard
|
||||
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
|
||||
from pretix.base.models.organizer import Organizer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -780,6 +782,28 @@ def test_payment_create_pending(token_client, organizer, event, order):
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_create_giftcard(token_client, organizer, event, order):
|
||||
djmail.outbox = []
|
||||
with scopes_disabled():
|
||||
p_before = order.payments.count()
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'provider': 'giftcard',
|
||||
'state': 'confirmed',
|
||||
'amount': order.total,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
order.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert order.payments.count() == p_before
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_confirm(token_client, organizer, event, order):
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/confirm/'.format(
|
||||
@@ -873,6 +897,24 @@ def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch)
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'detail': 'Invalid state of payment.'}
|
||||
|
||||
with scopes_disabled():
|
||||
o2 = Organizer.objects.create(name='Dummy 2', slug='dummy2', plugins='pretix.plugins.banktransfer')
|
||||
gc = GiftCard.objects.create(issuer=o2, currency='EUR')
|
||||
p1 = order.payments.create(
|
||||
provider='giftcard',
|
||||
state='confirmed',
|
||||
amount=Decimal('23.00'),
|
||||
payment_date=order.datetime,
|
||||
info=json.dumps({"gift_card": gc.pk})
|
||||
)
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/{}/refund/'.format(
|
||||
organizer.slug, event.slug, order.code, p1.pk
|
||||
), format='json', data={})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'detail': 'External error: Gift card not found.'}
|
||||
p1.refresh_from_db()
|
||||
assert p1.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_refund_success(token_client, organizer, event, order, monkeypatch):
|
||||
@@ -916,6 +958,22 @@ def test_payment_refund_success(token_client, organizer, event, order, monkeypat
|
||||
assert r.source == OrderRefund.REFUND_SOURCE_ADMIN
|
||||
assert r.comment == "Foo"
|
||||
|
||||
with scopes_disabled():
|
||||
gc = GiftCard.objects.create(issuer=organizer, currency='EUR')
|
||||
p1 = order.payments.create(
|
||||
provider='giftcard',
|
||||
state='confirmed',
|
||||
amount=Decimal('23.00'),
|
||||
payment_date=order.datetime,
|
||||
info=json.dumps({"gift_card": gc.pk})
|
||||
)
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/{}/refund/'.format(
|
||||
organizer.slug, event.slug, order.code, p1.pk
|
||||
), format='json', data={})
|
||||
assert resp.status_code == 200
|
||||
p1.refresh_from_db()
|
||||
assert p1.state == OrderPayment.PAYMENT_STATE_REFUNDED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_refund_unavailable(token_client, organizer, event, order, monkeypatch):
|
||||
@@ -1935,6 +1993,42 @@ def test_refund_create_invalid_payment(token_client, organizer, event, order):
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_create_giftcard(token_client, organizer, event, order):
|
||||
with scopes_disabled():
|
||||
o2 = Organizer.objects.create(name='Dummy 2', slug='dummy2', plugins='pretix.plugins.banktransfer')
|
||||
gc = GiftCard.objects.create(issuer=o2, currency='EUR')
|
||||
p1 = order.payments.create(
|
||||
provider='giftcard',
|
||||
state='confirmed',
|
||||
amount=Decimal('23.00'),
|
||||
payment_date=order.datetime,
|
||||
info=json.dumps({"gift_card": gc.pk})
|
||||
)
|
||||
with scopes_disabled():
|
||||
r_before = order.refunds.count()
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/refunds/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
"state": "created",
|
||||
"provider": "giftcard",
|
||||
"amount": "23.00",
|
||||
"source": "admin",
|
||||
"payment": p1.pk,
|
||||
"info": {
|
||||
"gift_card": gc.pk,
|
||||
}
|
||||
}
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
with scopes_disabled():
|
||||
assert order.refunds.count() == r_before
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_delete(token_client, organizer, event, order):
|
||||
resp = token_client.delete(
|
||||
|
||||
@@ -72,6 +72,9 @@ def ssoclient(env, client):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_final_errors(env, client, ssoclient):
|
||||
ssoclient[0].authorization_grant_type = "implicit"
|
||||
ssoclient[0].save()
|
||||
|
||||
r = client.get('/bigevents/oauth2/v1/authorize')
|
||||
assert r.status_code == 400
|
||||
assert b'client_id missing' in r.content
|
||||
@@ -111,6 +114,9 @@ def test_authorize_final_errors(env, client, ssoclient):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_basic_redirect_errors(env, client, ssoclient):
|
||||
ssoclient[0].authorization_grant_type = "implicit"
|
||||
ssoclient[0].save()
|
||||
|
||||
r = client.get(
|
||||
f'/bigevents/oauth2/v1/authorize?client_id={ssoclient[0].client_id}&redirect_uri=https://example.net&response_type=code')
|
||||
assert r.status_code == 302
|
||||
@@ -494,8 +500,29 @@ def test_token_client_secret_post(env, client, ssoclient):
|
||||
assert 86390 < d['expires_in'] <= 86400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_implicit_flow_not_allowed_for_authcode_client(env, client, ssoclient):
|
||||
r = client.post('/bigevents/account/login', {
|
||||
'email': 'john@example.org',
|
||||
'password': 'foo',
|
||||
})
|
||||
assert r.status_code == 302
|
||||
|
||||
url = f'/bigevents/oauth2/v1/authorize?client_id={ssoclient[0].client_id}&' \
|
||||
f'redirect_uri=https://example.net&' \
|
||||
f'response_type=id_token&' \
|
||||
f'state=STATE&' \
|
||||
f'nonce=NONCE123&' \
|
||||
f'scope=openid+profile+email'
|
||||
r = client.get(url)
|
||||
assert r.status_code == 400
|
||||
assert b'clients with grant type set to authorization code' in r.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_implicit_flow(env, client, ssoclient):
|
||||
ssoclient[0].authorization_grant_type = "implicit"
|
||||
ssoclient[0].save()
|
||||
r = client.post('/bigevents/account/login', {
|
||||
'email': 'john@example.org',
|
||||
'password': 'foo',
|
||||
@@ -544,6 +571,8 @@ def test_authorize_implicit_flow(env, client, ssoclient):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_hybrid_flow(env, client, ssoclient):
|
||||
ssoclient[0].authorization_grant_type = "implicit"
|
||||
ssoclient[0].save()
|
||||
r = client.post('/bigevents/account/login', {
|
||||
'email': 'john@example.org',
|
||||
'password': 'foo',
|
||||
|
||||
Reference in new issue
Block a user