Améliore formulaires gestion utilisateurs

This commit is contained in:
Emmanuel Viennet 2021-08-28 16:01:41 +02:00
parent dcd4d3bcbd
commit c48c52f7aa
9 changed files with 101 additions and 30 deletions

View File

@ -7,7 +7,7 @@ from app.email import send_email
def send_password_reset_email(user):
token = user.get_reset_password_token()
send_email(
"[ScoDoc] Reset Your Password",
"[ScoDoc] Réinitialisation de votre mot de passe",
sender=current_app.config["ADMINS"][0],
recipients=[user.email],
text_body=render_template("email/reset_password.txt", user=user, token=token),

View File

@ -53,3 +53,8 @@ class ResetPasswordForm(FlaskForm):
_l("Repeat Password"), validators=[DataRequired(), EqualTo("password")]
)
submit = SubmitField(_l("Request Password Reset"))
class DeactivateUserForm(FlaskForm):
submit = SubmitField("Modifier l'utilisateur")
cancel = SubmitField(label="Annuler", render_kw={"formnovalidate": True})

View File

@ -5,7 +5,6 @@
import base64
from datetime import datetime, timedelta
import json
import os
import re
from time import time
@ -115,7 +114,7 @@ class User(UserMixin, db.Model):
{"reset_password": self.id, "exp": time() + expires_in},
current_app.config["SECRET_KEY"],
algorithm="HS256",
).decode("utf-8")
)
@staticmethod
def verify_reset_password_token(token):

View File

@ -3,7 +3,9 @@
auth.routes.py
"""
from flask import render_template, redirect, url_for, current_app, flash, request
from app.scodoc.sco_exceptions import ScoValueError
from flask import current_app, g, flash, render_template
from flask import redirect, url_for, request
from flask_login.utils import login_required
from werkzeug.urls import url_parse
from flask_login import login_user, logout_user, current_user
@ -15,12 +17,13 @@ from app.auth.forms import (
UserCreationForm,
ResetPasswordRequestForm,
ResetPasswordForm,
DeactivateUserForm,
)
from app.auth.models import Permission
from app.auth.models import User
from app.auth.email import send_password_reset_email
from app.decorators import admin_required
from app.decorators import permission_required
_ = lambda x: x # sans babel
_l = _
@ -69,13 +72,23 @@ def create_user():
@bp.route("/reset_password_request", methods=["GET", "POST"])
def reset_password_request():
"""Form demande renvoi de mot de passe par mail
Si l'utilisateur est déjà authentifié, le renvoie simplement sur
la page d'accueil.
"""
if current_user.is_authenticated:
return redirect(url_for("scodoc.index"))
form = ResetPasswordRequestForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user:
send_password_reset_email(user)
users = User.query.filter_by(email=form.email.data).all()
if len(users) == 1:
send_password_reset_email(users[0])
elif len(users) > 1:
current_app.logger.info(
"reset_password_request: multiple users with email '{}' (ignoring)".format(
form.email.data
)
)
else:
current_app.logger.info(
"reset_password_request: for unkown user '{}'".format(form.email.data)

View File

@ -255,7 +255,7 @@ def formsemestre_synchro_etuds(
url_for("scolar.affectGroups",
scodoc_dept=g.scodoc_dept,
partition_id=partitions[0]["partition_id"]
)}">Répartir les groupes de partitions[0]["partition_name"]</a></li>
)}">Répartir les groupes de {partitions[0]["partition_name"]}</a></li>
"""
)

View File

@ -343,11 +343,14 @@ def user_info_page(user_name=None):
)
if current_user.has_permission(Permission.ScoUsersAdmin, dept):
H.append(
"""
<li><a class="stdlink" href="create_user_form?user_name=%(user_name)s&edit=1">modifier ou désactiver ce compte</a><br/>
<em>(pour "supprimer" un utilisateur, le rendre inactif via le formulaire)</em>
f"""
<li><a class="stdlink" href="{url_for('users.create_user_form', scodoc_dept=g.scodoc_dept,
user_name=user.user_name, edit=1)}">modifier ce compte</a>
</li>
<li><a class="stdlink" href="{url_for('users.toggle_active_user', scodoc_dept=g.scodoc_dept,
user_name=user.user_name)
}">{"désactiver" if user.active else "activer"} ce compte</a>
</li>
"""
% info
)

View File

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>{{ "Désactiver" if u.active else "Activer" }} l'utilisateur {{ u.get_nomplogin() }} ?</h1>
<div class="help">
Dans ScoDoc on ne supprime pas les utilisateurs mais on les rend inactifs:
ils n'apparaissent plus dans les listes et ne peuvent plus se connecter.
<br />
Ces utilisateurs peuvent être réactivés à tout moment.
</div>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}

View File

@ -38,11 +38,13 @@ import re
from xml.etree import ElementTree
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask import redirect, render_template
from flask_login import current_user
from app import db
from app.auth.forms import DeactivateUserForm
from app.auth.models import Permission
from app.auth.models import User
from app.auth.models import Role
@ -210,7 +212,8 @@ def create_user_form(REQUEST, user_name=None, edit=0):
"title": "Mot de passe",
"input_type": "password",
"size": 14,
"allow_null": False,
"allow_null": True,
"explanation": "optionnel, l'utilisateur pourra le saisir avec son mail",
},
),
(
@ -219,7 +222,7 @@ def create_user_form(REQUEST, user_name=None, edit=0):
"title": "Confirmer mot de passe",
"input_type": "password",
"size": 14,
"allow_null": False,
"allow_null": True,
},
),
]
@ -237,9 +240,9 @@ def create_user_form(REQUEST, user_name=None, edit=0):
{
"title": "e-mail",
"input_type": "text",
"explanation": "vivement recommandé: utilisé pour contacter l'utilisateur",
"explanation": "requis, doit fonctionner",
"size": 20,
"allow_null": True,
"allow_null": False,
},
)
]
@ -437,13 +440,16 @@ def create_user_form(REQUEST, user_name=None, edit=0):
)
return "\n".join(H) + msg + "\n" + tf[1] + F
# check passwords
if vals["passwd"]:
if vals["passwd"] != vals["passwd2"]:
msg = tf_error_message(
"""Les deux mots de passes ne correspondent pas !"""
)
return "\n".join(H) + msg + "\n" + tf[1] + F
if not sco_users.is_valid_password(vals["passwd"]):
msg = tf_error_message("""Mot de passe trop simple, recommencez !""")
msg = tf_error_message(
"""Mot de passe trop simple, recommencez !"""
)
return "\n".join(H) + msg + "\n" + tf[1] + F
if not can_choose_dept:
vals["dept"] = auth_dept
@ -457,8 +463,12 @@ def create_user_form(REQUEST, user_name=None, edit=0):
db.session.add(u)
db.session.commit()
return flask.redirect(
"user_info_page?user_name=%s&head_message=Nouvel utilisateur créé"
% (user_name)
url_for(
"users.user_info_page",
scodoc_dept=g.scodoc_dept,
user_name=user_name,
head_message="Nouvel utilisateur créé",
)
)
@ -611,7 +621,9 @@ def form_change_password(REQUEST, user_name=None):
<input type="hidden" value="%(user_name)s" name="user_name">
<input type="submit" value="Changer">
</p>
<p>Vous pouvez aussi: <a class="stdlink" href="reset_password_form?user_name=%(user_name)s">renvoyer un mot de passe aléatoire temporaire par mail à l'utilisateur</a>
<p class="help">Note: en ScoDoc 9, les utilisateurs peuvent changer eux-même leur mot de passe
en indiquant l'adresse mail associée à leur compte.
</p>
"""
% {"nomplogin": u.get_nomplogin(), "user_name": user_name}
)
@ -676,3 +688,25 @@ def change_password(user_name, password, password2, REQUEST):
% scu.ScoURL()
)
return html_sco_header.sco_header() + "\n".join(H) + F
@bp.route("/toggle_active_user/<user_name>", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoUsersAdmin)
def toggle_active_user(user_name: str = None):
"""Change active status of a user account"""
u = User.query.filter_by(user_name=user_name).first()
if not u:
raise ScoValueError("invalid user_name")
form = DeactivateUserForm()
if (
request.method == "POST" and form.cancel.data
): # if cancel button is clicked, the form.cancel.data will be True
# flash
return redirect(url_for("users.index_html", scodoc_dept=g.scodoc_dept))
if form.validate_on_submit():
u.active = not u.active
db.session.add(u)
db.session.commit()
return redirect(url_for("users.index_html", scodoc_dept=g.scodoc_dept))
return render_template("auth/toogle_active_user.html", form=form, u=u)

View File

@ -32,7 +32,6 @@ iniconfig==1.1.1
isort==5.9.3
itsdangerous==2.0.1
Jinja2==3.0.1
jwt==1.2.0
lazy-object-proxy==1.6.0
Mako==1.1.4
MarkupSafe==2.0.1
@ -45,6 +44,7 @@ psycopg2==2.9.1
py==1.10.0
pycparser==2.20
pydot==1.4.2
PyJWT==2.1.0
pylint==2.9.6
pyOpenSSL==20.0.1
pyparsing==2.4.7