Fix: édition des utilisateurs. #305

This commit is contained in:
Emmanuel Viennet 2022-08-26 14:05:25 +02:00
parent cbcff63b35
commit 257248aa2b
5 changed files with 229 additions and 174 deletions

View File

@ -730,11 +730,7 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
R.append("%s</td>" % title)
R.append('<td class="tf-ro-field%s">' % klass)
if (
input_type == "text"
or input_type == "text_suggest"
or input_type == "color"
):
if input_type in ("text", "text_suggest", "color", "datedmy"):
R.append(("%(" + field + ")s") % self.values)
elif input_type in ("radio", "menu", "checkbox", "boolcheckbox"):
if input_type == "boolcheckbox":

View File

@ -47,6 +47,15 @@ class ScoValueError(ScoException):
self.dest_url = dest_url
class ScoPermissionDenied(ScoValueError):
"""Permission non accordée (appli web)"""
def __init__(self, msg=None, dest_url=None):
if msg is None:
msg = "Opération non autorisée !"
super().__init__(msg, dest_url=dest_url)
class ScoBugCatcher(ScoException):
"bug avec enquete en cours"

View File

@ -15,8 +15,8 @@ _SCO_PERMISSIONS = (
(1 << 2, "ScoView", "Voir"),
(1 << 3, "ScoEnsView", "Voir les parties pour les enseignants"),
(1 << 4, "ScoObservateur", "Observer (accès lecture restreint aux bulletins)"),
(1 << 5, "ScoUsersAdmin", "Gérer les utilisateurs"),
(1 << 6, "ScoUsersView", "Voir les utilisateurs"),
(1 << 5, "ScoUsersAdmin", "Gérer les utilisateurs (de son département)"),
(1 << 6, "ScoUsersView", "Voir les utilisateurs (de tous les dépts)"),
(1 << 7, "ScoChangePreferences", "Modifier les préférences"),
(1 << 8, "ScoChangeFormation", "Changer les formations"),
(1 << 9, "ScoEditFormationTags", "Tagguer les formations"),

View File

@ -18,23 +18,33 @@
{{user.date_expiration.isoformat() if user.date_expiration else "(sans limite)"}}
<p>
<ul>
{% if (
current_user.is_administrator()
or current_user.has_permission(Permission.ScoUsersAdmin, user.dept)
) %}
<li><a class="stdlink" href="{{
url_for( 'users.form_change_password',
scodoc_dept=g.scodoc_dept, user_name=user.user_name)
}}">modifier le mot de passe ou l'adresse mail</a>
</li>
{% endif %}
{% if current_user.has_permission(Permission.ScoUsersAdmin, dept) %}
<li><a class="stdlink" href="{{
url_for('users.create_user_form', scodoc_dept=g.scodoc_dept,
user_name=user.user_name, edit=1)
}}">modifier ce compte</a>
</li>
<li><a class="stdlink" href="{{
url_for('users.toggle_active_user', scodoc_dept=g.scodoc_dept,
user_name=user.user_name)
}}">{{"désactiver" if user.active else "activer"}} ce compte</a>
}}">modifier ce compte et ses rôles</a>
</li>
{% endif %}
{% if (
current_user.is_administrator()
or current_user.has_permission(Permission.ScoUsersAdmin, user.dept)
) %}
<li><a class="stdlink" href="{{
url_for('users.toggle_active_user', scodoc_dept=g.scodoc_dept,
user_name=user.user_name)
}}">{{"désactiver" if user.active else "activer"}} ce compte</a>
</li>
{% endif %}
</ul>
{% if current_user.id == user.id %}
@ -45,7 +55,7 @@
{# Liste des permissions #}
<div class="permissions">
<p>Permissions de cet utilisateur dans le département {{dept}}:</p>
<p><b>Permissions de cet utilisateur dans le département {{dept}}:</b></p>
<ul>
{% for p in Permission.description %}
<li>{{Permission.description[p]}} :

View File

@ -68,7 +68,7 @@ from app.scodoc import sco_users
from app.scodoc import sco_utils as scu
from app.scodoc import sco_xml
from app import log
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_exceptions import AccessDenied, ScoPermissionDenied, ScoValueError
from app.scodoc.sco_import_users import generate_password
from app.scodoc.sco_permissions_check import can_handle_passwd
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
@ -141,11 +141,71 @@ def index_html(all_depts=False, with_inactives=False, format="html"):
)
def _get_administrable_depts() -> list[str]:
"""Liste des acronymes des départements dans lesquels l'utilisateur
courant peut administrer des utilisateurs.
Si SuperAdmin, tous les départements
Sinon, les départements dans lesquels l'utilisateur a la permission ScoUsersAdmin
"""
#
if current_user.is_administrator():
log(f"create_user_form called by {current_user.user_name} (super admin)")
administrable_dept_acronyms = sorted(
[d.acronym for d in Departement.query.all()]
)
else:
administrable_dept_acronyms = current_user.get_depts_with_permission(
Permission.ScoUsersAdmin
)
if None in administrable_dept_acronyms:
administrable_dept_acronyms = sorted(
[d.acronym for d in Departement.query.all()]
)
return administrable_dept_acronyms
def _get_editable_roles(
administrable_dept_acronyms: list = None, all_roles=True
) -> set[tuple[Role, str]]:
"""Rôles modifiables: ensemble de tuples (role, dept_acronym)
( dept_acronym est None si tous dept.)
Si all_roles, tous les rôles définis et modifiables par l'utilisateurs.
Sinon, seulement les rôles "standards" de ScoDoc.
"""
if all_roles:
# tous sauf SuperAdmin
roles = [
r
for r in Role.query.all()
if r.permissions != Permission.ALL_PERMISSIONS[0]
]
else:
# Les rôles standards créés à l'initialisation de ScoDoc:
roles = [
Role.get_named_role(r) for r in sco_roles_default.ROLES_ATTRIBUABLES_DEPT
]
# Génère toutes les combinaisons roles/départements
editable_roles_set = {
(r, dept) for r in roles for dept in administrable_dept_acronyms
}
if current_user.is_administrator():
editable_roles_set |= {
(Role.get_named_role(r), None)
for r in sco_roles_default.ROLES_ATTRIBUABLES_SCODOC
}
# Un super-admin peut nommer d'autres super-admin:
editable_roles_set |= {(Role.get_named_role("SuperAdmin"), None)}
return editable_roles_set
@bp.route("/create_user_form", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoUsersAdmin)
@scodoc7func
def create_user_form(user_name=None, edit=0, all_roles=False):
def create_user_form(user_name=None, edit=0, all_roles=True):
"form. création ou édition utilisateur"
if user_name is not None: # scodoc7func converti en int !
user_name = str(user_name)
@ -162,6 +222,7 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
)
]
F = html_sco_header.sco_footer()
the_user: User = None
if edit:
if not user_name:
raise ValueError("missing argument: user_name")
@ -170,60 +231,6 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
raise ScoValueError("utilisateur inexistant")
initvalues = the_user.to_dict()
H.append(f"<h2>Modification de l'utilisateur {user_name}</h2>")
else:
H.append("<h2>Création d'un utilisateur</h2>")
is_super_admin = False
if current_user.has_permission(Permission.ScoSuperAdmin, g.scodoc_dept):
H.append("""<p class="warning">Vous êtes super administrateur !</p>""")
is_super_admin = True
if all_roles:
# tous sauf SuperAdmin
standard_roles = [
r
for r in Role.query.all()
if r.permissions != Permission.ALL_PERMISSIONS[0]
]
else:
# Les rôles standards créés à l'initialisation de ScoDoc:
standard_roles = [
Role.get_named_role(r) for r in sco_roles_default.ROLES_ATTRIBUABLES_DEPT
]
# Départements auxquels ont peut associer des rôles via ce dialogue:
# si SuperAdmin, tous les rôles standards dans tous les départements
# sinon, les départements dans lesquels l'utilisateur a la permission ScoUsersAdmin
if is_super_admin:
log(f"create_user_form called by {current_user.user_name} (super admin)")
administrable_dept_acronyms = [d.acronym for d in Departement.query.all()]
else:
# Si on n'est pas SuperAdmin, liste les départements dans lesquels on a la
# permission ScoUsersAdmin
administrable_dept_acronyms = sorted(
set(
[
x.dept or ""
for x in UserRole.query.filter_by(user=current_user)
if x.role.has_permission(Permission.ScoUsersAdmin) and x.dept
]
)
)
editable_roles_set = {
(r, dept) for r in standard_roles for dept in administrable_dept_acronyms
}
if current_user.is_administrator():
editable_roles_set |= {
(Role.get_named_role(r), None)
for r in sco_roles_default.ROLES_ATTRIBUABLES_SCODOC
}
# Un super-admin peut nommer d'autres super-admin:
editable_roles_set |= {(Role.get_named_role("SuperAdmin"), None)}
#
if not edit:
submitlabel = "Créer utilisateur"
orig_roles = set()
else:
submitlabel = "Modifier utilisateur"
if "roles_string" in initvalues:
initvalues["roles"] = initvalues["roles_string"].split(",")
@ -243,6 +250,26 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
}
if not initvalues["active"]:
editable_roles_set = set() # can't change roles of a disabled user
else:
H.append("<h2>Création d'un utilisateur</h2>")
submitlabel = "Créer utilisateur"
orig_roles = set()
is_super_admin = current_user.is_administrator()
if is_super_admin:
H.append("""<p class="warning">Vous êtes super administrateur !</p>""")
administrable_dept_acronyms = _get_administrable_depts()
if edit:
if the_user.dept is None: # seul le super admin peut le toucher
edit_only_roles = not current_user.is_administrator()
else:
edit_only_roles = the_user.dept not in administrable_dept_acronyms
else:
edit_only_roles = False # création nouvel utilisateur
editable_roles_set = _get_editable_roles(
administrable_dept_acronyms, all_roles=all_roles
)
editable_roles_strings = {
r.name + "_" + (dept or "") for (r, dept) in editable_roles_set
}
@ -265,12 +292,29 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
for i, role_string in enumerate(displayed_roles_strings):
if role_string not in editable_roles_strings:
disabled_roles[i] = True
# Formulaire:
descr = [
("edit", {"input_type": "hidden", "default": edit}),
("nom", {"title": "Nom", "size": 20, "allow_null": False}),
("prenom", {"title": "Prénom", "size": 20, "allow_null": False}),
(
"nom",
{
"title": "Nom",
"size": 20,
"allow_null": False,
"readonly": edit_only_roles,
},
),
(
"prenom",
{
"title": "Prénom",
"size": 20,
"allow_null": False,
"readonly": edit_only_roles,
},
),
]
if current_user.user_name != user_name:
if current_user.user_name != user_name and not edit_only_roles:
# no one can change its own status
descr.append(
(
@ -316,13 +360,12 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
},
),
]
else:
else: # edition: on ne peut pas changer user_name
descr += [
(
"user_name",
{"input_type": "hidden", "default": initvalues["user_name"]},
),
("user_name", {"input_type": "hidden", "default": initvalues["user_name"]}),
)
]
descr += [
(
@ -330,9 +373,12 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
{
"title": "e-mail",
"input_type": "text",
"explanation": "requis, doit fonctionner",
"explanation": "requis, doit fonctionner"
if not edit_only_roles
else "",
"size": 20,
"allow_null": False,
"readonly": edit_only_roles,
},
)
]
@ -362,72 +408,50 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
},
),
]
# Si auth n'a pas de departement (admin global)
# propose de choisir librement le dept du nouvel utilisateur
# sinon, menu proposant l'ensembe des départements dans lesquels
# nous avons la permission ScoUserAdmin + le dept actuel de l'utilisateur
# modifié.
if not auth_dept:
# Si SuperAdmin, propose de choisir librement le dept du nouvel utilisateur
selectable_dept_acronyms = set(administrable_dept_acronyms)
if edit and the_user.dept is not None: # ajoute dept actuel de l'utilisateur
selectable_dept_acronyms |= {the_user.dept}
if is_super_admin and len(selectable_dept_acronyms) > 1:
selectable_dept_acronyms = sorted(list(selectable_dept_acronyms))
descr.append(
(
"dept",
{
"title": "Département",
"input_type": "text",
"size": 12,
"allow_null": True,
"explanation": """département de rattachement de l'utilisateur
(s'il s'agit d'un administrateur, laisser vide si vous voulez
qu'il puisse créer des utilisateurs dans d'autres départements)
""",
"input_type": "menu",
"explanation": """département de rattachement de l'utilisateur""",
"labels": selectable_dept_acronyms,
"allowed_values": selectable_dept_acronyms,
"default": g.scodoc_dept
if g.scodoc_dept in selectable_dept_acronyms
else (auth_dept or ""),
},
)
)
can_choose_dept = True
else:
selectable_dept_acronyms = set(administrable_dept_acronyms)
if edit and the_user.dept is not None: # ajoute dept actuel de l'utilisateur
selectable_dept_acronyms |= {the_user.dept}
if len(selectable_dept_acronyms) > 1:
can_choose_dept = True
selectable_dept_acronyms = sorted(list(selectable_dept_acronyms))
else: # pas de choix de département
can_choose_dept = False
if edit:
descr.append(
(
"dept",
"d",
{
"title": "Département",
"input_type": "menu",
"explanation": """département de rattachement de l'utilisateur""",
"labels": selectable_dept_acronyms,
"allowed_values": selectable_dept_acronyms,
"default": g.scodoc_dept
if g.scodoc_dept in selectable_dept_acronyms
else "",
"input_type": "separator",
"title": f"""L'utilisateur appartient au département {the_user.dept or "(tous)"}""",
},
)
)
else: # pas de choix de département
can_choose_dept = False
if edit:
descr.append(
(
"d",
{
"input_type": "separator",
"title": f"L'utilisateur appartient au département {auth_dept}",
},
)
)
else:
descr.append(
(
"d",
{
"input_type": "separator",
"title": f"L'utilisateur sera crée dans le département {auth_dept}",
},
)
else:
descr.append(
(
"d",
{
"input_type": "separator",
"title": f"L'utilisateur sera crée dans le département {auth_dept}",
},
)
)
descr += [
(
@ -435,9 +459,12 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
{
"title": "Date d'expiration", # j/m/a
"input_type": "datedmy",
"explanation": "j/m/a, laisser vide si pas de limite",
"explanation": "j/m/a, laisser vide si pas de limite"
if not edit_only_roles
else "",
"size": 9,
"allow_null": True,
"readonly": edit_only_roles,
},
),
(
@ -451,17 +478,20 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
"disabled_items": disabled_roles,
},
),
(
"force",
{
"title": "Ignorer les avertissements",
"input_type": "checkbox",
"explanation": "passer outre les avertissements (homonymes, etc)",
"labels": ("",),
"allowed_values": ("1",),
},
),
]
if not edit_only_roles:
descr += [
(
"force",
{
"title": "Ignorer les avertissements",
"input_type": "checkbox",
"explanation": "passer outre les avertissements (homonymes, etc)",
"labels": ("",),
"allowed_values": ("1",),
},
),
]
vals = scu.get_request_args()
if "tf_submitted" in vals and "roles" not in vals:
vals["roles"] = []
@ -491,7 +521,7 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
else:
edit = 0
try:
force = int(vals["force"][0])
force = int(vals.get("force", "0")[0])
except (IndexError, ValueError, TypeError):
force = 0
@ -511,35 +541,37 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
if err:
H.append(tf_error_message(f"""Erreur: {err}"""))
return "\n".join(H) + "\n" + tf[1] + F
ok, msg = sco_users.check_modif_user(
edit,
enforce_optionals=not force,
user_name=user_name,
nom=vals["nom"],
prenom=vals["prenom"],
email=vals["email"],
dept=vals.get("dept", auth_dept),
roles=vals["roles"],
)
if not ok:
H.append(tf_error_message(msg))
return "\n".join(H) + "\n" + tf[1] + F
if "date_expiration" in vals:
try:
if vals["date_expiration"]:
vals["date_expiration"] = datetime.datetime.strptime(
vals["date_expiration"], "%d/%m/%Y"
)
if vals["date_expiration"] < datetime.datetime.now():
H.append(tf_error_message("date expiration passée"))
return "\n".join(H) + "\n" + tf[1] + F
else:
vals["date_expiration"] = None
except ValueError:
H.append(tf_error_message("date expiration invalide"))
if not edit_only_roles:
ok_modif, msg = sco_users.check_modif_user(
edit,
enforce_optionals=not force,
user_name=user_name,
nom=vals["nom"],
prenom=vals["prenom"],
email=vals["email"],
dept=vals.get("dept", auth_dept),
roles=vals["roles"],
)
if not ok_modif:
H.append(tf_error_message(msg))
return "\n".join(H) + "\n" + tf[1] + F
if "date_expiration" in vals:
try:
if vals["date_expiration"]:
vals["date_expiration"] = datetime.datetime.strptime(
vals["date_expiration"], "%d/%m/%Y"
)
if vals["date_expiration"] < datetime.datetime.now():
H.append(tf_error_message("date expiration passée"))
return "\n".join(H) + "\n" + tf[1] + F
else:
vals["date_expiration"] = None
except ValueError:
H.append(tf_error_message("date expiration invalide"))
return "\n".join(H) + "\n" + tf[1] + F
if edit: # modif utilisateur (mais pas password ni user_name !)
if (not can_choose_dept) and "dept" in vals:
del vals["dept"]
@ -566,11 +598,14 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
vals["roles_string"] = ",".join(roles)
# ok, edit
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}")
sco_users.user_edit(user_name, vals)
flash(f"Utilisateur {user_name} modifié")
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}")
sco_users.user_edit(user_name, vals)
flash(f"Utilisateur {user_name} modifié")
else:
sco_users.user_edit(user_name, {"roles_string": vals["roles_string"]})
return flask.redirect(
url_for(
"users.user_info_page",
@ -946,14 +981,19 @@ def toggle_active_user(user_name: str = None):
"""Change active status of a user account"""
if user_name is not None: # scodoc7func converti en int !
user_name = str(user_name)
u = User.query.filter_by(user_name=user_name).first()
if not u:
raise ScoValueError("invalid user_name")
# permission check:
if not (
current_user.is_administrator()
or current_user.has_permission(Permission.ScoUsersAdmin, u.dept)
):
raise ScoPermissionDenied()
form = DeactivateUserForm()
if (
request.method == "POST" and form.cancel.data
): # if cancel button is clicked, the form.cancel.data will be True
# flash
if request.method == "POST" and form.cancel.data:
# if cancel button is clicked, the form.cancel.data will be True
return redirect(url_for("users.index_html", scodoc_dept=g.scodoc_dept))
if form.validate_on_submit():
u.active = not u.active