Fix download from UploadedFileWidget in backend

This commit is contained in:
Mira Weller
2026-08-12 21:02:01 +02:00
parent 8bee2b4a4f
commit 1bc00f0cb4
4 changed files with 24 additions and 8 deletions
+2 -2
View File
@@ -758,7 +758,7 @@ class BaseQuestionsForm(forms.Form):
label=label, required=required,
help_text=help_text,
initial=initial.file if initial else None,
widget=PortraitImageWidget(answer=initial,
widget=PortraitImageWidget(answer=initial, request=request,
attrs={'data-portrait-photo': 'true'}),
)
else:
@@ -766,7 +766,7 @@ class BaseQuestionsForm(forms.Form):
label=label, required=required,
help_text=help_text,
initial=initial.file if initial else None,
widget=UploadedFileWidget(answer=initial),
widget=UploadedFileWidget(answer=initial, request=request),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_OTHER,
max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
)
+10 -3
View File
@@ -47,6 +47,7 @@ from pretix.helpers.i18n import (
get_format_without_seconds, get_javascript_format,
get_javascript_format_without_seconds,
)
from pretix.helpers.safedownload import get_token
def replace_arabic_numbers(inp):
@@ -158,19 +159,25 @@ class TimePickerWidget(forms.TimeInput):
class UploadedFileWidget(forms.ClearableFileInput):
def __init__(self, *args, **kwargs):
self.answer = kwargs.pop('answer')
self.request = kwargs.pop('request')
super().__init__(*args, **kwargs)
class FakeFile:
def __init__(self, file, answer):
def __init__(self, file, answer, request):
self.file = file
self.answer = answer
self.request = request
def __str__(self):
return os.path.basename(self.file.name).split('.', 1)[-1]
@property
def url(self):
return self.answer.frontend_file_url
token = get_token(self.request, self.answer)
if self.request.resolver_match.namespace == 'control':
return self.answer.backend_file_url + '?token=' + token
else:
return self.answer.frontend_file_url + '?token=' + token
def get_context(self, name, value, attrs):
# Browsers can't recognize that the server already has a file uploaded
@@ -183,7 +190,7 @@ class UploadedFileWidget(forms.ClearableFileInput):
def format_value(self, value):
if self.is_initial(value):
return self.FakeFile(value, self.answer)
return self.FakeFile(value, self.answer, self.request)
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
+6 -2
View File
@@ -24,17 +24,21 @@ import hashlib
from django.core.signing import BadSignature, TimestampSigner
class SafeDownloadSigner(TimestampSigner):
pass
def get_token(request, answer):
if not request.session.session_key:
request.session.create()
payload = '{}:{}'.format(request.session.session_key, answer.pk)
signer = TimestampSigner()
signer = SafeDownloadSigner()
return signer.sign(hashlib.sha1(payload.encode()).hexdigest())
def check_token(request, answer, token):
payload = hashlib.sha1('{}:{}'.format(request.session.session_key, answer.pk).encode()).hexdigest()
signer = TimestampSigner()
signer = SafeDownloadSigner()
try:
return payload == signer.unsign(token, max_age=3600 * 24)
except BadSignature:
+6 -1
View File
@@ -67,6 +67,7 @@ from pretix.base.services.cart import (
from pretix.base.timemachine import time_machine_now
from pretix.base.views.tasks import AsyncAction
from pretix.helpers.http import redirect_to_url
from pretix.helpers.safedownload import check_token
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.productlist import (
item_group_by_category, prepare_item_list_for_shop,
@@ -848,6 +849,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
class AnswerDownload(EventViewMixin, View):
def get(self, request, *args, **kwargs):
answid = kwargs.get('answer')
token = request.GET.get('token', '')
cart_id = get_or_create_cart_id(self.request)
answer = get_object_or_404(
QuestionAnswer,
@@ -856,8 +858,10 @@ class AnswerDownload(EventViewMixin, View):
)
if not answer.file:
return Http404()
if not check_token(request, answer, token):
raise Http404(_("This link is no longer valid. Please go back, refresh the page, and try again."))
ftype, _ = mimetypes.guess_type(answer.file.name)
ftype, _1 = mimetypes.guess_type(answer.file.name)
filename = '{}-cart-{}'.format(
self.request.event.slug.upper(),
os.path.basename(answer.file.name).split('.', 1)[1]
@@ -865,6 +869,7 @@ class AnswerDownload(EventViewMixin, View):
resp = FileResponse(
answer.file,
filename=filename,
as_attachment=True,
content_type=ftype or 'application/binary'
)
return resp