From acee0440ad716a08e30e9e8ed44b370c9ad69186 Mon Sep 17 00:00:00 2001 From: Martin Mares <mj@ucw.cz> Date: Sat, 18 Mar 2023 19:50:54 +0100 Subject: [PATCH] =?UTF-8?q?"@nomail"=20vyrob=C3=AD=20novou=20pravd=C4=9Bpo?= =?UTF-8?q?dobn=C4=9B=20unik=C3=A1tn=C3=AD=20nomailovou=20adresu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Toto funguje všude, kde organizátor zakládá nového uživatele: v editaci uživatelů, v přídávání do soutěže, v importech. --- mo/imports.py | 2 +- mo/users.py | 14 ++++++++++++-- mo/web/fields.py | 8 +++++--- mo/web/org_contest.py | 5 +++-- mo/web/org_users.py | 3 ++- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/mo/imports.py b/mo/imports.py index 5833b3a4..46b1e18e 100644 --- a/mo/imports.py +++ b/mo/imports.py @@ -93,7 +93,7 @@ class Import: try: # XXX: Zde si nemůžeme dovolit kontrolovat existenci domén, # protože import by byl příliš pomalý. Možná z něj jednou uděláme job... - return mo.users.normalize_email(email) + return mo.users.normalize_email(email, make_unique_nomail=True) except mo.CheckError as e: return self.error(str(e)) diff --git a/mo/users.py b/mo/users.py index 7c9f887e..d3de7e54 100644 --- a/mo/users.py +++ b/mo/users.py @@ -1,5 +1,6 @@ # Správa uživatelů +import base64 import bcrypt import datetime import dateutil.tz @@ -292,10 +293,19 @@ def email_check_domain(domain: str): raise mo.CheckError(f'Doména {domain} nepřijímá poštu') -def normalize_email(addr: str, check_existence: bool = False) -> str: - if not re.fullmatch(r'.+@.+', addr): +def normalize_email(addr: str, check_existence: bool = False, make_unique_nomail: bool = False) -> str: + if make_unique_nomail and addr.endswith('@nomail'): + if addr == '@nomail': + addr = base64.b32encode(secrets.token_bytes(10)).decode('US-ASCII').lower() + '@nomail' + else: + raise mo.CheckError('Adresa @nomail nesmí obsahovat jméno uživatele před zavináčem') + + if '@' not in addr: raise mo.CheckError('V e-mailové adrese chybí zavináč') + if not re.fullmatch(r'.*@.+', addr): + raise mo.CheckError('E-mailová adresa nesmí ani začínat, ani končit zavináčem') + if re.search(r'[ \t]', addr): raise mo.CheckError('E-mailová adresa obsahuje mezeru') diff --git a/mo/web/fields.py b/mo/web/fields.py index 4c6ce5f5..2f939534 100644 --- a/mo/web/fields.py +++ b/mo/web/fields.py @@ -73,17 +73,19 @@ class Points(Decimal): super().__init__(label, validators, **kwargs) -class Email(Stripped, EmailField): +class Email(String): check_existence: bool + make_unique_nomail: bool - def __init__(self, label="E-mail", validators=None, check_existence: bool = False, **kwargs): + def __init__(self, label="E-mail", validators=None, check_existence: bool = False, make_unique_nomail: bool = False, **kwargs): self.check_existence = check_existence + self.make_unique_nomail = make_unique_nomail super().__init__(label, validators, **kwargs) def pre_validate(field, form): if field.data: try: - field.data = mo.users.normalize_email(field.data, check_existence=field.check_existence) + field.data = mo.users.normalize_email(field.data, check_existence=field.check_existence, make_unique_nomail=field.make_unique_nomail) except mo.CheckError as e: raise wtforms.ValidationError(str(e)) diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index 67396174..a49d464b 100644 --- a/mo/web/org_contest.py +++ b/mo/web/org_contest.py @@ -1809,7 +1809,7 @@ def check_contest_state(round: db.Round, contest: Optional[db.Contest], state: d class ParticipantAddForm(FlaskForm): - email = mo_fields.Email(validators=[validators.DataRequired()], check_existence=True) + email = mo_fields.Email(validators=[validators.DataRequired()], check_existence=True, make_unique_nomail=True) first_name = mo_fields.FirstName(validators=[validators.Optional()]) last_name = mo_fields.LastName(validators=[validators.Optional()]) school = mo_fields.School(validators=[validators.Optional()]) @@ -1819,7 +1819,8 @@ class ParticipantAddForm(FlaskForm): save = wtforms.SubmitField("Přidat") def set_descriptions(self, contest: db.Contest, place_desc: bool): - self.email.description = "Nepoužívejte prosím e-mailové adresy ve školních doménách, na které nejde posílat pošta z veřejné sítě." + self.email.description = ("Nepoužívejte prosím e-mailové adresy ve školních doménách, na které nejde posílat pošta z veřejné sítě. " + + "Pokud zatím neznáte e-mail, zadejte @nomail, ale pak adresu doplňte.") self.school.description = f'Kód školy najdete v <a href="{url_for("org_place", id=contest.place.place_id)}">katalogu míst</a>.' if place_desc: self.participation_place.description = f'Pokud účastník soutěží někde jinde než {contest.place.name_locative()}, vyplňte <a href="{url_for("org_place", id=contest.place.place_id)}">kód místa</a>. Dozor na tomto místě pak může za účastníka odevzdávat řešení.' diff --git a/mo/web/org_users.py b/mo/web/org_users.py index 4fabde7c..d09b600e 100644 --- a/mo/web/org_users.py +++ b/mo/web/org_users.py @@ -412,7 +412,8 @@ def org_user(id: int): class UserEditForm(FlaskForm): first_name = mo_fields.FirstName(validators=[DataRequired()], render_kw={'autofocus': True}) last_name = mo_fields.LastName(validators=[DataRequired()]) - email = mo_fields.Email(validators=[DataRequired()], check_existence=True) + email = mo_fields.Email(validators=[DataRequired()], check_existence=True, make_unique_nomail=True, + description="Pokud zadáte @nomail, vznikne účet bez e-mailové adresy, ke kterému se ale nepůjde přihlásit.") note = wtforms.TextAreaField("Poznámka") is_test = wtforms.BooleanField("Testovací účet") email_notify = wtforms.BooleanField("Mailové notifikace") -- GitLab