Fix #122 -- Allow users to re-send order links (#197)

If the provided mail address has not ordered anything, there will still
be a mail generated and sent to an invalid mail address, to avoid
obvious timing attacks to determine active users.
This commit is contained in:
Tobias Kunze
2016-09-01 08:59:36 +02:00
committed by Raphael Michel
parent 841cfe52a2
commit 7e19effe3c
7 changed files with 189 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ from pretix.base.models import Event, Order
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger('pretix.base.mail')
INVALID_ADDRESS = 'invalid-pretix-mail-address'
class TolerantDict(dict):
@@ -53,6 +54,9 @@ def mail(email: str, subject: str, template: str,
:raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean
that the email has been sent, just that it has been queued by the email backend.
"""
if email == INVALID_ADDRESS:
return
with language(locale):
if isinstance(template, LazyI18nString):
body = str(template)

View File

@@ -163,6 +163,18 @@ to your order for {event}.
You can change your order details and view the status of your order at
{url}
Best regards,
Your {event} team"""))
},
'mail_text_resend_all_links': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
somebody requested a list of your orders for {event}.
The list is as follows:
{orders}
Best regards,
Your {event} team"""))
},

View File

@@ -0,0 +1,7 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
class ResendLinkForm(forms.Form):
email = forms.EmailField(label=_('E-mail'))

View File

@@ -0,0 +1,31 @@
{% extends "pretixpresale/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Resend order links" %}{% endblock %}
{% block content %}
<h2>
{% trans "Resend order links" %}
</h2>
<div class="row">
<div class="panel-body">
{% blocktrans trimmed %}
If you lost the link to your order or orders, please enter the email address you
used for your order. We will send you an email with links to all orders you placed
using this email address.
{% endblocktrans %}
</div>
</div>
<div class="row">
<form class="form" method="post">
{% csrf_token %}
<div class="col-md-8 col-sm-6 col-xs-12">
{% bootstrap_form form layout="inline" %}
</div>
<div class="col-md-4 col-sm-6 col-xs-12">
<button class="btn btn-primary btn-block" type="submit">
{% trans "Send links" %}
</button>
</div>
</form>
</div>
{% endblock %}

View File

@@ -6,6 +6,7 @@ import pretix.presale.views.event
import pretix.presale.views.locale
import pretix.presale.views.order
import pretix.presale.views.organizer
import pretix.presale.views.user
# This is not a valid Django URL configuration, as the final
# configuration is done by the pretix.multidomain package.
@@ -18,6 +19,7 @@ event_patterns = [
name='event.redeem'),
url(r'^checkout/(?P<step>[^/]+)/$', pretix.presale.views.checkout.CheckoutView.as_view(),
name='event.checkout'),
url(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(),
name='event.order'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/invoice$',

View File

@@ -0,0 +1,73 @@
import logging
from django.conf import settings
from django.contrib import messages
from django.shortcuts import redirect
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView
from pretix.base.services.mail import INVALID_ADDRESS, SendMailException, mail
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
from pretix.presale.forms.user import ResendLinkForm
from pretix.presale.views import EventViewMixin
class ResendLinkView(EventViewMixin, TemplateView):
template_name = 'pretixpresale/event/resend_link.html'
@cached_property
def link_form(self):
return ResendLinkForm(data=self.request.POST if self.request.method == 'POST' else None)
def post(self, request, *args, **kwargs):
if not self.link_form.is_valid():
messages.error(self.request, _('We had difficulties processing your input.'))
return self.get(request, *args, **kwargs)
user = self.link_form.cleaned_data.get('email')
if settings.HAS_REDIS:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
if rc.exists('pretix_resend_{}'.format(user)):
messages.error(request, _('We already sent you an email in the last 24 hours.'))
return redirect('presale:user.resend')
else:
rc.setex('pretix_resend_{}'.format(user), 3600 * 24, '1')
orders = self.request.event.orders.filter(email=user)
order_context = []
for order in orders:
url = build_absolute_uri(
self.request.event,
'presale:event.order',
kwargs={'order': order.code, 'secret': order.secret}
)
order_context.append(' - {} - {}'.format(order, url))
if not orders:
user = INVALID_ADDRESS
subject = _('Your orders for {}'.format(self.request.event))
template = self.request.event.settings.mail_text_resend_all_links
context = {
'orders': '\n'.join(order_context),
'event': self.request.event,
}
try:
mail(user, subject, template, context, event=self.request.event, locale=self.request.LANGUAGE_CODE)
except SendMailException:
logger = logging.getLogger('pretix.presale.user')
logger.exception('A mail resending order links to {} could not be sent.'.format(user))
messages.error(self.request, _('We have trouble sending emails right now, please check back later.'))
return self.get(request, *args, **kwargs)
messages.success(self.request, _('If there were any orders by this user, they will receive an email with their order codes.'))
return redirect(eventreverse(self.request.event, 'presale:event.index'))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = self.link_form
return context