forked from ScoDoc/ScoDoc
User: clarifie code vérif. login ScoDoc si CAS, et adapte messages reset passwd
This commit is contained in:
parent
60d31a5e6f
commit
2967596de2
@ -38,6 +38,7 @@ from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
|
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
|
||||||
|
DEFAULT_RESET_TOKEN_DURATION = 24 * 60 * 60 # seconds (default 24h)
|
||||||
|
|
||||||
|
|
||||||
def is_valid_password(cleartxt) -> bool:
|
def is_valid_password(cleartxt) -> bool:
|
||||||
@ -178,6 +179,43 @@ class User(UserMixin, ScoDocModel):
|
|||||||
raise ValueError("invalid user_id")
|
raise ValueError("invalid user_id")
|
||||||
return query.first_or_404() if not accept_none else query.first()
|
return query.first_or_404() if not accept_none else query.first()
|
||||||
|
|
||||||
|
def can_login_using_scodoc(self) -> bool:
|
||||||
|
"""True si l'utilisateur peut (essayer de) se connecter avec son compte local ScoDoc
|
||||||
|
(si par ailleurs un mot de passe valide existe et que le compte est actif)
|
||||||
|
|
||||||
|
Toujours vrai pour le super-admin.
|
||||||
|
Si CAS activé and cas_id renseigné, il faut cas_allow_scodoc_login.
|
||||||
|
|
||||||
|
Réglages possibles:
|
||||||
|
- Global : cas_force CAS forcé pour tous sauf super-admin
|
||||||
|
- Par utilisateur:
|
||||||
|
- cas_allow_login : Peut-on se logguer via le CAS ?
|
||||||
|
- cas_allow_scodoc_login : Si CAS activé, peut-on se logguer sur ScoDoc ?
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.is_administrator():
|
||||||
|
return True # super admin ou autorisation individuelle
|
||||||
|
cas_enabled = ScoDocSiteConfig.is_cas_enabled()
|
||||||
|
if not cas_enabled:
|
||||||
|
return True # CAS not enabled
|
||||||
|
|
||||||
|
if not self.cas_allow_scodoc_login:
|
||||||
|
log(
|
||||||
|
f"""auth: {self.user_name
|
||||||
|
}: cas enabled, scodoc login not allowed"""
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if ScoDocSiteConfig.is_cas_forced() and self.cas_id and self.cas_allow_login:
|
||||||
|
log(
|
||||||
|
f"""auth: {self.user_name
|
||||||
|
} (cas_id='{
|
||||||
|
self.cas_id}'): cas forced and cas_id set: scodoc login not allowed"""
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def set_password(self, password: str):
|
def set_password(self, password: str):
|
||||||
"Set password"
|
"Set password"
|
||||||
log(f"set_password({self})")
|
log(f"set_password({self})")
|
||||||
@ -197,6 +235,7 @@ class User(UserMixin, ScoDocModel):
|
|||||||
def check_password(self, password: str) -> bool:
|
def check_password(self, password: str) -> bool:
|
||||||
"""Check given password vs current one.
|
"""Check given password vs current one.
|
||||||
Returns `True` if the password matched, `False` otherwise.
|
Returns `True` if the password matched, `False` otherwise.
|
||||||
|
Also checks for temporary passwords and if CAS disables scodoc login.
|
||||||
"""
|
"""
|
||||||
if not self.active: # inactived users can't login
|
if not self.active: # inactived users can't login
|
||||||
current_app.logger.warning(
|
current_app.logger.warning(
|
||||||
@ -214,28 +253,8 @@ class User(UserMixin, ScoDocModel):
|
|||||||
send_notif_desactivation_user(self)
|
send_notif_desactivation_user(self)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# if CAS activated and cas_id, allow only super-user and users with cas_allow_scodoc_login
|
if not self.can_login_using_scodoc():
|
||||||
cas_enabled = ScoDocSiteConfig.is_cas_enabled()
|
return False
|
||||||
if cas_enabled and not self.is_administrator():
|
|
||||||
if not self.cas_allow_scodoc_login:
|
|
||||||
# CAS activé et compte non autorisé à se logguer sur ScoDoc
|
|
||||||
log(
|
|
||||||
f"""auth: login attempt for user {self.user_name}: scodoc login not allowed
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
# si CAS activé et forcé et cas_id renseigné, on ne peut pas se logguer
|
|
||||||
if (
|
|
||||||
self.cas_id
|
|
||||||
and self.cas_allow_login
|
|
||||||
and ScoDocSiteConfig.get("cas_force")
|
|
||||||
):
|
|
||||||
log(
|
|
||||||
f"""auth: login attempt for user {self.user_name
|
|
||||||
} (cas_id='{
|
|
||||||
self.cas_id}'): cas forced and cas_id set: scodoc login not allowed"""
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not self.password_hash: # user without password can't login
|
if not self.password_hash: # user without password can't login
|
||||||
if self.password_scodoc7:
|
if self.password_scodoc7:
|
||||||
@ -258,10 +277,16 @@ class User(UserMixin, ScoDocModel):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_reset_password_token(self, expires_in=24 * 60 * 60):
|
def get_reset_password_token(
|
||||||
|
self, expires_in=DEFAULT_RESET_TOKEN_DURATION
|
||||||
|
) -> str | None:
|
||||||
"""Un token pour réinitialiser son mot de passe.
|
"""Un token pour réinitialiser son mot de passe.
|
||||||
Par défaut valide durant 24 heures.
|
Par défaut valide durant 24 heures.
|
||||||
|
Note: si le CAS est obligatoire pour l'utilisateur, renvoie None
|
||||||
"""
|
"""
|
||||||
|
# si la config CAS interdit le login ScoDoc, pas de token
|
||||||
|
if not self.can_login_using_scodoc():
|
||||||
|
return None
|
||||||
token = jwt.encode(
|
token = jwt.encode(
|
||||||
{"reset_password": self.id, "exp": time() + expires_in},
|
{"reset_password": self.id, "exp": time() + expires_in},
|
||||||
current_app.config["SECRET_KEY"],
|
current_app.config["SECRET_KEY"],
|
||||||
|
@ -87,7 +87,7 @@ def login():
|
|||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
|
|
||||||
if ScoDocSiteConfig.get("cas_force"):
|
if ScoDocSiteConfig.is_cas_forced():
|
||||||
current_app.logger.info("login: forcing CAS")
|
current_app.logger.info("login: forcing CAS")
|
||||||
return redirect(url_for("cas.login"))
|
return redirect(url_for("cas.login"))
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
|
||||||
<p>Bonjour {{ user.user_name }},</p>
|
<p>Bonjour {{ user.user_name }},</p>
|
||||||
|
|
||||||
|
{% if token %}
|
||||||
<p>
|
<p>
|
||||||
Pour réinitialiser votre mot de passe ScoDoc,
|
Pour réinitialiser votre mot de passe ScoDoc,
|
||||||
<a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">
|
<a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">
|
||||||
@ -8,6 +11,14 @@
|
|||||||
<p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
|
<p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
|
||||||
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
|
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<p>Vous ne pouvez pas changer votre mot de passe sur ScoDoc:
|
||||||
|
en effet, pour vous connecter, vous devez utiliser votre identifiant universitaire
|
||||||
|
sur le système d'authentification de votre établissement (CAS, ENT).
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<p>Si vous n'avez pas demandé à réinitialiser votre mot de passe sur
|
<p>Si vous n'avez pas demandé à réinitialiser votre mot de passe sur
|
||||||
ScoDoc, vous pouvez simplement ignorer ce message.
|
ScoDoc, vous pouvez simplement ignorer ce message.
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
Bonjour {{ user.user_name }},
|
Bonjour {{ user.user_name }},
|
||||||
|
|
||||||
|
{% if token %}
|
||||||
|
|
||||||
Pour réinitialiser votre mot de passe ScoDoc, suivre le lien:
|
Pour réinitialiser votre mot de passe ScoDoc, suivre le lien:
|
||||||
|
|
||||||
{{ url_for('auth.reset_password', token=token, _external=True) }}
|
{{ url_for('auth.reset_password', token=token, _external=True) }}
|
||||||
|
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
Vous ne pouvez pas changer votre mot de passe sur ScoDoc:
|
||||||
|
en effet, pour vous connecter, vous devez utiliser votre identifiant universitaire
|
||||||
|
sur le système d'authentification de votre établissement (CAS, ENT).
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
Si vous n'avez pas demandé à réinitialiser votre mot de passe sur
|
Si vous n'avez pas demandé à réinitialiser votre mot de passe sur
|
||||||
ScoDoc, vous pouvez simplement ignorer ce message.
|
ScoDoc, vous pouvez simplement ignorer ce message.
|
||||||
|
|
||||||
|
@ -5,20 +5,21 @@
|
|||||||
<p>
|
<p>
|
||||||
Votre identifiant ScoDoc est: <b>{{ user.user_name }}</b>
|
Votre identifiant ScoDoc est: <b>{{ user.user_name }}</b>
|
||||||
</p>
|
</p>
|
||||||
{% if cas_force %}
|
|
||||||
<p>
|
|
||||||
Pour vous connecter, vous devrez utiliser votre identifiant universitaire
|
|
||||||
sur le système d'authentification de votre établissement (CAS, ENT).
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if token %}
|
{% if token %}
|
||||||
<p>Pour initialiser votre mot de passe ScoDoc,
|
<p>Pour initialiser votre mot de passe ScoDoc,
|
||||||
<a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">
|
<a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">
|
||||||
cliquez sur ce lien
|
cliquez sur ce lien
|
||||||
</a>.
|
</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
|
<p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
|
||||||
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
|
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
|
||||||
|
|
||||||
|
<p>Ce lien expirera le {{date_expiration_token}}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Pour vous connecter, vous devrez utiliser votre identifiant universitaire
|
||||||
|
sur le système d'authentification de votre établissement (CAS, ENT).
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>A bientôt !</p>
|
<p>A bientôt !</p>
|
||||||
|
@ -3,16 +3,16 @@ Bienvenue {{ user.prenom }} {{ user.nom }},
|
|||||||
Votre accès à ScoDoc vient d'être validé.
|
Votre accès à ScoDoc vient d'être validé.
|
||||||
Votre identifiant ScoDoc est: {{ user.user_name }}
|
Votre identifiant ScoDoc est: {{ user.user_name }}
|
||||||
|
|
||||||
{% if cas_force %}
|
|
||||||
<p>
|
|
||||||
Pour vous connecter, vous devrez utiliser votre identifiant universitaire
|
|
||||||
sur le système d'authentification de votre établissement (CAS, ENT).
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if token %}
|
{% if token %}
|
||||||
Pour initialiser votre mot de passe ScoDoc, suivre le lien:
|
|
||||||
{{ url_for('auth.reset_password', token=token, _external=True) }}
|
Pour initialiser votre mot de passe ScoDoc, suivre le lien:
|
||||||
|
{{ url_for('auth.reset_password', token=token, _external=True) }}
|
||||||
|
|
||||||
|
Ce lien expirera le {{date_expiration_token}}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
Pour vous connecter, vous devrez utiliser votre identifiant universitaire
|
||||||
|
sur le système d'authentification de votre établissement (CAS, ENT).
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
A bientôt !
|
A bientôt !
|
||||||
|
@ -35,6 +35,7 @@ Emmanuel Viennet, 2021
|
|||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
from enum import auto, IntEnum
|
from enum import auto, IntEnum
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
@ -50,11 +51,14 @@ from wtforms.validators import DataRequired, Email, ValidationError, EqualTo
|
|||||||
from app import db
|
from app import db
|
||||||
from app import email
|
from app import email
|
||||||
from app.auth.forms import DeactivateUserForm
|
from app.auth.forms import DeactivateUserForm
|
||||||
from app.auth.models import Permission
|
from app.auth.models import (
|
||||||
from app.auth.models import User
|
DEFAULT_RESET_TOKEN_DURATION,
|
||||||
from app.auth.models import Role
|
Permission,
|
||||||
from app.auth.models import UserRole
|
User,
|
||||||
from app.auth.models import is_valid_password
|
Role,
|
||||||
|
UserRole,
|
||||||
|
is_valid_password,
|
||||||
|
)
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
from app.models.config import ScoDocSiteConfig
|
from app.models.config import ScoDocSiteConfig
|
||||||
|
|
||||||
@ -64,10 +68,13 @@ from app.decorators import (
|
|||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.scodoc import sco_import_users, sco_roles_default
|
from app.scodoc import (
|
||||||
from app.scodoc import sco_users
|
sco_import_users,
|
||||||
from app.scodoc import sco_utils as scu
|
sco_roles_default,
|
||||||
from app.scodoc import sco_xml
|
sco_users,
|
||||||
|
sco_utils as scu,
|
||||||
|
sco_xml,
|
||||||
|
)
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoPermissionDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoPermissionDenied, ScoValueError
|
||||||
from app.scodoc.sco_import_users import generate_password
|
from app.scodoc.sco_import_users import generate_password
|
||||||
@ -779,11 +786,9 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
|||||||
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
|
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
|
||||||
# C: Aucun envoi (mot de passe saisi dans le formulaire)
|
# C: Aucun envoi (mot de passe saisi dans le formulaire)
|
||||||
if vals["welcome"]: # "Envoie un mail d'accueil" coché
|
if vals["welcome"]: # "Envoie un mail d'accueil" coché
|
||||||
if vals["reset_password"] and (
|
if vals["reset_password"]:
|
||||||
(not ScoDocSiteConfig.get("cas_force"))
|
# nb: le token ne sera envoyé que si le login ScoDoc est autorisé,
|
||||||
or vals.get("cas_allow_scodoc_login", False)
|
# voir get_reset_password_token()
|
||||||
):
|
|
||||||
# nb: si login scodoc non autorisé car CAS seul, n'envoie pas le mot de passe.
|
|
||||||
mode = Mode.WELCOME_AND_CHANGE_PASSWORD
|
mode = Mode.WELCOME_AND_CHANGE_PASSWORD
|
||||||
else:
|
else:
|
||||||
mode = Mode.WELCOME_ONLY
|
mode = Mode.WELCOME_ONLY
|
||||||
@ -828,13 +833,10 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
# envoi éventuel d'un message
|
# envoi éventuel d'un message
|
||||||
if mode in (Mode.WELCOME_AND_CHANGE_PASSWORD, Mode.WELCOME_ONLY):
|
if mode in (Mode.WELCOME_AND_CHANGE_PASSWORD, Mode.WELCOME_ONLY):
|
||||||
token = (
|
token = the_user.get_reset_password_token()
|
||||||
the_user.get_reset_password_token()
|
date_expiration_token = datetime.datetime.fromtimestamp(
|
||||||
if mode == Mode.WELCOME_AND_CHANGE_PASSWORD
|
time.time() + DEFAULT_RESET_TOKEN_DURATION
|
||||||
else None
|
).strftime(scu.DATEATIME_FMT)
|
||||||
)
|
|
||||||
|
|
||||||
cas_force = ScoDocSiteConfig.get("cas_force")
|
|
||||||
# Le from doit utiliser la préférence du département de l'utilisateur
|
# Le from doit utiliser la préférence du département de l'utilisateur
|
||||||
email.send_email(
|
email.send_email(
|
||||||
"[ScoDoc] Création de votre compte",
|
"[ScoDoc] Création de votre compte",
|
||||||
@ -844,13 +846,13 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
|||||||
"email/welcome.txt",
|
"email/welcome.txt",
|
||||||
user=the_user,
|
user=the_user,
|
||||||
token=token,
|
token=token,
|
||||||
cas_force=cas_force,
|
date_expiration_token=date_expiration_token,
|
||||||
),
|
),
|
||||||
html_body=render_template(
|
html_body=render_template(
|
||||||
"email/welcome.j2",
|
"email/welcome.j2",
|
||||||
user=the_user,
|
user=the_user,
|
||||||
token=token,
|
token=token,
|
||||||
cas_force=cas_force,
|
date_expiration_token=date_expiration_token,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
flash(f"Mail accueil envoyé à {the_user.email}")
|
flash(f"Mail accueil envoyé à {the_user.email}")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user