summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Sulfrian <alexander@sulfrian.net>2016-01-23 19:24:42 +0100
committerAlexander Sulfrian <alexander@sulfrian.net>2016-01-25 01:56:48 +0100
commitea3983d891bc6e34a827902ac8cf15734923e14c (patch)
tree87abdc6d4c5976341b3d12ddc94af7a55e7c7d95
parent64ab1d6c61905df0c1c81ca8b7fb2135bb5692ee (diff)
downloadweb-ea3983d891bc6e34a827902ac8cf15734923e14c.tar.gz
web-ea3983d891bc6e34a827902ac8cf15734923e14c.tar.bz2
web-ea3983d891bc6e34a827902ac8cf15734923e14c.zip
backend/mail: Allow different backends for mail
-rw-r--r--accounts/__init__.py13
-rw-r--r--accounts/backend/__init__.py0
-rw-r--r--accounts/backend/mail/__init__.py9
-rw-r--r--accounts/backend/mail/dummy.py48
-rw-r--r--accounts/backend/mail/sendmail.py35
-rw-r--r--accounts/default_settings.py2
-rw-r--r--accounts/utils.py42
-rw-r--r--accounts/views/admin/__init__.py4
8 files changed, 115 insertions, 38 deletions
diff --git a/accounts/__init__.py b/accounts/__init__.py
index 8fbcdd0..09aa875 100644
--- a/accounts/__init__.py
+++ b/accounts/__init__.py
@@ -18,6 +18,7 @@ if 'SPLINE_ACCOUNT_WEB_SETTINGS' in os.environ:
app.all_services = account.SERVICES #TODO: take that from our json file or so
app.username_blacklist = list()
+app.mail_backend = get_backend(app.config['MAIL_BACKEND'], app)
@app.before_request
def session_permanent():
@@ -118,7 +119,7 @@ def register_complete(token):
assert login_user(user.uid, user.password)
if app.config.get('MAIL_REGISTER_NOTIFY'):
- send_mail(
+ app.mail_backend.send(
app.config['MAIL_REGISTER_NOTIFY'],
u'[accounts] Neuer Benutzer %s erstellt' % username,
u'Benutzername: %s\nE-Mail: %s\n\nSpammer? Deaktivieren: '
@@ -151,8 +152,9 @@ def lost_password():
body = render_template('mail/lost_password.txt', username=form.user.uid,
link=confirm_link)
- send_mail(form.user.attributes['mail'], u'Passwort vergessen', body,
- sender=app.config.get('MAIL_CONFIRM_SENDER'))
+ app.mail_backend.send(
+ form.user.attributes['mail'], u'Passwort vergessen', body,
+ sender=app.config.get('MAIL_CONFIRM_SENDER'))
flash(u'Wir haben dir eine E-Mail mit einem Link zum Passwort ändern '
u'geschickt. Bitte folge den Anweisungen in der E-Mail.', 'success')
@@ -210,8 +212,9 @@ def settings():
body = render_template('mail/change_mail.txt', username=g.user.uid,
mail=form.mail.data, link=confirm_link)
- send_mail(form.mail.data, u'E-Mail-Adresse bestätigen', body,
- sender=app.config.get('MAIL_CONFIRM_SENDER'))
+ app.mail_backend.send(
+ form.mail.data, u'E-Mail-Adresse bestätigen', body,
+ sender=app.config.get('MAIL_CONFIRM_SENDER'))
flash(u'Es wurde eine E-Mail an die angegebene Adresse geschickt, '
u'um diese zu überprüfen. Bitte folge den Anweisungen in der '
diff --git a/accounts/backend/__init__.py b/accounts/backend/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/accounts/backend/__init__.py
diff --git a/accounts/backend/mail/__init__.py b/accounts/backend/mail/__init__.py
new file mode 100644
index 0000000..c22b037
--- /dev/null
+++ b/accounts/backend/mail/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+class Backend(object):
+
+ def __init__(self, app):
+ self.app = app
+
+ def send(self, recipient, subject, body, sender=None):
+ raise NotImplementedError()
diff --git a/accounts/backend/mail/dummy.py b/accounts/backend/mail/dummy.py
new file mode 100644
index 0000000..d62a66b
--- /dev/null
+++ b/accounts/backend/mail/dummy.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+from . import Backend
+from accounts.utils import ensure_utf8
+
+
+FANCY_FORMAT = '''\
+,---------------------------------------------------------------------------
+| Subject: {subject}
+| To: {to}
+| From: {sender}
+|---------------------------------------------------------------------------
+| {body}
+`---------------------------------------------------------------------------'''
+
+
+PLAIN_FORMAT = '''Subject: {subject}
+To: {to}
+From: {sender}
+
+{body}'''
+
+
+class DummyBackend(Backend):
+
+ def __init__(self, app, plain=False, format=None):
+ super(DummyBackend, self).__init__(app)
+ self.plain = plain
+
+ if format is None:
+ if self.plain:
+ self.format = PLAIN_FORMAT
+ else:
+ self.format = FANCY_FORMAT
+ else:
+ self.format = format
+
+ def send(self, recipient, subject, body, sender=None):
+ if sender is None:
+ sender = self.app.config['MAIL_DEFAULT_SENDER']
+
+ if not self.plain:
+ body = "\n| ".join(body.split("\n"))
+
+ print(self.format.format(
+ subject=ensure_utf8(subject), to=ensure_utf8(recipient),
+ sender=ensure_utf8(sender), body=ensure_utf8(body)))
diff --git a/accounts/backend/mail/sendmail.py b/accounts/backend/mail/sendmail.py
new file mode 100644
index 0000000..92b354e
--- /dev/null
+++ b/accounts/backend/mail/sendmail.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import subprocess
+from email.mime.text import MIMEText
+from email.utils import parseaddr
+
+from . import Backend
+
+
+class SendmailBackend(Backend):
+
+ def send(self, recipient, subject, body, sender=None):
+ if sender is None:
+ sender = self.app.config['MAIL_DEFAULT_SENDER']
+
+ safe = lambda s: s.split('\n', 1)[0]
+
+ msg = MIMEText(body, _charset='utf-8')
+ msg['Subject'] = safe(subject)
+ msg['To'] = safe(recipient)
+ msg['From'] = safe(sender)
+
+ envelope = []
+ _, address = parseaddr(safe(sender))
+ if address != '':
+ envelope = ['-f', address]
+
+ p = subprocess.Popen([self.app.config['SENDMAIL_COMMAND']] +
+ envelope + ['-t'], stdin=subprocess.PIPE)
+ p.stdin.write(msg.as_string())
+ p.stdin.close()
+
+ if p.wait() != 0:
+ raise RuntimeError('sendmail terminated with %d' % p.returncode)
diff --git a/accounts/default_settings.py b/accounts/default_settings.py
index b51cbb3..538a8ed 100644
--- a/accounts/default_settings.py
+++ b/accounts/default_settings.py
@@ -22,3 +22,5 @@ LDAP_ADMIN_USER = 'admin'
LDAP_ADMIN_PASS = 'admin'
PREFERRED_URL_SCHEME = 'https'
+
+MAIL_BACKEND = 'accounts.backend.mail.dummy'
diff --git a/accounts/utils.py b/accounts/utils.py
index 86991b3..87a2ba8 100644
--- a/accounts/utils.py
+++ b/accounts/utils.py
@@ -1,14 +1,12 @@
# -*- coding: utf-8 -*-
import hmac
+import importlib
import ldap
import pickle
import re
import struct
-import subprocess
from base64 import urlsafe_b64encode, urlsafe_b64decode
from Crypto.Cipher import AES
-from email.mime.text import MIMEText
-from email.utils import parseaddr
from functools import wraps
from flask import current_app, flash, g, redirect, render_template, request, session
from flask import url_for as flask_url_for
@@ -20,7 +18,6 @@ from werkzeug.exceptions import Forbidden
from wtforms.validators import Regexp, ValidationError
-
_username_re = re.compile(r'^[a-zA-Z][a-zA-Z0-9-]{1,15}$')
_username_exclude_re = re.compile(r'^(admin|root)')
@@ -194,31 +191,6 @@ def constant_time_compare(val1, val2):
return result == 0
-def send_mail(recipient, subject, body, sender=None):
- if sender is None:
- sender = current_app.config['MAIL_DEFAULT_SENDER']
-
- safe = lambda s: s.split('\n', 1)[0]
-
- msg = MIMEText(body, _charset='utf-8')
- msg['Subject'] = safe(subject)
- msg['To'] = safe(recipient)
- msg['From'] = safe(sender)
-
- envelope = []
- (name, address) = parseaddr(safe(sender))
- if address != '':
- envelope = ['-f', address]
-
- p = subprocess.Popen([current_app.config['SENDMAIL_COMMAND']] + envelope + ['-t'],
- stdin=subprocess.PIPE)
- p.stdin.write(msg.as_string())
- p.stdin.close()
-
- if p.wait() != 0:
- raise RuntimeError('sendmail terminated with %d' % p.returncode)
-
-
class Service(object):
def __init__(self, id, name, url):
self.id = id
@@ -243,8 +215,9 @@ def send_register_confirmation_mail(username, mail):
body = render_template('mail/register.txt', username=username,
mail=mail, link=confirm_link)
- send_mail(mail, u'E-Mail-Adresse bestätigen', body,
- sender=current_app.config.get('MAIL_CONFIRM_SENDER'))
+ current_app.mail_backend.send(
+ mail, u'E-Mail-Adresse bestätigen', body,
+ sender=current_app.config.get('MAIL_CONFIRM_SENDER'))
class NotRegexp(Regexp):
@@ -272,3 +245,10 @@ def url_for(endpoint, **values):
# used when we encounter inconsistent data etc
class ShouldNotHappen(RuntimeError):
pass
+
+
+def get_backend(path, app):
+ module = path.rsplit(".", 1).pop()
+ class_name = '%sBackend' % module.title()
+ backend_class = getattr(importlib.import_module(path), class_name)
+ return backend_class(app)
diff --git a/accounts/views/admin/__init__.py b/accounts/views/admin/__init__.py
index 998bf8b..02a2da1 100644
--- a/accounts/views/admin/__init__.py
+++ b/accounts/views/admin/__init__.py
@@ -6,7 +6,7 @@ from flask import current_app, redirect, request, g, flash, url_for
from uuid import uuid4
from werkzeug.exceptions import Forbidden
-from accounts.utils import templated, send_register_confirmation_mail, send_mail
+from accounts.utils import templated, send_register_confirmation_mail
from accounts.forms import AdminCreateAccountForm, AdminDisableAccountForm
@@ -78,7 +78,7 @@ def disable_account():
u'gesetzt.' % mail, 'success')
if current_app.config.get('MAIL_REGISTER_NOTIFY'):
- send_mail(
+ current_app.mail_backend.send(
current_app.config['MAIL_REGISTER_NOTIFY'],
u'[accounts] Benutzer %s deaktiviert' % form.user.uid,
'Benutzername: %s\nE-Mail war: %s\n\ndurch: %s\n' % \