forked from ScoDoc/ScoDoc
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:
parent
e65c60873f
commit
6872c20344
@ -19,7 +19,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
|||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
|
|
||||||
from app import db, log, login
|
from app import db, email, log, login
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
from app.models import SHORT_STR_LEN, USERNAME_STR_LEN
|
from app.models import SHORT_STR_LEN, USERNAME_STR_LEN
|
||||||
from app.models.config import ScoDocSiteConfig
|
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_created = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
date_expiration = db.Column(db.DateTime, default=None)
|
date_expiration = db.Column(db.DateTime, default=None)
|
||||||
passwd_temp = db.Column(db.Boolean, default=False)
|
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 = db.Column(db.Text(), index=True, unique=True)
|
||||||
token_expiration = db.Column(db.DateTime)
|
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))
|
# current_app.logger.info("creating user with roles={}".format(self.roles))
|
||||||
|
|
||||||
def __repr__(self):
|
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):
|
def __str__(self):
|
||||||
return self.user_name
|
return self.user_name
|
||||||
@ -140,6 +143,19 @@ class User(UserMixin, db.Model):
|
|||||||
Returns `True` if the password matched, `False` otherwise.
|
Returns `True` if the password matched, `False` otherwise.
|
||||||
"""
|
"""
|
||||||
if not self.active: # inactived users can't login
|
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
|
return False
|
||||||
|
|
||||||
# if CAS activated and forced, allow only super-user and users with cas_allow_scodoc_login
|
# 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:
|
def _migrate_scodoc7_password(self, password) -> bool:
|
||||||
"""After migration, rehash password."""
|
"""After migration, rehash password."""
|
||||||
if scu.check_scodoc7_password(self.password_scodoc7, 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.set_password(password)
|
||||||
self.password_scodoc7 = None
|
self.password_scodoc7 = None
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
@ -182,7 +200,7 @@ class User(UserMixin, db.Model):
|
|||||||
token, current_app.config["SECRET_KEY"], algorithms=["HS256"]
|
token, current_app.config["SECRET_KEY"], algorithms=["HS256"]
|
||||||
)
|
)
|
||||||
except jwt.exceptions.ExpiredSignatureError:
|
except jwt.exceptions.ExpiredSignatureError:
|
||||||
log(f"verify_reset_password_token: token expired")
|
log("verify_reset_password_token: token expired")
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@ -387,24 +405,17 @@ class User(UserMixin, db.Model):
|
|||||||
"""nomplogin est le nom en majuscules suivi du prénom et du login
|
"""nomplogin est le nom en majuscules suivi du prénom et du login
|
||||||
e.g. Dupont Pierre (dupont)
|
e.g. Dupont Pierre (dupont)
|
||||||
"""
|
"""
|
||||||
if self.nom:
|
nom = sco_etud.format_nom(self.nom) if self.nom else self.user_name.upper()
|
||||||
n = sco_etud.format_nom(self.nom)
|
return f"{nom} {sco_etud.format_prenom(self.prenom)} ({self.user_name})"
|
||||||
else:
|
|
||||||
n = self.user_name.upper()
|
|
||||||
return "%s %s (%s)" % (
|
|
||||||
n,
|
|
||||||
sco_etud.format_prenom(self.prenom),
|
|
||||||
self.user_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_user_id_from_nomplogin(nomplogin: str) -> Optional[int]:
|
def get_user_id_from_nomplogin(nomplogin: str) -> Optional[int]:
|
||||||
"""Returns id from the string "Dupont Pierre (dupont)"
|
"""Returns id from the string "Dupont Pierre (dupont)"
|
||||||
or None if user does not exist
|
or None if user does not exist
|
||||||
"""
|
"""
|
||||||
m = re.match(r".*\((.*)\)", nomplogin.strip())
|
match = re.match(r".*\((.*)\)", nomplogin.strip())
|
||||||
if m:
|
if match:
|
||||||
user_name = m.group(1)
|
user_name = match.group(1)
|
||||||
u = User.query.filter_by(user_name=user_name).first()
|
u = User.query.filter_by(user_name=user_name).first()
|
||||||
if u:
|
if u:
|
||||||
return u.id
|
return u.id
|
||||||
@ -441,6 +452,8 @@ class User(UserMixin, db.Model):
|
|||||||
|
|
||||||
|
|
||||||
class AnonymousUser(AnonymousUserMixin):
|
class AnonymousUser(AnonymousUserMixin):
|
||||||
|
"Notre utilisateur anonyme"
|
||||||
|
|
||||||
def has_permission(self, perm, dept=None):
|
def has_permission(self, perm, dept=None):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -569,7 +582,7 @@ class UserRole(db.Model):
|
|||||||
# maxsplit=1, le dept peut contenir un "_"
|
# maxsplit=1, le dept peut contenir un "_"
|
||||||
if len(fields) != 2:
|
if len(fields) != 2:
|
||||||
current_app.logger.warning(
|
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")
|
raise ScoValueError("Invalid role_dept")
|
||||||
role_name, dept = fields
|
role_name, dept = fields
|
||||||
@ -596,3 +609,23 @@ def get_super_admin():
|
|||||||
)
|
)
|
||||||
assert admin_user
|
assert admin_user
|
||||||
return 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
|
||||||
|
@ -279,16 +279,6 @@ def sco_header(
|
|||||||
# Barre menu semestre:
|
# Barre menu semestre:
|
||||||
H.append(formsemestre_page_title(formsemestre_id))
|
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:
|
if head_message:
|
||||||
H.append('<div class="head_message">' + html.escape(head_message) + "</div>")
|
H.append('<div class="head_message">' + html.escape(head_message) + "</div>")
|
||||||
|
@ -141,8 +141,13 @@ def list_users(
|
|||||||
d["_prenom_target"] = target
|
d["_prenom_target"] = target
|
||||||
|
|
||||||
# Hide passwd modification date (depending on visitor's permission)
|
# 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["date_modif_passwd"] = "(non visible)"
|
||||||
|
d["non_migre"] = ""
|
||||||
|
|
||||||
columns_ids = [
|
columns_ids = [
|
||||||
"user_name",
|
"user_name",
|
||||||
@ -153,7 +158,7 @@ def list_users(
|
|||||||
"roles_string",
|
"roles_string",
|
||||||
"date_expiration",
|
"date_expiration",
|
||||||
"date_modif_passwd",
|
"date_modif_passwd",
|
||||||
"passwd_temp",
|
"non_migre",
|
||||||
"status_txt",
|
"status_txt",
|
||||||
]
|
]
|
||||||
# Seul l'admin peut voir les dates de dernière connexion
|
# Seul l'admin peut voir les dates de dernière connexion
|
||||||
@ -182,7 +187,7 @@ def list_users(
|
|||||||
"date_expiration": "Expiration",
|
"date_expiration": "Expiration",
|
||||||
"date_modif_passwd": "Modif. mot de passe",
|
"date_modif_passwd": "Modif. mot de passe",
|
||||||
"last_seen": "Dernière cnx.",
|
"last_seen": "Dernière cnx.",
|
||||||
"passwd_temp": "Temp.",
|
"non_migre": "Non migré (!)",
|
||||||
"status_txt": "Etat",
|
"status_txt": "Etat",
|
||||||
"cas_id": "Id CAS",
|
"cas_id": "Id CAS",
|
||||||
"cas_allow_login": "CAS autorisé",
|
"cas_allow_login": "CAS autorisé",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{% macro render_field(field, auth_name=None) %}
|
{% macro render_field(field, auth_name=None) %}
|
||||||
<tr style="">
|
<tr style="">
|
||||||
{% if auth_name %}
|
{% 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 %}
|
{% else %}
|
||||||
<td class="wtf-field">{{ field.label }}</td>
|
<td class="wtf-field">{{ field.label }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -17,8 +17,12 @@
|
|||||||
<b>Nom :</b> {{user.nom or ""}}<br>
|
<b>Nom :</b> {{user.nom or ""}}<br>
|
||||||
<b>Prénom :</b> {{user.prenom or ""}}<br>
|
<b>Prénom :</b> {{user.prenom or ""}}<br>
|
||||||
<b>Mail :</b> {{user.email}}<br>
|
<b>Mail :</b> {{user.email}}<br>
|
||||||
<b>Roles :</b> {{user.get_roles_string()}}<br>
|
<b>Rôles :</b> {{user.get_roles_string()}}<br>
|
||||||
<b>Dept :</b> {{user.dept or ""}}
|
<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>
|
</div>
|
||||||
{% if current_user.is_administrator() %}
|
{% if current_user.is_administrator() %}
|
||||||
<div class="user_info_admin">
|
<div class="user_info_admin">
|
||||||
|
@ -83,7 +83,7 @@ class ChangePasswordForm(FlaskForm):
|
|||||||
"""formulaire changement mot de passe et mail"""
|
"""formulaire changement mot de passe et mail"""
|
||||||
|
|
||||||
user_name = HiddenField()
|
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"))
|
new_password = PasswordField(_l("Nouveau mot de passe de l'utilisateur"))
|
||||||
bis_password = PasswordField(
|
bis_password = PasswordField(
|
||||||
_l("Répéter"),
|
_l("Répéter"),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.4.59"
|
SCOVERSION = "9.4.60"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user