diff --git a/app/auth/cas.py b/app/auth/cas.py index b9584f83a0..3b9f14e478 100644 --- a/app/auth/cas.py +++ b/app/auth/cas.py @@ -39,6 +39,15 @@ def after_cas_login(): "scodoc_cas_login_date" ] = datetime.datetime.now().isoformat() user.cas_last_login = datetime.datetime.utcnow() + if flask.session.get("CAS_EDT_ID"): + # essaie de récupérer l'edt_id s'il est présent + # cet ID peut être renvoyé par le CAS et extrait par ScoDoc + # via l'expression `cas_edt_id_from_xml_regexp` + # voir flask_cas.routing + edt_id = flask.session.get("CAS_EDT_ID") + current_app.logger.info(f"""after_cas_login: storing edt_id for { + user.user_name}: '{edt_id}'""") + user.edt_id = edt_id db.session.add(user) db.session.commit() return flask.redirect(url_for("scodoc.index")) diff --git a/app/auth/models.py b/app/auth/models.py index 899a5ab27e..dcf762a2d1 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -185,18 +185,7 @@ class User(UserMixin, db.Model): return self._migrate_scodoc7_password(password) return False - password_ok = check_password_hash(self.password_hash, password) - if password_ok and cas_enabled and flask.session.get("CAS_EDT_ID"): - # essaie de récupérer l'edt_id s'il est présent - # cet ID peut être renvoyé par le CAS et extrait par ScoDoc - # via l'expression `cas_edt_id_from_xml_regexp` - # voir flask_cas.routing - edt_id = flask.session.get("CAS_EDT_ID") - log(f"Storing edt_id for {self.user_name}: '{edt_id}'") - self.edt_id = edt_id - db.session.add(self) - db.session.commit() - return password_ok + return check_password_hash(self.password_hash, password) def _migrate_scodoc7_password(self, password) -> bool: """After migration, rehash password.""" diff --git a/app/forms/main/config_cas.py b/app/forms/main/config_cas.py index 7e2b73c568..978b84cc84 100644 --- a/app/forms/main/config_cas.py +++ b/app/forms/main/config_cas.py @@ -38,10 +38,14 @@ from app.models import ScoDocSiteConfig def check_cas_uid_from_mail_regexp(form, field): - "Vérifie la regexp fournie pur l'extraction du CAS id" + "Vérifie la regexp fournie pour l'extraction du CAS id" if not ScoDocSiteConfig.cas_uid_from_mail_regexp_is_valid(field.data): raise ValidationError("expression régulière invalide") +def check_cas_edt_id_from_xml_regexp(form, field): + "Vérifie la regexp fournie pour l'extraction du CAS id" + if not ScoDocSiteConfig.cas_edt_id_from_xml_regexp_is_valid(field.data): + raise ValidationError("expression régulière pour edt_id invalide") class ConfigCASForm(FlaskForm): "Formulaire paramétrage CAS" @@ -58,18 +62,18 @@ class ConfigCASForm(FlaskForm): description="""url complète. Commence en général par https://.""", ) cas_login_route = StringField( - label="Route du login CAS", + label="Optionnel: route du login CAS", description="""ajouté à l'URL du serveur: exemple /cas (si commence par /, part de la racine)""", default="/cas", ) cas_logout_route = StringField( - label="Route du logout CAS", + label="Optionnel: route du logout CAS", description="""ajouté à l'URL du serveur: exemple /cas/logout""", default="/cas/logout", ) cas_validate_route = StringField( - label="Route de validation CAS", + label="Optionnel: route de validation CAS", description="""ajouté à l'URL du serveur: exemple /cas/serviceValidate""", default="/cas/serviceValidate", ) @@ -81,7 +85,7 @@ class ConfigCASForm(FlaskForm): ) cas_uid_from_mail_regexp = StringField( - label="Expression pour extraire l'identifiant utilisateur", + label="Optionnel: expression pour extraire l'identifiant utilisateur", description="""regexp python appliquée au mail institutionnel de l'utilisateur, dont le premier groupe doit donner l'identifiant CAS. Si non fournie, le super-admin devra saisir cet identifiant pour chaque compte. @@ -92,6 +96,17 @@ class ConfigCASForm(FlaskForm): validators=[Optional(), check_cas_uid_from_mail_regexp], ) + cas_edt_id_from_xml_regexp = StringField( + label="Optionnel: expression pour extraire l'identifiant edt", + description="""regexp python appliquée à la réponse XML du serveur CAS pour + retrouver l'id de l'utilisateur sur le SI de l'institution, et notamment sur les + calendrier d'emploi du temps. Par exemple, si cet id est renvoyé dans le champ + supannEmpId, utiliser: + <cas:supannEmpId>(.*?)</cas:supannEmpId> + """, + validators=[Optional(), check_cas_edt_id_from_xml_regexp], + ) + cas_ssl_verify = BooleanField("Vérification du certificat SSL") cas_ssl_certificate_file = FileField( label="Certificat (PEM)", diff --git a/app/models/config.py b/app/models/config.py index fe76bec888..561bf6dfc2 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -286,7 +286,7 @@ class ScoDocSiteConfig(db.Model): @classmethod def set(cls, name: str, value: str) -> bool: "Set parameter, returns True if change. Commit session." - value_str = str(value or "") + value_str = str(value or "").strip() if (cls.get(name) or "") != value_str: cfg = ScoDocSiteConfig.query.filter_by(name=name).first() if cfg is None: @@ -429,7 +429,17 @@ class ScoDocSiteConfig(db.Model): return False # and returns at least one group on a simple cannonical address match = pattern.search("emmanuel@exemple.fr") - return len(match.groups()) > 0 + return match is not None and len(match.groups()) > 0 + + @classmethod + def cas_edt_id_from_xml_regexp_is_valid(cls, exp: str) -> bool: + "True si l'expression régulière semble valide" + # check that it compiles + try: + _ = re.compile(exp) + except re.error: + return False + return True @classmethod def assi_get_rounded_time(cls, label: str, default: str) -> float: diff --git a/app/templates/config_cas.j2 b/app/templates/config_cas.j2 index ca500b1c9e..05fb21586d 100644 --- a/app/templates/config_cas.j2 +++ b/app/templates/config_cas.j2 @@ -24,6 +24,7 @@ {{ wtf.form_field(form.cas_validate_route) }} {{ wtf.form_field(form.cas_attribute_id) }} {{ wtf.form_field(form.cas_uid_from_mail_regexp) }} + {{ wtf.form_field(form.cas_edt_id_from_xml_regexp) }}