diff --git a/app/auth/forms.py b/app/auth/forms.py index 143f65546..dd1f68ef1 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -8,7 +8,7 @@ TODO: à revoir complètement pour reprendre ZScoUsers et les pages d'authentifi from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField 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 @@ -44,7 +44,7 @@ class UserCreationForm(FlaskForm): class ResetPasswordRequestForm(FlaskForm): email = StringField(_l("Email"), validators=[DataRequired(), Email()]) - submit = SubmitField(_l("Request Password Reset")) + submit = SubmitField(_l("Valider ce mot de passe")) class ResetPasswordForm(FlaskForm): @@ -52,7 +52,11 @@ class ResetPasswordForm(FlaskForm): password2 = PasswordField( _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): diff --git a/app/auth/models.py b/app/auth/models.py index ed20d5eeb..f243f0e79 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -10,6 +10,7 @@ import re from time import time from typing import Optional +import cracklib # pylint: disable=import-error from flask import current_app, url_for, g 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@\\\-_\.]+$") +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): """ScoDoc users, handled by Flask / SQLAlchemy""" diff --git a/app/auth/routes.py b/app/auth/routes.py index bf7272a75..61f2bceac 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -116,6 +116,6 @@ def reset_password(token): if form.validate_on_submit(): user.set_password(form.password.data) db.session.commit() - flash(_("Your password has been reset.")) + flash(_("Votre mot de passe a été changé.")) 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) diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py index be43125d9..3f1b646d2 100644 --- a/app/scodoc/sco_users.py +++ b/app/scodoc/sco_users.py @@ -34,7 +34,6 @@ import re from flask import url_for, g, request from flask_login import current_user -import cracklib # pylint: disable=import-error 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 if enforce_optionals and not roles: 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 # Des noms/prénoms semblables existent ? nom = nom.lower().strip() diff --git a/app/static/js/user_form.js b/app/static/js/user_form.js index 5cbdf490e..4ad06672a 100644 --- a/app/static/js/user_form.js +++ b/app/static/js/user_form.js @@ -6,7 +6,7 @@ function refresh() { $("#tf_password").closest('tr').css("display", "none"); $("#tf_password2").closest('tr').css("display", "none"); } else { - // Le mot de passe doit être saisi + // Le mot de passe doit être saisi $("#tf_password").closest('tr').css("display", "table-row"); $("#tf_password2").closest('tr').css("display", "table-row"); } @@ -18,11 +18,11 @@ function refresh() { } } -$(function() { - $("input[name='welcome:list']").click(function() { +$(function () { + $("input[name='welcome:list']").click(function () { refresh(); }) - $("input[name='reset_password:list']").click(function() { + $("input[name='reset_password:list']").click(function () { refresh(); }) refresh(); diff --git a/app/templates/auth/reset_password.html b/app/templates/auth/reset_password.html index d054674f6..261e37db0 100644 --- a/app/templates/auth/reset_password.html +++ b/app/templates/auth/reset_password.html @@ -2,8 +2,13 @@ {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} -

Reset Your Password

-
+

Changez votre mot de passe ScoDoc

+ +
+
Votre identifiant: {{user.user_name}}
+
+ +
{{ wtf.quick_form(form) }}
diff --git a/app/templates/email/welcome.html b/app/templates/email/welcome.html index 7f2d6ad6e..41ee5f65c 100644 --- a/app/templates/email/welcome.html +++ b/app/templates/email/welcome.html @@ -1,16 +1,18 @@

Bienvenue {{ user.prenom }} {{ user.nom }},

Votre accès à ScoDoc vient d'être validé. - votre identifiant de connexion est: {{ user.user_name }} - - {% if token %} - Pour initialiser votre mot de passe ScoDoc, +

+

+ Votre identifiant de connexion est: {{ user.user_name }} +

+{% if token %} +

Pour initialiser votre mot de passe ScoDoc, cliquez sur ce lien .

Vous pouvez aussi copier ce lien dans votre navigateur Web:

{{ url_for('auth.reset_password', token=token, _external=True) }}

- {% endif %} +{% endif %}

A bientôt !

\ No newline at end of file diff --git a/app/views/users.py b/app/views/users.py index 430633d5a..d293f3bc0 100644 --- a/app/views/users.py +++ b/app/views/users.py @@ -49,6 +49,7 @@ from app.auth.models import Permission from app.auth.models import User from app.auth.models import Role from app.auth.models import UserRole +from app.auth.models import is_valid_password from app.email import send_email 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"}), - ( - "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", { @@ -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: # 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 # A: envoi de welcome + procedure de reset # 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["reset_password:list"] == "1": 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 !""" ) 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( """Mot de passe trop simple, recommencez !""" ) @@ -521,7 +525,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1): else: token = None send_email( - "[ScoDoc] Réinitialisation de votre mot de passe", + "[ScoDoc] Création de votre compte", sender=current_app.config["ADMINS"][0], recipients=[u.email], text_body=render_template("email/welcome.txt", user=u, token=token), @@ -735,7 +739,7 @@ def change_password(user_name, password, password2): % user_name ) else: - if not sco_users.is_valid_password(password): + if not is_valid_password(password): H.append( """

ce mot de passe n\'est pas assez compliqué !
(oui, il faut un mot de passe vraiment compliqué !)

Recommencer