invitations expire after 48h

This commit is contained in:
Camille 2022-01-01 10:56:48 +00:00 committed by Éloi Rivard
parent ebc16b91c2
commit db1d011a3b
10 changed files with 286 additions and 120 deletions

View file

@ -1,4 +1,9 @@
import io
from dataclasses import astuple
from dataclasses import dataclass
from datetime import datetime
from datetime import timedelta
from typing import List
import pkg_resources
import wtforms
@ -162,6 +167,32 @@ def users(user):
return render_template("users.html", users=users, menuitem="users")
@dataclass
class Invitation:
creation_date_isoformat: str
uid: str
mail: str
groups: List[str]
@property
def creation_date(self):
return datetime.fromisoformat(self.creation_date_isoformat)
def has_expired(self):
DEFAULT_INVITATION_DURATION = 2 * 24 * 60 * 60
return datetime.now() - self.creation_date > timedelta(
seconds=current_app.config.get(
"INVITATION_EXPIRATION", DEFAULT_INVITATION_DURATION
)
)
def b64(self):
return obj_to_b64(astuple(self))
def profile_hash(self):
return profile_hash(*astuple(self))
@bp.route("/invite", methods=["GET", "POST"])
@smtp_needed()
@permissions_needed("manage_users")
@ -173,10 +204,16 @@ def user_invitation(user):
form_validated = False
if request.form and form.validate():
form_validated = True
invitation = Invitation(
datetime.now().isoformat(),
form.uid.data,
form.mail.data,
form.groups.data,
)
registration_url = url_for(
"account.registration",
data=obj_to_b64([form.uid.data, form.mail.data, form.groups.data]),
hash=profile_hash(form.uid.data, form.mail.data, form.groups.data),
data=invitation.b64(),
hash=invitation.profile_hash(),
_external=True,
)
@ -223,7 +260,7 @@ def profile_creation(user):
@bp.route("/register/<data>/<hash>", methods=["GET", "POST"])
def registration(data, hash):
try:
data = b64_to_obj(data)
invitation = Invitation(*b64_to_obj(data))
except:
flash(
_("The invitation link that brought you here was invalid."),
@ -231,7 +268,14 @@ def registration(data, hash):
)
return redirect(url_for("account.index"))
if User.get(data[0]):
if invitation.has_expired():
flash(
_("The invitation link that brought you here has expired."),
"error",
)
return redirect(url_for("account.index"))
if User.get(invitation.uid):
flash(
_("Your account has already been created."),
"error",
@ -245,18 +289,14 @@ def registration(data, hash):
)
return redirect(url_for("account.index"))
if hash != profile_hash(*data):
if hash != invitation.profile_hash():
flash(
_("The invitation link that brought you here was invalid."),
"error",
)
return redirect(url_for("account.index"))
data = {
"uid": data[0],
"mail": data[1],
"groups": data[2],
}
data = {"uid": invitation.uid, "mail": invitation.mail, "groups": invitation.groups}
readable_fields, writable_fields = default_fields()

View file

@ -40,6 +40,10 @@ OIDC_METADATA_FILE = "canaille/conf/openid-configuration.json"
# wether the login exists or not.
# HIDE_INVALID_LOGINS = false
# The validity duration of registration invitations, in seconds.
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL

View file

@ -5,13 +5,13 @@
<div class="ui segment">
<h2 class="ui center aligned header">
<div class="content">
{{ _("Test d'envoi") }}
{{ _("Mail sending test") }}
</div>
</h2>
<div class="ui info message">
{% trans -%}
This form will a fake invitation email to the address you want.
This form will send a fake invitation email to the address you want.
This should be used for testing mail configuration.
{%- endtrans %}
</div>

View file

@ -3,14 +3,14 @@
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
# Éloi Rivard <eloi.rivard@aquilenet.fr>, 2020-2021.
# Camille <camille@yaal.coop>, 2021.
# Camille <camille@yaal.coop>, 2021-2022.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: contact@yaal.fr\n"
"POT-Creation-Date: 2021-12-23 18:45+0100\n"
"PO-Revision-Date: 2021-12-23 18:48+0100\n"
"POT-Creation-Date: 2022-01-01 11:24+0100\n"
"PO-Revision-Date: 2022-01-01 11:26+0100\n"
"Last-Translator: Camille <camille@yaal.coop>\n"
"Language: fr\n"
"Language-Team: French <contact@yaal.coop>\n"
@ -21,34 +21,34 @@ msgstr ""
"Generated-By: Babel 2.9.1\n"
"X-Generator: Gtranslator 40.0\n"
#: canaille/__init__.py:134
#: canaille/__init__.py:131
msgid "Could not connect to the LDAP server '{uri}'"
msgstr "Impossible de se connecter au serveur LDAP '{uri}'"
#: canaille/__init__.py:150
#: canaille/__init__.py:147
msgid "LDAP authentication failed with user '{user}'"
msgstr ""
"L'authentification au serveur LDAP a échoué pour l'utilisateur '{user}'"
#: canaille/account.py:77 canaille/account.py:102 canaille/oauth.py:77
#: canaille/account.py:82 canaille/account.py:107 canaille/oauth.py:77
msgid "Login failed, please check your information"
msgstr "La connexion a échoué, veuillez vérifier vos informations."
#: canaille/account.py:108
#: canaille/account.py:113
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr "Connexion réussie. Bienvenue %(user)s"
#: canaille/account.py:121
#: canaille/account.py:126
#, python-format
msgid "You have been disconnected. See you next time %(user)s"
msgstr "Vous avez été déconnecté·e. À bientôt %(user)s"
#: canaille/account.py:138
#: canaille/account.py:143
msgid "Could not send the password initialization link."
msgstr "Impossible d'envoyer le courriel d'initialisation de mot de passe."
#: canaille/account.py:143
#: canaille/account.py:148
msgid ""
"A password initialization link has been sent at your email address. You "
"should receive it within 10 minutes."
@ -56,35 +56,39 @@ msgstr ""
"Un lien d'initialisation de votre mot de passe vous a été envoyé par mail. "
"Vous devriez le recevoir d'ici une dizaine de minutes."
#: canaille/account.py:149 canaille/account.py:353
#: canaille/account.py:154 canaille/account.py:388
msgid "Could not send the password initialization email"
msgstr "Impossible d'envoyer le courriel d'initialisation de mot de passe."
#: canaille/account.py:208 canaille/account.py:279
#: canaille/account.py:240 canaille/account.py:314
msgid "User account creation failed."
msgstr "La création du compte utilisateur a échoué."
#: canaille/account.py:229 canaille/account.py:250
#: canaille/account.py:261 canaille/account.py:289
msgid "The invitation link that brought you here was invalid."
msgstr "Le lien d'invitation qui vous a amené ici est invalide."
#: canaille/account.py:236
#: canaille/account.py:268
msgid "The invitation link that brought you here has expired."
msgstr "Le lien d'invitation qui vous a amené ici a expiré."
#: canaille/account.py:275
msgid "Your account has already been created."
msgstr "Votre compte a déjà été créé."
#: canaille/account.py:243
#: canaille/account.py:282
msgid "You are already logged in, you cannot create an account."
msgstr "Vous êtes déjà connectés, vous ne pouvez pas créer de compte."
#: canaille/account.py:284
#: canaille/account.py:319
msgid "You account has been created successfuly."
msgstr "Votre compte utilisateur a été créé avec succès."
#: canaille/account.py:326
#: canaille/account.py:361
msgid "User account creation succeed."
msgstr "La création du compte utilisateur a réussi."
#: canaille/account.py:347
#: canaille/account.py:382
msgid ""
"A password initialization link has been sent at the user email address. It "
"should be received within 10 minutes."
@ -92,7 +96,7 @@ msgstr ""
"Un lien d'initialisation de mot de passe a été envoyé à l'utilisateur par "
"mail. Il devrait arriver d'ici une dizaine de minutes."
#: canaille/account.py:361
#: canaille/account.py:396
msgid ""
"A password reset link has been sent at the user email address. It should be "
"received within 10 minutes."
@ -100,28 +104,28 @@ msgstr ""
"Un lien de réinitialisation de mot de passe a été envoyé à l'utilisateur par "
"mail. Il devrait arriver d'ici une dizaine de minutes."
#: canaille/account.py:367
#: canaille/account.py:402
msgid "Could not send the password reset email"
msgstr "Impossible d'envoyer le lien de réinitialisation."
#: canaille/account.py:398
#: canaille/account.py:433
msgid "Profile edition failed."
msgstr "L'édition du profil a échoué."
#: canaille/account.py:426
#: canaille/account.py:461
msgid "Profile updated successfuly."
msgstr "Le profil a été mis à jour avec succès."
#: canaille/account.py:446
#: canaille/account.py:481
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr "L'utilisateur %(user)s a bien été supprimé"
#: canaille/account.py:470
#: canaille/account.py:505
msgid "Could not send the password reset link."
msgstr "Impossible d'envoyer le lien de réinitialisation."
#: canaille/account.py:477 canaille/account.py:488
#: canaille/account.py:512 canaille/account.py:523
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within 10 minutes."
@ -129,15 +133,15 @@ msgstr ""
"Un lien de ré-initialisation de votre mot de passe vous a été envoyé par "
"mail. Vous devriez le recevoir d'ici une dizaine de minutes."
#: canaille/account.py:494
#: canaille/account.py:529
msgid "Could not reset your password"
msgstr "Impossible de réinitialiser votre mot de passe"
#: canaille/account.py:508
#: canaille/account.py:543
msgid "The password reset link that brought you here was invalid."
msgstr "Le lien de réinitialisation qui vous a amené ici est invalide."
#: canaille/account.py:517
#: canaille/account.py:552
msgid "Your password has been updated successfuly"
msgstr "Votre mot de passe a correctement été mis à jour."
@ -189,8 +193,8 @@ msgstr "L'identifiant '{login}' n'existe pas"
msgid "Login"
msgstr "Identifiant"
#: canaille/forms.py:47 canaille/forms.py:71 canaille/forms.py:122
#: canaille/forms.py:222
#: canaille/admin/mail.py:29 canaille/forms.py:47 canaille/forms.py:71
#: canaille/forms.py:122 canaille/forms.py:222
msgid "jane@doe.com"
msgstr "camille@dupont.fr"
@ -311,11 +315,11 @@ msgstr "L'édition du groupe a échoué."
msgid "The group %(group)s has been sucessfully deleted"
msgstr "Le groupe %(group)s a bien été supprimé"
#: canaille/admin/mail.py:79 canaille/mails.py:25
#: canaille/admin/mail.py:108 canaille/mails.py:25
msgid "Password reset on {website_name}"
msgstr "Réinitialisation du mot de passe sur {website_name}"
#: canaille/admin/mail.py:37 canaille/mails.py:65
#: canaille/admin/mail.py:66 canaille/mails.py:65
msgid "Password initialization on {website_name}"
msgstr "Initialisation de votre mot de passe sur {website_name}"
@ -431,11 +435,19 @@ msgstr "Le client a été édité."
msgid "The client has been deleted."
msgstr "Le client a été supprimé."
#: canaille/admin/mail.py:121
#: canaille/admin/mail.py:23 canaille/templates/userlist.html:14
msgid "Email"
msgstr "Courriel"
#: canaille/admin/mail.py:42 canaille/admin/mail.py:44
msgid "The test invitation mail has been sent correctly"
msgstr "L'invitation de test a été envoyée correctement"
#: canaille/admin/mail.py:150
msgid "Invitation on {website_name}"
msgstr "Invitation sur {website_name}"
#: canaille/templates/about.html:12 canaille/themes/default/base.html:98
#: canaille/templates/about.html:12 canaille/themes/default/base.html:102
msgid "About canaille"
msgstr "À propos de canaille"
@ -583,11 +595,11 @@ msgstr "Envoyer le courriel d'initialisation"
#: canaille/templates/admin/client_edit.html:35
#: canaille/templates/admin/client_edit.html:44
#: canaille/templates/admin/client_edit.html:53
#: canaille/templates/fomanticui.html:62
#: canaille/templates/fomanticui.html:64
msgid "This field is not editable"
msgstr "Ce champ n'est pas modifiable"
#: canaille/templates/fomanticui.html:66
#: canaille/templates/fomanticui.html:68
msgid "This field is required"
msgstr "Ce champ est requis"
@ -618,6 +630,7 @@ msgstr ""
msgid "Send again"
msgstr "Envoyer à nouveau"
#: canaille/templates/admin/mails.html:23
#: canaille/templates/forgotten-password.html:40
msgid "Send"
msgstr "Envoyer"
@ -934,7 +947,7 @@ msgstr ""
msgid "Send mail"
msgstr "Envoyer l'email"
#: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:221
#: canaille/templates/admin/mails.html:56 canaille/templates/profile.html:221
#: canaille/templates/reset-password.html:11
#: canaille/templates/reset-password.html:16
msgid "Password reset"
@ -964,10 +977,6 @@ msgstr "Prendre l'identité"
msgid "Invite a user"
msgstr "Inviter un utilisateur"
#: canaille/templates/userlist.html:14
msgid "Email"
msgstr "Courriel"
#: canaille/templates/users.html:18
msgid "Add a user"
msgstr "Nouvel utilisateur"
@ -1049,17 +1058,29 @@ msgstr "Ajouter un client"
msgid "URL"
msgstr "URL"
#: canaille/templates/admin/mails.html:7
#: canaille/templates/admin/mails.html:8
msgid "Mail sending test"
msgstr "Test d'envoi d'email"
#: canaille/templates/admin/mails.html:13
msgid ""
"This form will send a fake invitation email to the address you want.\n"
" This should be used for testing mail configuration."
msgstr ""
"Ce formulaire enverra un faux mail d'invitation à l'adresse que vous "
"indiquerez. À utiliser pour tester les configurations de mail."
#: canaille/templates/admin/mails.html:32
msgid "Email preview"
msgstr "Prévisualisation des emails"
#: canaille/templates/admin/mails.html:22
#: canaille/templates/admin/mails.html:44
#: canaille/templates/mail/firstlogin.html:19
#: canaille/templates/mail/reset.txt:1
msgid "Password initialization"
msgstr "Initialisation du mot de passe"
#: canaille/templates/admin/mails.html:46
#: canaille/templates/admin/mails.html:68
msgid "Invitation"
msgstr "Invitation"
@ -1189,7 +1210,11 @@ msgstr "Jetons"
msgid "Codes"
msgstr "Codes"
#: canaille/themes/default/base.html:84
#: canaille/themes/default/base.html:81
msgid "Emails"
msgstr "Courriels"
#: canaille/themes/default/base.html:88
msgid "Log out"
msgstr "Déconnexion"

View file

@ -1,14 +1,14 @@
# Translations template for PROJECT.
# Copyright (C) 2021 ORGANIZATION
# Copyright (C) 2022 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-12-23 18:45+0100\n"
"POT-Creation-Date: 2022-01-01 11:24+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,114 +17,118 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n"
#: canaille/__init__.py:134
#: canaille/__init__.py:131
msgid "Could not connect to the LDAP server '{uri}'"
msgstr ""
#: canaille/__init__.py:150
#: canaille/__init__.py:147
msgid "LDAP authentication failed with user '{user}'"
msgstr ""
#: canaille/account.py:77 canaille/account.py:102 canaille/oauth.py:77
#: canaille/account.py:82 canaille/account.py:107 canaille/oauth.py:77
msgid "Login failed, please check your information"
msgstr ""
#: canaille/account.py:108
#: canaille/account.py:113
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr ""
#: canaille/account.py:121
#: canaille/account.py:126
#, python-format
msgid "You have been disconnected. See you next time %(user)s"
msgstr ""
#: canaille/account.py:138
#: canaille/account.py:143
msgid "Could not send the password initialization link."
msgstr ""
#: canaille/account.py:143
#: canaille/account.py:148
msgid ""
"A password initialization link has been sent at your email address. You "
"should receive it within 10 minutes."
msgstr ""
#: canaille/account.py:149 canaille/account.py:353
#: canaille/account.py:154 canaille/account.py:388
msgid "Could not send the password initialization email"
msgstr ""
#: canaille/account.py:208 canaille/account.py:279
#: canaille/account.py:240 canaille/account.py:314
msgid "User account creation failed."
msgstr ""
#: canaille/account.py:229 canaille/account.py:250
#: canaille/account.py:261 canaille/account.py:289
msgid "The invitation link that brought you here was invalid."
msgstr ""
#: canaille/account.py:236
#: canaille/account.py:268
msgid "The invitation link that brought you here has expired."
msgstr ""
#: canaille/account.py:275
msgid "Your account has already been created."
msgstr ""
#: canaille/account.py:243
#: canaille/account.py:282
msgid "You are already logged in, you cannot create an account."
msgstr ""
#: canaille/account.py:284
#: canaille/account.py:319
msgid "You account has been created successfuly."
msgstr ""
#: canaille/account.py:326
#: canaille/account.py:361
msgid "User account creation succeed."
msgstr ""
#: canaille/account.py:347
#: canaille/account.py:382
msgid ""
"A password initialization link has been sent at the user email address. "
"It should be received within 10 minutes."
msgstr ""
#: canaille/account.py:361
#: canaille/account.py:396
msgid ""
"A password reset link has been sent at the user email address. It should "
"be received within 10 minutes."
msgstr ""
#: canaille/account.py:367
#: canaille/account.py:402
msgid "Could not send the password reset email"
msgstr ""
#: canaille/account.py:398
#: canaille/account.py:433
msgid "Profile edition failed."
msgstr ""
#: canaille/account.py:426
#: canaille/account.py:461
msgid "Profile updated successfuly."
msgstr ""
#: canaille/account.py:446
#: canaille/account.py:481
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr ""
#: canaille/account.py:470
#: canaille/account.py:505
msgid "Could not send the password reset link."
msgstr ""
#: canaille/account.py:477 canaille/account.py:488
#: canaille/account.py:512 canaille/account.py:523
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within 10 minutes."
msgstr ""
#: canaille/account.py:494
#: canaille/account.py:529
msgid "Could not reset your password"
msgstr ""
#: canaille/account.py:508
#: canaille/account.py:543
msgid "The password reset link that brought you here was invalid."
msgstr ""
#: canaille/account.py:517
#: canaille/account.py:552
msgid "Your password has been updated successfuly"
msgstr ""
@ -176,8 +180,8 @@ msgstr ""
msgid "Login"
msgstr ""
#: canaille/forms.py:47 canaille/forms.py:71 canaille/forms.py:122
#: canaille/forms.py:222
#: canaille/admin/mail.py:29 canaille/forms.py:47 canaille/forms.py:71
#: canaille/forms.py:122 canaille/forms.py:222
msgid "jane@doe.com"
msgstr ""
@ -297,11 +301,11 @@ msgstr ""
msgid "The group %(group)s has been sucessfully deleted"
msgstr ""
#: canaille/admin/mail.py:79 canaille/mails.py:25
#: canaille/admin/mail.py:108 canaille/mails.py:25
msgid "Password reset on {website_name}"
msgstr ""
#: canaille/admin/mail.py:37 canaille/mails.py:65
#: canaille/admin/mail.py:66 canaille/mails.py:65
msgid "Password initialization on {website_name}"
msgstr ""
@ -417,11 +421,19 @@ msgstr ""
msgid "The client has been deleted."
msgstr ""
#: canaille/admin/mail.py:121
#: canaille/admin/mail.py:23 canaille/templates/userlist.html:14
msgid "Email"
msgstr ""
#: canaille/admin/mail.py:42 canaille/admin/mail.py:44
msgid "The test invitation mail has been sent correctly"
msgstr ""
#: canaille/admin/mail.py:150
msgid "Invitation on {website_name}"
msgstr ""
#: canaille/templates/about.html:12 canaille/themes/default/base.html:98
#: canaille/templates/about.html:12 canaille/themes/default/base.html:102
msgid "About canaille"
msgstr ""
@ -558,11 +570,11 @@ msgstr ""
#: canaille/templates/admin/client_edit.html:35
#: canaille/templates/admin/client_edit.html:44
#: canaille/templates/admin/client_edit.html:53
#: canaille/templates/fomanticui.html:62
#: canaille/templates/fomanticui.html:64
msgid "This field is not editable"
msgstr ""
#: canaille/templates/fomanticui.html:66
#: canaille/templates/fomanticui.html:68
msgid "This field is required"
msgstr ""
@ -587,6 +599,7 @@ msgstr ""
msgid "Send again"
msgstr ""
#: canaille/templates/admin/mails.html:23
#: canaille/templates/forgotten-password.html:40
msgid "Send"
msgstr ""
@ -876,7 +889,7 @@ msgstr ""
msgid "Send mail"
msgstr ""
#: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:221
#: canaille/templates/admin/mails.html:56 canaille/templates/profile.html:221
#: canaille/templates/reset-password.html:11
#: canaille/templates/reset-password.html:16
msgid "Password reset"
@ -904,10 +917,6 @@ msgstr ""
msgid "Invite a user"
msgstr ""
#: canaille/templates/userlist.html:14
msgid "Email"
msgstr ""
#: canaille/templates/users.html:18
msgid "Add a user"
msgstr ""
@ -987,17 +996,27 @@ msgstr ""
msgid "URL"
msgstr ""
#: canaille/templates/admin/mails.html:7
#: canaille/templates/admin/mails.html:8
msgid "Mail sending test"
msgstr ""
#: canaille/templates/admin/mails.html:13
msgid ""
"This form will send a fake invitation email to the address you want.\n"
" This should be used for testing mail configuration."
msgstr ""
#: canaille/templates/admin/mails.html:32
msgid "Email preview"
msgstr ""
#: canaille/templates/admin/mails.html:22
#: canaille/templates/admin/mails.html:44
#: canaille/templates/mail/firstlogin.html:19
#: canaille/templates/mail/reset.txt:1
msgid "Password initialization"
msgstr ""
#: canaille/templates/admin/mails.html:46
#: canaille/templates/admin/mails.html:68
msgid "Invitation"
msgstr ""
@ -1104,6 +1123,10 @@ msgstr ""
msgid "Codes"
msgstr ""
#: canaille/themes/default/base.html:84
#: canaille/themes/default/base.html:81
msgid "Emails"
msgstr ""
#: canaille/themes/default/base.html:88
msgid "Log out"
msgstr ""

View file

@ -40,6 +40,10 @@ OIDC_METADATA_FILE = "conf/openid-configuration.json"
# wether the login exists or not.
# HIDE_INVALID_LOGINS = false
# The validity duration of registration invitations, in seconds.
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL

View file

@ -51,6 +51,10 @@ Canaille is based on Flask, so any `flask configuration <https://flask.palletspr
*Optional.* Wether to tell the users if a username exists during failing login attempts.
Defaults to ``True``. This may be a security issue to disable this, as this give a way to malicious people to guess who has an account on this canaille instance.
:INVITATION_EXPIRATION:
*Optional* The validity duration of registration invitations, in seconds.
Defaults to 2 days.
LOGGING
-------

View file

@ -36,6 +36,7 @@ install_requires =
sentry-sdk[flask]
toml
wtforms
dataclasses;python_version<'3.7'
[options.packages.find]
exclude =

View file

@ -1,3 +1,8 @@
from datetime import datetime
from datetime import timedelta
from canaille.account import Invitation
from canaille.apputils import b64_to_obj
from canaille.apputils import obj_to_b64
from canaille.apputils import profile_hash
from canaille.models import User
@ -37,7 +42,10 @@ def test_invitation(testclient, slapd_connection, logged_admin, foo_group, smtpd
with testclient.app.app_context():
user = User.get("someone", conn=slapd_connection)
user.load_groups(conn=slapd_connection)
foo_group.reload(slapd_connection)
assert user.check_password("whatever")
assert user.groups == [foo_group]
with testclient.session_transaction() as sess:
assert "user_dn" in sess
@ -80,7 +88,10 @@ def test_generate_link(testclient, slapd_connection, logged_admin, foo_group, sm
with testclient.app.app_context():
user = User.get("sometwo", conn=slapd_connection)
user.load_groups(conn=slapd_connection)
foo_group.reload(slapd_connection)
assert user.check_password("whatever")
assert user.groups == [foo_group]
with testclient.session_transaction() as sess:
assert "user_dn" in sess
@ -100,37 +111,86 @@ def test_invitation_login_already_taken(testclient, slapd_connection, logged_adm
assert "The email &#39;jane@doe.com&#39; already exists" in res.text
def test_registration_invalid_data(testclient, slapd_connection, foo_group):
def test_registration(testclient, slapd_connection, foo_group):
with testclient.app.app_context():
data = ["someoneelse", "someone@mydomain.tld", foo_group.dn]
b64 = obj_to_b64(data)
invitation = Invitation(
datetime.now().isoformat(),
"someoneelse",
"someone@mydomain.tld",
foo_group.dn,
)
b64 = invitation.b64()
hash = invitation.profile_hash()
testclient.get(f"/register/{b64}/invalid", status=302)
testclient.get(f"/register/{b64}/{hash}", status=200)
def test_registration_invalid_hash(testclient, slapd_connection, foo_group):
with testclient.app.app_context():
data = ["someoneelse", "someone@mydomain.tld", foo_group.dn]
hash = profile_hash(*data)
data = ["anything", "someone@mydomain.tld", foo_group.dn]
b64 = obj_to_b64(data)
invitation = Invitation(
datetime.now().isoformat(),
"someoneelse",
"someone@mydomain.tld",
foo_group.dn,
)
b64 = invitation.b64()
testclient.get(f"/register/{b64}/{hash}", status=302)
testclient.get(f"/register/{b64}/invalid", status=302)
def test_registration_bad_hash(testclient, slapd_connection, foo_group):
with testclient.app.app_context():
data = ["someoneelse", "someone@mydomain.tld", foo_group.dn]
hash = profile_hash(*data)
now = datetime.now().isoformat()
invitation1 = Invitation(
now, "someoneelse", "someone@mydomain.tld", foo_group.dn
)
hash = invitation1.profile_hash()
invitation2 = Invitation(now, "anything", "someone@mydomain.tld", foo_group.dn)
b64 = invitation2.b64()
testclient.get(f"/register/invalid/{hash}", status=302)
testclient.get(f"/register/{b64}/{hash}", status=302)
def test_registration_invalid_data(testclient, slapd_connection, foo_group):
with testclient.app.app_context():
invitation = Invitation(
datetime.now().isoformat(),
"someoneelse",
"someone@mydomain.tld",
foo_group.dn,
)
hash = invitation.profile_hash()
res = testclient.get(f"/register/invalid/{hash}", status=302)
def test_registration_more_than_48_hours_after_invitation(
testclient, slapd_connection, foo_group
):
with testclient.app.app_context():
two_days_ago = datetime.now() - timedelta(hours=48)
invitation = Invitation(
two_days_ago.isoformat(),
"someoneelse",
"someone@mydomain.tld",
foo_group.dn,
)
hash = invitation.profile_hash()
b64 = invitation.b64()
testclient.get(f"/register/{b64}/{hash}", status=302)
def test_registration_no_password(testclient, slapd_connection, foo_group):
with testclient.app.app_context():
data = ["someoneelse", "someone@mydomain.tld", foo_group.dn]
hash = profile_hash(*data)
b64 = obj_to_b64(data)
invitation = Invitation(
datetime.now().isoformat(),
"someoneelse",
"someone@mydomain.tld",
foo_group.dn,
)
hash = invitation.profile_hash()
b64 = invitation.b64()
url = f"/register/{b64}/{hash}"
res = testclient.get(url, status=200)
@ -151,9 +211,14 @@ def test_no_registration_if_logged_in(
testclient, slapd_connection, logged_user, foo_group
):
with testclient.app.app_context():
data = ["anyone", "someone@mydomain.tld", foo_group.dn]
hash = profile_hash(*data)
b64 = obj_to_b64(data)
invitation = Invitation(
datetime.now().isoformat(),
"someoneelse",
"someone@mydomain.tld",
foo_group.dn,
)
hash = invitation.profile_hash()
b64 = invitation.b64()
url = f"/register/{b64}/{hash}"
testclient.get(url, status=302)