mirror of
https://github.com/pretix/pretix.git
synced 2026-08-28 13:34:40 +00:00
Fix download from UploadedFileWidget in backend
This commit is contained in:
@@ -758,7 +758,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
label=label, required=required,
|
label=label, required=required,
|
||||||
help_text=help_text,
|
help_text=help_text,
|
||||||
initial=initial.file if initial else None,
|
initial=initial.file if initial else None,
|
||||||
widget=PortraitImageWidget(answer=initial,
|
widget=PortraitImageWidget(answer=initial, request=request,
|
||||||
attrs={'data-portrait-photo': 'true'}),
|
attrs={'data-portrait-photo': 'true'}),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -766,7 +766,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
label=label, required=required,
|
label=label, required=required,
|
||||||
help_text=help_text,
|
help_text=help_text,
|
||||||
initial=initial.file if initial else None,
|
initial=initial.file if initial else None,
|
||||||
widget=UploadedFileWidget(answer=initial),
|
widget=UploadedFileWidget(answer=initial, request=request),
|
||||||
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_OTHER,
|
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_OTHER,
|
||||||
max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
|
max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ from pretix.helpers.i18n import (
|
|||||||
get_format_without_seconds, get_javascript_format,
|
get_format_without_seconds, get_javascript_format,
|
||||||
get_javascript_format_without_seconds,
|
get_javascript_format_without_seconds,
|
||||||
)
|
)
|
||||||
|
from pretix.helpers.safedownload import get_token
|
||||||
|
|
||||||
|
|
||||||
def replace_arabic_numbers(inp):
|
def replace_arabic_numbers(inp):
|
||||||
@@ -158,19 +159,25 @@ class TimePickerWidget(forms.TimeInput):
|
|||||||
class UploadedFileWidget(forms.ClearableFileInput):
|
class UploadedFileWidget(forms.ClearableFileInput):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.answer = kwargs.pop('answer')
|
self.answer = kwargs.pop('answer')
|
||||||
|
self.request = kwargs.pop('request')
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
class FakeFile:
|
class FakeFile:
|
||||||
def __init__(self, file, answer):
|
def __init__(self, file, answer, request):
|
||||||
self.file = file
|
self.file = file
|
||||||
self.answer = answer
|
self.answer = answer
|
||||||
|
self.request = request
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return os.path.basename(self.file.name).split('.', 1)[-1]
|
return os.path.basename(self.file.name).split('.', 1)[-1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
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):
|
def get_context(self, name, value, attrs):
|
||||||
# Browsers can't recognize that the server already has a file uploaded
|
# 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):
|
def format_value(self, value):
|
||||||
if self.is_initial(value):
|
if self.is_initial(value):
|
||||||
return self.FakeFile(value, self.answer)
|
return self.FakeFile(value, self.answer, self.request)
|
||||||
|
|
||||||
|
|
||||||
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
|
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
|
||||||
|
|||||||
@@ -24,17 +24,21 @@ import hashlib
|
|||||||
from django.core.signing import BadSignature, TimestampSigner
|
from django.core.signing import BadSignature, TimestampSigner
|
||||||
|
|
||||||
|
|
||||||
|
class SafeDownloadSigner(TimestampSigner):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_token(request, answer):
|
def get_token(request, answer):
|
||||||
if not request.session.session_key:
|
if not request.session.session_key:
|
||||||
request.session.create()
|
request.session.create()
|
||||||
payload = '{}:{}'.format(request.session.session_key, answer.pk)
|
payload = '{}:{}'.format(request.session.session_key, answer.pk)
|
||||||
signer = TimestampSigner()
|
signer = SafeDownloadSigner()
|
||||||
return signer.sign(hashlib.sha1(payload.encode()).hexdigest())
|
return signer.sign(hashlib.sha1(payload.encode()).hexdigest())
|
||||||
|
|
||||||
|
|
||||||
def check_token(request, answer, token):
|
def check_token(request, answer, token):
|
||||||
payload = hashlib.sha1('{}:{}'.format(request.session.session_key, answer.pk).encode()).hexdigest()
|
payload = hashlib.sha1('{}:{}'.format(request.session.session_key, answer.pk).encode()).hexdigest()
|
||||||
signer = TimestampSigner()
|
signer = SafeDownloadSigner()
|
||||||
try:
|
try:
|
||||||
return payload == signer.unsign(token, max_age=3600 * 24)
|
return payload == signer.unsign(token, max_age=3600 * 24)
|
||||||
except BadSignature:
|
except BadSignature:
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ from pretix.base.services.cart import (
|
|||||||
from pretix.base.timemachine import time_machine_now
|
from pretix.base.timemachine import time_machine_now
|
||||||
from pretix.base.views.tasks import AsyncAction
|
from pretix.base.views.tasks import AsyncAction
|
||||||
from pretix.helpers.http import redirect_to_url
|
from pretix.helpers.http import redirect_to_url
|
||||||
|
from pretix.helpers.safedownload import check_token
|
||||||
from pretix.multidomain.urlreverse import eventreverse
|
from pretix.multidomain.urlreverse import eventreverse
|
||||||
from pretix.presale.productlist import (
|
from pretix.presale.productlist import (
|
||||||
item_group_by_category, prepare_item_list_for_shop,
|
item_group_by_category, prepare_item_list_for_shop,
|
||||||
@@ -848,6 +849,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
|
|||||||
class AnswerDownload(EventViewMixin, View):
|
class AnswerDownload(EventViewMixin, View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
answid = kwargs.get('answer')
|
answid = kwargs.get('answer')
|
||||||
|
token = request.GET.get('token', '')
|
||||||
cart_id = get_or_create_cart_id(self.request)
|
cart_id = get_or_create_cart_id(self.request)
|
||||||
answer = get_object_or_404(
|
answer = get_object_or_404(
|
||||||
QuestionAnswer,
|
QuestionAnswer,
|
||||||
@@ -856,8 +858,10 @@ class AnswerDownload(EventViewMixin, View):
|
|||||||
)
|
)
|
||||||
if not answer.file:
|
if not answer.file:
|
||||||
return Http404()
|
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(
|
filename = '{}-cart-{}'.format(
|
||||||
self.request.event.slug.upper(),
|
self.request.event.slug.upper(),
|
||||||
os.path.basename(answer.file.name).split('.', 1)[1]
|
os.path.basename(answer.file.name).split('.', 1)[1]
|
||||||
@@ -865,6 +869,7 @@ class AnswerDownload(EventViewMixin, View):
|
|||||||
resp = FileResponse(
|
resp = FileResponse(
|
||||||
answer.file,
|
answer.file,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
|
as_attachment=True,
|
||||||
content_type=ftype or 'application/binary'
|
content_type=ftype or 'application/binary'
|
||||||
)
|
)
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
Reference in New Issue
Block a user