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,12 +355,15 @@ class User(UserMixin, ScoDocModel):
super().from_dict(data, excluded={"user_name", "roles_string", "roles"}) super().from_dict(data, excluded={"user_name", "roles_string", "roles"})
# Set cas_id using regexp if configured: if ScoDocSiteConfig.cas_uid_use_scodoc():
exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp") self.cas_id = self.user_name
if exp and self.email_institutionnel: else:
cas_id = ScoDocSiteConfig.extract_cas_id(self.email_institutionnel) # Set cas_id using regexp if configured:
if cas_id: exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
self.cas_id = cas_id if exp and self.email_institutionnel:
cas_id = ScoDocSiteConfig.extract_cas_id(self.email_institutionnel)
if cas_id:
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é."

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>
<li><a class="stdlink" href="{{
url_for('auth.cas_users_generate_excel_sample')
}}">Obtenir la feuille excel à remplir</a>,
avec la liste complète des utilisateurs.
</li>
</ul>
</div>
<div class="scobox">
<div class="scobox-title">Étape 2: charger le fichier Excel modifié</div>
<ul>
<li><b>Étape 1: </b><a class="stdlink" href="{{
url_for('auth.cas_users_generate_excel_sample')
}}">Obtenir la feuille excel à remplir</a>,
avec la liste complète des utilisateurs.
</li>
<li style="margin-top: 16px;"><b>Étape 2:</b>
<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) }}
{{ wtf.form_field(form.cas_server) }} <div class="scobox">
{{ wtf.form_field(form.cas_login_route) }} <div class="scobox-title">Routes CAS</div>
{{ wtf.form_field(form.cas_logout_route) }} {{ wtf.form_field(form.cas_server) }}
{{ wtf.form_field(form.cas_validate_route) }} {{ wtf.form_field(form.cas_login_route) }}
{{ wtf.form_field(form.cas_attribute_id) }} {{ wtf.form_field(form.cas_logout_route) }}
{{ wtf.form_field(form.cas_uid_from_mail_regexp) }} {{ wtf.form_field(form.cas_validate_route) }}
{{ wtf.form_field(form.cas_edt_id_from_xml_regexp) }} </div>
<div class="cas_settings"> <div class="scobox">
{{ 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_use_scodoc) }}
</div>
<div class="scobox">
{{ wtf.form_field(form.cas_edt_id_from_xml_regexp) }}
</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,9 +404,13 @@ 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>sera déduit de son e-mail institutionnel</b>) " "<b>pa défaut identique à l'identifiant ScoDoc</b> "
if ScoDocSiteConfig.get("cas_uid_from_mail_regexp") if ScoDocSiteConfig.get("cas_uid_use_scodoc")
else "" else (
"(<b>sera déduit de son e-mail institutionnel</b>) "
if ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
else ""
)
) )
+ ( + (
"(service CAS activé)" "(service CAS activé)"
@ -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,240 +611,238 @@ 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))
vals = tf[2]
roles = set(vals["roles"]).intersection(editable_roles_strings)
if not current_user.is_administrator():
# empeche modification des paramètres CAS
if "cas_allow_login" in vals:
vals["cas_allow_login"] = cas_allow_login_default
if "cas_allow_scodoc_login" in vals:
if the_user is None:
vals.pop("cas_allow_scodoc_login", None)
else:
vals["cas_allow_scodoc_login"] = the_user.cas_allow_scodoc_login
if not current_user.has_permission(Permission.UsersChangeCASId):
vals.pop("cas_id", None)
if "edit" in vals:
edit = int(vals["edit"])
else: else:
vals = tf[2] edit = 0
roles = set(vals["roles"]).intersection(editable_roles_strings) try:
if not current_user.is_administrator(): force = int(vals.get("force", "0")[0])
# empeche modification des paramètres CAS except (IndexError, ValueError, TypeError):
if "cas_allow_login" in vals: force = 0
vals["cas_allow_login"] = cas_allow_login_default
if "cas_allow_scodoc_login" in vals:
if the_user is None:
vals.pop("cas_allow_scodoc_login", None)
else:
vals["cas_allow_scodoc_login"] = the_user.cas_allow_scodoc_login
if not current_user.has_permission(Permission.UsersChangeCASId): if edit:
vals.pop("cas_id", None) user_name = initvalues["user_name"]
if "edit" in vals: else:
edit = int(vals["edit"]) user_name = vals["user_name"]
else: # ce login existe ?
edit = 0 err_msg = None
try: nb_existing_user = User.query.filter_by(user_name=user_name).count() > 0
force = int(vals.get("force", "0")[0]) if edit and (
except (IndexError, ValueError, TypeError): nb_existing_user == 0
force = 0 ): # safety net, le user_name ne devrait pas changer
err_msg = f"identifiant {user_name} inexistant"
if not edit and nb_existing_user > 0:
err_msg = f"identifiant {user_name} déjà utilisé"
if err_msg:
H.append(tf_error_message(f"""Erreur: {err_msg}"""))
return render_template(
"base.j2",
content="\n".join(H) + "\n" + tf[1],
javascripts=["js/user_form.js"],
)
if edit: if not edit_only_roles:
user_name = initvalues["user_name"] ok_modif, msg = sco_users.check_modif_user(
else: edit,
user_name = vals["user_name"] enforce_optionals=not force,
# ce login existe ? user_name=user_name,
err_msg = None nom=vals["nom"],
nb_existing_user = User.query.filter_by(user_name=user_name).count() > 0 prenom=vals["prenom"],
if edit and ( email=vals["email"],
nb_existing_user == 0 dept=vals.get("dept", auth_dept),
): # safety net, le user_name ne devrait pas changer roles=vals["roles"],
err_msg = f"identifiant {user_name} inexistant" cas_id=vals.get("cas_id"), # pas présent si pas super-admin
if not edit and nb_existing_user > 0: )
err_msg = f"identifiant {user_name} déjà utilisé" if not ok_modif:
if err_msg: H.append(tf_error_message(msg))
H.append(tf_error_message(f"""Erreur: {err_msg}"""))
return render_template( return render_template(
"base.j2", "base.j2",
content="\n".join(H) + "\n" + tf[1], content="\n".join(H) + "\n" + tf[1],
javascripts=["js/user_form.js"], javascripts=["js/user_form.js"],
) )
if "date_expiration" in vals:
if not edit_only_roles: try:
ok_modif, msg = sco_users.check_modif_user( if vals["date_expiration"]:
edit, vals["date_expiration"] = datetime.datetime.strptime(
enforce_optionals=not force, vals["date_expiration"], scu.DATE_FMT
user_name=user_name, )
nom=vals["nom"], if vals["date_expiration"] < datetime.datetime.now():
prenom=vals["prenom"], H.append(tf_error_message("date expiration passée"))
email=vals["email"], return render_template(
dept=vals.get("dept", auth_dept), "base.j2",
roles=vals["roles"], content="\n".join(H) + "\n" + tf[1],
cas_id=vals.get("cas_id"), # pas présent si pas super-admin javascripts=["js/user_form.js"],
) )
if not ok_modif: else:
H.append(tf_error_message(msg)) vals["date_expiration"] = None
except ValueError:
H.append(tf_error_message("date expiration invalide"))
return render_template( return render_template(
"base.j2", "base.j2",
content="\n".join(H) + "\n" + tf[1], content="\n".join(H) + "\n" + tf[1],
javascripts=["js/user_form.js"], javascripts=["js/user_form.js"],
) )
if "date_expiration" in vals:
try: if edit: # modif utilisateur (mais pas password ni user_name !)
if vals["date_expiration"]: if (not can_choose_dept) and "dept" in vals:
vals["date_expiration"] = datetime.datetime.strptime( del vals["dept"]
vals["date_expiration"], scu.DATE_FMT if "password" in vals:
) del vals["passwordd"]
if vals["date_expiration"] < datetime.datetime.now(): if "date_modif_passwd" in vals:
H.append(tf_error_message("date expiration passée")) del vals["date_modif_passwd"]
return render_template( if "user_name" in vals:
"base.j2", del vals["user_name"]
content="\n".join(H) + "\n" + tf[1], if (current_user.user_name == user_name) and "status" in vals:
javascripts=["js/user_form.js"], del vals["status"] # no one can't change its own status
) if "status" in vals:
else: vals["active"] = vals["status"] == ""
vals["date_expiration"] = None # Département:
except ValueError: if ("dept" in vals) and (vals["dept"] not in selectable_dept_acronyms):
H.append(tf_error_message("date expiration invalide")) del vals["dept"] # ne change pas de dept
# Traitement des roles: ne doit pas affecter les rôles
# que l'on en contrôle pas:
for role in orig_roles_strings: # { "Ens_RT", "Secr_CJ", ... }
if role and not role in editable_roles_strings:
roles.add(role)
vals["roles_string"] = ",".join(roles)
# ok, edit
if not edit_only_roles:
log(f"sco_users: editing {user_name} by {current_user.user_name}")
log(f"sco_users: previous_values={initvalues}")
log(f"sco_users: new_values={vals}")
else:
vals = {"roles_string": vals["roles_string"]}
the_user.from_dict(vals)
db.session.add(the_user)
db.session.commit()
flash(f"Utilisateur {user_name} modifié")
return flask.redirect(
url_for(
"users.user_info_page",
scodoc_dept=g.scodoc_dept,
user_name=user_name,
)
)
else: # création utilisateur
vals["roles_string"] = ",".join(vals["roles"])
# check identifiant
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]+$", vals["user_name"]):
msg = tf_error_message(
"identifiant invalide (pas d'accents ni de caractères spéciaux)"
)
return render_template(
"base.j2",
content="\n".join(H) + msg + "\n" + tf[1],
javascripts=["js/user_form.js"],
)
# Traitement initial (mode) : 3 cas
# cf énumération Mode
# A: envoi de welcome + procedure de reset
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
# C: Aucun envoi (mot de passe saisi dans le formulaire)
if vals["welcome"]: # "Envoie un mail d'accueil" coché
if vals["reset_password"] and (
(not ScoDocSiteConfig.get("cas_force"))
or vals.get("cas_allow_scodoc_login", False)
):
# nb: si login scodoc non autorisé car CAS seul, n'envoie pas le mot de passe.
mode = Mode.WELCOME_AND_CHANGE_PASSWORD
else:
mode = Mode.WELCOME_ONLY
else:
mode = Mode.SILENT
# check passwords
if mode == Mode.WELCOME_AND_CHANGE_PASSWORD:
vals["password"] = generate_password()
else:
if vals["password"]:
if vals["password"] != vals["password2"]:
msg = tf_error_message(
"""Les deux mots de passes ne correspondent pas !"""
)
return render_template( return render_template(
"base.j2", "base.j2",
content="\n".join(H) + "\n" + tf[1], content="\n".join(H) + msg + "\n" + tf[1],
javascripts=["js/user_form.js"], javascripts=["js/user_form.js"],
) )
if not is_valid_password(vals["password"]):
if edit: # modif utilisateur (mais pas password ni user_name !) msg = tf_error_message(
if (not can_choose_dept) and "dept" in vals: """Mot de passe trop simple, recommencez !"""
del vals["dept"] )
if "password" in vals: return render_template(
del vals["passwordd"] "base.j2",
if "date_modif_passwd" in vals: content="\n".join(H) + msg + "\n" + tf[1],
del vals["date_modif_passwd"] javascripts=["js/user_form.js"],
if "user_name" in vals: )
del vals["user_name"] # Département:
if (current_user.user_name == user_name) and "status" in vals: if not can_choose_dept:
del vals["status"] # no one can't change its own status vals["dept"] = auth_dept
if "status" in vals: else:
vals["active"] = vals["status"] == "" if auth_dept: # pas super-admin
# Département: if vals["dept"] not in selectable_dept_acronyms:
if ("dept" in vals) and (vals["dept"] not in selectable_dept_acronyms): raise ScoValueError("département invalide")
del vals["dept"] # ne change pas de dept # ok, go
# Traitement des roles: ne doit pas affecter les rôles log(f"""sco_users: new_user {vals["user_name"]} by {current_user.user_name}""")
# que l'on en contrôle pas: the_user = User(user_name=user_name)
for role in orig_roles_strings: # { "Ens_RT", "Secr_CJ", ... } the_user.from_dict(vals, new_user=True)
if role and not role in editable_roles_strings: db.session.add(the_user)
roles.add(role) db.session.commit()
# envoi éventuel d'un message
vals["roles_string"] = ",".join(roles) if mode == Mode.WELCOME_AND_CHANGE_PASSWORD or mode == Mode.WELCOME_ONLY:
# ok, edit
if not edit_only_roles:
log(f"sco_users: editing {user_name} by {current_user.user_name}")
log(f"sco_users: previous_values={initvalues}")
log(f"sco_users: new_values={vals}")
else:
vals = {"roles_string": vals["roles_string"]}
the_user.from_dict(vals)
db.session.add(the_user)
db.session.commit()
flash(f"Utilisateur {user_name} modifié")
return flask.redirect(
url_for(
"users.user_info_page",
scodoc_dept=g.scodoc_dept,
user_name=user_name,
)
)
else: # création utilisateur
vals["roles_string"] = ",".join(vals["roles"])
# check identifiant
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]+$", vals["user_name"]):
msg = tf_error_message(
"identifiant invalide (pas d'accents ni de caractères spéciaux)"
)
return render_template(
"base.j2",
content="\n".join(H) + msg + "\n" + tf[1],
javascripts=["js/user_form.js"],
)
# Traitement initial (mode) : 3 cas
# cf énumération Mode
# A: envoi de welcome + procedure de reset
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
# C: Aucun envoi (mot de passe saisi dans le formulaire)
if vals["welcome"]: # "Envoie un mail d'accueil" coché
if vals["reset_password"] and (
(not ScoDocSiteConfig.get("cas_force"))
or vals.get("cas_allow_scodoc_login", False)
):
# nb: si login scodoc non autorisé car CAS seul, n'envoie pas le mot de passe.
mode = Mode.WELCOME_AND_CHANGE_PASSWORD
else:
mode = Mode.WELCOME_ONLY
else:
mode = Mode.SILENT
# check passwords
if mode == Mode.WELCOME_AND_CHANGE_PASSWORD: if mode == Mode.WELCOME_AND_CHANGE_PASSWORD:
vals["password"] = generate_password() token = the_user.get_reset_password_token()
else: else:
if vals["password"]: token = None
if vals["password"] != vals["password2"]: cas_force = ScoDocSiteConfig.get("cas_force")
msg = tf_error_message( # Le from doit utiliser la préférence du département de l'utilisateur
"""Les deux mots de passes ne correspondent pas !""" email.send_email(
) "[ScoDoc] Création de votre compte",
return render_template( sender=email.get_from_addr(),
"base.j2", recipients=[the_user.email],
content="\n".join(H) + msg + "\n" + tf[1], text_body=render_template(
javascripts=["js/user_form.js"], "email/welcome.txt",
) user=the_user,
if not is_valid_password(vals["password"]): token=token,
msg = tf_error_message( cas_force=cas_force,
"""Mot de passe trop simple, recommencez !""" ),
) html_body=render_template(
return render_template( "email/welcome.j2",
"base.j2", user=the_user,
content="\n".join(H) + msg + "\n" + tf[1], token=token,
javascripts=["js/user_form.js"], cas_force=cas_force,
) ),
# Département:
if not can_choose_dept:
vals["dept"] = auth_dept
else:
if auth_dept: # pas super-admin
if vals["dept"] not in selectable_dept_acronyms:
raise ScoValueError("département invalide")
# ok, go
log(
f"""sco_users: new_user {vals["user_name"]} by {current_user.user_name}"""
) )
the_user = User(user_name=user_name) flash(f"Mail accueil envoyé à {the_user.email}")
the_user.from_dict(vals, new_user=True)
db.session.add(the_user)
db.session.commit()
# envoi éventuel d'un message
if mode == Mode.WELCOME_AND_CHANGE_PASSWORD or mode == Mode.WELCOME_ONLY:
if mode == Mode.WELCOME_AND_CHANGE_PASSWORD:
token = the_user.get_reset_password_token()
else:
token = None
cas_force = ScoDocSiteConfig.get("cas_force")
# Le from doit utiliser la préférence du département de l'utilisateur
email.send_email(
"[ScoDoc] Création de votre compte",
sender=email.get_from_addr(),
recipients=[the_user.email],
text_body=render_template(
"email/welcome.txt",
user=the_user,
token=token,
cas_force=cas_force,
),
html_body=render_template(
"email/welcome.j2",
user=the_user,
token=token,
cas_force=cas_force,
),
)
flash(f"Mail accueil envoyé à {the_user.email}")
flash("Nouvel utilisateur créé") flash("Nouvel utilisateur créé")
return flask.redirect( return flask.redirect(
url_for( url_for(
"users.user_info_page", "users.user_info_page",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
user_name=user_name, user_name=user_name,
)
) )
)
@bp.route("/import_users_generate_excel_sample") @bp.route("/import_users_generate_excel_sample")