CAS: options pour SSL

This commit is contained in:
Emmanuel Viennet 2023-02-26 23:27:40 +01:00 committed by iziram
parent a7437bfdc5
commit a407856cbb
6 changed files with 52 additions and 5 deletions

View File

@ -71,7 +71,11 @@ def set_cas_configuration(app: flask.app.Flask = None):
app.config["CAS_SERVER"] = ScoDocSiteConfig.get("cas_server") app.config["CAS_SERVER"] = ScoDocSiteConfig.get("cas_server")
app.config["CAS_AFTER_LOGIN"] = "auth.after_cas_login" app.config["CAS_AFTER_LOGIN"] = "auth.after_cas_login"
app.config["CAS_AFTER_LOGOUT"] = "auth.after_cas_logout" 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: else:
app.config.pop("CAS_SERVER", None) app.config.pop("CAS_SERVER", None)
app.config.pop("CAS_AFTER_LOGIN", None) app.config.pop("CAS_AFTER_LOGIN", None)
app.config.pop("CAS_AFTER_LOGOUT", None) app.config.pop("CAS_AFTER_LOGOUT", None)
app.config.pop("CAS_SSL_VERIFY", None)
app.config.pop("CAS_SSL_CERTIFICATE", None)

View File

@ -31,7 +31,7 @@ Formulaires configuration Exports Apogée (codes)
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import BooleanField, SubmitField from wtforms import BooleanField, SubmitField
from wtforms.fields.simple import StringField from wtforms.fields.simple import StringField, TextAreaField
class ConfigCASForm(FlaskForm): class ConfigCASForm(FlaskForm):
@ -49,5 +49,13 @@ class ConfigCASForm(FlaskForm):
comptes utilisateurs.""", 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 <tt>-----BEGIN CERTIFICATE-----</tt>)""",
render_kw={"class": "form-control", "rows": 8},
)
submit = SubmitField("Valider") submit = SubmitField("Valider")
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True}) cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})

View File

@ -4,7 +4,7 @@
""" """
from flask import flash from flask import flash
from app import db, log from app import current_app, db, log
from app.comp import bonus_spo from app.comp import bonus_spo
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -265,6 +265,9 @@ class ScoDocSiteConfig(db.Model):
cfg = ScoDocSiteConfig(name=name, value=str(value)) cfg = ScoDocSiteConfig(name=name, value=str(value))
else: else:
cfg.value = str(value or "") cfg.value = str(value or "")
current_app.logger.info(
f"""ScoDocSiteConfig: recording {cfg.name}='{cfg.value[:32]}...'"""
)
db.session.add(cfg) db.session.add(cfg)
db.session.commit() db.session.commit()
return True return True

View File

@ -150,12 +150,21 @@ def config_cas():
flash("Serveur CAS enregistré") flash("Serveur CAS enregistré")
if ScoDocSiteConfig.set("cas_attribute_id", form.data["cas_attribute_id"]): if ScoDocSiteConfig.set("cas_attribute_id", form.data["cas_attribute_id"]):
flash("Serveur CAS enregistré") 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() set_cas_configuration()
return redirect(url_for("scodoc.configuration")) return redirect(url_for("scodoc.configuration"))
elif request.method == "GET": elif request.method == "GET":
form.cas_enable.data = ScoDocSiteConfig.get("cas_enable") form.cas_enable.data = ScoDocSiteConfig.get("cas_enable")
form.cas_server.data = ScoDocSiteConfig.get("cas_server") form.cas_server.data = ScoDocSiteConfig.get("cas_server")
form.cas_attribute_id.data = ScoDocSiteConfig.get("cas_attribute_id") 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( return render_template(
"config_cas.j2", "config_cas.j2",
form=form, form=form,

View File

@ -1,3 +1,5 @@
import ssl
import flask import flask
from xmltodict import parse from xmltodict import parse
from flask import current_app from flask import current_app
@ -7,9 +9,10 @@ from .cas_urls import create_cas_validate_url
try: try:
from urllib import urlopen from urllib import urlopen # python 2
except ImportError: except ImportError:
from urllib.request import urlopen from urllib.request import urlopen # python 3
from urllib.error import URLError
blueprint = flask.Blueprint("cas", __name__) blueprint = flask.Blueprint("cas", __name__)
@ -129,8 +132,25 @@ def validate(ticket):
xml_from_dict = {} xml_from_dict = {}
isValid = False 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: 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) xml_from_dict = parse(xmldump)
isValid = ( isValid = (
True True
@ -139,6 +159,8 @@ def validate(ticket):
) )
except ValueError: except ValueError:
current_app.logger.error("CAS returned unexpected result") current_app.logger.error("CAS returned unexpected result")
except URLError:
current_app.logger.error("CAS : error validating token: check SSL certificate")
if isValid: if isValid:
current_app.logger.debug("valid") current_app.logger.debug("valid")

View File

@ -101,6 +101,7 @@ def make_shell_context():
"res_sem": res_sem, "res_sem": res_sem,
"ResultatsSemestreBUT": ResultatsSemestreBUT, "ResultatsSemestreBUT": ResultatsSemestreBUT,
"Role": Role, "Role": Role,
"ScoDocSiteConfig": models.ScoDocSiteConfig,
"scolar": scolar, "scolar": scolar,
"ScolarAutorisationInscription": ScolarAutorisationInscription, "ScolarAutorisationInscription": ScolarAutorisationInscription,
"ScolarFormSemestreValidation": ScolarFormSemestreValidation, "ScolarFormSemestreValidation": ScolarFormSemestreValidation,