forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -306,6 +306,13 @@ class User(UserMixin, db.Model):
|
|||||||
role, dept = UserRole.role_dept_from_string(r_d)
|
role, dept = UserRole.role_dept_from_string(r_d)
|
||||||
self.add_role(role, dept)
|
self.add_role(role, dept)
|
||||||
|
|
||||||
|
# Set cas_id using regexp if configured:
|
||||||
|
exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
|
||||||
|
if exp and self.email_institutionnel:
|
||||||
|
cas_id = ScoDocSiteConfig.extract_cas_id(self.email_institutionnel)
|
||||||
|
if cas_id is not None:
|
||||||
|
self.cas_id = cas_id
|
||||||
|
|
||||||
def get_token(self, expires_in=3600):
|
def get_token(self, expires_in=3600):
|
||||||
"Un jeton pour cet user. Stocké en base, non commité."
|
"Un jeton pour cet user. Stocké en base, non commité."
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
|
@ -30,8 +30,17 @@ Formulaire configuration CAS
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import BooleanField, SubmitField
|
from wtforms import BooleanField, SubmitField, ValidationError
|
||||||
from wtforms.fields.simple import FileField, StringField
|
from wtforms.fields.simple import FileField, StringField
|
||||||
|
from wtforms.validators import Optional
|
||||||
|
|
||||||
|
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"
|
||||||
|
if not ScoDocSiteConfig.cas_uid_from_mail_regexp_is_valid(field.data):
|
||||||
|
raise ValidationError("expression régulière invalide")
|
||||||
|
|
||||||
|
|
||||||
class ConfigCASForm(FlaskForm):
|
class ConfigCASForm(FlaskForm):
|
||||||
@ -50,7 +59,8 @@ class ConfigCASForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
cas_login_route = StringField(
|
cas_login_route = StringField(
|
||||||
label="Route du login CAS",
|
label="Route du login CAS",
|
||||||
description="""ajouté à l'URL du serveur: exemple <tt>/cas</tt> (si commence par <tt>/</tt>, part de la racine)""",
|
description="""ajouté à l'URL du serveur: exemple <tt>/cas</tt>
|
||||||
|
(si commence par <tt>/</tt>, part de la racine)""",
|
||||||
default="/cas",
|
default="/cas",
|
||||||
)
|
)
|
||||||
cas_logout_route = StringField(
|
cas_logout_route = StringField(
|
||||||
@ -70,6 +80,18 @@ class ConfigCASForm(FlaskForm):
|
|||||||
comptes utilisateurs.""",
|
comptes utilisateurs.""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cas_uid_from_mail_regexp = StringField(
|
||||||
|
label="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.
|
||||||
|
Par exemple, <tt>(.*)@</tt> indique que le mail sans le domaine (donc toute
|
||||||
|
la partie avant le <tt>@</tt>) est l'identifiant.
|
||||||
|
Pour prendre le mail complet, utiliser <tt>(.*)</tt>.
|
||||||
|
""",
|
||||||
|
validators=[Optional(), check_cas_uid_from_mail_regexp],
|
||||||
|
)
|
||||||
|
|
||||||
cas_ssl_verify = BooleanField("Vérification du certificat SSL")
|
cas_ssl_verify = BooleanField("Vérification du certificat SSL")
|
||||||
cas_ssl_certificate_file = FileField(
|
cas_ssl_certificate_file = FileField(
|
||||||
label="Certificat (PEM)",
|
label="Certificat (PEM)",
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import re
|
||||||
|
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from app import current_app, db, log
|
from app import current_app, db, log
|
||||||
@ -103,6 +104,7 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
"cas_logout_route": str,
|
"cas_logout_route": str,
|
||||||
"cas_validate_route": str,
|
"cas_validate_route": str,
|
||||||
"cas_attribute_id": str,
|
"cas_attribute_id": str,
|
||||||
|
"cas_uid_from_mail_regexp": str,
|
||||||
# Assiduité
|
# Assiduité
|
||||||
"morning_time": str,
|
"morning_time": str,
|
||||||
"lunch_time": str,
|
"lunch_time": str,
|
||||||
@ -395,6 +397,41 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
data_links = json.dumps(links_dict)
|
data_links = json.dumps(links_dict)
|
||||||
cls.set("personalized_links", data_links)
|
cls.set("personalized_links", data_links)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def extract_cas_id(cls, email_addr: str) -> str | None:
|
||||||
|
"Extract cas_id from maill, using regexp in config. None if not possible."
|
||||||
|
exp = cls.get("cas_uid_from_mail_regexp")
|
||||||
|
if not exp or not email_addr:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
match = re.search(exp, email_addr)
|
||||||
|
except re.error:
|
||||||
|
log("error extracting CAS id from '{email_addr}' using regexp '{exp}'")
|
||||||
|
return None
|
||||||
|
if not match:
|
||||||
|
log("no match extracting CAS id from '{email_addr}' using regexp '{exp}'")
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
cas_id = match.group(1)
|
||||||
|
except IndexError:
|
||||||
|
log(
|
||||||
|
"no group found extracting CAS id from '{email_addr}' using regexp '{exp}'"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return cas_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cas_uid_from_mail_regexp_is_valid(cls, exp: str) -> bool:
|
||||||
|
"True si l'expression régulière semble valide"
|
||||||
|
# check that it compiles
|
||||||
|
try:
|
||||||
|
pattern = re.compile(exp)
|
||||||
|
except re.error:
|
||||||
|
return False
|
||||||
|
# and returns at least one group on a simple cannonical address
|
||||||
|
match = pattern.search("emmanuel@exemple.fr")
|
||||||
|
return len(match.groups()) > 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def assi_get_rounded_time(cls, label: str, default: str) -> float:
|
def assi_get_rounded_time(cls, label: str, default: str) -> float:
|
||||||
"Donne l'heure stockée dans la config globale sous label, en float arrondi au quart d'heure"
|
"Donne l'heure stockée dans la config globale sous label, en float arrondi au quart d'heure"
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
{{ wtf.form_field(form.cas_logout_route) }}
|
{{ wtf.form_field(form.cas_logout_route) }}
|
||||||
{{ wtf.form_field(form.cas_validate_route) }}
|
{{ wtf.form_field(form.cas_validate_route) }}
|
||||||
{{ wtf.form_field(form.cas_attribute_id) }}
|
{{ wtf.form_field(form.cas_attribute_id) }}
|
||||||
|
{{ wtf.form_field(form.cas_uid_from_mail_regexp) }}
|
||||||
<div class="cas_settings">
|
<div class="cas_settings">
|
||||||
{{ wtf.form_field(form.cas_ssl_verify) }}
|
{{ wtf.form_field(form.cas_ssl_verify) }}
|
||||||
{{ wtf.form_field(form.cas_ssl_certificate_file) }}
|
{{ wtf.form_field(form.cas_ssl_certificate_file) }}
|
||||||
|
@ -173,6 +173,10 @@ def config_cas():
|
|||||||
flash("Route de validation CAS enregistrée")
|
flash("Route de validation CAS enregistrée")
|
||||||
if ScoDocSiteConfig.set("cas_attribute_id", form.data["cas_attribute_id"]):
|
if ScoDocSiteConfig.set("cas_attribute_id", form.data["cas_attribute_id"]):
|
||||||
flash("Attribut CAS ID enregistré")
|
flash("Attribut CAS ID enregistré")
|
||||||
|
if ScoDocSiteConfig.set(
|
||||||
|
"cas_uid_from_mail_regexp", form.data["cas_uid_from_mail_regexp"]
|
||||||
|
):
|
||||||
|
flash("Expression extraction identifiant CAS enregistrée")
|
||||||
if ScoDocSiteConfig.set("cas_ssl_verify", form.data["cas_ssl_verify"]):
|
if ScoDocSiteConfig.set("cas_ssl_verify", form.data["cas_ssl_verify"]):
|
||||||
flash("Vérification SSL modifiée")
|
flash("Vérification SSL modifiée")
|
||||||
if form.cas_ssl_certificate_file.data:
|
if form.cas_ssl_certificate_file.data:
|
||||||
@ -197,6 +201,9 @@ def config_cas():
|
|||||||
form.cas_logout_route.data = ScoDocSiteConfig.get("cas_logout_route")
|
form.cas_logout_route.data = ScoDocSiteConfig.get("cas_logout_route")
|
||||||
form.cas_validate_route.data = ScoDocSiteConfig.get("cas_validate_route")
|
form.cas_validate_route.data = ScoDocSiteConfig.get("cas_validate_route")
|
||||||
form.cas_attribute_id.data = ScoDocSiteConfig.get("cas_attribute_id")
|
form.cas_attribute_id.data = ScoDocSiteConfig.get("cas_attribute_id")
|
||||||
|
form.cas_uid_from_mail_regexp.data = ScoDocSiteConfig.get(
|
||||||
|
"cas_uid_from_mail_regexp"
|
||||||
|
)
|
||||||
form.cas_ssl_verify.data = ScoDocSiteConfig.get("cas_ssl_verify")
|
form.cas_ssl_verify.data = ScoDocSiteConfig.get("cas_ssl_verify")
|
||||||
return render_template(
|
return render_template(
|
||||||
"config_cas.j2",
|
"config_cas.j2",
|
||||||
|
@ -400,9 +400,16 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
|||||||
"title": "Identifiant CAS",
|
"title": "Identifiant CAS",
|
||||||
"input_type": "text",
|
"input_type": "text",
|
||||||
"explanation": "id du compte utilisateur sur le CAS de l'établissement "
|
"explanation": "id du compte utilisateur sur le CAS de l'établissement "
|
||||||
+ "(service CAS activé)"
|
+ (
|
||||||
|
"(<b>sera déduit de son e-mail institutionnel</b>) "
|
||||||
|
if ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
+ (
|
||||||
|
"(service CAS activé)"
|
||||||
if cas_enabled
|
if cas_enabled
|
||||||
else "(service CAS non activé)",
|
else "(service CAS non activé)"
|
||||||
|
),
|
||||||
"size": 36,
|
"size": 36,
|
||||||
"allow_null": True,
|
"allow_null": True,
|
||||||
"readonly": not cas_enabled
|
"readonly": not cas_enabled
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.34"
|
SCOVERSION = "9.6.35"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user