diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 1885923b2..8a4c31eee 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -381,8 +381,23 @@ class Order(LockModel, LoggedModel): self.event.cache.delete('complain_testmode_orders') self.delete() - def email_confirm_hash(self): - return hashlib.sha256(settings.SECRET_KEY.encode() + self.secret.encode()).hexdigest()[:9] + def email_confirm_secret(self): + return self.tagged_secret("email_confirm", 9) + + def check_email_confirm_secret(self, received_secret): + return ( + hmac.compare_digest( + self.tagged_secret("email_confirm", 9), + received_secret[:9].lower() + ) or any( + # TODO: remove this clause after a while (compatibility with old secrets currently in flight) + hmac.compare_digest( + hashlib.sha256(sk.encode() + self.secret.encode()).hexdigest()[:9], + received_secret + ) + for sk in [settings.SECRET_KEY, *settings.SECRET_KEY_FALLBACKS] + ) + ) def get_extended_status_display(self): # Changes in this method should to be replicated in pretixcontrol/orders/fragment_order_status.html diff --git a/src/pretix/base/services/mail.py b/src/pretix/base/services/mail.py index ae5387ebc..9d30d927a 100644 --- a/src/pretix/base/services/mail.py +++ b/src/pretix/base/services/mail.py @@ -301,7 +301,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La order.event, 'presale:event.order.open', kwargs={ 'order': order.code, 'secret': order.secret, - 'hash': order.email_confirm_hash() + 'hash': order.email_confirm_secret() } ) ) diff --git a/src/pretix/base/services/placeholders.py b/src/pretix/base/services/placeholders.py index 8ab1a42e6..65d4530b7 100644 --- a/src/pretix/base/services/placeholders.py +++ b/src/pretix/base/services/placeholders.py @@ -262,7 +262,7 @@ def base_placeholders(sender, **kwargs): 'presale:event.order.open', kwargs={ 'order': order.code, 'secret': order.secret, - 'hash': order.email_confirm_hash() + 'hash': order.email_confirm_secret() } ), lambda event: build_absolute_uri( event, @@ -443,7 +443,7 @@ def base_placeholders(sender, **kwargs): 'organizer': event.organizer.slug, 'order': order.code, 'secret': order.secret, - 'hash': order.email_confirm_hash(), + 'hash': order.email_confirm_secret(), }), ) for order in orders diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index eb5f8d378..0073e013c 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -156,11 +156,10 @@ class OrderOpen(EventViewMixin, OrderDetailMixin, View): def get(self, request, *args, **kwargs): if not self.order: raise Http404(_('Unknown order code or not authorized to access this order.')) - if kwargs.get('hash') == self.order.email_confirm_hash(): - if not self.order.email_known_to_work: - self.order.log_action('pretix.event.order.contact.confirmed') - self.order.email_known_to_work = True - self.order.save(update_fields=['email_known_to_work']) + if self.order.check_email_confirm_secret(kwargs.get('hash')) and not self.order.email_known_to_work: + self.order.log_action('pretix.event.order.contact.confirmed') + self.order.email_known_to_work = True + self.order.save(update_fields=['email_known_to_work']) return redirect(self.get_order_url()) diff --git a/src/tests/presale/test_orders.py b/src/tests/presale/test_orders.py index f3c608447..9e41baf2a 100644 --- a/src/tests/presale/test_orders.py +++ b/src/tests/presale/test_orders.py @@ -221,7 +221,7 @@ class OrdersTest(BaseOrdersTest): assert not self.order.email_known_to_work response = self.client.get( - '/%s/%s/order/%s/%s/open/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret, self.order.email_confirm_hash()) + '/%s/%s/order/%s/%s/open/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret, self.order.email_confirm_secret()) ) assert response.status_code == 302 self.order.refresh_from_db()