form edit user

This commit is contained in:
Emmanuel Viennet 2021-07-03 16:19:42 +02:00
parent fe9e5e84b0
commit 3f90b71009
6 changed files with 197 additions and 55 deletions

View File

@ -8,6 +8,7 @@ from datetime import datetime, timedelta
from hashlib import md5 from hashlib import md5
import json import json
import os import os
import re
from time import time from time import time
from flask import current_app, url_for, g from flask import current_app, url_for, g
@ -18,6 +19,7 @@ import jwt
from app import db, login from app import db, login
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -58,7 +60,7 @@ class User(UserMixin, db.Model):
and self.email == current_app.config["SCODOC_ADMIN_MAIL"] and self.email == current_app.config["SCODOC_ADMIN_MAIL"]
): ):
# super-admin # super-admin
admin_role = Role.query.filter_by(name="Admin").first() admin_role = Role.query.filter_by(name="SuperAdmin").first()
assert admin_role assert admin_role
self.add_role(admin_role, None) self.add_role(admin_role, None)
db.session.commit() db.session.commit()
@ -123,7 +125,7 @@ class User(UserMixin, db.Model):
"last_seen": self.last_seen.isoformat() + "Z", "last_seen": self.last_seen.isoformat() + "Z",
"nom": (self.nom or "").encode("utf-8"), # sco8 "nom": (self.nom or "").encode("utf-8"), # sco8
"prenom": (self.prenom or "").encode("utf-8"), # sco8 "prenom": (self.prenom or "").encode("utf-8"), # sco8
"roles_string": self.get_roles_string(), "roles_string": self.get_roles_string(), # eg "Ens_RT, Ens_Info"
"user_name": self.user_name.encode("utf-8"), # sco8 "user_name": self.user_name.encode("utf-8"), # sco8
} }
if include_email: if include_email:
@ -131,11 +133,24 @@ class User(UserMixin, db.Model):
return data return data
def from_dict(self, data, new_user=False): def from_dict(self, data, new_user=False):
for field in ["user_name", "non", "prenom", "dept", "status", "email"]: """Set users' attributes from given dict values.
Roles must be encodes as "roles_string", like "Ens_RT, Secr_CJ"
"""
for field in ["nom", "prenom", "dept", "status", "email"]:
if field in data: if field in data:
setattr(self, field, data[field]) setattr(self, field, data[field])
if new_user and "password" in data: if new_user:
self.set_password(data["password"]) if "user_name" in data:
# never change name of existing users
self.user_name = data["user_name"]
if "password" in data:
self.set_password(data["password"])
# Roles: roles_string is "Ens_RT, Secr_RT, ..."
if "roles_string" in data:
self.user_roles = []
for r_d in data["roles_string"].split(","):
role, dept = UserRole.role_dept_from_string(r_d)
self.add_role(role, dept)
def get_token(self, expires_in=3600): def get_token(self, expires_in=3600):
now = datetime.utcnow() now = datetime.utcnow()
@ -163,7 +178,7 @@ class User(UserMixin, db.Model):
Args: Args:
perm: integer, one of the value defined in Permission class. perm: integer, one of the value defined in Permission class.
dept: dept id (eg 'RT') dept: dept id (eg 'RT'), default to current departement.
""" """
if not self.active: if not self.active:
return False return False
@ -195,20 +210,23 @@ class User(UserMixin, db.Model):
self.add_role(role, dept) self.add_role(role, dept)
def set_roles(self, roles, dept): def set_roles(self, roles, dept):
"set roles in the given dept"
self.user_roles = [UserRole(user=self, role=r, dept=dept) for r in roles] self.user_roles = [UserRole(user=self, role=r, dept=dept) for r in roles]
def get_roles(self): def get_roles(self):
"iterator on my roles"
for role in self.roles: for role in self.roles:
yield role yield role
def get_roles_string(self): def get_roles_string(self):
"""string repr. of user's roles (with depts) """string repr. of user's roles (with depts)
e.g. "EnsRT, EnsInfo, SecrCJ" e.g. "Ens_RT, Ens_Info, Secr_CJ"
""" """
return ", ".join("{r.role.name}{r.dept}".format(r=r) for r in self.user_roles) return ",".join("{r.role.name}_{r.dept}".format(r=r) for r in self.user_roles)
def is_administrator(self): def is_administrator(self):
return self.active and self.has_permission(Permission.ScoSuperAdmin, None) "True if i'm an active SuperAdmin"
return self.active and self.has_permission(Permission.ScoSuperAdmin, dept=None)
# Some useful strings: # Some useful strings:
def get_nomplogin(self): def get_nomplogin(self):
@ -311,6 +329,21 @@ class UserRole(db.Model):
def __repr__(self): def __repr__(self):
return "<UserRole u={} r={} dept={}>".format(self.user, self.role, self.dept) return "<UserRole u={} r={} dept={}>".format(self.user, self.role, self.dept)
@staticmethod
def role_dept_from_string(role_dept):
"""Return tuple (role, dept) from the string
role_dept, of the forme "Role_Dept".
role is a Role instance, dept is a string.
"""
fields = role_dept.split("_", 1) # maxsplit=1, le dept peut contenir un "_"
if len(fields) != 2:
raise ScoValueError("Invalid role_dept")
role_name, dept = fields
role = Role.query.filter_by(name=role_name).first()
if role is None:
raise ScoValueError("role %s does not exists" % role_name)
return (role, dept)
@login.user_loader @login.user_loader
def load_user(id): def load_user(id):

View File

@ -51,7 +51,7 @@ class ZRequest(object):
self.AUTHENTICATED_USER = current_user self.AUTHENTICATED_USER = current_user
self.REMOTE_ADDR = request.remote_addr self.REMOTE_ADDR = request.remote_addr
if request.method == "POST": if request.method == "POST":
self.form = request.form # xxx encode en utf-8 ! # self.form = request.form # xxx encode en utf-8 !
# Encode en utf-8 pour ScoDoc8 #sco8 # Encode en utf-8 pour ScoDoc8 #sco8
self.form = {k: v.encode("utf-8") for (k, v) in request.form.items()} self.form = {k: v.encode("utf-8") for (k, v) in request.form.items()}
if request.files: if request.files:
@ -59,6 +59,10 @@ class ZRequest(object):
# request.form is a werkzeug.datastructures.ImmutableMultiDict # request.form is a werkzeug.datastructures.ImmutableMultiDict
# self.form = self.form.copy() # self.form = self.form.copy()
self.form.update(request.files) self.form.update(request.files)
# self.cf = request.form.copy()
for k in request.form:
if k.endswith(":list"):
self.form[k[:-5]] = request.form.getlist(k)
elif request.method == "GET": elif request.method == "GET":
# Encode en utf-8 pour ScoDoc8 #sco8 # Encode en utf-8 pour ScoDoc8 #sco8
self.form = {k: v.encode("utf-8") for (k, v) in request.args.items()} self.form = {k: v.encode("utf-8") for (k, v) in request.args.items()}

View File

@ -37,6 +37,7 @@ from app.scodoc.notes_log import log
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoException from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoException
from app.scodoc import sco_excel from app.scodoc import sco_excel
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_users
TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept") TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
@ -112,7 +113,7 @@ def import_users(U, auth_dept="", context=None):
created = [] # liste de uid créés created = [] # liste de uid créés
try: try:
for u in U: for u in U:
ok, msg = context._check_modif_user( ok, msg = sco_users.check_modif_user(
0, 0,
user_name=u["user_name"], user_name=u["user_name"],
nom=u["nom"], nom=u["nom"],

View File

@ -42,6 +42,7 @@ from flask_login import current_user
import cracklib # pylint: disable=import-error import cracklib # pylint: disable=import-error
from app import db
from app.auth.models import Permission from app.auth.models import Permission
from app.auth.models import User from app.auth.models import User
@ -50,7 +51,6 @@ from app.scodoc import html_sco_header
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_excel from app.scodoc import sco_excel
from app.scodoc import sco_import_users
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log from app.scodoc.notes_log import log
@ -164,7 +164,7 @@ def list_users(
users = get_user_list(dept=dept, with_inactives=with_inactives) users = get_user_list(dept=dept, with_inactives=with_inactives)
comm = "dept. %s" % dept.encode(scu.SCO_ENCODING) # sco8 comm = "dept. %s" % dept.encode(scu.SCO_ENCODING) # sco8
else: else:
r = get_user_list(with_inactives=with_inactives) users = get_user_list(with_inactives=with_inactives)
comm = "tous" comm = "tous"
if with_inactives: if with_inactives:
comm += ", avec anciens" comm += ", avec anciens"
@ -410,3 +410,62 @@ def user_info_page(context, user_name=None, REQUEST=None):
% url_for("users.index_html", scodoc_dept=g.scodoc_dept) % url_for("users.index_html", scodoc_dept=g.scodoc_dept)
) )
return scu.sco8_join(H) + F return scu.sco8_join(H) + F
def check_modif_user(edit, user_name="", nom="", prenom="", email="", roles=[]):
"""Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1)
Cherche homonymes.
returns (ok, msg)
- ok : si vrai, peut continuer avec ces parametres
(si ok est faux, l'utilisateur peut quand même forcer la creation)
- msg: message warning a presenter l'utilisateur
"""
if not user_name or not nom or not prenom:
return False, "champ requis vide"
if not email:
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
# ce login existe ?
users = _user_list(user_name)
if edit and not users: # safety net, le user_name ne devrait pas changer
return False, "identifiant %s inexistant" % user_name
if not edit and users:
return False, "identifiant %s déjà utilisé" % user_name
# Des noms/prénoms semblables existent ?
nom = nom.lower().strip()
prenom = prenom.lower().strip()
similar_users = User.query.filter(
User.nom.ilike(nom), User.prenom.ilike(prenom)
).all()
if edit:
minmatch = 1
else:
minmatch = 0
if len(similar_users) > minmatch:
return (
False,
"des utilisateurs proches existent: "
+ ", ".join(
[
"%s %s (pseudo=%s)" % (x.prenom, x.nom, x.user_name)
for x in similar_users
]
),
)
# Roles ?
if not roles:
return False, "aucun rôle sélectionné, êtes vous sûr ?"
# ok
return True, ""
def user_edit(user_name, vals):
"""Edit the user specified by user_name
(ported from Zope to SQLAlchemy, hence strange !)
"""
u = User.query.filter_by(user_name=user_name).first()
if not u:
raise ScoValueError("Invalid user_name")
u.from_dict(vals)
db.session.add(u)
db.session.commit()

View File

@ -33,7 +33,7 @@ Vues s'appuyant sur auth et sco_users
Emmanuel Viennet, 2021 Emmanuel Viennet, 2021
""" """
import re
import jaxml import jaxml
from flask import g from flask import g
@ -44,6 +44,8 @@ from app import db
from app.auth.models import Permission from app.auth.models import Permission
from app.auth.models import User from app.auth.models import User
from app.auth.models import Role
from app.auth.models import UserRole
from app.decorators import ( from app.decorators import (
scodoc7func, scodoc7func,
ScoDoc7Context, ScoDoc7Context,
@ -57,9 +59,11 @@ from app.scodoc import html_sco_header
from app.scodoc import sco_users from app.scodoc import sco_users
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.notes_log import log from app.scodoc.notes_log import log
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_permissions_check import can_handle_passwd from app.scodoc.sco_permissions_check import can_handle_passwd
from app.scodoc.sco_exceptions import AccessDenied from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.views import users_bp as bp from app.views import users_bp as bp
from scodoc_manager import sco_mgr
context = ScoDoc7Context("users") # sco8 context = ScoDoc7Context("users") # sco8
@ -100,7 +104,10 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
if edit: if edit:
if not user_name: if not user_name:
raise ValueError("missing argument: user_name") raise ValueError("missing argument: user_name")
initvalues = sco_users._user_list(user_name) u = User.query.filter_by(user_name=user_name).first()
if not u:
raise ScoValueError("utilisateur inexistant")
initvalues = u.to_dict()
H.append("<h2>Modification de l'utilisateur %s</h2>" % user_name) H.append("<h2>Modification de l'utilisateur %s</h2>" % user_name)
else: else:
H.append("<h2>Création d'un utilisateur</h2>") H.append("<h2>Création d'un utilisateur</h2>")
@ -110,42 +117,67 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
H.append("""<p class="warning">Vous êtes super administrateur !</p>""") H.append("""<p class="warning">Vous êtes super administrateur !</p>""")
is_super_admin = True is_super_admin = True
# Noms de roles pouvant etre attribues aux utilisateurs via ce dialogue # Les rôles standards créés à l'initialisation de ScoDoc:
# si pas SuperAdmin, restreint aux rôles EnsX, SecrX, AdminX standard_roles = [Role.get_named_role(r) for r in (u"Ens", u"Secr", u"Admin")]
# # Rôles pouvant etre attribués aux utilisateurs via ce dialogue:
editable_roles = Role.query.all() # si SuperAdmin, tous les rôles standards dans tous les départements
# sinon, les départements dans lesquels l'utilisateur a le droit
if is_super_admin: if is_super_admin:
log("create_user_form called by %s (super admin)" % (current_user.user_name,)) log("create_user_form called by %s (super admin)" % (current_user.user_name,))
dept_ids = sco_mgr.get_dept_ids()
else: else:
editable_roles = [ # Si on n'est pas SuperAdmin, liste les départements dans lesquels on a la
r for r in editable_roles if r.name in {u"Ens", u"Secr", u"Admin"} # permission ScoUsersAdmin
] dept_ids = sorted(
set(
[
x.dept
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 dept_ids}
# #
if not edit: if not edit:
submitlabel = "Créer utilisateur" submitlabel = "Créer utilisateur"
orig_roles = set() orig_roles = set()
else: else:
submitlabel = "Modifier utilisateur" submitlabel = "Modifier utilisateur"
initvalues["roles"] = initvalues["roles"].split(",") or [] if "roles_string" in initvalues:
orig_roles = set(initvalues["roles"]) initvalues["roles"] = initvalues["roles_string"].split(",")
if initvalues["status"] == "old": else:
editable_roles = set() # can't change roles of a disabled user initvalues["roles"] = []
orig_roles = { # set des roles existants avant édition
UserRole.role_dept_from_string(role_dept)
for role_dept in initvalues["roles"]
}
if not initvalues["active"]:
editable_roles_set = set() # can't change roles of a disabled user
editable_roles_strings = {r.name + "_" + dept for (r, dept) in editable_roles_set}
orig_roles_strings = {r.name + "_" + dept for (r, dept) in orig_roles}
# add existing user roles # add existing user roles
displayed_roles = list(editable_roles.union(orig_roles)) displayed_roles = list(editable_roles_set.union(orig_roles))
displayed_roles.sort() displayed_roles.sort(key=lambda x: (x[1], x[0].name))
displayed_roles_strings = [r.name + "_" + dept for (r, dept) in displayed_roles]
displayed_roles_labels = [
"{dept}: {r.name}".format(dept=dept, r=r) for (r, dept) in displayed_roles
]
disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer
for i in range(len(displayed_roles)): for i in range(len(displayed_roles_strings)):
if displayed_roles[i] not in editable_roles: if displayed_roles_strings[i] not in editable_roles_strings:
disabled_roles[i] = True disabled_roles[i] = True
# log('create_user_form: displayed_roles=%s' % displayed_roles) # stop() # XXX
descr = [ descr = [
("edit", {"input_type": "hidden", "default": edit}), ("edit", {"input_type": "hidden", "default": edit}),
("nom", {"title": "Nom", "size": 20, "allow_null": False}), ("nom", {"title": "Nom", "size": 20, "allow_null": False}),
("prenom", {"title": "Prénom", "size": 20, "allow_null": False}), ("prenom", {"title": "Prénom", "size": 20, "allow_null": False}),
] ]
if auth_name != user_name: # no one can't change its own status if current_user.user_name != user_name:
# no one can change its own status
descr.append( descr.append(
( (
"status", "status",
@ -193,7 +225,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
"user_name", "user_name",
{"input_type": "hidden", "default": initvalues["user_name"]}, {"input_type": "hidden", "default": initvalues["user_name"]},
), ),
("user_id", {"input_type": "hidden", "default": initvalues["user_id"]}), ("user_name", {"input_type": "hidden", "default": initvalues["user_name"]}),
] ]
descr += [ descr += [
( (
@ -267,7 +299,8 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
"title": "Rôles", "title": "Rôles",
"input_type": "checkbox", "input_type": "checkbox",
"vertical": True, "vertical": True,
"allowed_values": displayed_roles, "labels": displayed_roles_labels,
"allowed_values": displayed_roles_strings,
"disabled_items": disabled_roles, "disabled_items": disabled_roles,
}, },
), ),
@ -282,14 +315,15 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
}, },
), ),
] ]
# stop() # XXX
if "tf-submitted" in REQUEST.form and not "roles" in REQUEST.form: if "tf-submitted" in REQUEST.form and not "roles" in REQUEST.form:
REQUEST.form["roles"] = [] REQUEST.form["roles"] = []
if "tf-submitted" in REQUEST.form: if "tf-submitted" in REQUEST.form:
# Ajoute roles existants mais non modifiables (disabled dans le form) # Ajoute roles existants mais non modifiables (disabled dans le form)
# orig_roles - editable_roles
REQUEST.form["roles"] = list( REQUEST.form["roles"] = list(
set(REQUEST.form["roles"]).union(orig_roles - editable_roles) set(REQUEST.form["roles"]).union(
orig_roles_strings - editable_roles_strings
)
) )
tf = TrivialFormulator( tf = TrivialFormulator(
@ -306,7 +340,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
return REQUEST.RESPONSE.redirect(context.UsersURL()) return REQUEST.RESPONSE.redirect(context.UsersURL())
else: else:
vals = tf[2] vals = tf[2]
roles = set(vals["roles"]).intersection(editable_roles) roles = set(vals["roles"]).intersection(editable_roles_strings)
if REQUEST.form.has_key("edit"): if REQUEST.form.has_key("edit"):
edit = int(REQUEST.form["edit"]) edit = int(REQUEST.form["edit"])
else: else:
@ -322,7 +356,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
user_name = vals["user_name"] user_name = vals["user_name"]
# ce login existe ? # ce login existe ?
err = None err = None
users = _user_list(user_name) users = sco_users._user_list(user_name)
if edit and not users: # safety net, le user_name ne devrait pas changer if edit and not users: # safety net, le user_name ne devrait pas changer
err = "identifiant %s inexistant" % user_name err = "identifiant %s inexistant" % user_name
if not edit and users: if not edit and users:
@ -332,7 +366,8 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
return "\n".join(H) + "\n" + tf[1] + F return "\n".join(H) + "\n" + tf[1] + F
if not force: if not force:
ok, msg = context._check_modif_user( # XXX x = stop()
ok, msg = sco_users.check_modif_user(
edit, edit,
user_name=user_name, user_name=user_name,
nom=vals["nom"], nom=vals["nom"],
@ -350,7 +385,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
return "\n".join(H) + "\n" + tf[1] + F return "\n".join(H) + "\n" + tf[1] + F
if edit: # modif utilisateur (mais pas passwd) if edit: # modif utilisateur (mais pas passwd ni user_name !)
if (not can_choose_dept) and vals.has_key("dept"): if (not can_choose_dept) and vals.has_key("dept"):
del vals["dept"] del vals["dept"]
if vals.has_key("passwd"): if vals.has_key("passwd"):
@ -359,24 +394,24 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
del vals["date_modif_passwd"] del vals["date_modif_passwd"]
if vals.has_key("user_name"): if vals.has_key("user_name"):
del vals["user_name"] del vals["user_name"]
if (auth_name == user_name) and vals.has_key("status"): if (current_user.user_name == user_name) and vals.has_key("status"):
del vals["status"] # no one can't change its own status del vals["status"] # no one can't change its own status
# traitement des roles: ne doit pas affecter les roles # traitement des roles: ne doit pas affecter les roles
# que l'on en controle pas: # que l'on en controle pas:
for role in orig_roles: for role in orig_roles_strings: # { "Ens_RT", "Secr_CJ", ... }
if role and not role in editable_roles: if role and not role in editable_roles_strings:
roles.add(role) roles.add(role)
vals["roles"] = ",".join(roles) vals["roles_string"] = ",".join(roles)
# ok, edit # ok, edit
log("sco_users: editing %s by %s" % (user_name, auth_name)) log("sco_users: editing %s by %s" % (user_name, current_user.user_name))
# log('sco_users: previous_values=%s' % initvalues) log("sco_users: previous_values=%s" % initvalues)
# log('sco_users: new_values=%s' % vals) log("sco_users: new_values=%s" % vals)
context._user_edit(user_name, vals) sco_users.user_edit(user_name, vals)
return REQUEST.RESPONSE.redirect( return REQUEST.RESPONSE.redirect(
"userinfo?user_name=%s&head_message=Utilisateur %s modifié" "user_info_page?user_name=%s&head_message=Utilisateur %s modifié"
% (user_name, user_name) % (user_name, user_name)
) )
else: # creation utilisateur else: # creation utilisateur
@ -399,8 +434,18 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
if not can_choose_dept: if not can_choose_dept:
vals["dept"] = auth_dept vals["dept"] = auth_dept
# ok, go # ok, go
log("sco_users: new_user %s by %s" % (vals["user_name"], auth_name)) log(
context.create_user(vals, REQUEST=REQUEST) "sco_users: new_user %s by %s"
% (vals["user_name"], current_user.user_name)
)
u = User()
u.from_dict(vals, new_user=True)
db.session.add(u)
db.session.commit()
return REQUEST.RESPONSE.redirect(
"user_info_page?user_name=%s&head_message=Nouvel utilisateur créé"
% (user_name)
)
@bp.route("/import_users_form") @bp.route("/import_users_form")

View File

@ -59,8 +59,8 @@ class ScoDocManager:
return self.dept_descriptions[dept_id].db_uri return self.dept_descriptions[dept_id].db_uri
def get_dept_ids(self): def get_dept_ids(self):
"get (unsorted) dept ids" "get (sorted) dept ids"
return self.dept_descriptions.keys() return sorted(self.dept_descriptions.keys())
def get_db_uri(self): def get_db_uri(self):
""" """