diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index 228f203e84..9d2eec601c 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -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, ) diff --git a/src/pretix/base/forms/widgets.py b/src/pretix/base/forms/widgets.py index 3197ad98ee..34cd55a524 100644 --- a/src/pretix/base/forms/widgets.py +++ b/src/pretix/base/forms/widgets.py @@ -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): diff --git a/src/pretix/helpers/safedownload.py b/src/pretix/helpers/safedownload.py index 87d1f56a3a..f6362e97b0 100644 --- a/src/pretix/helpers/safedownload.py +++ b/src/pretix/helpers/safedownload.py @@ -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: diff --git a/src/pretix/presale/views/cart.py b/src/pretix/presale/views/cart.py index 79d3798da2..6285c67fc1 100644 --- a/src/pretix/presale/views/cart.py +++ b/src/pretix/presale/views/cart.py @@ -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