From 9a196839970e7d98a2bd9375bbd470846ccb3a27 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Tue, 2 Feb 2016 03:07:44 +0100 Subject: templates/mail: Get all parts from the template Also render sender and subject with the mail template, so the mails can be created with only a template name, a recipient and the template args. The required confirmation links are also generated in the templates. --- accounts/__init__.py | 5 +++++ accounts/backend/mail/__init__.py | 13 ++++++++++- accounts/backend/mail/dummy.py | 12 +++++----- accounts/backend/mail/sendmail.py | 13 +++++------ accounts/templates/mail/_base.txt | 15 +++++++++++++ accounts/templates/mail/change_mail.txt | 12 ++++++++-- accounts/templates/mail/disable_notify.txt | 12 ++++++++++ accounts/templates/mail/lost_password.txt | 10 ++++++++- accounts/templates/mail/register.txt | 12 ++++++++-- accounts/templates/mail/register_notify.txt | 13 +++++++++++ accounts/utils/__init__.py | 12 ---------- accounts/views/admin/__init__.py | 16 ++++++-------- accounts/views/default/__init__.py | 34 ++++++++--------------------- 13 files changed, 113 insertions(+), 66 deletions(-) create mode 100644 accounts/templates/mail/_base.txt create mode 100644 accounts/templates/mail/disable_notify.txt create mode 100644 accounts/templates/mail/register_notify.txt diff --git a/accounts/__init__.py b/accounts/__init__.py index e650586..01e5774 100644 --- a/accounts/__init__.py +++ b/accounts/__init__.py @@ -3,6 +3,7 @@ from flask import Flask, g, session from models import Service from utils import get_backend +from utils.confirmation import Confirmation from utils.sessions import EncryptedSessionInterface from utils.login import create_login_manager from views import default, login, admin @@ -34,4 +35,8 @@ def create_app(): login_manager = create_login_manager() login_manager.init_app(app) + def confirm(realm, *args): + return Confirmation(realm).dumps(tuple(args)) + app.jinja_env.globals.update(confirm=confirm) + return app diff --git a/accounts/backend/mail/__init__.py b/accounts/backend/mail/__init__.py index c22b037..ecd0d45 100644 --- a/accounts/backend/mail/__init__.py +++ b/accounts/backend/mail/__init__.py @@ -5,5 +5,16 @@ class Backend(object): def __init__(self, app): self.app = app - def send(self, recipient, subject, body, sender=None): + def _send(self, recipient, content): raise NotImplementedError() + + def send(self, recipient, template, **kwargs): + if recipient is None: + return + + tmpl = self.app.jinja_env.get_or_select_template(template) + + kwargs['recipient'] = recipient + module = tmpl.make_module(kwargs) + + self._send(recipient, module) diff --git a/accounts/backend/mail/dummy.py b/accounts/backend/mail/dummy.py index d62a66b..ad38d42 100644 --- a/accounts/backend/mail/dummy.py +++ b/accounts/backend/mail/dummy.py @@ -36,13 +36,13 @@ class DummyBackend(Backend): else: self.format = format - def send(self, recipient, subject, body, sender=None): - if sender is None: - sender = self.app.config['MAIL_DEFAULT_SENDER'] - + def _send(self, recipient, content): + body = content.body() 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))) + subject=ensure_utf8(content.subject()), + sender=ensure_utf8(content.sender()), + to=ensure_utf8(recipient), + body=ensure_utf8(body))) diff --git a/accounts/backend/mail/sendmail.py b/accounts/backend/mail/sendmail.py index 92b354e..9a39b03 100644 --- a/accounts/backend/mail/sendmail.py +++ b/accounts/backend/mail/sendmail.py @@ -10,19 +10,16 @@ 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'] - + def _send(self, recipient, content): safe = lambda s: s.split('\n', 1)[0] - msg = MIMEText(body, _charset='utf-8') - msg['Subject'] = safe(subject) + msg = MIMEText(content.body(), _charset='utf-8') + msg['Subject'] = safe(content.subject()) msg['To'] = safe(recipient) - msg['From'] = safe(sender) + msg['From'] = safe(content.sender()) envelope = [] - _, address = parseaddr(safe(sender)) + _, address = parseaddr(safe(content.sender())) if address != '': envelope = ['-f', address] diff --git a/accounts/templates/mail/_base.txt b/accounts/templates/mail/_base.txt new file mode 100644 index 0000000..b5941da --- /dev/null +++ b/accounts/templates/mail/_base.txt @@ -0,0 +1,15 @@ +{% macro subject() -%} + {% block subject %}{% endblock %} +{%- endmacro %} + +{% macro sender() -%} + {% if self.sender() -%} + {% block sender %}{% endblock %} + {%- else -%} + {{ config['MAIL_DEFAULT_SENDER'] }} + {%- endif %} +{%- endmacro %} + +{% macro body() -%} + {% block body %}{% endblock %} +{%- endmacro %} diff --git a/accounts/templates/mail/change_mail.txt b/accounts/templates/mail/change_mail.txt index 64cda14..54b6ca5 100644 --- a/accounts/templates/mail/change_mail.txt +++ b/accounts/templates/mail/change_mail.txt @@ -1,12 +1,19 @@ +{% extends 'mail/_base.txt' %} + +{% block subject %}E-Mail-Adresse bestätigen{% endblock %} +{% block sender %}{{ config['MAIL_CONFIRM_SENDER'] }}{% endblock %} + +{% block body -%} Hallo, Jemand, vermutlich du, möchte auf spline accounts [1] die E-Mail-Adresse des Accounts {{ username }} auf diese Adresse - {{ mail }} + {{ recipient }} ändern. Um diese Änderung zu bestätigen, benutze bitte folgenden Link: - <{{ link }}> + <{{ url_for('default.change_mail', _external=True, + token=confirm('change_mail', username, recipient)) }}> Wenn du dies nicht möchtest, brauchst du nichts weiter zu tun. @@ -14,3 +21,4 @@ Ohne deine Bestätigung wird die Adresse nicht geändert. [1] {{ url_for('default.index', _external=True) }} +{%- endblock %} diff --git a/accounts/templates/mail/disable_notify.txt b/accounts/templates/mail/disable_notify.txt new file mode 100644 index 0000000..a9662b6 --- /dev/null +++ b/accounts/templates/mail/disable_notify.txt @@ -0,0 +1,12 @@ +{% extends 'mail/_base.txt' %} + +{% block subject -%} + [accounts] Benutzer {{ username }} deaktiviert +{%- endblock %} + +{% block body -%} +Benutzername: {{ username }} +E-Mail war: {{ mail }} + +durch: {{ admin }} +{%- endblock %} diff --git a/accounts/templates/mail/lost_password.txt b/accounts/templates/mail/lost_password.txt index 5eaa654..6d38f15 100644 --- a/accounts/templates/mail/lost_password.txt +++ b/accounts/templates/mail/lost_password.txt @@ -1,11 +1,19 @@ +{% extends 'mail/_base.txt' %} + +{% block subject %}Passwort vergessen{% endblock %} +{% block sender %}{{ config['MAIL_CONFIRM_SENDER'] }}{% endblock %} + +{% block body -%} Hallo {{ username }}, Jemand, vermutlich du, hat auf spline accounts einen Link zum Ändern deines Passworts angefordert. Hier kannst du dein Passwort ändern: - <{{ link }}> + <{{ url_for('default.lost_password_complete', _external=True, + token=confirm('lost_password', username)) }}> Wenn du diese Mail nicht angefordert hast, brauchst du nichts weiter zu tun. Dein altes Passwort bleibt dann weiter gültig. +{% endblock %} diff --git a/accounts/templates/mail/register.txt b/accounts/templates/mail/register.txt index c5af422..0dc7cd1 100644 --- a/accounts/templates/mail/register.txt +++ b/accounts/templates/mail/register.txt @@ -1,15 +1,22 @@ +{% extends 'mail/_base.txt' %} + +{% block subject %}E-Mail-Adresse bestätigen{% endblock %} +{% block sender %}{{ config['MAIL_CONFIRM_SENDER'] }}{% endblock %} + +{% block body -%} Hallo, Jemand, vermutlich du, möchte auf spline accounts [1] einen Account mit folgenden Daten anlegen: Benutzername: {{ username }} - E-Mail-Adresse: {{ mail }} + E-Mail-Adresse: {{ recipient }} Wenn du diesen Account anlegen möchtest, bestätige mit folgendem Link deine E-Mail-Adresse: - <{{ link }}> + <{{ url_for('default.register_complete', _external=True, + token=confirm('register', username, recipient)) }}> Wenn du diesen Account nicht anlegen möchtest, brauchst du nichts @@ -17,3 +24,4 @@ weiter zu tun. Ohne deine Bestätigung wird der Account nicht erstellt. [1] {{ url_for('default.index', _external=True) }} +{%- endblock %} diff --git a/accounts/templates/mail/register_notify.txt b/accounts/templates/mail/register_notify.txt new file mode 100644 index 0000000..7a649f4 --- /dev/null +++ b/accounts/templates/mail/register_notify.txt @@ -0,0 +1,13 @@ +{% extends 'mail/_base.txt' %} + +{% block subject -%} + [accounts] Neuer Benutzer {{ username }} erstellt +{%- endblock %} + +{% block body -%} +Benutzername: {{ username }} +E-Mail: {{ mail }} + +Spammer? Deaktivieren: {{ url_for('admin.disable_account', uid=username, + _external=True) }} +{%- endblock %} diff --git a/accounts/utils/__init__.py b/accounts/utils/__init__.py index 0a3fa10..0759fd1 100644 --- a/accounts/utils/__init__.py +++ b/accounts/utils/__init__.py @@ -35,18 +35,6 @@ def ensure_utf8(s): return s -def send_register_confirmation_mail(username, mail): - confirm_token = Confirmation('register').dumps((username, mail)) - confirm_link = url_for('default.register_complete', token=confirm_token, _external=True) - - body = render_template('mail/register.txt', username=username, - mail=mail, link=confirm_link) - - current_app.mail_backend.send( - mail, u'E-Mail-Adresse bestätigen', body, - sender=current_app.config.get('MAIL_CONFIRM_SENDER')) - - class NotRegexp(Regexp): """ Like wtforms.validators.Regexp, but rejects data that DOES match the regex. diff --git a/accounts/views/admin/__init__.py b/accounts/views/admin/__init__.py index f6ac51a..d008d98 100644 --- a/accounts/views/admin/__init__.py +++ b/accounts/views/admin/__init__.py @@ -7,7 +7,7 @@ from flask.ext.login import current_user from uuid import uuid4 from werkzeug.exceptions import Forbidden -from accounts.utils import templated, send_register_confirmation_mail +from accounts.utils import templated from accounts.forms import AdminCreateAccountForm, AdminDisableAccountForm @@ -33,7 +33,8 @@ def index(): def create_account(): form = AdminCreateAccountForm() if request.method == 'POST' and form.validate(): - send_register_confirmation_mail(form.username.data, form.mail.data) + current_app.mail_backend.send(form.mail.data, 'mail/register.txt', + username=form.username.data) flash(u'Mail versandt.', 'success') return redirect(url_for('admin.index')) @@ -78,13 +79,10 @@ def disable_account(): flash(u'Passwort auf ein zufälliges und Mailadresse auf %s ' u'gesetzt.' % mail, 'success') - if current_app.config.get('MAIL_REGISTER_NOTIFY'): - 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' % \ - (form.user.uid, oldmail, session['username']) - ) + current_app.mail_backend.send( + current_app.config['MAIL_REGISTER_NOTIFY'], + 'mail/disable_notify.txt', + username=form.user.uid, mail=oldmail, admin=current_user.uid) return redirect(url_for('admin.index')) diff --git a/accounts/views/default/__init__.py b/accounts/views/default/__init__.py index 0a7e65d..eec173e 100644 --- a/accounts/views/default/__init__.py +++ b/accounts/views/default/__init__.py @@ -23,7 +23,8 @@ bp = Blueprint('default', __name__) def register(): form = RegisterForm(request.form) if form.validate_on_submit(): - send_register_confirmation_mail(form.username.data, form.mail.data) + current_app.mail_backend.send(form.mail.data, 'mail/register.txt', + username=form.username.data) flash(u'Es wurde eine E-Mail an die angegebene Adresse geschickt, ' u'um diese zu überprüfen. Bitte folge den Anweisungen in der ' @@ -58,14 +59,10 @@ def register_complete(token): current_app.user_backend.register(user) login_user(user) - if current_app.config.get('MAIL_REGISTER_NOTIFY'): - current_app.mail_backend.send( - current_app.config['MAIL_REGISTER_NOTIFY'], - u'[accounts] Neuer Benutzer %s erstellt' % username, - u'Benutzername: %s\nE-Mail: %s\n\nSpammer? Deaktivieren: ' - u'%s\n' % (username, mail, - url_for('admin.disable_account', uid=username, _external=True)) - ) + current_app.mail_backend.send( + current_app.config['MAIL_REGISTER_NOTIFY'], + 'mail/register_notify.txt', + username=username, mail=mail) flash(u'Benutzer erfolgreich angelegt.', 'success') return redirect(url_for('.index')) @@ -86,15 +83,8 @@ def lost_password(): if form.validate_on_submit(): #TODO: make the link only usable once (e.g include a hash of the old pw) # atm the only thing we do is make the link valid for only little time - confirm_token = Confirmation('lost_password').dumps(form.user.uid) - confirm_link = url_for('.lost_password_complete', token=confirm_token, _external=True) - - body = render_template('mail/lost_password.txt', username=form.user.uid, - link=confirm_link) - current_app.mail_backend.send( - form.user.attributes['mail'], u'Passwort vergessen', body, - sender=current_app.config.get('MAIL_CONFIRM_SENDER')) + form.user.mail, 'mail/lost_password.txt', username=form.user.uid) 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') @@ -144,15 +134,9 @@ def index(): elif request.form.get('submit_main'): if form.mail.data and form.mail.data != current_user.mail: - confirm_token = Confirmation('change_mail').dumps((current_user.uid, form.mail.data)) - confirm_link = url_for('.change_mail', token=confirm_token, _external=True) - - body = render_template('mail/change_mail.txt', username=current_user.uid, - mail=form.mail.data, link=confirm_link) - current_app.mail_backend.send( - form.mail.data, u'E-Mail-Adresse bestätigen', body, - sender=current_app.config.get('MAIL_CONFIRM_SENDER')) + form.mail.data, 'mail/change_mail.txt', + username=current_user.uid) flash(u'Es wurde eine E-Mail an die angegebene Adresse geschickt, ' u'um diese zu überprüfen. Bitte folge den Anweisungen in der ' -- cgit v1.2.3-1-g7c22