1
0
forked from ScoDoc/ScoDoc

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
import json
import os
import re
from time import time
from flask import current_app, url_for, g
@ -18,6 +19,7 @@ import jwt
from app import db, login
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
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"]
):
# super-admin
admin_role = Role.query.filter_by(name="Admin").first()
admin_role = Role.query.filter_by(name="SuperAdmin").first()
assert admin_role
self.add_role(admin_role, None)
db.session.commit()
@ -123,7 +125,7 @@ class User(UserMixin, db.Model):
"last_seen": self.last_seen.isoformat() + "Z",
"nom": (self.nom 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
}
if include_email:
@ -131,11 +133,24 @@ class User(UserMixin, db.Model):
return data
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:
setattr(self, field, data[field])
if new_user and "password" in data:
if new_user:
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):
now = datetime.utcnow()
@ -163,7 +178,7 @@ class User(UserMixin, db.Model):
Args:
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:
return False
@ -195,20 +210,23 @@ class User(UserMixin, db.Model):
self.add_role(role, 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]
def get_roles(self):
"iterator on my roles"
for role in self.roles:
yield role
def get_roles_string(self):
"""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):
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:
def get_nomplogin(self):
@ -311,6 +329,21 @@ class UserRole(db.Model):
def __repr__(self):
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
def load_user(id):

View File

@ -51,7 +51,7 @@ class ZRequest(object):
self.AUTHENTICATED_USER = current_user
self.REMOTE_ADDR = request.remote_addr
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
self.form = {k: v.encode("utf-8") for (k, v) in request.form.items()}
if request.files:
@ -59,6 +59,10 @@ class ZRequest(object):
# request.form is a werkzeug.datastructures.ImmutableMultiDict
# self.form = self.form.copy()
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":
# Encode en utf-8 pour ScoDoc8 #sco8
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 import sco_excel
from app.scodoc import sco_preferences
from app.scodoc import sco_users
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
try:
for u in U:
ok, msg = context._check_modif_user(
ok, msg = sco_users.check_modif_user(
0,
user_name=u["user_name"],
nom=u["nom"],

View File

@ -42,6 +42,7 @@ from flask_login import current_user
import cracklib # pylint: disable=import-error
from app import db
from app.auth.models import Permission
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_etud
from app.scodoc import sco_excel
from app.scodoc import sco_import_users
from app.scodoc import sco_preferences
from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log
@ -164,7 +164,7 @@ def list_users(
users = get_user_list(dept=dept, with_inactives=with_inactives)
comm = "dept. %s" % dept.encode(scu.SCO_ENCODING) # sco8
else:
r = get_user_list(with_inactives=with_inactives)
users = get_user_list(with_inactives=with_inactives)
comm = "tous"
if with_inactives:
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)
)
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
"""
import re
import jaxml
from flask import g
@ -44,6 +44,8 @@ from app import db
from app.auth.models import Permission
from app.auth.models import User
from app.auth.models import Role
from app.auth.models import UserRole
from app.decorators import (
scodoc7func,
ScoDoc7Context,
@ -57,9 +59,11 @@ from app.scodoc import html_sco_header
from app.scodoc import sco_users
from app.scodoc import sco_utils as scu
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_exceptions import AccessDenied
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.views import users_bp as bp
from scodoc_manager import sco_mgr
context = ScoDoc7Context("users") # sco8
@ -100,7 +104,10 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
if edit:
if not 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)
else:
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>""")
is_super_admin = True
# Noms de roles pouvant etre attribues aux utilisateurs via ce dialogue
# si pas SuperAdmin, restreint aux rôles EnsX, SecrX, AdminX
#
editable_roles = Role.query.all()
# Les rôles standards créés à l'initialisation de ScoDoc:
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:
# 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:
log("create_user_form called by %s (super admin)" % (current_user.user_name,))
dept_ids = sco_mgr.get_dept_ids()
else:
editable_roles = [
r for r in editable_roles if r.name in {u"Ens", u"Secr", u"Admin"}
# Si on n'est pas SuperAdmin, liste les départements dans lesquels on a la
# 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:
submitlabel = "Créer utilisateur"
orig_roles = set()
else:
submitlabel = "Modifier utilisateur"
initvalues["roles"] = initvalues["roles"].split(",") or []
orig_roles = set(initvalues["roles"])
if initvalues["status"] == "old":
editable_roles = set() # can't change roles of a disabled user
if "roles_string" in initvalues:
initvalues["roles"] = initvalues["roles_string"].split(",")
else:
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
displayed_roles = list(editable_roles.union(orig_roles))
displayed_roles.sort()
displayed_roles = list(editable_roles_set.union(orig_roles))
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
for i in range(len(displayed_roles)):
if displayed_roles[i] not in editable_roles:
for i in range(len(displayed_roles_strings)):
if displayed_roles_strings[i] not in editable_roles_strings:
disabled_roles[i] = True
# log('create_user_form: displayed_roles=%s' % displayed_roles)
# stop() # XXX
descr = [
("edit", {"input_type": "hidden", "default": edit}),
("nom", {"title": "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(
(
"status",
@ -193,7 +225,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
"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 += [
(
@ -267,7 +299,8 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
"title": "Rôles",
"input_type": "checkbox",
"vertical": True,
"allowed_values": displayed_roles,
"labels": displayed_roles_labels,
"allowed_values": displayed_roles_strings,
"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:
REQUEST.form["roles"] = []
if "tf-submitted" in REQUEST.form:
# Ajoute roles existants mais non modifiables (disabled dans le form)
# orig_roles - editable_roles
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(
@ -306,7 +340,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
return REQUEST.RESPONSE.redirect(context.UsersURL())
else:
vals = tf[2]
roles = set(vals["roles"]).intersection(editable_roles)
roles = set(vals["roles"]).intersection(editable_roles_strings)
if REQUEST.form.has_key("edit"):
edit = int(REQUEST.form["edit"])
else:
@ -322,7 +356,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
user_name = vals["user_name"]
# ce login existe ?
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
err = "identifiant %s inexistant" % user_name
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
if not force:
ok, msg = context._check_modif_user(
# XXX x = stop()
ok, msg = sco_users.check_modif_user(
edit,
user_name=user_name,
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
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"):
del vals["dept"]
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"]
if vals.has_key("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
# traitement des roles: ne doit pas affecter les roles
# que l'on en controle pas:
for role in orig_roles:
if role and not role in editable_roles:
for role in orig_roles_strings: # { "Ens_RT", "Secr_CJ", ... }
if role and not role in editable_roles_strings:
roles.add(role)
vals["roles"] = ",".join(roles)
vals["roles_string"] = ",".join(roles)
# ok, edit
log("sco_users: editing %s by %s" % (user_name, auth_name))
# log('sco_users: previous_values=%s' % initvalues)
# log('sco_users: new_values=%s' % vals)
context._user_edit(user_name, vals)
log("sco_users: editing %s by %s" % (user_name, current_user.user_name))
log("sco_users: previous_values=%s" % initvalues)
log("sco_users: new_values=%s" % vals)
sco_users.user_edit(user_name, vals)
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)
)
else: # creation utilisateur
@ -399,8 +434,18 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
if not can_choose_dept:
vals["dept"] = auth_dept
# ok, go
log("sco_users: new_user %s by %s" % (vals["user_name"], auth_name))
context.create_user(vals, REQUEST=REQUEST)
log(
"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")

View File

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