From 98accd7a6a331a398b496e66537e2064e1114bae Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 3 Mar 2023 14:33:26 +0100 Subject: [PATCH] =?UTF-8?q?Utilisateurs:=20=20-=20d=C3=A9sactive=20automat?= =?UTF-8?q?iquement=20les=20comptes=20scodoc7=20avec=20temp=3D1=20=20-=20a?= =?UTF-8?q?m=C3=A9liore=20table=20export=20et=20affichages.=20=20-=20am?= =?UTF-8?q?=C3=A9liore=20log=20(et=20pr=C3=A9fixe=20par=20'auth:=20')?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/auth/models.py | 67 ++++++++++++++++++++------- app/scodoc/html_sco_header.py | 10 ---- app/scodoc/sco_users.py | 11 +++-- app/templates/auth/change_password.j2 | 2 +- app/templates/auth/user_info_page.j2 | 8 +++- app/views/users.py | 2 +- sco_version.py | 2 +- 7 files changed, 67 insertions(+), 35 deletions(-) diff --git a/app/auth/models.py b/app/auth/models.py index 18dcddbf..e9a8615c 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -19,7 +19,7 @@ from werkzeug.security import generate_password_hash, check_password_hash import jwt -from app import db, log, login +from app import db, email, log, login from app.models import Departement from app.models import SHORT_STR_LEN, USERNAME_STR_LEN from app.models.config import ScoDocSiteConfig @@ -85,6 +85,8 @@ class User(UserMixin, db.Model): date_created = db.Column(db.DateTime, default=datetime.utcnow) date_expiration = db.Column(db.DateTime, default=None) passwd_temp = db.Column(db.Boolean, default=False) + """champ obsolete. Si connexion alors que passwd_temp est vrai, + efface mot de passe et redirige vers accueil.""" token = db.Column(db.Text(), index=True, unique=True) token_expiration = db.Column(db.DateTime) @@ -121,7 +123,8 @@ class User(UserMixin, db.Model): # current_app.logger.info("creating user with roles={}".format(self.roles)) def __repr__(self): - return f"" + return f"""""" def __str__(self): return self.user_name @@ -140,6 +143,19 @@ class User(UserMixin, db.Model): Returns `True` if the password matched, `False` otherwise. """ if not self.active: # inactived users can't login + current_app.logger.warning( + f"auth: login attempt from inactive account {self}" + ) + return False + if self.passwd_temp: + # Anciens comptes ScoDoc 7 non migrés + # désactive le compte par sécurité. + current_app.logger.warning(f"auth: desactivating legacy account {self}") + self.active = False + self.passwd_temp = True + db.session.add(self) + db.session.commit() + send_notif_desactivation_user(self) return False # if CAS activated and forced, allow only super-user and users with cas_allow_scodoc_login @@ -158,7 +174,9 @@ class User(UserMixin, db.Model): def _migrate_scodoc7_password(self, password) -> bool: """After migration, rehash password.""" if scu.check_scodoc7_password(self.password_scodoc7, password): - current_app.logger.warning(f"migrating legacy ScoDoc7 password for {self}") + current_app.logger.warning( + f"auth: migrating legacy ScoDoc7 password for {self}" + ) self.set_password(password) self.password_scodoc7 = None db.session.add(self) @@ -182,7 +200,7 @@ class User(UserMixin, db.Model): token, current_app.config["SECRET_KEY"], algorithms=["HS256"] ) except jwt.exceptions.ExpiredSignatureError: - log(f"verify_reset_password_token: token expired") + log("verify_reset_password_token: token expired") except: return None try: @@ -387,24 +405,17 @@ class User(UserMixin, db.Model): """nomplogin est le nom en majuscules suivi du prénom et du login e.g. Dupont Pierre (dupont) """ - if self.nom: - n = sco_etud.format_nom(self.nom) - else: - n = self.user_name.upper() - return "%s %s (%s)" % ( - n, - sco_etud.format_prenom(self.prenom), - self.user_name, - ) + nom = sco_etud.format_nom(self.nom) if self.nom else self.user_name.upper() + return f"{nom} {sco_etud.format_prenom(self.prenom)} ({self.user_name})" @staticmethod def get_user_id_from_nomplogin(nomplogin: str) -> Optional[int]: """Returns id from the string "Dupont Pierre (dupont)" or None if user does not exist """ - m = re.match(r".*\((.*)\)", nomplogin.strip()) - if m: - user_name = m.group(1) + match = re.match(r".*\((.*)\)", nomplogin.strip()) + if match: + user_name = match.group(1) u = User.query.filter_by(user_name=user_name).first() if u: return u.id @@ -441,6 +452,8 @@ class User(UserMixin, db.Model): class AnonymousUser(AnonymousUserMixin): + "Notre utilisateur anonyme" + def has_permission(self, perm, dept=None): return False @@ -569,7 +582,7 @@ class UserRole(db.Model): # maxsplit=1, le dept peut contenir un "_" if len(fields) != 2: current_app.logger.warning( - f"role_dept_from_string: Invalid role_dept '{role_dept}'" + f"auth: role_dept_from_string: Invalid role_dept '{role_dept}'" ) raise ScoValueError("Invalid role_dept") role_name, dept = fields @@ -596,3 +609,23 @@ def get_super_admin(): ) assert admin_user return admin_user + + +def send_notif_desactivation_user(user: User): + """Envoi un message mail de notification à l'admin et à l'adresse du compte désactivé""" + if not user.email: + return + txt = [ + f"""Le compte ScoDoc '{user.user_name}' associé à votre adresse <{user.email}>""", + """a été désactivé par le système car son mot de passe n'était pas valide.\n""", + """Contactez votre responsable pour le ré-activer.\n""", + """Ceci est un message automatique, ne pas répondre.""", + ] + txt = "\n".join(txt) + email.send_email( + f"ScoDoc: désactivation automatique du compte {user.user_name}", + email.get_from_addr(), + [user.email, current_app.config.get("SCODOC_ADMIN_MAIL")], + txt, + ) + return txt diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py index e31162e3..9f537be8 100644 --- a/app/scodoc/html_sco_header.py +++ b/app/scodoc/html_sco_header.py @@ -279,16 +279,6 @@ def sco_header( # Barre menu semestre: H.append(formsemestre_page_title(formsemestre_id)) - # Avertissement si mot de passe à changer - if user_check: - if current_user.passwd_temp: - H.append( - f"""
- Attention !
- Vous avez reçu un mot de passe temporaire.
- Vous devez le changer: cliquez ici -
""" - ) # if head_message: H.append('
' + html.escape(head_message) + "
") diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py index d44fbbce..d6d8c7d5 100644 --- a/app/scodoc/sco_users.py +++ b/app/scodoc/sco_users.py @@ -141,8 +141,13 @@ def list_users( d["_prenom_target"] = target # Hide passwd modification date (depending on visitor's permission) - if not can_modify: + if can_modify: + d["non_migre"] = ( + "NON MIGRÉ" if u.passwd_temp or u.password_scodoc7 else "ok" + ) + else: d["date_modif_passwd"] = "(non visible)" + d["non_migre"] = "" columns_ids = [ "user_name", @@ -153,7 +158,7 @@ def list_users( "roles_string", "date_expiration", "date_modif_passwd", - "passwd_temp", + "non_migre", "status_txt", ] # Seul l'admin peut voir les dates de dernière connexion @@ -182,7 +187,7 @@ def list_users( "date_expiration": "Expiration", "date_modif_passwd": "Modif. mot de passe", "last_seen": "Dernière cnx.", - "passwd_temp": "Temp.", + "non_migre": "Non migré (!)", "status_txt": "Etat", "cas_id": "Id CAS", "cas_allow_login": "CAS autorisé", diff --git a/app/templates/auth/change_password.j2 b/app/templates/auth/change_password.j2 index 5f0dbe00..67509f3d 100644 --- a/app/templates/auth/change_password.j2 +++ b/app/templates/auth/change_password.j2 @@ -5,7 +5,7 @@ {% macro render_field(field, auth_name=None) %} {% if auth_name %} - {{ field.label }} ({{ auth_name }}): + {{ field.label }} (compte {{ auth_name }}): {% else %} {{ field.label }} {% endif %} diff --git a/app/templates/auth/user_info_page.j2 b/app/templates/auth/user_info_page.j2 index 80186d4d..f2ca51bf 100644 --- a/app/templates/auth/user_info_page.j2 +++ b/app/templates/auth/user_info_page.j2 @@ -17,8 +17,12 @@ Nom : {{user.nom or ""}}
Prénom : {{user.prenom or ""}}
Mail : {{user.email}}
- Roles : {{user.get_roles_string()}}
- Dept : {{user.dept or ""}} + Rôles : {{user.get_roles_string()}}
+ Dept : {{user.dept or ""}}
+ {% if user.passwd_temp or user.password_scodoc7 %} + ⚠️ mot de passe invalide (compte ancien non migré à réactiver ou à fermer)
+ {% endif %} + {% if current_user.is_administrator() %}