From 5d7d967cf2838b033148c45a64e7c274103df993 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Nov 2023 17:58:11 +0100 Subject: [PATCH] =?UTF-8?q?Extraction=20identifiant=20utilisateur=20EDT=20?= =?UTF-8?q?depuis=20la=20r=C3=A9ponse=20CAS.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/auth/cas.py | 9 +++++++++ app/auth/models.py | 13 +------------ app/forms/main/config_cas.py | 25 ++++++++++++++++++++----- app/models/config.py | 14 ++++++++++++-- app/templates/config_cas.j2 | 9 +++++---- app/views/scodoc.py | 7 +++++++ 6 files changed, 54 insertions(+), 23 deletions(-) diff --git a/app/auth/cas.py b/app/auth/cas.py index b9584f83..3b9f14e4 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 899a5ab2..dcf762a2 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 7e2b73c5..978b84cc 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 fe76bec8..561bf6df 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 ca500b1c..05fb2158 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) }}
{{ wtf.form_field(form.cas_ssl_verify) }} {{ wtf.form_field(form.cas_ssl_certificate_file) }} @@ -40,8 +41,8 @@ {{ wtf.form_field(form.cancel) }}
- ℹ️ Note: si le CAS est forcé, le super-admin et les utilisateurs autorisés - à "se connecter via ScoDoc" pourront toujours se + ℹ️ Note: si le CAS est forcé, le super-admin et les utilisateurs autorisés + à "se connecter via ScoDoc" pourront toujours se connecter via l'adresse spéciale {{url_for("auth.login_scodoc", _external=True)}}
@@ -50,6 +51,6 @@ - -{% endblock %} \ No newline at end of file + +{% endblock %} diff --git a/app/views/scodoc.py b/app/views/scodoc.py index 46dd8d8f..c2ee067d 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -273,6 +273,10 @@ def config_cas(): "cas_uid_from_mail_regexp", form.data["cas_uid_from_mail_regexp"] ): flash("Expression extraction identifiant CAS enregistrée") + if ScoDocSiteConfig.set( + "cas_edt_id_from_xml_regexp", form.data["cas_edt_id_from_xml_regexp"] + ): + flash("Expression extraction identifiant edt enregistrée") if ScoDocSiteConfig.set("cas_ssl_verify", form.data["cas_ssl_verify"]): flash("Vérification SSL modifiée") if form.cas_ssl_certificate_file.data: @@ -300,6 +304,9 @@ def config_cas(): form.cas_uid_from_mail_regexp.data = ScoDocSiteConfig.get( "cas_uid_from_mail_regexp" ) + form.cas_edt_id_from_xml_regexp.data = ScoDocSiteConfig.get( + "cas_edt_id_from_xml_regexp" + ) form.cas_ssl_verify.data = ScoDocSiteConfig.get("cas_ssl_verify") return render_template( "config_cas.j2",