User admin page. Fixes #8

This commit is contained in:
Éloi Rivard 2020-11-01 11:33:56 +01:00
parent 1300265a14
commit 4a20fb3b55
12 changed files with 612 additions and 152 deletions

View file

@ -17,8 +17,14 @@ from flask import (
)
from flask_babel import gettext as _
from .forms import LoginForm, ProfileForm, PasswordResetForm, ForgottenPasswordForm
from .flaskutils import current_user, user_needed
from .forms import (
LoginForm,
AddProfileForm,
EditProfileForm,
PasswordResetForm,
ForgottenPasswordForm,
)
from .flaskutils import current_user, user_needed, admin_needed
from .models import User
@ -29,7 +35,9 @@ bp = Blueprint(__name__, "home")
def index():
if not current_user():
return redirect(url_for("canaille.account.login"))
return redirect(url_for("canaille.account.profile", subject=current_user().uid[0]))
return redirect(
url_for("canaille.account.profile_edition", username=current_user().uid[0])
)
@bp.route("/login", methods=("GET", "POST"))
@ -44,7 +52,7 @@ def login():
return render_template("login.html", form=form)
user = User.get(form.login.data)
flash(_(f"Connection successful. Welcome {user.name}"), "success")
flash(_("Connection successful. Welcome %(user)s", user=user.name), "success")
return redirect(url_for("canaille.account.index"))
return render_template("login.html", form=form)
@ -55,25 +63,87 @@ def logout():
user = current_user()
if user:
flash(
_(f"You have been disconnected. See you next time {user.name}"), "success"
_("You have been disconnected. See you next time %(user)s", user=user.name),
"success",
)
user.logout()
return redirect("/")
@bp.route("/profile/<subject>", methods=("GET", "POST"))
@user_needed()
def profile(user, subject):
subject == user.uid[0] or abort(403)
@bp.route("/users")
@admin_needed()
def users(user):
users = User.filter(objectClass=current_app.config["LDAP"]["USER_CLASS"])
return render_template("users.html", users=users, menuitem="users")
@bp.route("/profile", methods=("GET", "POST"))
@admin_needed()
def profile_creation(user):
claims = current_app.config["JWT"]["MAPPING"]
form = AddProfileForm(request.form or None)
try:
del form.sub.render_kw["readonly"]
except KeyError:
pass
if request.form:
if not form.validate():
flash(_("User creation failed."), "error")
else:
user = User(objectClass=current_app.config["LDAP"]["USER_CLASS"])
for attribute in form:
model_attribute_name = claims.get(attribute.name.upper())
if (
not model_attribute_name
or model_attribute_name not in user.must + user.may
):
continue
user[model_attribute_name] = [attribute.data]
user.cn = [f"{user.givenName[0]} {user.sn[0]}"]
user.save()
flash(_("User creation succeed."), "success")
return redirect(
url_for("canaille.account.profile_edition", username=user.uid[0])
)
return render_template(
"profile.html", form=form, menuitem="users", edited_user=None
)
@bp.route("/profile/<username>", methods=("GET", "POST"))
@user_needed()
def profile_edition(user, username):
user.admin or username == user.uid[0] or abort(403)
if request.method == "GET" or request.form.get("action") == "edit":
return profile_edit(user, username)
if request.form.get("action") == "delete":
return profile_delete(user, username)
abort(400)
def profile_edit(user, username):
menuitem = "profile" if username == user.uid[0] else "users"
claims = current_app.config["JWT"]["MAPPING"]
if username != user.uid[0]:
user = User.get(username) or abort(404)
data = {
k.lower(): getattr(user, v)[0]
if getattr(user, v) and isinstance(getattr(user, v), list)
else getattr(user, v) or ""
for k, v in claims.items()
}
form = ProfileForm(request.form or None, data=data)
form = EditProfileForm(request.form or None, data=data)
form.sub.render_kw["readonly"] = "true"
if request.form:
@ -83,7 +153,10 @@ def profile(user, subject):
else:
for attribute in form:
model_attribute_name = claims.get(attribute.name.upper())
if not model_attribute_name or not hasattr(user, model_attribute_name):
if (
not model_attribute_name
or model_attribute_name not in user.must + user.may
):
continue
user[model_attribute_name] = [attribute.data]
@ -93,7 +166,24 @@ def profile(user, subject):
user.save()
return render_template("profile.html", form=form, menuitem="profile")
return render_template(
"profile.html", form=form, menuitem=menuitem, edited_user=user
)
def profile_delete(user, username):
self_deletion = username == user.uid[0]
if self_deletion:
user.logout()
else:
user = User.get(username) or abort(404)
flash(_("The user %(user)s has been sucessfuly deleted", user=user.name), "success")
user.delete()
if self_deletion:
return redirect(url_for("canaille.account.index"))
return redirect(url_for("canaille.account.users"))
def profile_hash(user, password):
@ -214,6 +304,6 @@ def reset(uid, hash):
user.login()
flash(_("Your password has been updated successfuly"), "success")
return redirect(url_for("canaille.account.profile", subject=uid))
return redirect(url_for("canaille.account.profile_edition", username=uid))
return render_template("reset-password.html", form=form, uid=uid, hash=hash)

View file

@ -36,6 +36,9 @@ USER_BASE = "ou=users,dc=mydomain,dc=tld"
# USER_FILTER = "(|(uid={login})(mail={login}))"
USER_FILTER = "(|(uid={login})(cn={login}))"
# A class to use for creating new users
USER_CLASS = "inetOrgPerson"
# Filter to match admin users. If your server has memberof
# you can filter against group membership
# ADMIN_FILTER = "uid=admin"

View file

@ -51,6 +51,7 @@ class ProfileForm(FlaskForm):
sub = wtforms.StringField(
_("Username"),
render_kw={"placeholder": _("jdoe")},
validators=[wtforms.validators.DataRequired()],
)
# name = wtforms.StringField(_("Name"))
given_name = wtforms.StringField(
@ -90,6 +91,12 @@ class ProfileForm(FlaskForm):
# picture = wtforms.StringField(_("Photo"))
# website = wtforms.fields.html5.URLField(_("Website"))
def validate_password2(self, field):
if self.password1.data and self.password1.data != field.data:
raise wtforms.ValidationError(_("Password and confirmation do not match."))
class EditProfileForm(ProfileForm):
password1 = wtforms.PasswordField(
_("Password"),
validators=[wtforms.validators.Optional(), wtforms.validators.Length(min=8)],
@ -98,6 +105,15 @@ class ProfileForm(FlaskForm):
_("Password confirmation"),
)
def validate_password2(self, field):
if self.password1.data and self.password1.data != field.data:
raise wtforms.ValidationError(_("Password and confirmation do not match."))
class AddProfileForm(ProfileForm):
password1 = wtforms.PasswordField(
_("Password"),
validators=[
wtforms.validators.DataRequired(),
wtforms.validators.Length(min=8),
],
)
password2 = wtforms.PasswordField(
_("Password confirmation"),
)

View file

@ -0,0 +1,10 @@
$('.confirm').click(function(e){
e.preventDefault();
$('.ui.modal')
.modal({
onApprove : function() {
$('.confirm').unbind('click').click();
},
})
.modal('show');
});

View file

@ -32,7 +32,7 @@
</div>
{% endif %}
<a class="item {% if menuitem == "profile" %}active{% endif %}"
href="{{ url_for('canaille.account.profile', subject=user.uid[0]) }}">
href="{{ url_for('canaille.account.profile_edition', username=user.uid[0]) }}">
<i class="id card icon"></i>
{% trans %}My profile{% endtrans %}
</a>
@ -42,6 +42,13 @@
{% trans %}My consents{% endtrans %}
</a>
{% if user.admin %}
<a class="item {% if menuitem == "users" %}active{% endif %}"
href="{{ url_for('canaille.account.users') }}">
<i class="users icon"></i>
{% trans %}Users{% endtrans %}
</a>
{% endif %}
{% if user.admin %}
<div class="ui dropdown item {% if menuitem == "admin" %}active{% endif %}">
<i class="settings icon"></i>
Admin

View file

@ -2,18 +2,70 @@
{% import 'fomanticui.j2' as sui %}
{% import 'flask.j2' as flask %}
{% block script %}
<script src="/static/js/profile.js"></script>
{% endblock %}
{% block content %}
{% if user.admin and edited_user %}
<div class="ui basic modal">
<div class="ui icon header">
<i class="user minus icon"></i>
{% trans %}User deletion{% endtrans %}
</div>
<div class="content">
<p>{{ _("Are you sure you want to delete this user? This action is unrevokable and all the data about this user will be removed.") }}</p>
</div>
<div class="actions">
<div class="ui inverted cancel button">{% trans %}Cancel{% endtrans %}</div>
<div class="ui inverted red approve button">{% trans %}Delete{% endtrans %}</div>
</div>
</div>
{% endif %}
<div class="ui clearing segment">
<h2 class="ui center aligned header">
<div class="content">
{{ _("My profile") }}
{% if not edited_user %}
{% trans %}User creation{% endtrans %}
{% elif user.uid == edited_user.uid %}
{% trans %}My profile{% endtrans %}
{% else %}
{% trans %}User profile edition{% endtrans %}
{% endif %}
</div>
<div class="sub header">
{% trans %}Edit your personal informations{% endtrans %}
{% if not edited_user %}
{% trans %}Create a new user account{% endtrans %}
{% elif user.uid == edited_user.uid %}
{% trans %}Edit your personal informations{% endtrans %}
{% else %}
{% trans %}Edit informations about an user{% endtrans %}
{% endif %}
</div>
</h2>
{{ flask.messages() }}
{{ sui.render_form(form, _("Edit"), action=request.url) }}
<form method="POST"
id="{{ form.__class__.__name__|lower }}"
action="{{ request.url }}"
role="form"
enctype="multipart/form-data"
class="ui form"
>
{{ flask.messages() }}
{{ sui.render_fields(form) }}
<button type="submit" class="ui right floated primary button" name="action" value="edit" id="edit">
{{ _("Send") }}
</button>
{% if user.admin and edited_user %}
<button type="submit" class="ui right floated negative button confirm" name="action" value="delete" id="delete">
{{ _("Delete the user") }}
</button>
{% endif %}
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,35 @@
{% extends 'base.html' %}
{% block style %}
<link href="/static/datatables/jquery.dataTables.min.css" rel="stylesheet">
<link href="/static/datatables/dataTables.semanticui.min.css" rel="stylesheet">
{% endblock %}
{% block script %}
<script src="/static/datatables/jquery.dataTables.min.js"></script>
<script src="/static/datatables/dataTables.semanticui.min.js"></script>
<script src="/static/js/users.js"></script>
{% endblock %}
{% block content %}
<div class="ui segment">
<a class="ui primary button" href="{{ url_for('canaille.account.profile_creation') }}">{% trans %}Add a user{% endtrans %}</a>
</div>
<table class="ui table">
<thead>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Email{% endtrans %}</th>
<th>{% trans %}Phone number{% endtrans %}</th>
</thead>
{% for user in users %}
<tr>
<td><a href="{{ url_for('canaille.account.profile_edition', username=user.uid[0]) }}">{{ user.name }}</a></td>
<td><a href="mailto:{{ user.mail[0] }}">{{ user.mail[0] }}</a></td>
<td>{{ user.telephoneNumber[0] }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: contact@yaal.fr\n"
"POT-Creation-Date: 2020-10-29 15:26+0100\n"
"PO-Revision-Date: 2020-10-29 15:27+0100\n"
"POT-Creation-Date: 2020-11-01 12:32+0100\n"
"PO-Revision-Date: 2020-11-01 12:38+0100\n"
"Last-Translator: Éloi Rivard <eloi@yaal.fr>\n"
"Language: fr_FR\n"
"Language-Team: French - France <equipe@yaal.fr>\n"
@ -20,39 +20,62 @@ msgstr ""
"Generated-By: Babel 2.8.0\n"
"X-Generator: Gtranslator 3.38.0\n"
#: canaille/account.py:35 canaille/oauth.py:58
#: canaille/account.py:51 canaille/oauth.py:58
msgid "Login failed, please check your information"
msgstr "La connexion a échoué, veuillez vérifier vos informations."
#: canaille/account.py:64
#: canaille/account.py:55
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr "Connexion réussie. Bienvenue %(user)s"
#: canaille/account.py:66
#, python-format
msgid "You have been disconnected. See you next time %(user)s"
msgstr "Vous avez été déconnectés. À bientôt %(user)s"
#: canaille/account.py:92
msgid "User creation failed."
msgstr "La création de l'utilisateur a échoué."
#: canaille/account.py:109
msgid "User creation succeed."
msgstr "La création de l'utilisateur a réussi."
#: canaille/account.py:151
msgid "Profile edition failed."
msgstr "L'édition du profil a échoué."
#: canaille/account.py:75
#: canaille/account.py:165
msgid "Profile updated successfuly."
msgstr "Le profil a été mis à jour avec succès."
#: canaille/account.py:97
#: canaille/account.py:181
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr "L'utilisateur %(user)s a bien été supprimé"
#: canaille/account.py:204
msgid "Could not send the password reset link."
msgstr "Impossible d'envoyer le lien de réinitialisation."
#: canaille/account.py:104 canaille/account.py:177
#: canaille/account.py:211 canaille/account.py:284
msgid "A password reset link has been sent at your email address."
msgstr "Un lien de réinitialisation vous a été envoyé à votre adresse."
#: canaille/account.py:126
#: canaille/account.py:233
msgid "Password reset on {website_name}"
msgstr "Réinitialisation du mot de passe sur {website_name}"
#: canaille/account.py:171
#: canaille/account.py:278
msgid "Could not reset your password"
msgstr "Impossible de réinitialiser votre mot de passe"
#: canaille/account.py:190
#: canaille/account.py:297
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:199
#: canaille/account.py:306
msgid "Your password has been updated successfuly"
msgstr "Votre mot de passe a correctement été mis à jour."
@ -64,55 +87,60 @@ msgstr "Impossible de supprimer cet accès."
msgid "The access has been revoked"
msgstr "L'accès a été révoqué."
#: canaille/forms.py:8 canaille/forms.py:25
#: canaille/forms.py:9 canaille/forms.py:26
msgid "Login"
msgstr "Identifiant"
#: canaille/forms.py:11 canaille/forms.py:28 canaille/forms.py:80
#: canaille/forms.py:12 canaille/forms.py:29 canaille/forms.py:82
msgid "jane@doe.com"
msgstr "martin@dupont.fr"
#: canaille/forms.py:18 canaille/forms.py:37 canaille/forms.py:93
#: canaille/forms.py:19 canaille/forms.py:38 canaille/forms.py:101
#: canaille/forms.py:111
msgid "Password"
msgstr "Mot de passe"
#: canaille/forms.py:40 canaille/forms.py:97
#: canaille/forms.py:41 canaille/forms.py:105 canaille/forms.py:118
msgid "Password confirmation"
msgstr "Confirmation du mot de passe"
#: canaille/forms.py:43 canaille/forms.py:102
#: canaille/forms.py:44 canaille/forms.py:96
msgid "Password and confirmation do not match."
msgstr "Le mot de passe et sa confirmation ne correspondent pas."
#: canaille/forms.py:51
#: canaille/forms.py:52
msgid "Username"
msgstr "Identifiant"
#: canaille/forms.py:56
#: canaille/forms.py:53
msgid "jdoe"
msgstr "mdupont"
#: canaille/forms.py:58
msgid "Given name"
msgstr "Prénom"
#: canaille/forms.py:58
#: canaille/forms.py:60
msgid "John"
msgstr "Martin"
#: canaille/forms.py:64
#: canaille/forms.py:66
msgid "Family Name"
msgstr "Nom de famille"
#: canaille/forms.py:66
#: canaille/forms.py:68
msgid "Doe"
msgstr "Dupont"
#: canaille/forms.py:77
#: canaille/forms.py:79
msgid "Email address"
msgstr "Courriel"
#: canaille/forms.py:87
#: canaille/forms.py:89 canaille/templates/users.html:24
msgid "Phone number"
msgstr "Numéro de téléphone"
#: canaille/forms.py:87
#: canaille/forms.py:89
msgid "555-000-555"
msgstr "06 01 02 03 04"
@ -137,6 +165,7 @@ msgid "You have been successfully logged out."
msgstr "Vous avez été déconnectés."
#: canaille/admin/clients.py:24 canaille/templates/admin/client_list.html:22
#: canaille/templates/users.html:22
msgid "Name"
msgstr "Nom"
@ -235,62 +264,66 @@ msgid "Accept"
msgstr "Accepter"
#: canaille/templates/base.html:8
msgid "OpenID Connect Server"
msgstr "OpenID Connect Server"
msgid "authorization interface"
msgstr " - Interface de gestion des autorisations"
#: canaille/templates/base.html:37 canaille/templates/profile.html:8
#: canaille/templates/base.html:37 canaille/templates/profile.html:32
msgid "My profile"
msgstr "Mon profil"
#: canaille/templates/base.html:42 canaille/templates/consent_list.html:18
#: canaille/templates/base.html:42 canaille/templates/consent_list.html:19
msgid "My consents"
msgstr "Mes autorisations"
#: canaille/templates/base.html:51
#: canaille/templates/base.html:48
msgid "Users"
msgstr "Utilisateurs"
#: canaille/templates/base.html:58
msgid "Clients"
msgstr "Clients"
#: canaille/templates/base.html:55
#: canaille/templates/base.html:62
msgid "Tokens"
msgstr "Jetons"
#: canaille/templates/base.html:59
#: canaille/templates/base.html:66
msgid "Codes"
msgstr "Codes"
#: canaille/templates/base.html:63
#: canaille/templates/base.html:70
msgid "Consents"
msgstr "Autorisations"
#: canaille/templates/base.html:70
#: canaille/templates/base.html:77
msgid "Log out"
msgstr "Déconnexion"
#: canaille/templates/consent_list.html:21
#: canaille/templates/consent_list.html:22
msgid "Consult and revoke the authorization you gave to websites."
msgstr "Consultez et révoquez les autorisation que vous avez données."
#: canaille/templates/consent_list.html:47
#: canaille/templates/consent_list.html:42
msgid "From:"
msgstr "À partir de :"
#: canaille/templates/consent_list.html:49
#: canaille/templates/consent_list.html:44
msgid "Revoked:"
msgstr "Révoqué le :"
#: canaille/templates/consent_list.html:52
#: canaille/templates/consent_list.html:47
msgid "Has access to:"
msgstr "A accès à :"
#: canaille/templates/consent_list.html:62
#: canaille/templates/consent_list.html:57
msgid "Remove access"
msgstr "Supprimer l'accès"
#: canaille/templates/consent_list.html:72
#: canaille/templates/consent_list.html:67
msgid "Nothing here"
msgstr "Rien ici"
#: canaille/templates/consent_list.html:73
#: canaille/templates/consent_list.html:68
msgid "You did not authorize applications yet."
msgstr ""
"Vous n'avez pas encore autorisé d'application à accéder à votre profil."
@ -311,12 +344,12 @@ msgstr "Page non trouvée"
msgid "Technical problem"
msgstr "Problème technique"
#: canaille/templates/forgotten-password.html:7
#: canaille/templates/login.html:39
#: canaille/templates/forgotten-password.html:12
#: canaille/templates/login.html:34
msgid "Forgotten password"
msgstr "Mot de passe oublié"
#: canaille/templates/forgotten-password.html:17
#: canaille/templates/forgotten-password.html:19
msgid ""
"\n"
" After this form is sent, if the email address or the login you "
@ -333,40 +366,85 @@ msgstr ""
" vous permettra de ré-initialiser votre mot de passe.\n"
" "
#: canaille/templates/forgotten-password.html:34
#: canaille/templates/forgotten-password.html:36
#: canaille/templates/profile.html:62
msgid "Send"
msgstr "Envoyer"
#: canaille/templates/forgotten-password.html:35
#: canaille/templates/forgotten-password.html:37
msgid "Login page"
msgstr "Page de connexion"
#: canaille/templates/login.html:14
#: canaille/templates/login.html:15
#, python-format
msgid "Sign in at %(website)s"
msgstr "Connexion à %(website)s"
#: canaille/templates/login.html:16
#: canaille/templates/login.html:17
msgid "Log-in and manage your authorizations."
msgstr "Connectez-vous et gérez vos autorisations."
#: canaille/templates/login.html:38
#: canaille/templates/login.html:33
msgid "Sign in"
msgstr "Se connecter"
#: canaille/templates/profile.html:11
#: canaille/templates/profile.html:14
msgid "User deletion"
msgstr "Suppression d'un utilisateur"
#: canaille/templates/profile.html:17
msgid ""
"Are you sure you want to delete this user? This action is unrevokable and "
"all the data about this user will be removed."
msgstr ""
"Êtes-vous sûrs de vouloir supprimer cet utilisateur ? Cette action est "
"irrévocable, et toutes les données de cet utilisateur seront supprimées."
#: canaille/templates/profile.html:20
msgid "Cancel"
msgstr "Annuler"
#: canaille/templates/profile.html:21
msgid "Delete"
msgstr "Supprimer"
#: canaille/templates/profile.html:30
msgid "User creation"
msgstr "Nouvel utilisateur"
#: canaille/templates/profile.html:34
msgid "User profile edition"
msgstr "Édition d'un profil utilisateur"
#: canaille/templates/profile.html:40
msgid "Create a new user account"
msgstr "Création d'un nouveau compte utilisateur"
#: canaille/templates/profile.html:42
msgid "Edit your personal informations"
msgstr "Éditez vos informations personnelles"
#: canaille/templates/profile.html:23
msgid "Edit"
msgstr "Éditer"
#: canaille/templates/profile.html:44
msgid "Edit informations about an user"
msgstr "Éditez les informations d'un utilisateur"
#: canaille/templates/reset-password.html:7
#: canaille/templates/reset-password.html:17
#: canaille/templates/profile.html:66
msgid "Delete the user"
msgstr "Supprimer l'utilisateur"
#: canaille/templates/reset-password.html:12
#: canaille/templates/reset-password.html:19
msgid "Password reset"
msgstr "Réinitialisation du mot de passe"
#: canaille/templates/users.html:17
msgid "Add a user"
msgstr "Nouvel utilisateur"
#: canaille/templates/users.html:23
msgid "Email"
msgstr "Courriel"
#: canaille/templates/admin/authorization_list.html:18
#: canaille/templates/admin/token_list.html:18
msgid "Token"
@ -388,32 +466,32 @@ msgstr "Utilisateur"
msgid "Created"
msgstr "Créé"
#: canaille/templates/admin/authorization_view.html:7
#: canaille/templates/admin/authorization_view.html:8
msgid "View a authorization"
msgstr "Voir une autorisation"
#: canaille/templates/admin/client_add.html:7
#: canaille/templates/admin/client_add.html:8
msgid "Add a client"
msgstr "Ajouter un client"
#: canaille/templates/admin/client_add.html:17
#: canaille/templates/admin/client_edit.html:34
#: canaille/templates/admin/client_add.html:14
#: canaille/templates/admin/client_edit.html:31
msgid "Confirm"
msgstr "Confirmer"
#: canaille/templates/admin/client_edit.html:7
#: canaille/templates/admin/client_edit.html:8
msgid "Edit a client"
msgstr "Éditer un client"
#: canaille/templates/admin/client_edit.html:20
#: canaille/templates/admin/client_edit.html:17
msgid "ID"
msgstr "ID"
#: canaille/templates/admin/client_edit.html:24
#: canaille/templates/admin/client_edit.html:21
msgid "Secret"
msgstr "Secret"
#: canaille/templates/admin/client_edit.html:28
#: canaille/templates/admin/client_edit.html:25
msgid "Issued at"
msgstr "Créé le"
@ -425,7 +503,7 @@ msgstr "Ajouter un client"
msgid "URL"
msgstr "URL"
#: canaille/templates/admin/token_view.html:7
#: canaille/templates/admin/token_view.html:8
msgid "View a token"
msgstr "Voir un jeton"
@ -499,3 +577,9 @@ msgstr ""
#~ msgid "from: %(user)s"
#~ msgstr "pour : %(user)s"
#~ msgid "OpenID Connect Server"
#~ msgstr "OpenID Connect Server"
#~ msgid "Edit"
#~ msgstr "Éditer"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-10-29 15:26+0100\n"
"POT-Creation-Date: 2020-11-01 12:32+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,39 +17,62 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.8.0\n"
#: canaille/account.py:35 canaille/oauth.py:58
#: canaille/account.py:51 canaille/oauth.py:58
msgid "Login failed, please check your information"
msgstr ""
#: canaille/account.py:64
#: canaille/account.py:55
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr ""
#: canaille/account.py:66
#, python-format
msgid "You have been disconnected. See you next time %(user)s"
msgstr ""
#: canaille/account.py:92
msgid "User creation failed."
msgstr ""
#: canaille/account.py:109
msgid "User creation succeed."
msgstr ""
#: canaille/account.py:151
msgid "Profile edition failed."
msgstr ""
#: canaille/account.py:75
#: canaille/account.py:165
msgid "Profile updated successfuly."
msgstr ""
#: canaille/account.py:97
#: canaille/account.py:181
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr ""
#: canaille/account.py:204
msgid "Could not send the password reset link."
msgstr ""
#: canaille/account.py:104 canaille/account.py:177
#: canaille/account.py:211 canaille/account.py:284
msgid "A password reset link has been sent at your email address."
msgstr ""
#: canaille/account.py:126
#: canaille/account.py:233
msgid "Password reset on {website_name}"
msgstr ""
#: canaille/account.py:171
#: canaille/account.py:278
msgid "Could not reset your password"
msgstr ""
#: canaille/account.py:190
#: canaille/account.py:297
msgid "The password reset link that brought you here was invalid."
msgstr ""
#: canaille/account.py:199
#: canaille/account.py:306
msgid "Your password has been updated successfuly"
msgstr ""
@ -61,55 +84,60 @@ msgstr ""
msgid "The access has been revoked"
msgstr ""
#: canaille/forms.py:8 canaille/forms.py:25
#: canaille/forms.py:9 canaille/forms.py:26
msgid "Login"
msgstr ""
#: canaille/forms.py:11 canaille/forms.py:28 canaille/forms.py:80
#: canaille/forms.py:12 canaille/forms.py:29 canaille/forms.py:82
msgid "jane@doe.com"
msgstr ""
#: canaille/forms.py:18 canaille/forms.py:37 canaille/forms.py:93
#: canaille/forms.py:19 canaille/forms.py:38 canaille/forms.py:101
#: canaille/forms.py:111
msgid "Password"
msgstr ""
#: canaille/forms.py:40 canaille/forms.py:97
#: canaille/forms.py:41 canaille/forms.py:105 canaille/forms.py:118
msgid "Password confirmation"
msgstr ""
#: canaille/forms.py:43 canaille/forms.py:102
#: canaille/forms.py:44 canaille/forms.py:96
msgid "Password and confirmation do not match."
msgstr ""
#: canaille/forms.py:51
#: canaille/forms.py:52
msgid "Username"
msgstr ""
#: canaille/forms.py:56
msgid "Given name"
#: canaille/forms.py:53
msgid "jdoe"
msgstr ""
#: canaille/forms.py:58
msgid "Given name"
msgstr ""
#: canaille/forms.py:60
msgid "John"
msgstr ""
#: canaille/forms.py:64
#: canaille/forms.py:66
msgid "Family Name"
msgstr ""
#: canaille/forms.py:66
#: canaille/forms.py:68
msgid "Doe"
msgstr ""
#: canaille/forms.py:77
#: canaille/forms.py:79
msgid "Email address"
msgstr ""
#: canaille/forms.py:87
#: canaille/forms.py:89 canaille/templates/users.html:24
msgid "Phone number"
msgstr ""
#: canaille/forms.py:87
#: canaille/forms.py:89
msgid "555-000-555"
msgstr ""
@ -134,6 +162,7 @@ msgid "You have been successfully logged out."
msgstr ""
#: canaille/admin/clients.py:24 canaille/templates/admin/client_list.html:22
#: canaille/templates/users.html:22
msgid "Name"
msgstr ""
@ -232,62 +261,66 @@ msgid "Accept"
msgstr ""
#: canaille/templates/base.html:8
msgid "OpenID Connect Server"
msgid "authorization interface"
msgstr ""
#: canaille/templates/base.html:37 canaille/templates/profile.html:8
#: canaille/templates/base.html:37 canaille/templates/profile.html:32
msgid "My profile"
msgstr ""
#: canaille/templates/base.html:42 canaille/templates/consent_list.html:18
#: canaille/templates/base.html:42 canaille/templates/consent_list.html:19
msgid "My consents"
msgstr ""
#: canaille/templates/base.html:51
#: canaille/templates/base.html:48
msgid "Users"
msgstr ""
#: canaille/templates/base.html:58
msgid "Clients"
msgstr ""
#: canaille/templates/base.html:55
#: canaille/templates/base.html:62
msgid "Tokens"
msgstr ""
#: canaille/templates/base.html:59
#: canaille/templates/base.html:66
msgid "Codes"
msgstr ""
#: canaille/templates/base.html:63
#: canaille/templates/base.html:70
msgid "Consents"
msgstr ""
#: canaille/templates/base.html:70
#: canaille/templates/base.html:77
msgid "Log out"
msgstr ""
#: canaille/templates/consent_list.html:21
#: canaille/templates/consent_list.html:22
msgid "Consult and revoke the authorization you gave to websites."
msgstr ""
#: canaille/templates/consent_list.html:47
#: canaille/templates/consent_list.html:42
msgid "From:"
msgstr ""
#: canaille/templates/consent_list.html:49
#: canaille/templates/consent_list.html:44
msgid "Revoked:"
msgstr ""
#: canaille/templates/consent_list.html:52
#: canaille/templates/consent_list.html:47
msgid "Has access to:"
msgstr ""
#: canaille/templates/consent_list.html:62
#: canaille/templates/consent_list.html:57
msgid "Remove access"
msgstr ""
#: canaille/templates/consent_list.html:72
#: canaille/templates/consent_list.html:67
msgid "Nothing here"
msgstr ""
#: canaille/templates/consent_list.html:73
#: canaille/templates/consent_list.html:68
msgid "You did not authorize applications yet."
msgstr ""
@ -307,12 +340,12 @@ msgstr ""
msgid "Technical problem"
msgstr ""
#: canaille/templates/forgotten-password.html:7
#: canaille/templates/login.html:39
#: canaille/templates/forgotten-password.html:12
#: canaille/templates/login.html:34
msgid "Forgotten password"
msgstr ""
#: canaille/templates/forgotten-password.html:17
#: canaille/templates/forgotten-password.html:19
msgid ""
"\n"
" After this form is sent, if the email address or the login you "
@ -323,40 +356,83 @@ msgid ""
" "
msgstr ""
#: canaille/templates/forgotten-password.html:34
#: canaille/templates/forgotten-password.html:36
#: canaille/templates/profile.html:62
msgid "Send"
msgstr ""
#: canaille/templates/forgotten-password.html:35
#: canaille/templates/forgotten-password.html:37
msgid "Login page"
msgstr ""
#: canaille/templates/login.html:14
#: canaille/templates/login.html:15
#, python-format
msgid "Sign in at %(website)s"
msgstr ""
#: canaille/templates/login.html:16
#: canaille/templates/login.html:17
msgid "Log-in and manage your authorizations."
msgstr ""
#: canaille/templates/login.html:38
#: canaille/templates/login.html:33
msgid "Sign in"
msgstr ""
#: canaille/templates/profile.html:11
#: canaille/templates/profile.html:14
msgid "User deletion"
msgstr ""
#: canaille/templates/profile.html:17
msgid ""
"Are you sure you want to delete this user? This action is unrevokable and"
" all the data about this user will be removed."
msgstr ""
#: canaille/templates/profile.html:20
msgid "Cancel"
msgstr ""
#: canaille/templates/profile.html:21
msgid "Delete"
msgstr ""
#: canaille/templates/profile.html:30
msgid "User creation"
msgstr ""
#: canaille/templates/profile.html:34
msgid "User profile edition"
msgstr ""
#: canaille/templates/profile.html:40
msgid "Create a new user account"
msgstr ""
#: canaille/templates/profile.html:42
msgid "Edit your personal informations"
msgstr ""
#: canaille/templates/profile.html:23
msgid "Edit"
#: canaille/templates/profile.html:44
msgid "Edit informations about an user"
msgstr ""
#: canaille/templates/reset-password.html:7
#: canaille/templates/reset-password.html:17
#: canaille/templates/profile.html:66
msgid "Delete the user"
msgstr ""
#: canaille/templates/reset-password.html:12
#: canaille/templates/reset-password.html:19
msgid "Password reset"
msgstr ""
#: canaille/templates/users.html:17
msgid "Add a user"
msgstr ""
#: canaille/templates/users.html:23
msgid "Email"
msgstr ""
#: canaille/templates/admin/authorization_list.html:18
#: canaille/templates/admin/token_list.html:18
msgid "Token"
@ -378,32 +454,32 @@ msgstr ""
msgid "Created"
msgstr ""
#: canaille/templates/admin/authorization_view.html:7
#: canaille/templates/admin/authorization_view.html:8
msgid "View a authorization"
msgstr ""
#: canaille/templates/admin/client_add.html:7
#: canaille/templates/admin/client_add.html:8
msgid "Add a client"
msgstr ""
#: canaille/templates/admin/client_add.html:17
#: canaille/templates/admin/client_edit.html:34
#: canaille/templates/admin/client_add.html:14
#: canaille/templates/admin/client_edit.html:31
msgid "Confirm"
msgstr ""
#: canaille/templates/admin/client_edit.html:7
#: canaille/templates/admin/client_edit.html:8
msgid "Edit a client"
msgstr ""
#: canaille/templates/admin/client_edit.html:20
#: canaille/templates/admin/client_edit.html:17
msgid "ID"
msgstr ""
#: canaille/templates/admin/client_edit.html:24
#: canaille/templates/admin/client_edit.html:21
msgid "Secret"
msgstr ""
#: canaille/templates/admin/client_edit.html:28
#: canaille/templates/admin/client_edit.html:25
msgid "Issued at"
msgstr ""
@ -415,7 +491,7 @@ msgstr ""
msgid "URL"
msgstr ""
#: canaille/templates/admin/token_view.html:7
#: canaille/templates/admin/token_view.html:8
msgid "View a token"
msgstr ""

View file

@ -129,7 +129,8 @@ def app(slapd_server, keypair_path):
"BIND_PW": slapd_server.root_pw,
"USER_BASE": "ou=users",
"USER_FILTER": "(|(uid={login})(cn={login}))",
"ADMIN_FILTER": "uid=admin",
"USER_CLASS": "inetOrgPerson",
"ADMIN_FILTER": "(|(uid=admin)(sn=admin))",
},
"JWT": {
"PUBLIC_KEY": public_key_path,

View file

@ -1,3 +1,6 @@
from canaille.models import User
def test_profile(testclient, slapd_connection, logged_user):
res = testclient.get("/profile/user", status=200)
@ -7,7 +10,7 @@ def test_profile(testclient, slapd_connection, logged_user):
res.form["email"] = "email@mydomain.tld"
res.form["phone_number"] = "555-666-777"
res = res.form.submit(status=200)
res = res.form.submit(name="action", value="edit", status=200)
logged_user.reload(slapd_connection)
@ -26,7 +29,7 @@ def test_bad_email(testclient, slapd_connection, logged_user):
res.form["email"] = "john@doe.com"
res = res.form.submit(status=200)
res = res.form.submit(name="action", value="edit", status=200)
assert ["john@doe.com"] == logged_user.mail
@ -34,7 +37,7 @@ def test_bad_email(testclient, slapd_connection, logged_user):
res.form["email"] = "yolo"
res = res.form.submit(status=200)
res = res.form.submit(name="action", value="edit", status=200)
logged_user.reload(slapd_connection)
@ -47,7 +50,7 @@ def test_password_change(testclient, slapd_connection, logged_user):
res.form["password1"] = "new_password"
res.form["password2"] = "new_password"
res = res.form.submit(status=200)
res = res.form.submit(name="action", value="edit", status=200)
with testclient.app.app_context():
assert logged_user.check_password("new_password")
@ -57,7 +60,7 @@ def test_password_change(testclient, slapd_connection, logged_user):
res.form["password1"] = "correct horse battery staple"
res.form["password2"] = "correct horse battery staple"
res = res.form.submit(status=200)
res = res.form.submit(name="action", value="edit", status=200)
with testclient.app.app_context():
assert logged_user.check_password("correct horse battery staple")
@ -69,7 +72,7 @@ def test_password_change_fail(testclient, slapd_connection, logged_user):
res.form["password1"] = "new_password"
res.form["password2"] = "other_password"
res = res.form.submit(status=200)
res = res.form.submit(name="action", value="edit", status=200)
with testclient.app.app_context():
assert logged_user.check_password("correct horse battery staple")
@ -79,7 +82,90 @@ def test_password_change_fail(testclient, slapd_connection, logged_user):
res.form["password1"] = "new_password"
res.form["password2"] = ""
res = res.form.submit(status=200)
res = res.form.submit(name="action", value="edit", status=200)
with testclient.app.app_context():
assert logged_user.check_password("correct horse battery staple")
def test_simple_user_cannot_edit_other(testclient, logged_user):
testclient.get("/profile/user", status=200)
testclient.get("/profile/admin", status=403)
testclient.post("/profile/admin", {"action": "edit"}, status=403)
testclient.post("/profile/admin", {"action": "delete"}, status=403)
testclient.get("/users", status=403)
def test_admin_bad_request(testclient, logged_admin):
testclient.post("/profile/admin", {"action": "foobar"}, status=400)
testclient.get("/profile/foobar", status=404)
def test_user_creation_edition_and_deletion(testclient, slapd_connection, logged_admin):
# The user does not exist.
res = testclient.get("/users", status=200)
with testclient.app.app_context():
assert User.get("george", conn=slapd_connection) is None
assert "george" not in res.text
# Fill the profile for a new user.
res = testclient.get("/profile", status=200)
res.form["sub"] = "george"
res.form["given_name"] = "George"
res.form["family_name"] = "Abitbol"
res.form["email"] = "george@abitbol.com"
res.form["phone_number"] = "555-666-888"
# Passwords have been forgotten
res = res.form.submit(name="action", value="edit", status=200)
with testclient.app.app_context():
assert User.get("george", conn=slapd_connection) is None
res.form["password1"] = "totoyolo"
res.form["password2"] = "totoyolo"
# User have been created
res = res.form.submit(name="action", value="edit", status=302).follow(status=200)
with testclient.app.app_context():
assert "George" == User.get("george", conn=slapd_connection).givenName[0]
assert "george" in testclient.get("/users", status=200).text
res.form["given_name"] = "Georgio"
# User have been edited
res = res.form.submit(name="action", value="edit", status=200)
with testclient.app.app_context():
assert "Georgio" == User.get("george", conn=slapd_connection).givenName[0]
assert "george" in testclient.get("/users", status=200).text
# User have been deleted.
res = res.form.submit(name="action", value="delete", status=302).follow(status=200)
with testclient.app.app_context():
assert User.get("george", conn=slapd_connection) is None
assert "george" not in res.text
def test_admin_self_deletion(testclient, slapd_connection):
User.ocs_by_name(slapd_connection)
admin = User(
objectClass=["inetOrgPerson"],
cn="Temp admin",
sn="admin",
uid="temp",
mail="temp@temp.com",
userPassword="{SSHA}Vmgh2jkD0idX3eZHf8RzGos31oerjGiU",
)
admin.save(slapd_connection)
with testclient.session_transaction() as sess:
sess["user_dn"] = admin.dn
res = testclient.get("/profile/temp")
res = (
res.form.submit(name="action", value="delete", status=302)
.follow(status=302)
.follow(status=200)
)
with testclient.app.app_context():
assert User.get("temp", conn=slapd_connection) is None
with testclient.session_transaction() as sess:
"user_dn" not in sess