diff --git a/app/auth/cas.py b/app/auth/cas.py index 352b48150b..fece96a0fe 100644 --- a/app/auth/cas.py +++ b/app/auth/cas.py @@ -71,7 +71,11 @@ def set_cas_configuration(app: flask.app.Flask = None): app.config["CAS_SERVER"] = ScoDocSiteConfig.get("cas_server") app.config["CAS_AFTER_LOGIN"] = "auth.after_cas_login" app.config["CAS_AFTER_LOGOUT"] = "auth.after_cas_logout" + app.config["CAS_SSL_VERIFY"] = ScoDocSiteConfig.get("cas_ssl_verify") + app.config["CAS_SSL_CERTIFICATE"] = ScoDocSiteConfig.get("cas_ssl_certificate") else: app.config.pop("CAS_SERVER", None) app.config.pop("CAS_AFTER_LOGIN", None) app.config.pop("CAS_AFTER_LOGOUT", None) + app.config.pop("CAS_SSL_VERIFY", None) + app.config.pop("CAS_SSL_CERTIFICATE", None) diff --git a/app/forms/main/config_cas.py b/app/forms/main/config_cas.py index 663e487b51..7d7f4f7ef7 100644 --- a/app/forms/main/config_cas.py +++ b/app/forms/main/config_cas.py @@ -31,7 +31,7 @@ Formulaires configuration Exports Apogée (codes) from flask_wtf import FlaskForm from wtforms import BooleanField, SubmitField -from wtforms.fields.simple import StringField +from wtforms.fields.simple import StringField, TextAreaField class ConfigCASForm(FlaskForm): @@ -49,5 +49,13 @@ class ConfigCASForm(FlaskForm): comptes utilisateurs.""", ) + cas_ssl_verify = BooleanField("Vérification du certificat SSL") + cas_ssl_certificate = TextAreaField( + label="Certificat (PEM)", + description="""Le contenu du certificat PEM + (commence typiquement par -----BEGIN CERTIFICATE-----)""", + render_kw={"class": "form-control", "rows": 8}, + ) + submit = SubmitField("Valider") cancel = SubmitField("Annuler", render_kw={"formnovalidate": True}) diff --git a/app/models/config.py b/app/models/config.py index bbe0c0ae75..131106308c 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -4,7 +4,7 @@ """ from flask import flash -from app import db, log +from app import current_app, db, log from app.comp import bonus_spo from app.scodoc import sco_utils as scu @@ -265,6 +265,9 @@ class ScoDocSiteConfig(db.Model): cfg = ScoDocSiteConfig(name=name, value=str(value)) else: cfg.value = str(value or "") + current_app.logger.info( + f"""ScoDocSiteConfig: recording {cfg.name}='{cfg.value[:32]}...'""" + ) db.session.add(cfg) db.session.commit() return True diff --git a/app/views/scodoc.py b/app/views/scodoc.py index feedfd297d..3c3e5aba21 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -150,12 +150,21 @@ def config_cas(): flash("Serveur CAS enregistré") if ScoDocSiteConfig.set("cas_attribute_id", form.data["cas_attribute_id"]): flash("Serveur CAS enregistré") + if ScoDocSiteConfig.set("cas_ssl_verify", form.data["cas_ssl_verify"]): + flash("Vérification SSL modifiée") + if ScoDocSiteConfig.set( + "cas_ssl_certificate", form.data["cas_ssl_certificate"] + ): + flash("Certificat SSL enregistré") set_cas_configuration() return redirect(url_for("scodoc.configuration")) + elif request.method == "GET": form.cas_enable.data = ScoDocSiteConfig.get("cas_enable") form.cas_server.data = ScoDocSiteConfig.get("cas_server") form.cas_attribute_id.data = ScoDocSiteConfig.get("cas_attribute_id") + form.cas_ssl_verify.data = ScoDocSiteConfig.get("cas_ssl_verify") + form.cas_ssl_certificate.data = ScoDocSiteConfig.get("cas_ssl_certificate") return render_template( "config_cas.j2", form=form, diff --git a/flask_cas/routing.py b/flask_cas/routing.py index e241dfcc23..c6e4dfe8a3 100644 --- a/flask_cas/routing.py +++ b/flask_cas/routing.py @@ -1,3 +1,5 @@ +import ssl + import flask from xmltodict import parse from flask import current_app @@ -7,9 +9,10 @@ from .cas_urls import create_cas_validate_url try: - from urllib import urlopen + from urllib import urlopen # python 2 except ImportError: - from urllib.request import urlopen + from urllib.request import urlopen # python 3 +from urllib.error import URLError blueprint = flask.Blueprint("cas", __name__) @@ -129,8 +132,25 @@ def validate(ticket): xml_from_dict = {} isValid = False + if current_app.config.get("CAS_SSL_VERIFY"): + ssl_context = ssl.SSLContext() + ssl_context.verify_mode = ssl.CERT_REQUIRED + ca_data = current_app.config.get("CAS_SSL_CERTIFICATE", "") + try: + ssl_context.load_verify_locations(cadata=ca_data) + except (ssl.SSLError, ValueError): + current_app.logger.error("CAS : error loading SSL cert.") + return False + else: + ssl_context = None + try: - xmldump = urlopen(cas_validate_url).read().strip().decode("utf8", "ignore") + xmldump = ( + urlopen(cas_validate_url, context=ssl_context) + .read() + .strip() + .decode("utf8", "ignore") + ) xml_from_dict = parse(xmldump) isValid = ( True @@ -139,6 +159,8 @@ def validate(ticket): ) except ValueError: current_app.logger.error("CAS returned unexpected result") + except URLError: + current_app.logger.error("CAS : error validating token: check SSL certificate") if isValid: current_app.logger.debug("valid") diff --git a/scodoc.py b/scodoc.py index ec197aa73e..2f152b29a7 100755 --- a/scodoc.py +++ b/scodoc.py @@ -101,6 +101,7 @@ def make_shell_context(): "res_sem": res_sem, "ResultatsSemestreBUT": ResultatsSemestreBUT, "Role": Role, + "ScoDocSiteConfig": models.ScoDocSiteConfig, "scolar": scolar, "ScolarAutorisationInscription": ScolarAutorisationInscription, "ScolarFormSemestreValidation": ScolarFormSemestreValidation,