Utilisateurs:

- désactive automatiquement les comptes scodoc7 avec temp=1
 - améliore table export et affichages.
 - améliore log (et préfixe par 'auth: ')
This commit is contained in:
Emmanuel Viennet 2023-03-03 14:33:26 +01:00 committed by iziram
parent 0011427302
commit 98accd7a6a
7 changed files with 67 additions and 35 deletions

View File

@ -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"<User {self.user_name} id={self.id} dept={self.dept}{' (inactive)' if not self.active else ''}>"
return f"""<User {self.user_name} id={self.id} dept={self.dept}{
' (inactive)' if not self.active else ''}>"""
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

View File

@ -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"""<div class="passwd_warn">
Attention !<br>
Vous avez reçu un mot de passe temporaire.<br>
Vous devez le changer: <a href="{scu.UsersURL}/form_change_password?user_name={current_user.user_name}">cliquez ici</a>
</div>"""
)
#
if head_message:
H.append('<div class="head_message">' + html.escape(head_message) + "</div>")

View File

@ -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é",

View File

@ -5,7 +5,7 @@
{% macro render_field(field, auth_name=None) %}
<tr style="">
{% if auth_name %}
<td class="wtf-field"> {{ field.label }}<span style="font-weight:700;"> ({{ auth_name }}):</span></td>
<td class="wtf-field"> {{ field.label }}<span style="font-weight:700;"> (compte {{ auth_name }}):</span></td>
{% else %}
<td class="wtf-field">{{ field.label }}</td>
{% endif %}

View File

@ -17,8 +17,12 @@
<b>Nom :</b> {{user.nom or ""}}<br>
<b>Prénom :</b> {{user.prenom or ""}}<br>
<b>Mail :</b> {{user.email}}<br>
<b>Roles :</b> {{user.get_roles_string()}}<br>
<b>Dept :</b> {{user.dept or ""}}
<b>Rôles :</b> {{user.get_roles_string()}}<br>
<b>Dept :</b> {{user.dept or ""}}<br>
{% if user.passwd_temp or user.password_scodoc7 %}
<b class="fontred">⚠️ mot de passe invalide (compte ancien non migré à réactiver ou à fermer)</b><br>
{% endif %}
</div>
{% if current_user.is_administrator() %}
<div class="user_info_admin">

View File

@ -83,7 +83,7 @@ class ChangePasswordForm(FlaskForm):
"""formulaire changement mot de passe et mail"""
user_name = HiddenField()
old_password = PasswordField(_l("Identifiez-vous"))
old_password = PasswordField(_l("Mot de passe actuel"))
new_password = PasswordField(_l("Nouveau mot de passe de l'utilisateur"))
bis_password = PasswordField(
_l("Répéter"),

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.4.59"
SCOVERSION = "9.4.60"
SCONAME = "ScoDoc"