diff --git a/app/api/users.py b/app/api/users.py index d5cb2f7ac..9437cebe4 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 5e11c0ac2..c3c8cb184 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 e6bebcb85..7020db177 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: + + if not ScoDocSiteConfig.is_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 + 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 51c2a939c..d8bae3fc2 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 0d453cf32..4c64c8ab5 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 e869a93ac..b8312f39e 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 b3f8da2ab..988f041d7 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 dece55410..2450b1f54 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 bca1f4e1a..521856764 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 ec82dcce5..22a93412f 100644 --- a/app/templates/auth/change_password.j2 +++ b/app/templates/auth/change_password.j2 @@ -25,7 +25,7 @@
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).