diff --git a/app/auth/models.py b/app/auth/models.py index c9cfa137f..b79ce7037 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -12,7 +12,7 @@ from typing import Optional import cracklib # pylint: disable=import-error -from flask import current_app, g +from flask import current_app, flash, g from flask_login import UserMixin, AnonymousUserMixin from sqlalchemy.exc import ( IntegrityError, @@ -67,6 +67,14 @@ def is_valid_user_name(user_name: str) -> bool: ) +def is_new_cas_id(cas_id: str) -> bool: + "Check that cas_id is a valid new id (uniqueness, allow nulls)" + if not cas_id: + return True + nb_with_this_id = db.session.query(User).filter_by(cas_id=cas_id).count() + return nb_with_this_id == 0 + + class User(UserMixin, ScoDocModel): """ScoDoc users, handled by Flask / SQLAlchemy""" @@ -178,13 +186,16 @@ class User(UserMixin, ScoDocModel): raise ValueError("invalid user_id") return query.first_or_404() if not accept_none else query.first() - def set_password(self, password): + def set_password(self, password: str): "Set password" log(f"set_password({self})") + previous_hash = self.password_hash if password: self.password_hash = generate_password_hash(password) else: self.password_hash = None + if self.password_hash != previous_hash: + self.date_modif_passwd = datetime.now() # La création d'un mot de passe efface l'éventuel mot de passe historique self.password_scodoc7 = None self.passwd_temp = False @@ -350,9 +361,7 @@ class User(UserMixin, ScoDocModel): date_expiration = args.get("date_expiration") if isinstance(date_expiration, str): args["date_expiration"] = ( - datetime.fromisoformat(date_expiration) - if date_expiration - else None + datetime.fromisoformat(date_expiration) if date_expiration else None ) # booléens: for field in ("active", "cas_allow_login", "cas_allow_scodoc_login"): @@ -394,17 +403,43 @@ class User(UserMixin, ScoDocModel): role, dept = UserRole.role_dept_from_string(r_d) self.add_role(role, dept) - super().from_dict(args, excluded={"user_name", "roles_string", "roles"}) + # email_institutionnel may not be unique, but check and warns user + email_institutionnel = args.get("email_institutionnel") + if email_institutionnel and email_institutionnel != self.email_institutionnel: + nb_with_this_mail = ( + db.session.query(User) + .filter_by(email_institutionnel=email_institutionnel) + .count() + ) + if nb_with_this_mail > 0: + log( + "User.from_dict: plusieurs utilisateurs avec ce mail institutionnel" + ) + flash( + "Attention: plusieurs utilisateurs avec ce mail institutionnel", + "warning", + ) + super().from_dict( + args, excluded={"cas_id", "user_name", "roles_string", "roles"} + ) + + new_cas_id = args.get("cas_id") if ScoDocSiteConfig.cas_uid_use_scodoc(): - self.cas_id = self.user_name + new_cas_id = self.user_name else: # Set cas_id using regexp if configured: exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp") if exp and self.email_institutionnel: cas_id = ScoDocSiteConfig.extract_cas_id(self.email_institutionnel) if cas_id: - self.cas_id = cas_id + new_cas_id = cas_id + if new_cas_id != self.cas_id: + if is_new_cas_id(new_cas_id): + self.cas_id = new_cas_id + else: + log(f"User.from_dict: CAS id invalide pour {self.user_name}") + raise ScoValueError(f"CAS id invalide pour {self.user_name}") def get_token(self, expires_in=3600): "Un jeton pour cet user. Stocké en base, non commité." diff --git a/app/templates/auth/user_info_page.j2 b/app/templates/auth/user_info_page.j2 index 6b36704a5..882b5370b 100644 --- a/app/templates/auth/user_info_page.j2 +++ b/app/templates/auth/user_info_page.j2 @@ -39,7 +39,7 @@ {% endif %}
Dernière modif mot de passe: - {{user.date_modif_passwd.strftime(scu.DATE_FMT) if user.date_modif_passwd else ""}}
+ {{user.date_modif_passwd.strftime(scu.DATEATIME_FMT) if user.date_modif_passwd else ""}}
Date d'expiration: {{user.date_expiration.strftime(scu.DATE_FMT) if user.date_expiration else "(sans limite)"}}