CAS: ajout option pour utiliser par défaut le même uid ScoDoc et CAS + cosmetic formulaires

This commit is contained in:
Emmanuel Viennet 2024-09-15 16:50:25 +02:00
parent c38b8aa297
commit 9ae2181904
10 changed files with 327 additions and 250 deletions

View File

@ -355,6 +355,9 @@ class User(UserMixin, ScoDocModel):
super().from_dict(data, excluded={"user_name", "roles_string", "roles"}) super().from_dict(data, excluded={"user_name", "roles_string", "roles"})
if ScoDocSiteConfig.cas_uid_use_scodoc():
self.cas_id = self.user_name
else:
# Set cas_id using regexp if configured: # Set cas_id using regexp if configured:
exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp") exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
if exp and self.email_institutionnel: if exp and self.email_institutionnel:

View File

@ -98,6 +98,15 @@ class ConfigCASForm(FlaskForm):
validators=[Optional(), check_cas_uid_from_mail_regexp], validators=[Optional(), check_cas_uid_from_mail_regexp],
) )
cas_uid_use_scodoc = BooleanField(
"Utiliser l'identifiant ScoDoc comme identifiant CAS",
description="""Si coché, l'identifiant ScoDoc sera utilisé comme identifiant CAS,
sans transformation. Cette option est utile si les identifiants ScoDoc sont déjà
des identifiants CAS.
Dans ce cas, l'adresse mail (réglage ci-dessus) n'est pas utilisée.
""",
)
cas_edt_id_from_xml_regexp = StringField( cas_edt_id_from_xml_regexp = StringField(
label="Optionnel: expression pour extraire l'identifiant edt", label="Optionnel: expression pour extraire l'identifiant edt",
description="""regexp python appliquée à la réponse XML du serveur CAS pour description="""regexp python appliquée à la réponse XML du serveur CAS pour

View File

@ -105,6 +105,7 @@ class ScoDocSiteConfig(db.Model):
"cas_validate_route": str, "cas_validate_route": str,
"cas_attribute_id": str, "cas_attribute_id": str,
"cas_uid_from_mail_regexp": str, "cas_uid_from_mail_regexp": str,
"cas_uid_use_scodoc": bool,
"cas_edt_id_from_xml_regexp": str, "cas_edt_id_from_xml_regexp": str,
# Assiduité # Assiduité
"morning_time": str, "morning_time": str,
@ -239,6 +240,12 @@ class ScoDocSiteConfig(db.Model):
cfg = ScoDocSiteConfig.query.filter_by(name="cas_force").first() cfg = ScoDocSiteConfig.query.filter_by(name="cas_force").first()
return cfg is not None and cfg.value return cfg is not None and cfg.value
@classmethod
def cas_uid_use_scodoc(cls) -> bool:
"""True si cas_uid_use_scodoc"""
cfg = ScoDocSiteConfig.query.filter_by(name="cas_uid_use_scodoc").first()
return cfg is not None and cfg.value
@classmethod @classmethod
def is_entreprises_enabled(cls) -> bool: def is_entreprises_enabled(cls) -> bool:
"""True si on doit activer le module entreprise""" """True si on doit activer le module entreprise"""
@ -404,7 +411,7 @@ class ScoDocSiteConfig(db.Model):
@classmethod @classmethod
def extract_cas_id(cls, email_addr: str) -> str | None: def extract_cas_id(cls, email_addr: str) -> str | None:
"Extract cas_id from maill, using regexp in config. None if not possible." "Extract cas_id from mail, using regexp in config. None if not possible."
exp = cls.get("cas_uid_from_mail_regexp") exp = cls.get("cas_uid_from_mail_regexp")
if not exp or not email_addr: if not exp or not email_addr:
return None return None

View File

@ -1246,7 +1246,8 @@ a.discretelink:hover {
text-align: center; text-align: center;
} }
.help { .help,
.help-block {
max-width: var(--sco-content-max-width); max-width: var(--sco-content-max-width);
font-style: italic; font-style: italic;
} }
@ -1260,6 +1261,10 @@ a.discretelink:hover {
color: red; color: red;
} }
div.help-block {
margin-bottom: 16px;
}
div.sco_box, div.sco_box,
div.sco_help { div.sco_help {
margin-top: 12px; margin-top: 12px;
@ -4919,7 +4924,6 @@ table.formation_table_recap td.heures_tp {
} }
div.cas_settings { div.cas_settings {
margin-left: -15px;
margin-bottom: 8px; margin-bottom: 8px;
border: 1px dashed rgb(191, 34, 191); border: 1px dashed rgb(191, 34, 191);
background-color: #feb4e54f; background-color: #feb4e54f;

View File

@ -430,11 +430,21 @@ textarea {
color: #333; color: #333;
} }
form.form div.checkbox {
margin-top: 6px;
margin-bottom: 6px;
}
div.form-group {
margin-top: 16px;
margin-bottom: 6px;
}
.form-group input, .form-group input,
.form-control { .form-control {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
margin-bottom: 16px; margin-bottom: 4px;
border: 1px solid #ced4da; border: 1px solid #ced4da;
border-radius: 4px; border-radius: 4px;
font-size: 16px; font-size: 16px;

View File

@ -4,7 +4,7 @@
{% block app_content %} {% block app_content %}
<h1>Chargement des configurations CAS des utilisateurs</h1> <h1>Chargement des configurations CAS des utilisateurs</h1>
<div style="max-width: 800px;"> <div class="scobox help explanation">
<p style="color: red">A utiliser pour modifier le paramétrage CAS de <p style="color: red">A utiliser pour modifier le paramétrage CAS de
<b>comptes utilisateurs existants</b> <b>comptes utilisateurs existants</b>
</p> </p>
@ -32,21 +32,26 @@
<li style="margin-bottom:32px;">Revenez sur cette page et chargez le fichier dans ScoDoc. <li style="margin-bottom:32px;">Revenez sur cette page et chargez le fichier dans ScoDoc.
</li> </li>
</ol> </ol>
</div>
<div class="scobox">
<div class="scobox-title">Étape 1: exporter fichier Excel à charger</div>
<ul> <ul>
<li><b>Étape 1: </b><a class="stdlink" href="{{ <li><a class="stdlink" href="{{
url_for('auth.cas_users_generate_excel_sample') url_for('auth.cas_users_generate_excel_sample')
}}">Obtenir la feuille excel à remplir</a>, }}">Obtenir la feuille excel à remplir</a>,
avec la liste complète des utilisateurs. avec la liste complète des utilisateurs.
</li> </li>
<li style="margin-top: 16px;"><b>Étape 2:</b> </ul>
</div>
<div class="scobox">
<div class="scobox-title">Étape 2: charger le fichier Excel modifié</div>
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
{{ wtf.quick_form(form) }} {{ wtf.quick_form(form) }}
</div> </div>
</div> </div>
</li> </div>
</ul>
{% endblock %} {% endblock %}

View File

@ -1,6 +1,14 @@
{% extends "base.j2" %} {% extends "base.j2" %}
{% import 'wtf.j2' as wtf %} {% import 'wtf.j2' as wtf %}
{% block styles %}
{{super()}}
<style>
div.checkbox label { font-weight: normal; }
</style>
{% endblock %}
{% block app_content %} {% block app_content %}
<h1>Configuration du Service d'Authentification Central (CAS)</h1> <h1>Configuration du Service d'Authentification Central (CAS)</h1>
@ -18,14 +26,33 @@
{{ wtf.form_field(form.cas_enable) }} {{ wtf.form_field(form.cas_enable) }}
{{ wtf.form_field(form.cas_force) }} {{ wtf.form_field(form.cas_force) }}
{{ wtf.form_field(form.cas_allow_for_new_users) }} {{ wtf.form_field(form.cas_allow_for_new_users) }}
<div class="scobox">
<div class="scobox-title">Routes CAS</div>
{{ wtf.form_field(form.cas_server) }} {{ wtf.form_field(form.cas_server) }}
{{ wtf.form_field(form.cas_login_route) }} {{ wtf.form_field(form.cas_login_route) }}
{{ 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) }}
</div>
<div class="scobox">
{{ wtf.form_field(form.cas_attribute_id) }} {{ wtf.form_field(form.cas_attribute_id) }}
</div>
<div class="scobox">
<div class="scobox-title">Identifiant utilisateur CAS</div>
<div class="help explanation">
Ces paramètres sont utilisés pour déduire
l'identifiant CAS des utilisateurs ScoDoc au moment de la création ou modification
de comptes utilisateurs. Pour modifier les comptes existants en masse, il peut être
pratique de passer par un
<a class="stdlink" href="{{ url_for('auth.cas_users_import_config') }}">export/import excel</a>.
</div>
{{ wtf.form_field(form.cas_uid_from_mail_regexp) }} {{ wtf.form_field(form.cas_uid_from_mail_regexp) }}
{{ wtf.form_field(form.cas_uid_use_scodoc) }}
</div>
<div class="scobox">
{{ wtf.form_field(form.cas_edt_id_from_xml_regexp) }} {{ wtf.form_field(form.cas_edt_id_from_xml_regexp) }}
<div class="cas_settings"> </div>
<div class="scobox cas_settings">
<div class="scobox-title">Certificat serveur CAS</div>
{{ 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) }}
<div class="cas_etat_certif_ssl">Certificat SSL <div class="cas_etat_certif_ssl">Certificat SSL

View File

@ -43,6 +43,9 @@ the necessary fix for required=False attributes, but will also not set the requi
<div class="checkbox"> <div class="checkbox">
<label> <label>
{{field()|safe}} {{field.label.text|safe}} {{field()|safe}} {{field.label.text|safe}}
{%- if field.description %}
<div class="help-block">{{field.description|safe}}</div>
{%- endif %}
</label> </label>
</div> </div>
{% endcall %} {% endcall %}
@ -108,12 +111,12 @@ the necessary fix for required=False attributes, but will also not set the requi
{%- if field.errors %} {%- if field.errors %}
{%- for error in field.errors %} {%- for error in field.errors %}
{% call _hz_form_wrap(horizontal_columns, form_type, required=required) %} {% call _hz_form_wrap(horizontal_columns, form_type, required=required) %}
<p class="help-block">{{error}}</p> <div class="help-block">{{error}}</div>
{% endcall %} {% endcall %}
{%- endfor %} {%- endfor %}
{%- elif field.description -%} {%- elif field.description -%}
{% call _hz_form_wrap(horizontal_columns, form_type, required=required) %} {% call _hz_form_wrap(horizontal_columns, form_type, required=required) %}
<p class="help-block">{{field.description|safe}}</p> <div class="help-block">{{field.description|safe}}</div>
{% endcall %} {% endcall %}
{%- endif %} {%- endif %}
{%- else -%} {%- else -%}
@ -126,10 +129,10 @@ the necessary fix for required=False attributes, but will also not set the requi
{%- if field.errors %} {%- if field.errors %}
{%- for error in field.errors %} {%- for error in field.errors %}
<p class="help-block">{{error}}</p> <div class="help-block">{{error}}</div>
{%- endfor %} {%- endfor %}
{%- elif field.description -%} {%- elif field.description -%}
<p class="help-block">{{field.description|safe}}</p> <div class="help-block">{{field.description|safe}}</div>
{%- endif %} {%- endif %}
{%- endif %} {%- endif %}
</div> </div>

View File

@ -296,6 +296,11 @@ def config_cas():
"cas_uid_from_mail_regexp", form.data["cas_uid_from_mail_regexp"] "cas_uid_from_mail_regexp", form.data["cas_uid_from_mail_regexp"]
): ):
flash("Expression extraction identifiant CAS enregistrée") flash("Expression extraction identifiant CAS enregistrée")
if ScoDocSiteConfig.set("cas_uid_use_scodoc", form.data["cas_uid_use_scodoc"]):
if form.data["cas_uid_use_scodoc"]:
flash("Utilisation de l'identifiant ScoDoc comme identifiant CAS")
else:
flash("N'utilise PAS l'identifiant ScoDoc pour le CAS")
if ScoDocSiteConfig.set( if ScoDocSiteConfig.set(
"cas_edt_id_from_xml_regexp", form.data["cas_edt_id_from_xml_regexp"] "cas_edt_id_from_xml_regexp", form.data["cas_edt_id_from_xml_regexp"]
): ):
@ -313,7 +318,7 @@ def config_cas():
set_cas_configuration() set_cas_configuration()
return redirect(url_for("scodoc.configuration")) return redirect(url_for("scodoc.configuration"))
elif request.method == "GET": if request.method == "GET":
form.cas_enable.data = ScoDocSiteConfig.get("cas_enable") form.cas_enable.data = ScoDocSiteConfig.get("cas_enable")
form.cas_force.data = ScoDocSiteConfig.get("cas_force") form.cas_force.data = ScoDocSiteConfig.get("cas_force")
form.cas_allow_for_new_users.data = ScoDocSiteConfig.get( form.cas_allow_for_new_users.data = ScoDocSiteConfig.get(
@ -327,6 +332,7 @@ def config_cas():
form.cas_uid_from_mail_regexp.data = ScoDocSiteConfig.get( form.cas_uid_from_mail_regexp.data = ScoDocSiteConfig.get(
"cas_uid_from_mail_regexp" "cas_uid_from_mail_regexp"
) )
form.cas_uid_use_scodoc.data = ScoDocSiteConfig.get("cas_uid_use_scodoc")
form.cas_edt_id_from_xml_regexp.data = ScoDocSiteConfig.get( form.cas_edt_id_from_xml_regexp.data = ScoDocSiteConfig.get(
"cas_edt_id_from_xml_regexp" "cas_edt_id_from_xml_regexp"
) )

View File

@ -404,10 +404,14 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
"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 "
+ ( + (
"<b>pa défaut identique à l'identifiant ScoDoc</b> "
if ScoDocSiteConfig.get("cas_uid_use_scodoc")
else (
"(<b>sera déduit de son e-mail institutionnel</b>) " "(<b>sera déduit de son e-mail institutionnel</b>) "
if ScoDocSiteConfig.get("cas_uid_from_mail_regexp") if ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
else "" else ""
) )
)
+ ( + (
"(service CAS activé)" "(service CAS activé)"
if cas_enabled if cas_enabled
@ -537,7 +541,8 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
"d", "d",
{ {
"input_type": "separator", "input_type": "separator",
"title": f"L'utilisateur sera créé dans le département {auth_dept or 'aucun'}", "title": f"""L'utilisateur sera créé dans le département {
auth_dept or 'aucun'}""",
}, },
) )
) )
@ -606,9 +611,9 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
content="\n".join(H) + "\n" + tf[1], content="\n".join(H) + "\n" + tf[1],
javascripts=["js/user_form.js"], javascripts=["js/user_form.js"],
) )
elif tf[0] == -1: if tf[0] == -1:
return flask.redirect(url_for("users.index_html", scodoc_dept=g.scodoc_dept)) return flask.redirect(url_for("users.index_html", scodoc_dept=g.scodoc_dept))
else:
vals = tf[2] vals = tf[2]
roles = set(vals["roles"]).intersection(editable_roles_strings) roles = set(vals["roles"]).intersection(editable_roles_strings)
if not current_user.is_administrator(): if not current_user.is_administrator():
@ -798,9 +803,7 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
if vals["dept"] not in selectable_dept_acronyms: if vals["dept"] not in selectable_dept_acronyms:
raise ScoValueError("département invalide") raise ScoValueError("département invalide")
# ok, go # ok, go
log( log(f"""sco_users: new_user {vals["user_name"]} by {current_user.user_name}""")
f"""sco_users: new_user {vals["user_name"]} by {current_user.user_name}"""
)
the_user = User(user_name=user_name) the_user = User(user_name=user_name)
the_user.from_dict(vals, new_user=True) the_user.from_dict(vals, new_user=True)
db.session.add(the_user) db.session.add(the_user)