mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
297 lines
14 KiB
Python
297 lines
14 KiB
Python
#
|
|
# 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.forms import SECRET_REDACTED
|
|
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:
|
|
r = dns.resolver.Resolver()
|
|
for resp in r.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)
|
|
elif p.startswith('redirect='):
|
|
_, 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:
|
|
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
|
|
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. This means that '
|
|
'there is a very high change most of the emails will be rejected or marked as spam. 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.'
|
|
)
|
|
|
|
verification = settings.MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED and not spf_warning
|
|
if verification:
|
|
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': verification,
|
|
'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():
|
|
if v != SECRET_REDACTED:
|
|
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:
|
|
self.smtp_form._unmask_secret_fields()
|
|
|
|
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,
|
|
)
|