diff --git a/app/api/users.py b/app/api/users.py
index d5cb2f7acae410e9c75464dbe389b1c3962f7d95..9437cebe4b7ceda425e7a491f8da598e42ca6bbc 100644
--- a/app/api/users.py
+++ b/app/api/users.py
@@ -105,13 +105,8 @@ def users_info_query():
def _is_allowed_user_edit(args: dict) -> tuple[bool, str]:
"Vrai si on peut"
- if "cas_id" in args and not current_user.has_permission(
- Permission.UsersChangeCASId
- ):
- return False, "non autorise a changer cas_id"
-
- if not current_user.is_administrator():
- for field in ("cas_allow_login", "cas_allow_scodoc_login"):
+ if not current_user.has_permission(Permission.UsersChangeCASId):
+ for field in ("cas_id", "cas_allow_login", "cas_allow_scodoc_login"):
if field in args:
return False, f"non autorise a changer {field}"
return True, ""
diff --git a/app/auth/cas.py b/app/auth/cas.py
index 5e11c0ac2de5dc9791a5507158bf65ceb39717c5..c3c8cb18439687df1096b4b640bda686ca22f000 100644
--- a/app/auth/cas.py
+++ b/app/auth/cas.py
@@ -158,7 +158,7 @@ CAS_USER_INFO_COMMENTS = (
autorise la connexion via CAS (optionnel, faux par défaut)
""",
"""cas_allow_scodoc_login
- autorise connexion via ScoDoc même si CAS activé (optionnel, vrai par défaut)
+ autorise connexion via ScoDoc même si CAS forcé (optionnel, faux par défaut)
""",
"""email_institutionnel
optionnel, le mail officiel de l'utilisateur.
diff --git a/app/auth/models.py b/app/auth/models.py
index e6bebcb85a2a6740b8e224d31d483a4da30f1e04..7020db177c34b4b7b3d78fc0db032776f12f0eb8 100644
--- a/app/auth/models.py
+++ b/app/auth/models.py
@@ -93,7 +93,7 @@ class User(UserMixin, ScoDocModel):
cas_allow_scodoc_login = db.Column(
db.Boolean, default=False, server_default="false", nullable=False
)
- """Si CAS activé et cas_id renseigné, peut-on se logguer sur ScoDoc directement ?
+ """Si CAS activé et forcé, peut-on se logguer sur ScoDoc directement ?
(le rôle ScoSuperAdmin peut toujours, mettre à True pour les utilisateur API)
"""
cas_last_login = db.Column(db.DateTime, nullable=True)
@@ -179,42 +179,31 @@ class User(UserMixin, ScoDocModel):
raise ValueError("invalid user_id")
return query.first_or_404() if not accept_none else query.first()
+ def can_login_using_cas(self, require_cas_id=False) -> bool:
+ """True si l'utilisateur peut se connecter via CAS.
+ Attention: si le cas_id est extrait de l'adresse mail, il est au départ vide.
+ L'argument require_cas_id indique si on le requiert ou pas.
+ """
+ return (
+ self.cas_allow_login
+ and (self.cas_id or not require_cas_id)
+ and ScoDocSiteConfig.is_cas_enabled()
+ )
+
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 ?
-
+ Toujours vrai pour le super-admin ou si CAS non activé.
+ Si CAS forcé, il faut cas_allow_scodoc_login.
"""
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
+ if not ScoDocSiteConfig.is_cas_enabled():
+ return True # CAS not enabled
- return True
+ return self.cas_allow_scodoc_login or not ScoDocSiteConfig.is_cas_forced()
def set_password(self, password: str):
"Set password"
diff --git a/app/auth/routes.py b/app/auth/routes.py
index 51c2a939cbaced1b409a188785670e6d342bc0bb..d8bae3fc25a746b61f4be6541a7a269e0bb7bd2e 100644
--- a/app/auth/routes.py
+++ b/app/auth/routes.py
@@ -36,7 +36,7 @@ def _login_form():
form = LoginForm()
if form.validate_on_submit():
# note: ceci est la première requête SQL déclenchée par un utilisateur arrivant
- user = (
+ user: User = (
User.query.filter_by(user_name=form.user_name.data).first()
if is_valid_user_name(form.user_name.data)
else None
@@ -51,7 +51,7 @@ def _login_form():
current_app.logger.info("login: success (%s)", form.user_name.data)
- if user.passwd_must_be_changed:
+ if user.passwd_must_be_changed and user.can_login_using_scodoc():
# Mot de passe à changer à la première connexion
dept = user.dept or getattr(g, "scodoc_dept", None)
if not dept:
@@ -87,8 +87,10 @@ def login():
if current_user.is_authenticated:
return redirect(url_for("scodoc.index"))
- if ScoDocSiteConfig.is_cas_forced():
- current_app.logger.info("login: forcing CAS")
+ if ScoDocSiteConfig.is_cas_forced() and ScoDocSiteConfig.get(
+ "cas_login_redirect", default=True
+ ):
+ current_app.logger.info("login: redirecting to CAS login")
return redirect(url_for("cas.login"))
return _login_form()
diff --git a/app/forms/main/config_cas.py b/app/forms/main/config_cas.py
index 0d453cf32a6ca74524ee1e263067fc0639d52a68..4c64c8ab573cb37d9de7ca97a2b9f0803f7ae704 100644
--- a/app/forms/main/config_cas.py
+++ b/app/forms/main/config_cas.py
@@ -58,6 +58,10 @@ class ConfigCASForm(FlaskForm):
cas_allow_for_new_users = BooleanField(
"Par défaut, autoriser le CAS aux nouveaux utilisateurs"
)
+ cas_login_redirect = BooleanField(
+ "Si le CAS est forcé, redirige immédiatement la page de login vers le CAS",
+ default=True,
+ )
cas_server = StringField(
label="URL du serveur CAS",
diff --git a/app/models/config.py b/app/models/config.py
index e869a93ac9f074a6ef7c073ead9d817deeafa89e..b8312f39e40fc0259d9042e3ce1ba4ccb51e4eab 100644
--- a/app/models/config.py
+++ b/app/models/config.py
@@ -99,6 +99,9 @@ class ScoDocSiteConfig(models.ScoDocModel):
"user_require_email_institutionnel": bool,
# CAS
"cas_enable": bool,
+ "cas_force": bool,
+ "cas_allow_for_new_users": bool,
+ "cas_login_redirect": bool,
"cas_server": str,
"cas_login_route": str,
"cas_logout_route": str,
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index b3f8da2abc31d4986f41f176b6b43dc6662a9bca..988f041d7fa2796a08617bd5b58dc81a1e534204 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -653,11 +653,11 @@ class FormSemestre(models.ScoDocModel):
Si le semestre est verrouillé, faux sauf si allow_locked.
"""
user = user or current_user
- if user.passwd_must_be_changed or not user.has_permission(
- Permission.EditFormSemestre
- ): # pas chef de dept.
+ if not user.has_permission(Permission.EditFormSemestre):
+ # pas chef de dept.
if not self.resp_can_edit or not self.est_responsable(user):
return False
+ # resp_can_edit et est_responsable
return allow_locked or self.etat
def est_courant(self) -> bool:
diff --git a/app/scodoc/sco_permissions.py b/app/scodoc/sco_permissions.py
index dece55410f1077ed92e757c780a70a8665d83bf1..2450b1f54f2c47eed448a7563d2dabba15e31ea5 100644
--- a/app/scodoc/sco_permissions.py
+++ b/app/scodoc/sco_permissions.py
@@ -62,7 +62,7 @@ _SCO_PERMISSIONS = (
"RelationsEntrepExport",
"Exporter les données de l'application relations entreprises",
),
- (1 << 29, "UsersChangeCASId", "Paramétrer l'id CAS"),
+ (1 << 29, "UsersChangeCASId", "Modifier les paramètres CAS des utilisateurs"),
(1 << 30, "ViewEtudData", "Accéder aux données personnelles des étudiants"),
#
# XXX inutilisée ? (1 << 40, "EtudChangePhoto", "Modifier la photo d'un étudiant"),
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index bca1f4e1aec823729d6447929c37d756827c0472..5218567640fb0007946d33955ab10a1a7aa952d3 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -323,31 +323,6 @@ nav li.logout a {
color: rgb(255, 0, 0);
}
-div.user_info div {
- padding: 8px;
- border-radius: 16px;
- margin-bottom: 8px;
-}
-
-div.user_info ul li {
- margin-bottom: 8px;
-}
-
-div.user_basics {
- border: 1px solid blue;
- background-color: #eeeeee;
-}
-
-div.user_info_admin {
- border: 1px solid red;
- background-color: #fdcaca;
-}
-
-div.user_info div.permissions {
- border: 1px solid rgb(0, 0, 255);
- background-color: #dedefd;
-}
-
/* ----- page content ------ */
div.about-logo {
diff --git a/app/templates/auth/change_password.j2 b/app/templates/auth/change_password.j2
index ec82dcce5690c83eaccaed9ee10d596d08557288..22a93412fd4b97a3043a4e63e73bc407e46f811e 100644
--- a/app/templates/auth/change_password.j2
+++ b/app/templates/auth/change_password.j2
@@ -25,7 +25,7 @@
<h1>Modification du compte ScoDoc <tt>{{form.user_name.data}}</tt></h1>
<div class="help" style="margin-top: 32px; margin-bottom: 32px;">
<p>Le mot de passe ScoDoc doit être suffisament complexe.
- Il n'a rien à voir avec celui de votre compte ENT (utilisé pour le service CAS).
+ Il n'a rien à voir avec ceux des comptes utilisés par le service CAS (ENT).
</p>
</div>
<form method="post">
diff --git a/app/templates/auth/login.j2 b/app/templates/auth/login.j2
index 10ea1491e54b091870ea249b279341d967de9591..dab260092a11b05113577d3b3590ab7bad4e1e9d 100644
--- a/app/templates/auth/login.j2
+++ b/app/templates/auth/login.j2
@@ -29,7 +29,10 @@ div.small_form {
{% endif %}
{% if is_cas_enabled %}
-<div class="cas_else">Sinon vous pouvez vous connecter avec votre compte ScoDoc:</div>
+<div class="cas_else">Sinon
+{%- if is_cas_forced -%}, si votre compte ScoDoc le permet, {% endif %}
+connectez-vous avec vos identifiants ScoDoc:
+</div>
{% endif %}
<div class="row {{ 'small_form' if is_cas_enabled else ''}}">
<div class="col-md-4">
@@ -38,12 +41,16 @@ div.small_form {
</div>
<div style="margin-top: 32px;">
-En cas d'oubli de votre mot de passe ScoDoc (indépendant de CAS)
+En cas d'oubli de votre mot de passe ScoDoc
+{% if is_cas_enabled %}(indépendant de CAS){% endif %},
<a href="{{ url_for('auth.reset_password_request') }}">cliquez ici pour le réinitialiser</a>.
</div>
-<p class="help" style="margin-top: 32px;">L'accès à ScoDoc est strictement réservé aux personnels de
- l'établissement. Les étudiants n'y ont pas accès. Pour toute information,
- contactez la personne responsable de votre établissement.</p>
+<p class="help" style="margin-top: 32px;">
+L'accès à ScoDoc est strictement réservé aux personnels de
+l'établissement. Les étudiants n'y ont pas accès. Pour toute information,
+contactez la personne responsable de votre établissement.
+</p>
+
{% endblock %}
diff --git a/app/templates/auth/user_info_page.j2 b/app/templates/auth/user_info_page.j2
index 01955929c8edf50df0952ef5295f737777eef135..3083297301c84a02c4cb1a89fb71220b1e1b01cf 100644
--- a/app/templates/auth/user_info_page.j2
+++ b/app/templates/auth/user_info_page.j2
@@ -2,52 +2,101 @@
{% extends "base.j2" %}
{% import 'wtf.j2' as wtf %}
+{% block styles %}
+{{super()}}
+<style>
+
+div.ubi div {
+
+}
+div.user_info_admin {
+ border: 1px solid red;
+ background-color: #fdcaca;
+}
+
+div.user_basics {
+ border: 1px solid blue;
+ background-color: #eeeeee;
+}
+
+div.user_info > div {
+ padding: 8px;
+ border-radius: 16px;
+ margin-bottom: 8px;
+}
+
+div.user_info ul li {
+ margin-bottom: 8px;
+}
+
+div.user_info div.permissions {
+ border: 1px solid rgb(0, 0, 255);
+ background-color: #dedefd;
+}
+</style>
+{% endblock %}
+
{% block app_content %}
<div class="user_info">
<h2>Utilisateur: {{user.user_name}} ({{'actif' if user.active else 'fermé'}})</h2>
<div class="user_basics">
- <b>Login :</b> {{user.user_name}}
+ <div><b>Login :</b> {{user.user_name}}
{% if ScoDocSiteConfig.is_cas_enabled() %}
(connexion via ce login ScoDoc
{% if user.can_login_using_scodoc() %}autorisée{% else %}<span class="fontred">interdite</span>
{% endif %})
{% endif -%}
- <br>
- <b>CAS id:</b> {{user.cas_id or "(aucun)"}}
+ </div>
+ <div><b>CAS id:</b> {{user.cas_id or "(aucun)"}}
{% if ScoDocSiteConfig.is_cas_enabled() %}
- (CAS {{'autorisé' if user.cas_allow_login else 'interdit'}} pour cet utilisateur)
+ (CAS {{'autorisé' if user.can_login_using_cas() else 'interdit'}} pour cet utilisateur)
{% if user.can_login_using_scodoc() %}
(connexion sans CAS autorisée)
{% endif %}
+ {% if user.cas_allow_login and not user.cas_id %}
+ (pas encore d'identifiant CAS)
+ {% endif %}
+ {% endif %}
+ {% if not user.can_login_using_scodoc() and not user.can_login_using_cas() %}
+ <div class="warning">cet utilisateur ne peut se connecter ni via ScoDoc ni via CAS</div>
{% endif %}
- <br>
- <b>Nom :</b> {{user.nom or ""}}<br>
- <b>Prénom :</b> {{user.prenom or ""}}<br>
+ </div>
+ <div><b>Nom :</b> {{user.nom or ""}}</div>
+ <div><b>Prénom :</b> {{user.prenom or ""}}</div>
+ <div>
{% if user.passwd_must_be_changed %}
- <div style="color:white; background-color: red; padding:8px; margin-top: 4px; width: fit-content;">mot de passe à changer</div>
+ <div style="color:white; background-color: red; padding:8px; margin-top: 4px; width: fit-content;">mot de passe ScoDoc à changer</div>
{% endif %}
- <b>Mail :</b> {{user.email}}<br>
- <b>Mail institutionnel:</b> {{user.email_institutionnel or ""}}<br>
- <b>Identifiant EDT:</b> {{user.edt_id or ""}}<br>
- <b>Rôles :</b> {{user.get_roles_string()}}<br>
- <b>Dept :</b> {{user.dept or ""}}<br>
+ </div>
+ <div><b>Mail :</b> {{user.email}}</div>
+ <div><b>Mail institutionnel:</b> {{user.email_institutionnel or ""}}</div>
+ <div><b>Identifiant EDT:</b> {{user.edt_id or ""}}</div>
+ <div><b>Rôles :</b> {{user.get_roles_string()}}</div>
+ <div><b>Dept :</b> {{user.dept or ""}}</div>
+ <div>
{% 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>
+ <b class="fontred">⚠️ mot de passe invalide (compte ancien non migré à réactiver ou à fermer)</b>
{% endif %}
-
+ </div>
</div>
{% if current_user.is_administrator() %}
-<div class="user_info_admin">
- <b>Dernière vue :</b> {{user.last_seen.strftime(scu.DATEATIME_FMT) if user.last_seen else "-"}}<br>
- <b>Dernière connexion CAS :</b> {{user.cas_last_login.strftime(scu.DATEATIME_FMT) if user.cas_last_login else "-"}}<br>
+<div class="ubi user_info_admin">
+ <div>
+ <b>Dernière vue :</b> {{user.last_seen.strftime(scu.DATEATIME_FMT) if user.last_seen else "-"}}</div><div>
+ <b>Dernière connexion CAS :</b> {{user.cas_last_login.strftime(scu.DATEATIME_FMT) if user.cas_last_login else "-"}}
+ </div>
</div>
{% endif %}
-<div class="user_basics">
+<div class="ubi user_basics">
+ <div>
<b>Dernière modif mot de passe:</b>
- {{user.date_modif_passwd.strftime(scu.DATEATIME_FMT) if user.date_modif_passwd else ""}}<br>
+ {{user.date_modif_passwd.strftime(scu.DATEATIME_FMT) if user.date_modif_passwd else ""}}
+ </div>
+ <div>
<b>Date d'expiration:</b>
{{user.date_expiration.strftime(scu.DATE_FMT) if user.date_expiration else "(sans limite)"}}
+ </div>
</div>
<div>
@@ -79,6 +128,10 @@
}}">{{"désactiver" if user.active else "activer"}} ce compte</a>
</li>
{% endif %}
+ <li><a class="stdlink" href="
+ {{url_for('users.index_html', scodoc_dept=g.scodoc_dept)}}
+ ">retour à la liste de tous les utilisateurs</a>
+ </li>
</ul>
</div>
@@ -105,7 +158,7 @@
{# Liste des permissions #}
<div class="permissions">
- <p><b>Permissions de cet utilisateur dans le département {{dept}}:</b></p>
+ <b>Permissions de l'utilisateur {{user.user_name}} dans le département {{dept}}</b>
<ul>
{% for p in Permission.description %}
<li>{{Permission.description[p]}} :
diff --git a/app/templates/config_cas.j2 b/app/templates/config_cas.j2
index a3bca2e4d94976b982ac96f9c58e022e114fee06..ad17c14761c27e1dc117a1e7680bd939ca6005eb 100644
--- a/app/templates/config_cas.j2
+++ b/app/templates/config_cas.j2
@@ -26,6 +26,7 @@
{{ wtf.form_field(form.cas_enable) }}
{{ wtf.form_field(form.cas_force) }}
{{ wtf.form_field(form.cas_allow_for_new_users) }}
+ {{ wtf.form_field(form.cas_login_redirect) }}
<div class="scobox">
<div class="scobox-title">Routes CAS</div>
{{ wtf.form_field(form.cas_server) }}
diff --git a/app/views/scodoc.py b/app/views/scodoc.py
index 05980c452556aa65c7e7bb069aaeb4491039f231..8918351d604d9ed9c96096d1322adfdeaf2b0dbb 100644
--- a/app/views/scodoc.py
+++ b/app/views/scodoc.py
@@ -282,6 +282,11 @@ def config_cas():
f"""CAS {'' if form.data['cas_allow_for_new_users'] else 'non'
} autorisé par défaut aux nouveaux"""
)
+ if ScoDocSiteConfig.set("cas_login_redirect", form.data["cas_login_redirect"]):
+ flash(
+ f"""Page login {'' if form.data['cas_login_redirect'] else 'non'
+ } redirigée si CAS forcé"""
+ )
if ScoDocSiteConfig.set("cas_server", form.data["cas_server"]):
flash("URL du serveur CAS enregistrée")
if ScoDocSiteConfig.set("cas_login_route", form.data["cas_login_route"]):
@@ -324,6 +329,7 @@ def config_cas():
form.cas_allow_for_new_users.data = ScoDocSiteConfig.get(
"cas_allow_for_new_users"
)
+ form.cas_login_redirect.data = ScoDocSiteConfig.get("cas_login_redirect")
form.cas_server.data = ScoDocSiteConfig.get("cas_server")
form.cas_login_route.data = ScoDocSiteConfig.get("cas_login_route")
form.cas_logout_route.data = ScoDocSiteConfig.get("cas_logout_route")
diff --git a/app/views/users.py b/app/views/users.py
index 472f9bde695d871db2ef102e92333a5f20c493a8..f9ec3dfc64fc1343dfe85a4724502d95a0adb0c6 100644
--- a/app/views/users.py
+++ b/app/views/users.py
@@ -406,6 +406,7 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
)
]
cas_enabled = ScoDocSiteConfig.is_cas_enabled()
+ can_edit_cas = current_user.has_permission(Permission.UsersChangeCASId)
if edit:
cas_allow_login_default = the_user.cas_allow_login
else:
@@ -453,8 +454,7 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
),
"size": 36,
"allow_null": True,
- "readonly": not cas_enabled
- or not current_user.has_permission(Permission.UsersChangeCASId),
+ "readonly": not cas_enabled or not can_edit_cas,
},
),
(
@@ -462,9 +462,8 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
{
"title": "Autorise connexion via CAS",
"input_type": "boolcheckbox",
- "explanation": """ si CAS est activé.
- Seul le super-administrateur peut changer ce réglage.""",
- "enabled": current_user.is_administrator(),
+ "explanation": """ si CAS est activé.""",
+ "enabled": can_edit_cas,
"default": cas_allow_login_default,
},
),
@@ -473,9 +472,8 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
{
"title": "Autorise connexion via ScoDoc",
"input_type": "boolcheckbox",
- "explanation": """ même si CAS est activé et cas_id renseigné.
- Seul le super-administrateur peut changer ce réglage""",
- "enabled": current_user.is_administrator(),
+ "explanation": """ même si CAS est forcé.""",
+ "enabled": can_edit_cas,
},
),
(
@@ -663,7 +661,7 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
if "obusfacted_u_ser_nam_" in initvalues:
initvalues["user_name"] = initvalues["obusfacted_u_ser_nam_"]
roles = set(vals["roles"]).intersection(editable_roles_strings)
- if not current_user.is_administrator():
+ if not can_edit_cas:
# empeche modification des paramètres CAS
if "cas_allow_login" in vals:
vals["cas_allow_login"] = cas_allow_login_default
@@ -672,9 +670,8 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
vals.pop("cas_allow_scodoc_login", None)
else:
vals["cas_allow_scodoc_login"] = the_user.cas_allow_scodoc_login
-
- if not current_user.has_permission(Permission.UsersChangeCASId):
vals.pop("cas_id", None)
+
if "edit" in vals:
edit = int(vals["edit"])
else:
diff --git a/sco_version.py b/sco_version.py
index df5a1143512d8a3c6300f9462b6295d78c623d30..55d346b734e67c15edda301783d6a58334ee5510 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -3,11 +3,21 @@
"Infos sur version ScoDoc"
-SCOVERSION = "9.7.56"
+SCOVERSION = "9.7.57"
SCONAME = "ScoDoc"
SCONEWS = """
+
+<h4>Année 2024-2025</h4>
+<ul>
+
+<li>ScoDoc 9.7</li>
+<ul>
+ <li>Amélioration gestion utilisateurs et CAS</li>
+ <li>TODO</li>
+</ul>
+
<h4>Année 2023-2024</h4>
<ul>
diff --git a/tests/unit/test_users.py b/tests/unit/test_users.py
index f947bd3b1103066ac2bdbb01eee4110ffc3992c9..3289f58aded49e7d82b40996bb1c9dbe6514f329 100644
--- a/tests/unit/test_users.py
+++ b/tests/unit/test_users.py
@@ -126,7 +126,7 @@ def test_create_delete(test_client):
def test_edit(test_client):
- "test edition object utlisateur"
+ "test edition object utilisateur"
args = {
"prenom": "No Totoro",
"edt_id": "totorito",