Compare commits

...

1 Commits

Author SHA1 Message Date
Raphael Michel
a362f4ad3b Revert "Redesign of email settings (#2426)"
This reverts commit e3c7cd7c6d.
2022-01-26 15:50:41 +01:00
27 changed files with 160 additions and 1223 deletions

View File

@@ -220,30 +220,12 @@ Example::
``user``, ``password``
The SMTP user data to use for the connection. Empty by default.
``tls``, ``ssl``
Use STARTTLS or SSL for the SMTP connection. Off by default.
``from``
The email address to set as ``From`` header in outgoing emails by the system.
Default: ``pretix@localhost``
``from_notifications``
The email address to set as ``From`` header in admin notification emails by the system.
Defaults to the value of ``from``.
``from_organizers``
The email address to set as ``From`` header in outgoing emails by the system sent on behalf of organizers.
Defaults to the value of ``from``.
``custom_sender_verification_required``
If this is on (the default), organizers need to verify email addresses they want to use as senders in their event.
``custom_sender_spf_string``
If this is set to a valid SPF string, pretix will show a warning if organizers use a sender address from a domain
that does not include this value.
``custom_smtp_allow_private_networks``
If this is off (the default), custom SMTP servers cannot be private network addresses.
``tls``, ``ssl``
Use STARTTLS or SSL for the SMTP connection. Off by default.
``admins``
Comma-separated list of email addresses that should receive a report about every error code 500 thrown by pretix.

View File

@@ -713,6 +713,7 @@ class EventSettingsSerializer(SettingsSerializer):
'ticket_download_require_validated_email',
'ticket_secret_length',
'mail_prefix',
'mail_from',
'mail_from_name',
'mail_attach_ical',
'mail_attach_tickets',

View File

@@ -665,13 +665,13 @@ class Event(EventMixin, LoggedModel):
return locking.LockManager(self)
def get_mail_backend(self, timeout=None):
def get_mail_backend(self, timeout=None, force_custom=False):
"""
Returns an email server connection, either by using the system-wide connection
or by returning a custom one based on the event's settings.
"""
if self.settings.smtp_use_custom:
if self.settings.smtp_use_custom or force_custom:
return get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
host=self.settings.smtp_host,
port=self.settings.smtp_port,

View File

@@ -191,12 +191,12 @@ class Organizer(LoggedModel):
e.delete()
self.teams.all().delete()
def get_mail_backend(self, timeout=None):
def get_mail_backend(self, timeout=None, force_custom=False):
"""
Returns an email server connection, either by using the system-wide connection
or by returning a custom one based on the organizer's settings.
"""
if self.settings.smtp_use_custom:
if self.settings.smtp_use_custom or force_custom:
return get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
host=self.settings.smtp_host,
port=self.settings.smtp_port,

View File

@@ -217,8 +217,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
for bcc_mail in settings_holder.settings.mail_bcc.split(','):
bcc.append(bcc_mail.strip())
if settings_holder.settings.mail_from not in (settings.DEFAULT_FROM_EMAIL, settings.MAIL_FROM_ORGANIZERS) \
and settings_holder.settings.contact_mail and not headers.get('Reply-To'):
if settings_holder.settings.mail_from == settings.DEFAULT_FROM_EMAIL and settings_holder.settings.contact_mail and not headers.get('Reply-To'):
headers['Reply-To'] = settings_holder.settings.contact_mail
prefix = settings_holder.settings.get('mail_prefix')

View File

@@ -148,7 +148,7 @@ def send_notification_mail(notification: Notification, user: User):
),
'body': body_plain,
'html': body_html,
'sender': settings.MAIL_FROM_NOTIFICATIONS,
'sender': settings.MAIL_FROM,
'headers': {},
'user': user.pk
})

View File

@@ -1589,7 +1589,7 @@ DEFAULTS = {
'type': str
},
'mail_from': {
'default': settings.MAIL_FROM_ORGANIZERS,
'default': settings.MAIL_FROM,
'type': str,
'form_class': forms.EmailField,
'serializer_class': serializers.EmailField,

View File

@@ -48,7 +48,7 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_scopes.forms import SafeModelMultipleChoiceField
from ...base.forms import I18nModelForm
from ...base.forms import I18nModelForm, SecretKeySettingsField
# Import for backwards compatibility with okd import paths
from ...base.forms.widgets import ( # noqa
@@ -373,6 +373,49 @@ class FontSelect(forms.RadioSelect):
option_template_name = 'pretixcontrol/font_option.html'
class SMTPSettingsMixin(forms.Form):
smtp_use_custom = forms.BooleanField(
label=_("Use custom SMTP server"),
help_text=_("All mail related to your event will be sent over the smtp server specified by you."),
required=False
)
smtp_host = forms.CharField(
label=_("Hostname"),
required=False,
widget=forms.TextInput(attrs={'placeholder': 'mail.example.org'})
)
smtp_port = forms.IntegerField(
label=_("Port"),
required=False,
widget=forms.TextInput(attrs={'placeholder': 'e.g. 587, 465, 25, ...'})
)
smtp_username = forms.CharField(
label=_("Username"),
widget=forms.TextInput(attrs={'placeholder': 'myuser@example.org'}),
required=False
)
smtp_password = SecretKeySettingsField(
label=_("Password"),
required=False,
)
smtp_use_tls = forms.BooleanField(
label=_("Use STARTTLS"),
help_text=_("Commonly enabled on port 587."),
required=False
)
smtp_use_ssl = forms.BooleanField(
label=_("Use SSL"),
help_text=_("Commonly enabled on port 465."),
required=False
)
def clean(self):
data = super().clean()
if data.get('smtp_use_tls') and data.get('smtp_use_ssl'):
raise ValidationError(_('You can activate either SSL or STARTTLS security, but not both at the same time.'))
return data
class ItemMultipleChoiceField(SafeModelMultipleChoiceField):
def label_from_instance(self, obj):
return str(obj) if obj.active else mark_safe(f'<strike class="text-muted">{escape(obj)}</strike>')

View File

@@ -64,7 +64,7 @@ from pretix.base.settings import (
PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, validate_event_settings,
)
from pretix.control.forms import (
MultipleLanguagesWidget, SlugWidget, SplitDateTimeField,
MultipleLanguagesWidget, SlugWidget, SMTPSettingsMixin, SplitDateTimeField,
SplitDateTimePickerWidget,
)
from pretix.control.forms.widgets import Select2
@@ -865,9 +865,10 @@ def contains_web_channel_validate(val):
raise ValidationError(_("The online shop must be selected to receive these emails."))
class MailSettingsForm(SettingsForm):
class MailSettingsForm(SMTPSettingsMixin, SettingsForm):
auto_fields = [
'mail_prefix',
'mail_from',
'mail_from_name',
'mail_attach_ical',
'mail_attach_tickets',

View File

@@ -1,129 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import ipaddress
import socket
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from pretix.base.forms import SecretKeySettingsField, SettingsForm
class SMTPMailForm(SettingsForm):
mail_from = forms.EmailField(
label=_("Sender address"),
help_text=_("Sender address for outgoing emails"),
required=True,
)
smtp_host = forms.CharField(
label=_("Hostname"),
required=True,
widget=forms.TextInput(attrs={'placeholder': 'mail.example.org'})
)
smtp_port = forms.IntegerField(
label=_("Port"),
required=True,
widget=forms.TextInput(attrs={'placeholder': 'e.g. 587, 465, 25, ...'})
)
smtp_username = forms.CharField(
label=_("Username"),
widget=forms.TextInput(attrs={'placeholder': 'myuser@example.org'}),
required=False
)
smtp_password = SecretKeySettingsField(
label=_("Password"),
required=False,
)
smtp_use_tls = forms.BooleanField(
label=_("Use STARTTLS"),
help_text=_("Commonly enabled on port 587."),
required=False
)
smtp_use_ssl = forms.BooleanField(
label=_("Use SSL"),
help_text=_("Commonly enabled on port 465."),
required=False
)
def clean(self):
data = super().clean()
if data.get('smtp_use_tls') and data.get('smtp_use_ssl'):
raise ValidationError(_('You can activate either SSL or STARTTLS security, but not both at the same time.'))
for k, v in self.fields.items():
val = data.get(k)
if v._required and not val:
self.add_error(k, _('This field is required.'))
return data
def clean_smtp_host(self):
v = self.cleaned_data['smtp_host']
if not settings.MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS:
try:
if ipaddress.ip_address(v).is_private:
raise ValidationError(_('You are not allowed to use this mail server, please choose one with a '
'public IP address instead.'))
except ValueError:
try:
if ipaddress.ip_address(socket.gethostbyname(v)).is_private:
raise ValidationError(_('You are not allowed to use this mail server, please choose one with a '
'public IP address instead.'))
except OSError:
raise ValidationError(_('We were unable to resolve this hostname.'))
return v
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.obj.settings.mail_from in (settings.MAIL_FROM, settings.MAIL_FROM_ORGANIZERS):
self.initial.pop('mail_from')
for k, v in self.fields.items():
v._required = v.required
v.required = False
v.widget.is_required = False
class SimpleMailForm(SettingsForm):
mail_from = forms.EmailField(
label=_("Sender address"),
help_text=_("Sender address for outgoing emails"),
required=True,
)
def clean(self):
cleaned_data = super().clean()
for k, v in self.fields.items():
val = cleaned_data.get(k)
if v._required and not val:
self.add_error(k, _('This field is required.'))
return cleaned_data
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.obj.settings.mail_from in (settings.MAIL_FROM, settings.MAIL_FROM_ORGANIZERS):
self.initial.pop('mail_from')
for k, v in self.fields.items():
v._required = v.required
v.required = False
v.widget.is_required = False

View File

@@ -60,7 +60,9 @@ from pretix.base.models import (
MembershipType, Organizer, Team,
)
from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS
from pretix.control.forms import ExtFileField, SplitDateTimeField
from pretix.control.forms import (
ExtFileField, SMTPSettingsMixin, SplitDateTimeField,
)
from pretix.control.forms.event import (
SafeEventMultipleChoiceField, multimail_validate,
)
@@ -356,8 +358,9 @@ class OrganizerSettingsForm(SettingsForm):
]
class MailSettingsForm(SettingsForm):
class MailSettingsForm(SMTPSettingsMixin, SettingsForm):
auto_fields = [
'mail_from',
'mail_from_name',
]

View File

@@ -1,14 +0,0 @@
{% load i18n %}{% blocktrans with code=code instance=instance %}Hello,
someone requested to use {{ address }} as a sender address on {{ instance }}.
This will allow them to send emails that are shown to originate from this email address.
If that was you, please enter the following confirmation code:
{{ code }}
If this was not requested by you, you can safely ignore this email.
Best regards,
Your {{ instance }} team
{% endblocktrans %}

View File

@@ -1,127 +0,0 @@
{% extends basetpl %}
{% load i18n %}
{% load bootstrap3 %}
{% load hierarkey_form %}
{% load static %}
{% block title %}{% trans "Organizer" %}{% endblock %}
{% block content %}
<h1>{% trans "E-mail sending" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<div class="panel-group" id="email">
<div class="panel panel-default">
<div class="accordion-radio">
<div class="panel-heading">
<p class="panel-title">
<input type="radio" name="mode" value="system"
data-parent="#email"
{% if mode == "system" %}checked="checked"{% endif %}
id="input_mode_system"
data-toggle="radiocollapse" data-target="#mode_system"/>
<label for="input_mode_system"><strong>{% trans "Use system default" %}</strong></label>
</p>
</div>
</div>
<div id="mode_system"
class="panel-collapse collapsed {% if mode == "system" %}in{% endif %}">
<div class="panel-body form-horizontal">
<p>
{% blocktrans trimmed %}
E-mails will be sent through the system's default server. They will show the following
sender information:
{% endblocktrans %}
</p>
<dl class="dl-horizontal">
<dt>{% trans "From" context "mail_header" %}</dt>
<dd>{{ object.settings.mail_from_name|default_if_none:object.name }}
&lt;{{ default_sender_address }}&gt;
</dd>
{% if object.settings.contact_mail %}
<dt>{% trans "Reply-To" context "mail_header" %}</dt>
<dd>{{ object.settings.contact_mail }}</dd>
{% endif %}
</dl>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="accordion-radio">
<div class="panel-heading">
<p class="panel-title">
<input type="radio" name="mode" value="simple"
data-parent="#email"
{% if mode == "simple" %}checked="checked"{% endif %}
id="input_mode_simple"
data-toggle="radiocollapse" data-target="#mode_simple"/>
<label for="input_mode_simple"><strong>{% trans "Use system email server with a custom sender address" %}</strong></label>
</p>
</div>
</div>
<div id="mode_simple"
class="panel-collapse collapsed {% if mode == "simple" %}in{% endif %}">
<div class="panel-body form-horizontal">
<p>
{% blocktrans trimmed %}
E-mails will be sent through the system's default server but with your own sender
address.
This will make your emails look more personalized and coming directly from you, but it
also might require some extra steps to ensure good deliverability.
{% endblocktrans %}
</p>
{% bootstrap_form simple_form layout="control" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="accordion-radio">
<div class="panel-heading">
<p class="panel-title">
<input type="radio" name="mode" value="smtp"
data-parent="#email"
{% if mode == "smtp" %}checked="checked"{% endif %}
id="input_mode_smtp"
data-toggle="radiocollapse" data-target="#mode_smtp"/>
<label for="input_mode_smtp"><strong>{% trans "Use a custom SMTP server" %}</strong></label>
</p>
</div>
</div>
<div id="mode_smtp"
class="panel-collapse collapsed {% if mode == "smtp" %}in{% endif %}">
<div class="panel-body form-horizontal">
<p>
{% blocktrans trimmed %}
For full customization, you can configure your own SMTP server that will be used for
email sending.
{% endblocktrans %}
</p>
{% bootstrap_form smtp_form layout="control" %}
</div>
</div>
</div>
{% if request.event %}
<div class="panel panel-default">
<div class="accordion-radio">
<div class="panel-heading">
<p class="panel-title">
<input type="radio" name="mode" value="reset"
data-parent="#reset"
id="input_mode_reset"
data-toggle="radiocollapse" data-target="#mode_reset"/>
<label for="input_mode_reset"><strong>{% trans "Reset to organizer settings" %}</strong></label>
</p>
</div>
</div>
<div id="mode_reset"
class="panel-collapse collapsed {% if mode == "reset" %}in{% endif %}">
</div>
</div>
{% endif %}
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Continue" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -1,79 +0,0 @@
{% extends basetpl %}
{% load i18n %}
{% load bootstrap3 %}
{% load hierarkey_form %}
{% load static %}
{% block title %}{% trans "Organizer" %}{% endblock %}
{% block content %}
<h1>{% trans "E-mail sending" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% for k, v in request.POST.items %}
<input type="hidden" name="{{ k }}" value="{{ v }}">
{% endfor %}
<input type="hidden" name="state" value="save">
<div class="panel panel-default">
<div class="panel-heading">
<p class="panel-title">
<strong>{% trans "Use system email server with a custom sender address" %}</strong>
</p>
</div>
<div class="panel-body form-horizontal">
{% if spf_warning %}
<div class="alert alert-warning">
<p>
{{ spf_warning }}
</p>
{% if spf_record %}
<p>
{% trans "This is the SPF record we found on your domain:" %}
</p>
<pre><code>{{ spf_record }}</code></pre>
<p>
{% trans "To fix this, include the following part before the last word:" %}
</p>
<pre><code>{{ spf_key }}</code></pre>
{% else %}
<p>
{% trans "Your new SPF record could look like this:" %}
</p>
<pre><code>v=spf1 a mx {{ spf_key }} ~all</code></pre>
{% endif %}
<p>
{% trans "Please keep in mind that updates to DNS might require multiple hours to take effect." %}
</p>
</div>
{% elif spf_key %}
<div class="alert alert-success">
{% blocktrans trimmed %}
We found an SPF record on your domain that includes this system. Great!
{% endblocktrans %}
</div>
{% endif %}
{% if verification %}
<h3>{% trans "Verification" %}</h3>
<p>
{% blocktrans trimmed with recp=recp %}
We've sent an email to {{ recp }} with a confirmation code to verify that this email address
is owned by you. Please enter the verification code below:
{% endblocktrans %}
</p>
<div class="form-group">
<label class="col-md-3 control-label" for="id_verification">
{% trans "Verification code" %}
</label>
<div class="col-md-9">
<input type="text" name="verification" class="form-control">
</div>
</div>
{% endif %}
</div>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -1,42 +0,0 @@
{% extends basetpl %}
{% load i18n %}
{% load bootstrap3 %}
{% load hierarkey_form %}
{% load static %}
{% block title %}{% trans "Organizer" %}{% endblock %}
{% block content %}
<h1>{% trans "E-mail sending" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% for k, v in request.POST.items %}
<input type="hidden" name="{{ k }}" value="{{ v }}">
{% endfor %}
<input type="hidden" name="state" value="save">
<div class="panel panel-default">
<div class="panel-heading">
<p class="panel-title">
<strong>{% trans "Use a custom SMTP server" %}</strong>
</p>
</div>
<div class="panel-body form-horizontal">
<div class="alert alert-success">
{% blocktrans trimmed %}
A test connection to your SMTP server was successful. You can now save your new settings
to put them in use.
{% endblocktrans %}
</div>
{% if known_host_problem %}
<div class="alert alert-warning">
{{ known_host_problem }}
</div>
{% endif %}
</div>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -12,49 +12,16 @@
<div class="tabbed-form">
<fieldset>
<legend>{% trans "General" %}</legend>
{% bootstrap_field form.mail_prefix layout="control" %}
{% bootstrap_field form.mail_attach_tickets layout="control" %}
{% bootstrap_field form.mail_attach_ical layout="control" %}
{% url "control:organizer.settings.mail" organizer=request.organizer.slug as org_url %}
{% propagated request.event org_url "mail_from" "smtp_use_custom" "smtp_host" "smtp_port" "smtp_username" "smtp_password" "smtp_use_tls" "smtp_use_ssl" %}
<div class="form-group">
<label class="col-md-3 control-label">
{% trans "Sending method" %}
</label>
<div class="col-md-9 static-form-row-with-btn">
{% if request.event.settings.smtp_use_custom %}
{% trans "Custom SMTP server" %}: {{ request.event.settings.smtp_host }}
{% else %}
{% trans "System-provided email server" %}
{% endif %}
&nbsp;&nbsp;
<a href="{% url "control:event.settings.mail.setup" organizer=request.organizer.slug event=request.event.slug %}"
class="btn btn-default">
<span class="fa fa-edit"></span>
{% trans "Edit" %}
</a>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">
{% trans "Sender address" %}
</label>
<div class="col-md-9 static-form-row-with-btn">
{{ request.event.settings.mail_from }}
&nbsp;&nbsp;
<a href="{% url "control:event.settings.mail.setup" organizer=request.organizer.slug event=request.event.slug %}"
class="btn btn-default">
<span class="fa fa-edit"></span>
{% trans "Edit" %}
</a>
</div>
</div>
{% endpropagated %}
{% propagated request.event org_url "mail_from_name" "mail_text_signature" "mail_bcc" %}
{% propagated request.event org_url "mail_from" "mail_from_name" "mail_text_signature" "mail_bcc" %}
{% bootstrap_field form.mail_from layout="control" %}
{% bootstrap_field form.mail_from_name layout="control" %}
{% bootstrap_field form.mail_text_signature layout="control" %}
{% bootstrap_field form.mail_bcc layout="control" %}
{% endpropagated %}
{% bootstrap_field form.mail_prefix layout="control" %}
{% bootstrap_field form.mail_attach_tickets layout="control" %}
{% bootstrap_field form.mail_attach_ical layout="control" %}
{% bootstrap_field form.mail_sales_channel_placed_paid layout="control" %}
</fieldset>
<fieldset>
@@ -118,11 +85,26 @@
<h4>{% trans "Attachments" %}</h4>
{% bootstrap_field form.mail_attachment_new_order layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "SMTP settings" %}</legend>
{% propagated request.event org_url "smtp_use_custom" "smtp_host" "smtp_port" "smtp_username" "smtp_password" "smtp_use_tls" "smtp_use_ssl" %}
{% bootstrap_field form.smtp_use_custom layout="control" %}
{% bootstrap_field form.smtp_host layout="control" %}
{% bootstrap_field form.smtp_port layout="control" %}
{% bootstrap_field form.smtp_username layout="control" %}
{% bootstrap_field form.smtp_password layout="control" %}
{% bootstrap_field form.smtp_use_tls layout="control" %}
{% bootstrap_field form.smtp_use_ssl layout="control" %}
{% endpropagated %}
</fieldset>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
<button type="submit" class="btn btn-default btn-save pull-left" name="test" value="1">
{% trans "Save and test custom SMTP connection" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -11,45 +11,13 @@
<h1>{% trans "E-mail settings" %}</h1>
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"
mail-preview-url="{% url "control:organizer.settings.mail.preview" organizer=request.organizer.slug %}">
mail-preview-url="{% url "control:organizer.settings.mail.preview" organizer=request.organizer.slug %}">
{% csrf_token %}
{% bootstrap_form_errors form %}
<div class="tabbed-form">
<fieldset>
<legend>{% trans "General" %}</legend>
<div class="form-group">
<label class="col-md-3 control-label">
{% trans "Sending method" %}
</label>
<div class="col-md-9 static-form-row-with-btn">
{% if request.organizer.settings.smtp_use_custom %}
{% trans "Custom SMTP server" %}: {{ request.organizer.settings.smtp_host }}
{% else %}
{% trans "System-provided email server" %}
{% endif %}
&nbsp;&nbsp;
<a href="{% url "control:organizer.settings.mail.setup" organizer=request.organizer.slug %}"
class="btn btn-default">
<span class="fa fa-edit"></span>
{% trans "Edit" %}
</a>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">
{% trans "Sender address" %}
</label>
<div class="col-md-9 static-form-row-with-btn">
{{ request.organizer.settings.mail_from }}
&nbsp;&nbsp;
<a href="{% url "control:organizer.settings.mail.setup" organizer=request.organizer.slug %}"
class="btn btn-default">
<span class="fa fa-edit"></span>
{% trans "Edit" %}
</a>
</div>
</div>
{% bootstrap_field form.mail_from layout="control" %}
{% bootstrap_field form.mail_from_name layout="control" %}
{% bootstrap_field form.mail_text_signature layout="control" %}
{% bootstrap_field form.mail_bcc layout="control" %}
@@ -67,11 +35,24 @@
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="reset" title=title_reset items="mail_text_customer_reset" %}
</div>
</fieldset>
<fieldset>
<legend>{% trans "SMTP settings" %}</legend>
{% bootstrap_field form.smtp_use_custom layout="control" %}
{% bootstrap_field form.smtp_host layout="control" %}
{% bootstrap_field form.smtp_port layout="control" %}
{% bootstrap_field form.smtp_username layout="control" %}
{% bootstrap_field form.smtp_password layout="control" %}
{% bootstrap_field form.smtp_use_tls layout="control" %}
{% bootstrap_field form.smtp_use_ssl layout="control" %}
</fieldset>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
<button type="submit" class="btn btn-default btn-save pull-left" name="test" value="1">
{% trans "Save and test custom SMTP connection" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -112,8 +112,6 @@ urlpatterns = [
re_path(r'^organizer/(?P<organizer>[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/settings/email$',
organizer.OrganizerMailSettings.as_view(), name='organizer.settings.mail'),
re_path(r'^organizer/(?P<organizer>[^/]+)/settings/email/setup$',
organizer.MailSettingsSetup.as_view(), name='organizer.settings.mail.setup'),
re_path(r'^organizer/(?P<organizer>[^/]+)/settings/email/preview$',
organizer.MailSettingsPreview.as_view(), name='organizer.settings.mail.preview'),
re_path(r'^organizer/(?P<organizer>[^/]+)/delete$', organizer.OrganizerDelete.as_view(), name='organizer.delete'),
@@ -216,7 +214,6 @@ urlpatterns = [
re_path(r'^settings/tickets/preview/(?P<output>[^/]+)$', event.TicketSettingsPreview.as_view(),
name='event.settings.tickets.preview'),
re_path(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'),
re_path(r'^settings/email/setup$', event.MailSettingsSetup.as_view(), name='event.settings.mail.setup'),
re_path(r'^settings/email/preview$', event.MailSettingsPreview.as_view(), name='event.settings.mail.preview'),
re_path(r'^settings/email/layoutpreview$', event.MailSettingsRendererPreview.as_view(),
name='event.settings.mail.preview.layout'),

View File

@@ -65,7 +65,9 @@ from i18nfield.utils import I18nJSONEncoder
from pytz import timezone
from pretix.base.channels import get_all_sales_channels
from pretix.base.email import get_available_placeholders
from pretix.base.email import (
get_available_placeholders, test_custom_smtp_backend,
)
from pretix.base.models import Event, LogEntry, Order, TaxRule, Voucher
from pretix.base.models.event import EventMetaValue
from pretix.base.services import tickets
@@ -81,7 +83,6 @@ from pretix.control.forms.event import (
TicketSettingsForm, WidgetCodeForm,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.views.mailsetup import MailSettingsSetupView
from pretix.control.views.user import RecentAuthenticationRequiredMixin
from pretix.helpers.database import rolledback_transaction
from pretix.multidomain.urlreverse import get_event_domain
@@ -638,29 +639,29 @@ class MailSettings(EventSettingsViewMixin, EventSettingsFormView):
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
messages.success(self.request, _('Your changes have been saved.'))
if request.POST.get('test', '0').strip() == '1':
backend = self.request.event.get_mail_backend(force_custom=True, timeout=10)
try:
test_custom_smtp_backend(backend, self.request.event.settings.mail_from)
except Exception as e:
messages.warning(self.request, _('An error occurred while contacting the SMTP server: %s') % str(e))
else:
if form.cleaned_data.get('smtp_use_custom'):
messages.success(self.request, _('Your changes have been saved and the connection attempt to '
'your SMTP server was successful.'))
else:
messages.success(self.request, _('We\'ve been able to contact the SMTP server you configured. '
'Remember to check the "use custom SMTP server" checkbox, '
'otherwise your SMTP server will not be used.'))
else:
messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
else:
messages.error(self.request, _('We could not save your changes. See below for details.'))
return self.get(request)
class MailSettingsSetup(EventPermissionRequiredMixin, MailSettingsSetupView):
permission = 'can_change_event_settings'
basetpl = 'pretixcontrol/event/base.html'
def get_success_url(self) -> str:
return reverse('control:event.settings.mail', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug
})
def log_action(self, data):
self.request.event.log_action(
'pretix.event.settings', user=self.request.user, data=data
)
class MailSettingsPreview(EventPermissionRequiredMixin, View):
permission = 'can_change_event_settings'

View File

@@ -1,278 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import logging
import dns.resolver
from django.conf import settings
from django.contrib import messages
from django.core.mail import get_connection
from django.shortcuts import redirect
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView
from pretix.base import email
from pretix.base.models import Event
from pretix.base.services.mail import mail
from pretix.control.forms.filter import OrganizerFilterForm
from pretix.control.forms.mailsetup import SimpleMailForm, SMTPMailForm
logger = logging.getLogger(__name__)
def get_spf_record(hostname):
try:
for resp in dns.resolver.resolve(hostname, 'TXT'):
data = b''.join(resp.strings).decode()
if data.lower().strip().startswith('v=spf1 '): # RFC7208, section 4.5
return data
except:
logger.exception("Could not fetch SPF record")
def _check_spf_record(not_found_lookup_parts, spf_record, depth):
if depth > 10: # prevent infinite loops
return
parts = spf_record.lower().split(" ") # RFC 7208, section 4.6.1
for p in parts:
try:
not_found_lookup_parts.remove(p)
except KeyError:
pass
if not not_found_lookup_parts: # save some DNS requests if we already found everything
return
for p in parts:
if p.startswith('include:') or p.startswith('+include:'):
_, hostname = p.split(':')
rec_record = get_spf_record(hostname)
if rec_record:
_check_spf_record(not_found_lookup_parts, rec_record, depth + 1)
def check_spf_record(lookup, spf_record):
"""
Check that all parts of lookup appear somewhere in the given SPF record, resolving
include: directives recursively
"""
not_found_lookup_parts = set(lookup.split(" "))
_check_spf_record(not_found_lookup_parts, spf_record, 0)
return not not_found_lookup_parts
class MailSettingsSetupView(TemplateView):
template_name = 'pretixcontrol/email_setup.html'
basetpl = None
@cached_property
def object(self):
return getattr(self.request, 'event', self.request.organizer)
@cached_property
def smtp_form(self):
return SMTPMailForm(
obj=self.object,
prefix='smtp',
data=self.request.POST if (self.request.method == "POST" and self.request.POST.get("mode") == "smtp") else None,
)
@cached_property
def simple_form(self):
return SimpleMailForm(
obj=self.object,
prefix='simple',
data=self.request.POST if (self.request.method == "POST" and self.request.POST.get("mode") == "simple") else None,
)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['basetpl'] = self.basetpl
ctx['object'] = self.object
ctx['smtp_form'] = self.smtp_form
ctx['simple_form'] = self.simple_form
ctx['default_sender_address'] = settings.MAIL_FROM_ORGANIZERS
if 'mode' in self.request.POST:
ctx['mode'] = self.request.POST.get('mode')
elif self.object.settings.smtp_use_custom:
ctx['mode'] = 'smtp'
elif self.object.settings.mail_from not in (settings.MAIL_FROM_ORGANIZERS, settings.MAIL_FROM):
ctx['mode'] = 'simple'
else:
ctx['mode'] = 'system'
return ctx
@cached_property
def filter_form(self):
return OrganizerFilterForm(data=self.request.GET, request=self.request)
def post(self, request, *args, **kwargs):
if request.POST.get('mode') == 'system':
if isinstance(self.object, Event) and 'mail_from' in self.object.organizer.settings._cache():
self.object.settings.mail_from = settings.MAIL_FROM_ORGANIZERS
else:
del self.object.settings.mail_from
self.object.settings.smtp_use_custom = False
del self.object.settings.smtp_host
del self.object.settings.smtp_port
del self.object.settings.smtp_username
del self.object.settings.smtp_password
del self.object.settings.smtp_use_tls
del self.object.settings.smtp_use_ssl
messages.success(request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
elif request.POST.get('mode') == 'reset':
del self.object.settings.mail_from
del self.object.settings.smtp_use_custom
del self.object.settings.smtp_host
del self.object.settings.smtp_port
del self.object.settings.smtp_username
del self.object.settings.smtp_password
del self.object.settings.smtp_use_tls
del self.object.settings.smtp_use_ssl
messages.success(request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
elif request.POST.get('mode') == 'simple':
if not self.simple_form.is_valid():
return super().get(request, *args, **kwargs)
session_key = f'sender_mail_verification_code_{self.request.path}_{self.simple_form.cleaned_data.get("mail_from")}'
allow_save = (
(not settings.MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED or
('verification' in self.request.POST and self.request.POST.get('verification', '') == self.request.session.get(session_key, None))) and
(not settings.MAIL_CUSTOM_SENDER_SPF_STRING or self.request.POST.get('state') == 'save')
)
if allow_save:
for k, v in self.simple_form.cleaned_data.items():
self.object.settings.set(k, v)
self.log_action(self.simple_form.cleaned_data)
if session_key in request.session:
del request.session[session_key]
messages.success(request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
spf_warning = None
spf_record = None
if settings.MAIL_CUSTOM_SENDER_SPF_STRING:
hostname = self.simple_form.cleaned_data['mail_from'].split('@')[-1]
spf_record = get_spf_record(hostname)
if not spf_record:
spf_warning = _(
'We could not find an SPF record set for the domain you are trying to use. You can still '
'proceed, but it will increase the chance of emails going to spam or being rejected. We '
'strongly recommend setting an SPF record on the domain. You can do so through the DNS '
'settings at the provider you registered your domain with.'
)
elif not check_spf_record(settings.MAIL_CUSTOM_SENDER_SPF_STRING, spf_record):
spf_warning = _(
'We found an SPF record set for the domain you are trying to use, but it does not include this '
'system\'s email server. This means that there is a very high chance most of the emails will be '
'rejected or marked as spam. You should update the DNS settings of your domain to include '
'this system in the SPF record.'
)
if settings.MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED:
if 'verification' in self.request.POST:
messages.error(request, _('The verification code was incorrect, please try again.'))
else:
self.request.session[session_key] = get_random_string(length=6, allowed_chars='1234567890')
mail(
self.simple_form.cleaned_data.get('mail_from'),
_('Sender address verification'),
'pretixcontrol/email/email_setup.txt',
{
'code': self.request.session[session_key],
'address': self.simple_form.cleaned_data.get('mail_from'),
'instance': settings.PRETIX_INSTANCE_NAME,
},
None,
locale=self.request.LANGUAGE_CODE,
user=self.request.user
)
return self.response_class(
request=self.request,
template='pretixcontrol/email_setup_simple.html',
context={
'basetpl': self.basetpl,
'object': self.object,
'verification': settings.MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED,
'spf_warning': spf_warning,
'spf_record': spf_record,
'spf_key': settings.MAIL_CUSTOM_SENDER_SPF_STRING,
'recp': self.simple_form.cleaned_data.get('mail_from')
},
using=self.template_engine,
)
elif request.POST.get('mode') == 'smtp':
if not self.smtp_form.is_valid():
return super().get(request, *args, **kwargs)
if request.POST.get('state') == 'save':
for k, v in self.smtp_form.cleaned_data.items():
self.object.settings.set(k, v)
self.object.settings.smtp_use_custom = True
self.log_action({**self.smtp_form.cleaned_data, 'smtp_use_custom': True})
messages.success(request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
else:
backend = get_connection(
backend=settings.EMAIL_CUSTOM_SMTP_BACKEND,
host=self.smtp_form.cleaned_data['smtp_host'],
port=self.smtp_form.cleaned_data['smtp_port'],
username=self.smtp_form.cleaned_data.get('smtp_username', ''),
password=self.smtp_form.cleaned_data.get('smtp_password', ''),
use_tls=self.smtp_form.cleaned_data.get('smtp_use_tls', False),
use_ssl=self.smtp_form.cleaned_data.get('smtp_use_ssl', False),
fail_silently=False,
timeout=10,
)
try:
email.test_custom_smtp_backend(backend, self.smtp_form.cleaned_data.get('mail_from'))
except Exception as e:
messages.error(self.request, _('An error occurred while contacting the SMTP server: %s') % str(e))
return self.get(request, *args, **kwargs)
return self.response_class(
request=self.request,
template='pretixcontrol/email_setup_smtp.html',
context={
'basetpl': self.basetpl,
'object': self.object,
'known_host_problem': {
'smtp.gmail.com': _(
'We recommend not using Google Mail for transactional emails. If you try sending many '
'emails in a short amount of time, e.g. when sending information to all your ticket '
'buyers, there is a high chance Google will not deliver all of your emails since they '
'impose a maximum number of emails per time period.'
),
}.get(self.smtp_form.cleaned_data['smtp_host']),
},
using=self.template_engine,
)

View File

@@ -64,6 +64,7 @@ from django.views.generic import (
from pretix.api.models import WebHook
from pretix.base.auth import get_auth_backends
from pretix.base.channels import get_all_sales_channels
from pretix.base.email import test_custom_smtp_backend
from pretix.base.i18n import language
from pretix.base.models import (
CachedFile, Customer, Device, Gate, GiftCard, Invoice, LogEntry,
@@ -101,7 +102,6 @@ from pretix.control.permissions import (
)
from pretix.control.signals import nav_organizer
from pretix.control.views import PaginationMixin
from pretix.control.views.mailsetup import MailSettingsSetupView
from pretix.helpers.dicts import merge_dicts
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri
@@ -262,6 +262,22 @@ class OrganizerMailSettings(OrganizerSettingsFormView):
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
if request.POST.get('test', '0').strip() == '1':
backend = self.request.organizer.get_mail_backend(force_custom=True, timeout=10)
try:
test_custom_smtp_backend(backend, self.request.organizer.settings.mail_from)
except Exception as e:
messages.warning(self.request, _('An error occurred while contacting the SMTP server: %s') % str(e))
else:
if form.cleaned_data.get('smtp_use_custom'):
messages.success(self.request, _('Your changes have been saved and the connection attempt to '
'your SMTP server was successful.'))
else:
messages.success(self.request, _('We\'ve been able to contact the SMTP server you configured. '
'Remember to check the "use custom SMTP server" checkbox, '
'otherwise your SMTP server will not be used.'))
else:
messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
else:
@@ -269,21 +285,6 @@ class OrganizerMailSettings(OrganizerSettingsFormView):
return self.get(request)
class MailSettingsSetup(OrganizerPermissionRequiredMixin, MailSettingsSetupView):
permission = 'can_change_organizer_settings'
basetpl = 'pretixcontrol/base.html'
def get_success_url(self):
return reverse('control:organizer.settings.mail', kwargs={
'organizer': self.request.organizer.slug,
})
def log_action(self, data):
self.request.organizer.log_action(
'pretix.organizer.settings', user=self.request.user, data=data
)
class MailSettingsPreview(OrganizerPermissionRequiredMixin, View):
permission = 'can_change_organizer_settings'

View File

@@ -213,12 +213,8 @@ ALLOWED_HOSTS = ['*']
LANGUAGE_CODE = config.get('locale', 'default', fallback='en')
TIME_ZONE = config.get('locale', 'timezone', fallback='UTC')
MAIL_FROM = SERVER_EMAIL = DEFAULT_FROM_EMAIL = config.get('mail', 'from', fallback='pretix@localhost')
MAIL_FROM_NOTIFICATIONS = config.get('mail', 'from_notifications', fallback=MAIL_FROM)
MAIL_FROM_ORGANIZERS = config.get('mail', 'from_organizers', fallback=MAIL_FROM)
MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED = config.getboolean('mail', 'custom_sender_verification_required', fallback=True)
MAIL_CUSTOM_SENDER_SPF_STRING = config.get('mail', 'custom_sender_spf_string', fallback='')
MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = config.getboolean('mail', 'custom_smtp_allow_private_networks', fallback=False)
MAIL_FROM = SERVER_EMAIL = DEFAULT_FROM_EMAIL = config.get(
'mail', 'from', fallback='pretix@localhost')
EMAIL_HOST = config.get('mail', 'host', fallback='localhost')
EMAIL_PORT = config.getint('mail', 'port', fallback=25)
EMAIL_HOST_USER = config.get('mail', 'user', fallback='')

View File

@@ -21,9 +21,6 @@ td > .form-group > .checkbox {
.static-form-row {
padding-top: 7px;
}
.static-form-row-with-btn {
padding-top: 3px;
}
.form-inline .form-control {
max-width: 100%;
@@ -343,7 +340,6 @@ input[type=number].short {
.propagated-settings-box.locked {
.propagated-settings-form {
opacity: 0.7;
pointer-events: none;
}
.panel-body.help-text {
border-bottom: 1px solid $panel-default-heading-bg;

View File

@@ -190,7 +190,6 @@ setup(
'django-scopes==1.2.*',
'django-statici18n==2.1.*',
'djangorestframework==3.12.*',
'dnspython==2.2.*',
'drf_ujson2==1.6.*',
'isoweek',
'jsonschema',

View File

@@ -36,11 +36,8 @@
import datetime
import time
from decimal import Decimal
from smtplib import SMTPResponseException
import pytest
import pytz
from django.test.utils import override_settings
from django.utils.timezone import now
from django_scopes import scopes_disabled
from i18nfield.strings import LazyI18nString
@@ -51,12 +48,6 @@ from pretix.base.models import Event, LogEntry, Order, Organizer, Team, User
from pretix.testutils.mock import mocker_context
@pytest.fixture
def class_monkeypatch(request, monkeypatch):
request.cls.monkeypatch = monkeypatch
@pytest.mark.usefixtures("class_monkeypatch")
class EventsTest(SoupTest):
@scopes_disabled()
def setUp(self):
@@ -515,204 +506,25 @@ class EventsTest(SoupTest):
assert self.event1.settings.primary_color == self.orga1.settings.primary_color
def test_email_settings(self):
doc = self.get_doc('/control/event/%s/%s/settings/email' % (self.orga1.slug, self.event1.slug))
data = extract_form_fields(doc.select("form")[0])
data['mail_from_name'] = 'test'
doc = self.post_doc('/control/event/%s/%s/settings/email' % (self.orga1.slug, self.event1.slug),
data, follow=True)
assert doc.select('.alert-success')
self.event1.settings.flush()
assert self.event1.settings.mail_from_name == "test"
def test_email_setup_system(self):
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
{
'mode': 'system'
},
follow=True
)
assert doc.select('.alert-success')
self.event1.settings.flush()
assert "mail_from" not in self.orga1.settings._cache()
assert not self.event1.settings.smtp_use_custom
@override_settings(MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED=True, MAIL_CUSTOM_SENDER_SPF_STRING=False)
def test_email_setup_simple_with_verification(self):
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
{
'mode': 'simple',
'simple-mail_from': 'test@test.pretix.dev',
},
follow=True
)
self.event1.settings.flush()
assert "mail_from" not in self.event1.settings._cache()
data = extract_form_fields(doc.select("form")[0])
data['verification'] = self.client.session[
f'sender_mail_verification_code_/control/event/{self.orga1.slug}/{self.event1.slug}/settings/email/setup_test@test.pretix.dev'
]
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
data,
follow=True
)
assert doc.select('.alert-success')
self.event1.settings.flush()
assert self.event1.settings.mail_from == 'test@test.pretix.dev'
@override_settings(MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED=True, MAIL_CUSTOM_SENDER_SPF_STRING=False)
def test_email_setup_simple_with_verification_wrong_code(self):
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
{
'mode': 'simple',
'simple-mail_from': 'test@test.pretix.dev',
},
follow=True
)
self.event1.settings.flush()
assert "mail_from" not in self.event1.settings._cache()
data = extract_form_fields(doc.select("form")[0])
data['verification'] = 'AAAA'
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
data,
follow=True
)
assert doc.select('.alert-danger')
self.event1.settings.flush()
assert "mail_from" not in self.event1.settings._cache()
@staticmethod
def _fake_spf_record(hostname):
return {
'test.pretix.dev': 'v=spf1 a mx include:level2.pretix.dev ~all',
'level2.pretix.dev': 'v=spf1 a mx +include:level3.pretix.dev include:spftest.pretix.dev '
'-include:level4.pretix.dev ~all',
'level3.pretix.dev': 'v=spf1 a mx include:test2.pretix.dev ~all',
'level4.pretix.dev': 'v=spf1 a mx include:test3.pretix.dev ~all',
'test2.pretix.dev': None,
'test3.pretix.dev': None,
'spftest.pretix.dev': None,
}[hostname]
@override_settings(MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED=False, MAIL_CUSTOM_SENDER_SPF_STRING="include:spftest.pretix.dev include:test2.pretix.dev")
def test_email_setup_no_verification_spf_success(self):
self.monkeypatch.setattr("pretix.control.views.mailsetup.get_spf_record", EventsTest._fake_spf_record)
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
{
'mode': 'simple',
'simple-mail_from': 'test@test.pretix.dev',
},
follow=True
)
assert doc.select('.alert-success')
self.event1.settings.flush()
# not yet saved
assert "mail_from" not in self.event1.settings._cache()
data = extract_form_fields(doc.select("form")[0])
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
data,
follow=True
)
assert doc.select('.alert-success')
self.event1.settings.flush()
assert self.event1.settings.mail_from == 'test@test.pretix.dev'
@override_settings(MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED=False, MAIL_CUSTOM_SENDER_SPF_STRING="include:spftest.pretix.dev include:test3.pretix.dev")
def test_email_setup_no_verification_spf_warning(self):
self.monkeypatch.setattr("pretix.control.views.mailsetup.get_spf_record", EventsTest._fake_spf_record)
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
{
'mode': 'simple',
'simple-mail_from': 'test@test.pretix.dev',
},
follow=True
)
assert doc.select('.alert-warning')
self.event1.settings.flush()
# not yet saved
assert "mail_from" not in self.event1.settings._cache()
def test_email_setup_smtp(self):
self.monkeypatch.setattr("pretix.base.email.test_custom_smtp_backend", lambda b, a: None)
self.monkeypatch.setattr("socket.gethostbyname", lambda h: "8.8.8.8")
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
{
'mode': 'smtp',
'smtp-mail_from': 'test@test.pretix.dev',
'smtp-smtp_host': 'test.pretix.dev',
'smtp-smtp_port': '587',
},
follow=True
)
assert doc.select('.alert-success')
# not yet saved
self.event1.settings.flush()
assert "smtp_use_custom" not in self.event1.settings._cache()
data = extract_form_fields(doc.select("form")[0])
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
data,
follow=True
)
assert doc.select('.alert-success')
self.event1.settings.flush()
assert self.event1.settings.mail_from == 'test@test.pretix.dev'
assert self.event1.settings.smtp_host == 'test.pretix.dev'
assert self.event1.settings.smtp_port == 587
assert self.event1.settings.smtp_use_custom
def test_email_setup_smtp_failure(self):
def fail(a, b):
raise SMTPResponseException(400, 'Auth denied')
self.monkeypatch.setattr("pretix.base.email.test_custom_smtp_backend", fail)
self.monkeypatch.setattr("socket.gethostbyname", lambda h: "8.8.8.8")
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
{
'mode': 'smtp',
'smtp-mail_from': 'test@test.pretix.dev',
'smtp-smtp_host': 'test.pretix.dev',
'smtp-smtp_port': '587',
},
follow=True
)
assert 'Auth denied' in doc.select('.alert-danger')[0].text
# not yet saved
self.event1.settings.flush()
assert "smtp_use_custom" not in self.event1.settings._cache()
assert "mail_from" not in self.event1.settings._cache()
def test_email_setup_do_not_allow_private_ip_by_default(self):
doc = self.post_doc(
'/control/event/%s/%s/settings/email/setup' % (self.orga1.slug, self.event1.slug),
{
'mode': 'simple',
'smtp-mail_from': 'test@test.pretix.dev',
'smtp-smtp_host': '10.0.1.1',
'smtp-smtp_port': '587',
},
follow=True
)
assert doc.select('.has-error')
# not yet saved
self.event1.settings.flush()
assert "smtp_use_custom" not in self.event1.settings._cache()
assert "mail_from" not in self.event1.settings._cache()
with mocker_context() as mocker:
mocked = mocker.patch('pretix.control.views.event.test_custom_smtp_backend')
doc = self.get_doc('/control/event/%s/%s/settings/email' % (self.orga1.slug, self.event1.slug))
data = extract_form_fields(doc.select("form")[0])
data['test'] = '1'
doc = self.post_doc('/control/event/%s/%s/settings/email' % (self.orga1.slug, self.event1.slug),
data, follow=True)
print(doc)
assert doc.select('.alert-success')
self.event1.settings.flush()
assert mocked.called
def test_ticket_settings(self):
doc = self.get_doc('/control/event/%s/%s/settings/tickets' % (self.orga1.slug, self.event1.slug))
data = extract_form_fields(doc.select("form")[0])
data['ticket_download'] = 'on'
data['ticketoutput_testdummy__enabled'] = 'on'
self.post_doc('/control/event/%s/%s/settings/tickets' % (self.orga1.slug, self.event1.slug), data, follow=True)
doc = self.post_doc('/control/event/%s/%s/settings/tickets' % (self.orga1.slug, self.event1.slug),
data, follow=True)
self.event1.settings.flush()
assert self.event1.settings.get('ticket_download', as_type=bool)

View File

@@ -20,15 +20,14 @@
# <https://www.gnu.org/licenses/>.
#
import datetime
from smtplib import SMTPResponseException
import pytest
from django.db import transaction
from django.test.utils import override_settings
from django_scopes import scopes_disabled
from tests.base import SoupTest, extract_form_fields
from pretix.base.models import Event, Organizer, Team, User
from pretix.testutils.mock import mocker_context
@pytest.fixture
@@ -101,194 +100,13 @@ class OrganizerTest(SoupTest):
assert called
def test_email_settings(self):
doc = self.get_doc('/control/organizer/%s/settings/email' % self.orga1.slug)
data = extract_form_fields(doc.select("form")[0])
data['mail_from_name'] = 'test'
doc = self.post_doc('/control/organizer/%s/settings/email' % self.orga1.slug,
data, follow=True)
assert doc.select('.alert-success')
self.orga1.settings.flush()
assert self.orga1.settings.mail_from_name == "test"
def test_email_setup_system(self):
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
{
'mode': 'system'
},
follow=True
)
assert doc.select('.alert-success')
self.orga1.settings.flush()
assert "mail_from" not in self.orga1.settings._cache()
assert not self.orga1.settings.smtp_use_custom
@override_settings(MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED=True, MAIL_CUSTOM_SENDER_SPF_STRING=False)
def test_email_setup_simple_with_verification(self):
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
{
'mode': 'simple',
'simple-mail_from': 'test@test.pretix.dev',
},
follow=True
)
self.orga1.settings.flush()
assert "mail_from" not in self.orga1.settings._cache()
data = extract_form_fields(doc.select("form")[0])
data['verification'] = self.client.session[
f'sender_mail_verification_code_/control/organizer/{self.orga1.slug}/settings/email/setup_test@test.pretix.dev'
]
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
data,
follow=True
)
assert doc.select('.alert-success')
self.orga1.settings.flush()
assert self.orga1.settings.mail_from == 'test@test.pretix.dev'
@override_settings(MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED=True, MAIL_CUSTOM_SENDER_SPF_STRING=False)
def test_email_setup_simple_with_verification_wrong_code(self):
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
{
'mode': 'simple',
'simple-mail_from': 'test@test.pretix.dev',
},
follow=True
)
self.orga1.settings.flush()
assert "mail_from" not in self.orga1.settings._cache()
data = extract_form_fields(doc.select("form")[0])
data['verification'] = 'AAAA'
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
data,
follow=True
)
assert doc.select('.alert-danger')
self.orga1.settings.flush()
assert "mail_from" not in self.orga1.settings._cache()
@staticmethod
def _fake_spf_record(hostname):
return {
'test.pretix.dev': 'v=spf1 a mx include:level2.pretix.dev ~all',
'level2.pretix.dev': 'v=spf1 a mx +include:level3.pretix.dev include:spftest.pretix.dev '
'-include:level4.pretix.dev ~all',
'level3.pretix.dev': 'v=spf1 a mx include:test2.pretix.dev ~all',
'level4.pretix.dev': 'v=spf1 a mx include:test3.pretix.dev ~all',
'test2.pretix.dev': None,
'test3.pretix.dev': None,
'spftest.pretix.dev': None,
}[hostname]
@override_settings(MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED=False, MAIL_CUSTOM_SENDER_SPF_STRING="include:spftest.pretix.dev include:test2.pretix.dev")
def test_email_setup_no_verification_spf_success(self):
self.monkeypatch.setattr("pretix.control.views.mailsetup.get_spf_record", OrganizerTest._fake_spf_record)
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
{
'mode': 'simple',
'simple-mail_from': 'test@test.pretix.dev',
},
follow=True
)
assert doc.select('.alert-success')
self.orga1.settings.flush()
# not yet saved
assert "mail_from" not in self.orga1.settings._cache()
data = extract_form_fields(doc.select("form")[0])
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
data,
follow=True
)
assert doc.select('.alert-success')
self.orga1.settings.flush()
assert self.orga1.settings.mail_from == 'test@test.pretix.dev'
@override_settings(MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED=False, MAIL_CUSTOM_SENDER_SPF_STRING="include:spftest.pretix.dev include:test3.pretix.dev")
def test_email_setup_no_verification_spf_warning(self):
self.monkeypatch.setattr("pretix.control.views.mailsetup.get_spf_record", OrganizerTest._fake_spf_record)
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
{
'mode': 'simple',
'simple-mail_from': 'test@test.pretix.dev',
},
follow=True
)
assert doc.select('.alert-warning')
self.orga1.settings.flush()
# not yet saved
assert "mail_from" not in self.orga1.settings._cache()
def test_email_setup_smtp(self):
self.monkeypatch.setattr("pretix.base.email.test_custom_smtp_backend", lambda b, a: None)
self.monkeypatch.setattr("socket.gethostbyname", lambda h: "8.8.8.8")
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
{
'mode': 'smtp',
'smtp-mail_from': 'test@test.pretix.dev',
'smtp-smtp_host': 'test.pretix.dev',
'smtp-smtp_port': '587',
},
follow=True
)
assert doc.select('.alert-success')
# not yet saved
self.orga1.settings.flush()
assert "smtp_use_custom" not in self.orga1.settings._cache()
data = extract_form_fields(doc.select("form")[0])
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
data,
follow=True
)
assert doc.select('.alert-success')
self.orga1.settings.flush()
assert self.orga1.settings.mail_from == 'test@test.pretix.dev'
assert self.orga1.settings.smtp_host == 'test.pretix.dev'
assert self.orga1.settings.smtp_port == 587
assert self.orga1.settings.smtp_use_custom
def test_email_setup_smtp_failure(self):
def fail(a, b):
raise SMTPResponseException(400, 'Auth denied')
self.monkeypatch.setattr("pretix.base.email.test_custom_smtp_backend", fail)
self.monkeypatch.setattr("socket.gethostbyname", lambda h: "8.8.8.8")
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
{
'mode': 'smtp',
'smtp-mail_from': 'test@test.pretix.dev',
'smtp-smtp_host': 'test.pretix.dev',
'smtp-smtp_port': '587',
},
follow=True
)
assert 'Auth denied' in doc.select('.alert-danger')[0].text
# not yet saved
self.orga1.settings.flush()
assert "smtp_use_custom" not in self.orga1.settings._cache()
assert "mail_from" not in self.orga1.settings._cache()
def test_email_setup_do_not_allow_private_ip_by_default(self):
doc = self.post_doc(
'/control/organizer/%s/settings/email/setup' % self.orga1.slug,
{
'mode': 'simple',
'smtp-mail_from': 'test@test.pretix.dev',
'smtp-smtp_host': '10.0.1.1',
'smtp-smtp_port': '587',
},
follow=True
)
assert doc.select('.has-error')
# not yet saved
self.orga1.settings.flush()
assert "smtp_use_custom" not in self.orga1.settings._cache()
assert "mail_from" not in self.orga1.settings._cache()
with mocker_context() as mocker:
mocked = mocker.patch('pretix.control.views.organizer.test_custom_smtp_backend')
doc = self.get_doc('/control/organizer/%s/settings/email' % self.orga1.slug)
data = extract_form_fields(doc.select("form")[0])
data['test'] = '1'
doc = self.post_doc('/control/organizer/%s/settings/email' % self.orga1.slug,
data, follow=True)
assert doc.select('.alert-success')
self.event1.settings.flush()
assert mocked.called

View File

@@ -92,7 +92,6 @@ event_urls = [
"settings/payment",
"settings/tickets",
"settings/email",
"settings/email/setup",
"settings/cancel",
"settings/invoice",
"settings/invoice/preview",
@@ -172,8 +171,6 @@ event_urls = [
organizer_urls = [
'organizer/abc/edit',
'organizer/abc/',
'organizer/abc/settings/email',
'organizer/abc/settings/email/setup',
'organizer/abc/teams',
'organizer/abc/team/1/',
'organizer/abc/team/1/edit',
@@ -288,7 +285,6 @@ event_permission_urls = [
("can_change_event_settings", "settings/payment", 200, HTTP_GET),
("can_change_event_settings", "settings/tickets", 200, HTTP_GET),
("can_change_event_settings", "settings/email", 200, HTTP_GET),
("can_change_event_settings", "settings/email/setup", 200, HTTP_GET),
("can_change_event_settings", "settings/cancel", 200, HTTP_GET),
("can_change_event_settings", "settings/invoice", 200, HTTP_GET),
("can_change_event_settings", "settings/widget", 200, HTTP_GET),
@@ -485,8 +481,6 @@ organizer_permission_urls = [
("can_change_teams", "organizer/dummy/team/1/edit", 200),
("can_change_teams", "organizer/dummy/team/1/delete", 200),
("can_change_organizer_settings", "organizer/dummy/edit", 200),
("can_change_organizer_settings", "organizer/dummy/settings/email", 200),
("can_change_organizer_settings", "organizer/dummy/settings/email/setup", 200),
("can_change_organizer_settings", "organizer/dummy/devices", 200),
("can_change_organizer_settings", "organizer/dummy/device/add", 200),
("can_change_organizer_settings", "organizer/dummy/device/1/edit", 404),