Améliore formulaires gestion utilisateurs
This commit is contained in:
parent
dcd4d3bcbd
commit
c48c52f7aa
@ -7,7 +7,7 @@ from app.email import send_email
|
|||||||
def send_password_reset_email(user):
|
def send_password_reset_email(user):
|
||||||
token = user.get_reset_password_token()
|
token = user.get_reset_password_token()
|
||||||
send_email(
|
send_email(
|
||||||
"[ScoDoc] Reset Your Password",
|
"[ScoDoc] Réinitialisation de votre mot de passe",
|
||||||
sender=current_app.config["ADMINS"][0],
|
sender=current_app.config["ADMINS"][0],
|
||||||
recipients=[user.email],
|
recipients=[user.email],
|
||||||
text_body=render_template("email/reset_password.txt", user=user, token=token),
|
text_body=render_template("email/reset_password.txt", user=user, token=token),
|
||||||
|
@ -53,3 +53,8 @@ class ResetPasswordForm(FlaskForm):
|
|||||||
_l("Repeat Password"), validators=[DataRequired(), EqualTo("password")]
|
_l("Repeat Password"), validators=[DataRequired(), EqualTo("password")]
|
||||||
)
|
)
|
||||||
submit = SubmitField(_l("Request Password Reset"))
|
submit = SubmitField(_l("Request Password Reset"))
|
||||||
|
|
||||||
|
|
||||||
|
class DeactivateUserForm(FlaskForm):
|
||||||
|
submit = SubmitField("Modifier l'utilisateur")
|
||||||
|
cancel = SubmitField(label="Annuler", render_kw={"formnovalidate": True})
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from time import time
|
from time import time
|
||||||
@ -115,7 +114,7 @@ class User(UserMixin, db.Model):
|
|||||||
{"reset_password": self.id, "exp": time() + expires_in},
|
{"reset_password": self.id, "exp": time() + expires_in},
|
||||||
current_app.config["SECRET_KEY"],
|
current_app.config["SECRET_KEY"],
|
||||||
algorithm="HS256",
|
algorithm="HS256",
|
||||||
).decode("utf-8")
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def verify_reset_password_token(token):
|
def verify_reset_password_token(token):
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
auth.routes.py
|
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 flask_login.utils import login_required
|
||||||
from werkzeug.urls import url_parse
|
from werkzeug.urls import url_parse
|
||||||
from flask_login import login_user, logout_user, current_user
|
from flask_login import login_user, logout_user, current_user
|
||||||
@ -15,12 +17,13 @@ from app.auth.forms import (
|
|||||||
UserCreationForm,
|
UserCreationForm,
|
||||||
ResetPasswordRequestForm,
|
ResetPasswordRequestForm,
|
||||||
ResetPasswordForm,
|
ResetPasswordForm,
|
||||||
|
DeactivateUserForm,
|
||||||
)
|
)
|
||||||
from app.auth.models import Permission
|
from app.auth.models import Permission
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.auth.email import send_password_reset_email
|
from app.auth.email import send_password_reset_email
|
||||||
from app.decorators import admin_required
|
from app.decorators import admin_required
|
||||||
|
from app.decorators import permission_required
|
||||||
|
|
||||||
_ = lambda x: x # sans babel
|
_ = lambda x: x # sans babel
|
||||||
_l = _
|
_l = _
|
||||||
@ -69,13 +72,23 @@ def create_user():
|
|||||||
|
|
||||||
@bp.route("/reset_password_request", methods=["GET", "POST"])
|
@bp.route("/reset_password_request", methods=["GET", "POST"])
|
||||||
def reset_password_request():
|
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:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
form = ResetPasswordRequestForm()
|
form = ResetPasswordRequestForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user = User.query.filter_by(email=form.email.data).first()
|
users = User.query.filter_by(email=form.email.data).all()
|
||||||
if user:
|
if len(users) == 1:
|
||||||
send_password_reset_email(user)
|
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:
|
else:
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
"reset_password_request: for unkown user '{}'".format(form.email.data)
|
"reset_password_request: for unkown user '{}'".format(form.email.data)
|
||||||
|
@ -255,7 +255,7 @@ def formsemestre_synchro_etuds(
|
|||||||
url_for("scolar.affectGroups",
|
url_for("scolar.affectGroups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=partitions[0]["partition_id"]
|
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>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -343,11 +343,14 @@ def user_info_page(user_name=None):
|
|||||||
)
|
)
|
||||||
if current_user.has_permission(Permission.ScoUsersAdmin, dept):
|
if current_user.has_permission(Permission.ScoUsersAdmin, dept):
|
||||||
H.append(
|
H.append(
|
||||||
"""
|
f"""
|
||||||
<li><a class="stdlink" href="create_user_form?user_name=%(user_name)s&edit=1">modifier ou désactiver ce compte</a><br/>
|
<li><a class="stdlink" href="{url_for('users.create_user_form', scodoc_dept=g.scodoc_dept,
|
||||||
<em>(pour "supprimer" un utilisateur, le rendre inactif via le formulaire)</em>
|
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>
|
</li>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
% info
|
% info
|
||||||
)
|
)
|
||||||
|
17
app/templates/auth/toogle_active_user.html
Normal file
17
app/templates/auth/toogle_active_user.html
Normal 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 %}
|
@ -38,11 +38,13 @@ import re
|
|||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
import flask
|
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 flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
|
from app.auth.forms import DeactivateUserForm
|
||||||
from app.auth.models import Permission
|
from app.auth.models import Permission
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.auth.models import Role
|
from app.auth.models import Role
|
||||||
@ -210,7 +212,8 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
"title": "Mot de passe",
|
"title": "Mot de passe",
|
||||||
"input_type": "password",
|
"input_type": "password",
|
||||||
"size": 14,
|
"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",
|
"title": "Confirmer mot de passe",
|
||||||
"input_type": "password",
|
"input_type": "password",
|
||||||
"size": 14,
|
"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",
|
"title": "e-mail",
|
||||||
"input_type": "text",
|
"input_type": "text",
|
||||||
"explanation": "vivement recommandé: utilisé pour contacter l'utilisateur",
|
"explanation": "requis, doit fonctionner",
|
||||||
"size": 20,
|
"size": 20,
|
||||||
"allow_null": True,
|
"allow_null": False,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -437,14 +440,17 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
)
|
)
|
||||||
return "\n".join(H) + msg + "\n" + tf[1] + F
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
||||||
# check passwords
|
# check passwords
|
||||||
if vals["passwd"] != vals["passwd2"]:
|
if vals["passwd"]:
|
||||||
msg = tf_error_message(
|
if vals["passwd"] != vals["passwd2"]:
|
||||||
"""Les deux mots de passes ne correspondent pas !"""
|
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"]):
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
||||||
msg = tf_error_message("""Mot de passe trop simple, recommencez !""")
|
if not sco_users.is_valid_password(vals["passwd"]):
|
||||||
return "\n".join(H) + msg + "\n" + tf[1] + F
|
msg = tf_error_message(
|
||||||
|
"""Mot de passe trop simple, recommencez !"""
|
||||||
|
)
|
||||||
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
||||||
if not can_choose_dept:
|
if not can_choose_dept:
|
||||||
vals["dept"] = auth_dept
|
vals["dept"] = auth_dept
|
||||||
# ok, go
|
# ok, go
|
||||||
@ -457,8 +463,12 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
db.session.add(u)
|
db.session.add(u)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"user_info_page?user_name=%s&head_message=Nouvel utilisateur créé"
|
url_for(
|
||||||
% (user_name)
|
"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="hidden" value="%(user_name)s" name="user_name">
|
||||||
<input type="submit" value="Changer">
|
<input type="submit" value="Changer">
|
||||||
</p>
|
</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}
|
% {"nomplogin": u.get_nomplogin(), "user_name": user_name}
|
||||||
)
|
)
|
||||||
@ -676,3 +688,25 @@ def change_password(user_name, password, password2, REQUEST):
|
|||||||
% scu.ScoURL()
|
% scu.ScoURL()
|
||||||
)
|
)
|
||||||
return html_sco_header.sco_header() + "\n".join(H) + F
|
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)
|
||||||
|
@ -32,7 +32,6 @@ iniconfig==1.1.1
|
|||||||
isort==5.9.3
|
isort==5.9.3
|
||||||
itsdangerous==2.0.1
|
itsdangerous==2.0.1
|
||||||
Jinja2==3.0.1
|
Jinja2==3.0.1
|
||||||
jwt==1.2.0
|
|
||||||
lazy-object-proxy==1.6.0
|
lazy-object-proxy==1.6.0
|
||||||
Mako==1.1.4
|
Mako==1.1.4
|
||||||
MarkupSafe==2.0.1
|
MarkupSafe==2.0.1
|
||||||
@ -45,6 +44,7 @@ psycopg2==2.9.1
|
|||||||
py==1.10.0
|
py==1.10.0
|
||||||
pycparser==2.20
|
pycparser==2.20
|
||||||
pydot==1.4.2
|
pydot==1.4.2
|
||||||
|
PyJWT==2.1.0
|
||||||
pylint==2.9.6
|
pylint==2.9.6
|
||||||
pyOpenSSL==20.0.1
|
pyOpenSSL==20.0.1
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
|
Loading…
Reference in New Issue
Block a user