amélioration formulaires creation/edition utilisateurs

This commit is contained in:
Emmanuel Viennet 2021-10-15 19:17:40 +02:00
parent c2de33f7f5
commit 9c50b58d5f
8 changed files with 85 additions and 61 deletions

View File

@ -8,7 +8,7 @@ TODO: à revoir complètement pour reprendre ZScoUsers et les pages d'authentifi
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
from app.auth.models import User from app.auth.models import User, is_valid_password
_ = lambda x: x # sans babel _ = lambda x: x # sans babel
@ -44,7 +44,7 @@ class UserCreationForm(FlaskForm):
class ResetPasswordRequestForm(FlaskForm): class ResetPasswordRequestForm(FlaskForm):
email = StringField(_l("Email"), validators=[DataRequired(), Email()]) email = StringField(_l("Email"), validators=[DataRequired(), Email()])
submit = SubmitField(_l("Request Password Reset")) submit = SubmitField(_l("Valider ce mot de passe"))
class ResetPasswordForm(FlaskForm): class ResetPasswordForm(FlaskForm):
@ -52,7 +52,11 @@ class ResetPasswordForm(FlaskForm):
password2 = PasswordField( password2 = PasswordField(
_l("Répéter"), validators=[DataRequired(), EqualTo("password")] _l("Répéter"), validators=[DataRequired(), EqualTo("password")]
) )
submit = SubmitField(_l("Request Password Reset")) submit = SubmitField(_l("Valider ce mot de passe"))
def validate_password(self, password):
if not is_valid_password(password.data):
raise ValidationError(f"Mot de passe trop simple, recommencez")
class DeactivateUserForm(FlaskForm): class DeactivateUserForm(FlaskForm):

View File

@ -10,6 +10,7 @@ import re
from time import time from time import time
from typing import Optional from typing import Optional
import cracklib # pylint: disable=import-error
from flask import current_app, url_for, g from flask import current_app, url_for, g
from flask_login import UserMixin, AnonymousUserMixin from flask_login import UserMixin, AnonymousUserMixin
@ -28,6 +29,23 @@ from app.scodoc import sco_etud # a deplacer dans scu
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$") VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
def is_valid_password(cleartxt):
"""Check password.
returns True if OK.
"""
if (
hasattr(scu.CONFIG, "MIN_PASSWORD_LENGTH")
and scu.CONFIG.MIN_PASSWORD_LENGTH > 0
and len(cleartxt) < scu.CONFIG.MIN_PASSWORD_LENGTH
):
return False # invalid: too short
try:
_ = cracklib.FascistCheck(cleartxt)
return True
except ValueError:
return False
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
"""ScoDoc users, handled by Flask / SQLAlchemy""" """ScoDoc users, handled by Flask / SQLAlchemy"""

View File

@ -116,6 +116,6 @@ def reset_password(token):
if form.validate_on_submit(): if form.validate_on_submit():
user.set_password(form.password.data) user.set_password(form.password.data)
db.session.commit() db.session.commit()
flash(_("Your password has been reset.")) flash(_("Votre mot de passe a été changé."))
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
return render_template("auth/reset_password.html", form=form) return render_template("auth/reset_password.html", form=form, user=user)

View File

@ -34,7 +34,6 @@ import re
from flask import url_for, g, request from flask import url_for, g, request
from flask_login import current_user from flask_login import current_user
import cracklib # pylint: disable=import-error
from app import db, Departement from app import db, Departement
@ -56,23 +55,6 @@ from app.scodoc.sco_exceptions import (
) )
def is_valid_password(cleartxt):
"""Check password.
returns True if OK.
"""
if (
hasattr(scu.CONFIG, "MIN_PASSWORD_LENGTH")
and scu.CONFIG.MIN_PASSWORD_LENGTH > 0
and len(cleartxt) < scu.CONFIG.MIN_PASSWORD_LENGTH
):
return False # invalid: too short
try:
_ = cracklib.FascistCheck(cleartxt)
return True
except ValueError:
return False
# --------------- # ---------------
# --------------- # ---------------
@ -439,6 +421,15 @@ def check_modif_user(
return False, "département '%s' inexistant" % dept + MSG_OPT return False, "département '%s' inexistant" % dept + MSG_OPT
if enforce_optionals and not roles: if enforce_optionals and not roles:
return False, "aucun rôle sélectionné, êtes vous sûr ?" + MSG_OPT return False, "aucun rôle sélectionné, êtes vous sûr ?" + MSG_OPT
# Unicité du mail
users_with_this_mail = User.query.filter_by(email=email).all()
if edit: # modification
if email != user["email"] and len(users_with_this_mail) > 0:
return False, "un autre utilisateur existe déjà avec cette adresse mail"
else: # création utilisateur
if len(users_with_this_mail) > 0:
return False, "un autre utilisateur existe déjà avec cette adresse mail"
# ok # ok
# Des noms/prénoms semblables existent ? # Des noms/prénoms semblables existent ?
nom = nom.lower().strip() nom = nom.lower().strip()

View File

@ -6,7 +6,7 @@ function refresh() {
$("#tf_password").closest('tr').css("display", "none"); $("#tf_password").closest('tr').css("display", "none");
$("#tf_password2").closest('tr').css("display", "none"); $("#tf_password2").closest('tr').css("display", "none");
} else { } else {
// Le mot de passe doit être saisi // Le mot de passe doit être saisi
$("#tf_password").closest('tr').css("display", "table-row"); $("#tf_password").closest('tr').css("display", "table-row");
$("#tf_password2").closest('tr').css("display", "table-row"); $("#tf_password2").closest('tr').css("display", "table-row");
} }
@ -18,11 +18,11 @@ function refresh() {
} }
} }
$(function() { $(function () {
$("input[name='welcome:list']").click(function() { $("input[name='welcome:list']").click(function () {
refresh(); refresh();
}) })
$("input[name='reset_password:list']").click(function() { $("input[name='reset_password:list']").click(function () {
refresh(); refresh();
}) })
refresh(); refresh();

View File

@ -2,8 +2,13 @@
{% import 'bootstrap/wtf.html' as wtf %} {% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %} {% block app_content %}
<h1>Reset Your Password</h1> <h1>Changez votre mot de passe ScoDoc</h1>
<div class="row">
<div class="row" style="margin-top: 30px;">
<div class="col-md-4">Votre identifiant: <b>{{user.user_name}}</b></div>
</div>
<div class="row" style="margin-top: 30px;">
<div class="col-md-4"> <div class="col-md-4">
{{ wtf.quick_form(form) }} {{ wtf.quick_form(form) }}
</div> </div>

View File

@ -1,16 +1,18 @@
<p>Bienvenue {{ user.prenom }} {{ user.nom }},</p> <p>Bienvenue {{ user.prenom }} {{ user.nom }},</p>
<p> <p>
Votre accès à ScoDoc vient d'être validé. Votre accès à ScoDoc vient d'être validé.
votre identifiant de connexion est: {{ user.user_name }} </p>
<p>
{% if token %} Votre identifiant de connexion est: <b>{{ user.user_name }}</b>
Pour initialiser votre mot de passe ScoDoc, </p>
{% if token %}
<p>Pour initialiser votre mot de passe ScoDoc,
<a href="{{ url_for('auth.reset_password', token=token, _external=True) }}"> <a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">
cliquez sur ce lien cliquez sur ce lien
</a>. </a>.
</p> </p>
<p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p> <p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p> <p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
{% endif %} {% endif %}
<p>A bientôt !</p> <p>A bientôt !</p>

View File

@ -49,6 +49,7 @@ 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
from app.auth.models import UserRole from app.auth.models import UserRole
from app.auth.models import is_valid_password
from app.email import send_email from app.email import send_email
from app.models import Departement from app.models import Departement
@ -226,29 +227,6 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
}, },
), ),
("formsemestre_id", {"input_type": "hidden"}), ("formsemestre_id", {"input_type": "hidden"}),
(
"welcome",
{
"title": "Message d'accueil",
"input_type": "checkbox",
"explanation": "Envoie un mail d'accueil à l'utilisateur.",
"labels": ("",),
"allowed_values": ("1",),
"default": "1",
},
),
(
"reset_password",
{
"title": "réinit. mot de passe.",
"input_type": "checkbox",
"explanation": "ajoute la procédure de changement de mot de passe au mail d'accueil",
"labels": ("",),
"allowed_values": ("1",),
"default": "1",
"attributes": ["style='margin-left:20pt'"],
},
),
( (
"password", "password",
{ {
@ -289,6 +267,32 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
}, },
) )
] ]
if not edit: # options création utilisateur
descr += [
(
"welcome",
{
"title": "Message d'accueil",
"input_type": "checkbox",
"explanation": "Envoie un mail d'accueil à l'utilisateur.",
"labels": ("",),
"allowed_values": ("1",),
"default": "1",
},
),
(
"reset_password",
{
"title": "",
"input_type": "checkbox",
"explanation": "indiquer par mail de changer le mot de passe initial",
"labels": ("",),
"allowed_values": ("1",),
"default": "1",
# "attributes": ["style='margin-left:20pt'"],
},
),
]
if not auth_dept: if not auth_dept:
# si auth n'a pas de departement (admin global) # si auth n'a pas de departement (admin global)
@ -479,7 +483,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
# Traitement initial (mode) : 3 cas # Traitement initial (mode) : 3 cas
# A: envoi de welcome + procedure de reset # A: envoi de welcome + procedure de reset
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire) # B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
# C: Aucun envoi (mot de pase saisi dans le formulaire) # C: Aucun envoi (mot de passe saisi dans le formulaire)
if vals["welcome:list"] == "1": if vals["welcome:list"] == "1":
if vals["reset_password:list"] == "1": if vals["reset_password:list"] == "1":
mode = "A" mode = "A"
@ -498,7 +502,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
"""Les deux mots de passes ne correspondent pas !""" """Les deux mots de passes ne correspondent pas !"""
) )
return "\n".join(H) + msg + "\n" + tf[1] + F return "\n".join(H) + msg + "\n" + tf[1] + F
if not sco_users.is_valid_password(vals["password"]): if not is_valid_password(vals["password"]):
msg = tf_error_message( msg = tf_error_message(
"""Mot de passe trop simple, recommencez !""" """Mot de passe trop simple, recommencez !"""
) )
@ -521,7 +525,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
else: else:
token = None token = None
send_email( send_email(
"[ScoDoc] Réinitialisation de votre mot de passe", "[ScoDoc] Création de votre compte",
sender=current_app.config["ADMINS"][0], sender=current_app.config["ADMINS"][0],
recipients=[u.email], recipients=[u.email],
text_body=render_template("email/welcome.txt", user=u, token=token), text_body=render_template("email/welcome.txt", user=u, token=token),
@ -735,7 +739,7 @@ def change_password(user_name, password, password2):
% user_name % user_name
) )
else: else:
if not sco_users.is_valid_password(password): if not is_valid_password(password):
H.append( H.append(
"""<p><b>ce mot de passe n\'est pas assez compliqué !</b><br/>(oui, il faut un mot de passe vraiment compliqué !)</p> """<p><b>ce mot de passe n\'est pas assez compliqué !</b><br/>(oui, il faut un mot de passe vraiment compliqué !)</p>
<p><a href="form_change_password?user_name=%s" class="stdlink">Recommencer</a></p> <p><a href="form_change_password?user_name=%s" class="stdlink">Recommencer</a></p>