forked from ScoDoc/ScoDoc
amélioration formulaires creation/edition utilisateurs
This commit is contained in:
parent
c2de33f7f5
commit
9c50b58d5f
@ -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):
|
||||||
|
@ -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"""
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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();
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user