Skip to content
Snippets Groups Projects
Commit 7c563b3e authored by Martin Mareš's avatar Martin Mareš
Browse files

Odesílání e-mailů přesunuto do samostatného modulu

parent f8470280
No related branches found
No related tags found
1 merge request!86Registrace
This commit is part of merge request !86. Comments created here will be created in the context of that merge request.
...@@ -4,6 +4,7 @@ import argparse ...@@ -4,6 +4,7 @@ import argparse
import sys import sys
import mo.db as db import mo.db as db
import mo.email
import mo.users import mo.users
import mo.util import mo.util
...@@ -49,4 +50,4 @@ if args.passwd is not None: ...@@ -49,4 +50,4 @@ if args.passwd is not None:
session.commit() session.commit()
if args.passwd is None: if args.passwd is None:
mo.util.send_new_account_email(user, token) mo.email.send_new_account_email(user, token)
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import argparse import argparse
import mo.config import mo.config
import mo.email
import mo.db as db import mo.db as db
import mo.users import mo.users
import mo.util import mo.util
...@@ -26,4 +27,4 @@ session.commit() ...@@ -26,4 +27,4 @@ session.commit()
if args.mail_instead: if args.mail_instead:
mo.config.MAIL_INSTEAD = args.mail_instead mo.config.MAIL_INSTEAD = args.mail_instead
mo.util.send_new_account_email(user, token) mo.email.send_new_account_email(user, token)
mo/email.py 0 → 100644
+ 122
0
View file @ 7c563b3e
# Rozesílání e-mailových notifikací všeho druhu
import datetime
import email.message
import email.headerregistry
import subprocess
import textwrap
import urllib.parse
import mo.db as db
import mo.config as config
from mo.util import logger
def send_user_email(user: db.User, subject: str, body: str) -> bool:
logger.info(f'Mail: "{subject}" -> {user.email}')
mail_from = getattr(config, 'MAIL_FROM', None)
if mail_from is None:
logger.error('Mail: V configu chybí nastavení MAIL_FROM')
return False
msg = email.message.EmailMessage()
msg['From'] = email.headerregistry.Address(
display_name='Odevzdávací Systém MO',
addr_spec=mail_from,
)
msg['To'] = [
email.headerregistry.Address(
display_name=user.full_name(),
addr_spec=user.email,
)
]
msg['Reply-To'] = email.headerregistry.Address(
display_name='Správce OSMO',
addr_spec=config.MAIL_CONTACT,
)
msg['Subject'] = 'OSMO – ' + subject
msg['Date'] = datetime.datetime.now()
msg.set_content(body, cte='quoted-printable')
mail_instead = getattr(config, 'MAIL_INSTEAD', None)
if mail_instead is None:
send_to = user.email
else:
send_to = mail_instead
sm = subprocess.Popen(
[
'/usr/sbin/sendmail',
'-oi',
'-f',
mail_from,
send_to,
],
stdin=subprocess.PIPE,
)
sm.communicate(msg.as_bytes())
if sm.returncode != 0:
logger.error('Mail: Sendmail failed with return code {}'.format(sm.returncode))
return False
return True
def activate_url(token: str) -> str:
return config.WEB_ROOT + 'auth/activate?' + urllib.parse.urlencode({'token': token}, safe=':')
def confirm_url(type: str, token: str) -> str:
return config.WEB_ROOT + f'auth/confirm/{type}?' + urllib.parse.urlencode({'token': token}, safe=':')
def send_new_account_email(user: db.User, token: str) -> bool:
return send_user_email(user, 'Založen nový účet', textwrap.dedent('''\
Vítejte!
Právě Vám byl založen účet v Odevzdávacím systému Matematické olympiády.
Nastavte si prosím heslo na následující stránce:
{}
Váš OSMO
'''.format(activate_url(token))))
def send_password_reset_email(user: db.User, token: str) -> bool:
return send_user_email(user, 'Obnova hesla', textwrap.dedent('''\
Někdo (pravděpodobně Vy) požádal o obnovení hesla k Vašemu účtu v Odevzdávacím
systému Matematické olympiády. Heslo si můžete nastavit, případně požadavek
zrušit, na následující stránce:
{}
Váš OSMO
'''.format(confirm_url('p', token))))
def send_confirm_create_email(user: db.User, token: str) -> bool:
return send_user_email(user, 'Založení účtu', textwrap.dedent('''\
Někdo (pravděpodobně Vy) požádal o založení účtu s touto e-mailovou adresou
v Odevzdávacím systému Matematické olympiády. Pokud účet chcete založit,
následujte tento odkaz:
{}
Váš OSMO
'''.format(confirm_url('r', token))))
def send_confirm_change_email(user: db.User, token: str) -> bool:
return send_user_email(user, 'Změna e-mailové adresy', textwrap.dedent('''\
Někdo (pravděpodobně Vy) požádal o nastavení e-mailové adresy k účtu
v Odevzdávacím systému Matematické olympiády na tuto adresu.
Pokud změnu chcete provést, následujte tento odkaz:
{}
Váš OSMO
'''.format(confirm_url('e', token))))
...@@ -10,6 +10,7 @@ from typing import List, Optional, Any, Dict, Type, Union ...@@ -10,6 +10,7 @@ from typing import List, Optional, Any, Dict, Type, Union
import mo.csv import mo.csv
from mo.csv import FileFormat, MissingHeaderError from mo.csv import FileFormat, MissingHeaderError
import mo.db as db import mo.db as db
import mo.email
import mo.rights import mo.rights
import mo.users import mo.users
import mo.util import mo.util
...@@ -374,7 +375,7 @@ class Import: ...@@ -374,7 +375,7 @@ class Import:
if u and not u.password_hash and not u.reset_at: if u and not u.password_hash and not u.reset_at:
token = mo.users.make_activation_token(u) token = mo.users.make_activation_token(u)
sess.commit() sess.commit()
mo.util.send_new_account_email(u, token) mo.email.send_new_account_email(u, token)
else: else:
sess.rollback() sess.rollback()
......
...@@ -4,18 +4,13 @@ from dataclasses import dataclass ...@@ -4,18 +4,13 @@ from dataclasses import dataclass
import datetime import datetime
import decimal import decimal
import dateutil.tz import dateutil.tz
import email.message
import email.headerregistry
import locale import locale
import logging import logging
import os import os
import re import re
import secrets import secrets
import subprocess
import sys import sys
from typing import Any, Optional, NoReturn, Tuple, List from typing import Any, Optional, NoReturn, Tuple, List
import textwrap
import urllib.parse
import mo import mo
import mo.db as db import mo.db as db
...@@ -60,124 +55,6 @@ def log(type: db.LogType, what: int, details: Any): ...@@ -60,124 +55,6 @@ def log(type: db.LogType, what: int, details: Any):
db.get_session().add(entry) db.get_session().add(entry)
def send_user_email(user: db.User, subject: str, body: str) -> bool:
logger.info(f'Mail: "{subject}" -> {user.email}')
mail_from = getattr(config, 'MAIL_FROM', None)
if mail_from is None:
logger.error('Mail: V configu chybí nastavení MAIL_FROM')
return False
msg = email.message.EmailMessage()
msg['From'] = email.headerregistry.Address(
display_name='Odevzdávací Systém MO',
addr_spec=mail_from,
)
msg['To'] = [
email.headerregistry.Address(
display_name=user.full_name(),
addr_spec=user.email,
)
]
msg['Reply-To'] = email.headerregistry.Address(
display_name='Správce OSMO',
addr_spec=config.MAIL_CONTACT,
)
msg['Subject'] = 'OSMO – ' + subject
msg['Date'] = datetime.datetime.now()
msg.set_content(body, cte='quoted-printable')
mail_instead = getattr(config, 'MAIL_INSTEAD', None)
if mail_instead is None:
send_to = user.email
else:
send_to = mail_instead
sm = subprocess.Popen(
[
'/usr/sbin/sendmail',
'-oi',
'-f',
mail_from,
send_to,
],
stdin=subprocess.PIPE,
)
sm.communicate(msg.as_bytes())
if sm.returncode != 0:
logger.error('Mail: Sendmail failed with return code {}'.format(sm.returncode))
return False
return True
def activate_url(token: str) -> str:
return config.WEB_ROOT + 'auth/activate?' + urllib.parse.urlencode({'token': token}, safe=':')
def password_reset_url(token: str) -> str:
return config.WEB_ROOT + 'auth/confirm/p?' + urllib.parse.urlencode({'token': token}, safe=':')
def confirm_create_url(token: str) -> str:
return config.WEB_ROOT + 'auth/confirm/r?' + urllib.parse.urlencode({'token': token}, safe=':')
def confirm_email_url(token: str) -> str:
return config.WEB_ROOT + 'auth/confirm/e?' + urllib.parse.urlencode({'token': token}, safe=':')
def send_new_account_email(user: db.User, token: str) -> bool:
return send_user_email(user, 'Založen nový účet', textwrap.dedent('''\
Vítejte!
Právě Vám byl založen účet v Odevzdávacím systému Matematické olympiády.
Nastavte si prosím heslo na následující stránce:
{}
Váš OSMO
'''.format(activate_url(token))))
def send_password_reset_email(user: db.User, token: str) -> bool:
return send_user_email(user, 'Obnova hesla', textwrap.dedent('''\
Někdo (pravděpodobně Vy) požádal o obnovení hesla k Vašemu účtu v Odevzdávacím
systému Matematické olympiády. Heslo si můžete nastavit, případně požadavek
zrušit, na následující stránce:
{}
Váš OSMO
'''.format(password_reset_url(token))))
def send_confirm_create_email(user: db.User, token: str) -> bool:
return send_user_email(user, 'Založení účtu', textwrap.dedent('''\
Někdo (pravděpodobně Vy) požádal o založení účtu s touto e-mailovou adresou
v Odevzdávacím systému Matematické olympiády. Pokud účet chcete založit,
následujte tento odkaz:
{}
Váš OSMO
'''.format(confirm_create_url(token))))
def send_confirm_change_email(user: db.User, token: str) -> bool:
return send_user_email(user, 'Změna e-mailové adresy', textwrap.dedent('''\
Někdo (pravděpodobně Vy) požádal o nastavení e-mailové adresy k účtu
v Odevzdávacím systému Matematické olympiády na tuto adresu.
Pokud změnu chcete provést, následujte tento odkaz:
{}
Váš OSMO
'''.format(confirm_email_url(token))))
def die(msg: str) -> NoReturn: def die(msg: str) -> NoReturn:
print(msg, file=sys.stderr) print(msg, file=sys.stderr)
sys.exit(1) sys.exit(1)
......
...@@ -65,7 +65,7 @@ def login(): ...@@ -65,7 +65,7 @@ def login():
rr = mo.users.request_reset_password(user, request.remote_addr) rr = mo.users.request_reset_password(user, request.remote_addr)
if rr: if rr:
db.get_session().commit() db.get_session().commit()
mo.util.send_password_reset_email(user, rr.email_token) mo.email.send_password_reset_email(user, rr.email_token)
flash('Na uvedenou adresu byl odeslán e-mail s odkazem na obnovu hesla.', 'success') flash('Na uvedenou adresu byl odeslán e-mail s odkazem na obnovu hesla.', 'success')
else: else:
flash('Příliš časté požadavky na obnovu hesla.', 'danger') flash('Příliš časté požadavky na obnovu hesla.', 'danger')
...@@ -169,7 +169,7 @@ def user_settings_change(): ...@@ -169,7 +169,7 @@ def user_settings_change():
sess.commit() sess.commit()
app.logger.info(f'Settings: Požadavek na změnu e-mailu uživatele #{user.user_id}') app.logger.info(f'Settings: Požadavek na změnu e-mailu uživatele #{user.user_id}')
flash('Odeslán e-mail s odkazem na potvrzení nové adresy.', 'success') flash('Odeslán e-mail s odkazem na potvrzení nové adresy.', 'success')
mo.util.send_confirm_change_email(user, rr.email_token) mo.email.send_confirm_change_email(user, rr.email_token)
else: else:
app.logger.info('Settings: Rate limit') app.logger.info('Settings: Rate limit')
flash('Příliš mnoho požadavků na změny e-mailu. Počkejte prosím chvíli a zkuste to znovu.', 'danger') flash('Příliš mnoho požadavků na změny e-mailu. Počkejte prosím chvíli a zkuste to znovu.', 'danger')
...@@ -194,6 +194,7 @@ class ResetForm(FlaskForm): ...@@ -194,6 +194,7 @@ class ResetForm(FlaskForm):
submit = wtforms.SubmitField('Nastavit heslo') submit = wtforms.SubmitField('Nastavit heslo')
# URL je explicitně uvedeno v mo.email.activate_url
@app.route('/auth/activate', methods=('GET', 'POST')) @app.route('/auth/activate', methods=('GET', 'POST'))
def activate(): def activate():
token = request.args.get('token') token = request.args.get('token')
...@@ -366,7 +367,7 @@ def create_acct(): ...@@ -366,7 +367,7 @@ def create_acct():
app.logger.debug(f'Reg1: E-mailový token {reg1.email_token}') app.logger.debug(f'Reg1: E-mailový token {reg1.email_token}')
flash('Odeslán e-mail s odkazem na založení účtu.', 'success') flash('Odeslán e-mail s odkazem na založení účtu.', 'success')
user = db.User(email=form.email.data, first_name='Nový', last_name='Uživatel') user = db.User(email=form.email.data, first_name='Nový', last_name='Uživatel')
mo.util.send_confirm_create_email(user, reg1.email_token) mo.email.send_confirm_create_email(user, reg1.email_token)
return redirect(url_for('confirm_reg')) return redirect(url_for('confirm_reg'))
form.captcha.description = reg1.captcha_task() form.captcha.description = reg1.captcha_task()
...@@ -541,6 +542,7 @@ class Reg2Form(FlaskForm): ...@@ -541,6 +542,7 @@ class Reg2Form(FlaskForm):
submit = wtforms.SubmitField('Vytvořit účet') submit = wtforms.SubmitField('Vytvořit účet')
# URL je explicitně uvedeno v mo.email.activate_url
@app.route('/auth/confirm/r', methods=('GET', 'POST')) @app.route('/auth/confirm/r', methods=('GET', 'POST'))
def confirm_reg(): def confirm_reg():
token = request.args.get('token') token = request.args.get('token')
...@@ -572,6 +574,7 @@ class ConfirmEmailForm(FlaskForm): ...@@ -572,6 +574,7 @@ class ConfirmEmailForm(FlaskForm):
cancel = wtforms.SubmitField('Zrušit požadavek') cancel = wtforms.SubmitField('Zrušit požadavek')
# URL je explicitně uvedeno v mo.email.activate_url
@app.route('/auth/confirm/e', methods=('GET', 'POST')) @app.route('/auth/confirm/e', methods=('GET', 'POST'))
def confirm_email(): def confirm_email():
reg2 = Reg2(request.args.get('token'), db.RegReqType.change) reg2 = Reg2(request.args.get('token'), db.RegReqType.change)
...@@ -599,6 +602,7 @@ class CancelResetForm(FlaskForm): ...@@ -599,6 +602,7 @@ class CancelResetForm(FlaskForm):
cancel = wtforms.SubmitField('Zrušit obnovu hesla') cancel = wtforms.SubmitField('Zrušit obnovu hesla')
# URL je explicitně uvedeno v mo.email.activate_url
@app.route('/auth/confirm/p', methods=('GET', 'POST')) @app.route('/auth/confirm/p', methods=('GET', 'POST'))
def confirm_reset(): def confirm_reset():
reg2 = Reg2(request.args.get('token'), db.RegReqType.reset) reg2 = Reg2(request.args.get('token'), db.RegReqType.reset)
......
...@@ -14,6 +14,7 @@ from wtforms.validators import Required ...@@ -14,6 +14,7 @@ from wtforms.validators import Required
import mo import mo
import mo.db as db import mo.db as db
import mo.email
from mo.rights import Right from mo.rights import Right
import mo.util import mo.util
import mo.users import mo.users
...@@ -319,7 +320,7 @@ class ResendInviteForm(FlaskForm): ...@@ -319,7 +320,7 @@ class ResendInviteForm(FlaskForm):
if user.last_login_at is None: if user.last_login_at is None:
token = mo.users.make_activation_token(user) token = mo.users.make_activation_token(user)
db.get_session().commit() db.get_session().commit()
if mo.util.send_new_account_email(user, token): if mo.email.send_new_account_email(user, token):
flash('Uvítací e-mail s odkazem na aktivaci účtu odeslán na {}.'.format(user.email), 'success') flash('Uvítací e-mail s odkazem na aktivaci účtu odeslán na {}.'.format(user.email), 'success')
else: else:
flash('Problém při odesílání e-mailu s odkazem na aktivaci účtu.', 'danger') flash('Problém při odesílání e-mailu s odkazem na aktivaci účtu.', 'danger')
...@@ -572,7 +573,7 @@ def org_user_new(): ...@@ -572,7 +573,7 @@ def org_user_new():
sess.commit() sess.commit()
flash('Nový uživatel vytvořen', 'success') flash('Nový uživatel vytvořen', 'success')
if mo.util.send_new_account_email(new_user, token): if mo.email.send_new_account_email(new_user, token):
flash('E-mail s odkazem na aktivaci účtu odeslán na {}.'.format(new_user.email), 'success') flash('E-mail s odkazem na aktivaci účtu odeslán na {}.'.format(new_user.email), 'success')
else: else:
flash('Problém při odesílání e-mailu s odkazem na aktivaci účtu.', 'danger') flash('Problém při odesílání e-mailu s odkazem na aktivaci účtu.', 'danger')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment