forked from ScoDoc/ScoDoc
API: users, WIP
This commit is contained in:
parent
dae762c3b1
commit
a069280746
171
app/api/users.py
171
app/api/users.py
@ -21,6 +21,7 @@ from app.decorators import scodoc, permission_required
|
||||
from app.models import Departement
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
@bp.route("/user/<int:uid>")
|
||||
@ -78,4 +79,172 @@ def users_info_query():
|
||||
)
|
||||
|
||||
query = query.order_by(User.user_name)
|
||||
return jsonify([u.to_dict() for u in query])
|
||||
return jsonify([user.to_dict() for user in query])
|
||||
|
||||
|
||||
@bp.route("/user/create", methods=["POST"])
|
||||
@api_web_bp.route("/user/create", methods=["POST"])
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoUsersAdmin)
|
||||
def user_create():
|
||||
"""Création d'un utilisateur
|
||||
The request content type should be "application/json":
|
||||
{
|
||||
"user_name": str,
|
||||
"dept": str or null,
|
||||
"nom": str,
|
||||
"prenom": str,
|
||||
"active":bool (default True)
|
||||
}
|
||||
"""
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
user_name = data.get("user_name")
|
||||
if not user_name:
|
||||
return error_response(404, "empty user_name")
|
||||
user = User.query.filter_by(user_name=user_name).first()
|
||||
if user:
|
||||
return error_response(404, f"user_create: user {user} already exists\n")
|
||||
dept = data.get("dept")
|
||||
if dept == "@all":
|
||||
dept = None
|
||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin)
|
||||
if dept not in allowed_depts:
|
||||
return error_response(403, "user_create: departement non autorise")
|
||||
if (dept is not None) and (
|
||||
Departement.query.filter_by(acronym=dept).first() is None
|
||||
):
|
||||
return error_response(404, "user_create: departement inexistant")
|
||||
nom = data.get("nom")
|
||||
prenom = data.get("prenom")
|
||||
active = scu.to_bool(data.get("active", True))
|
||||
user = User(user_name=user_name, active=active, dept=dept, nom=nom, prenom=prenom)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return jsonify(user.to_dict())
|
||||
|
||||
|
||||
@bp.route("/user/edit/<int:uid>", methods=["POST"])
|
||||
@api_web_bp.route("/user/edit/<int:uid>", methods=["POST"])
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoUsersAdmin)
|
||||
def user_edit(uid: int):
|
||||
"""Modification d'un utilisateur
|
||||
Champs modifiables:
|
||||
{
|
||||
"dept": str or null,
|
||||
"nom": str,
|
||||
"prenom": str,
|
||||
"active":bool
|
||||
}
|
||||
"""
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
user: User = User.query.get_or_404(uid)
|
||||
# L'utilisateur doit avoir le droit dans le département de départ et celui d'arrivée
|
||||
orig_dept = user.dept
|
||||
dest_dept = data.get("dept", False)
|
||||
if dest_dept is not False:
|
||||
if dest_dept == "@all":
|
||||
dest_dept = None
|
||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin)
|
||||
if (None not in allowed_depts) and (
|
||||
(orig_dept not in allowed_depts) or (dest_dept not in allowed_depts)
|
||||
):
|
||||
return error_response(403, "user_edit: departement non autorise")
|
||||
if dest_dept != orig_dept:
|
||||
if (dest_dept is not None) and (
|
||||
Departement.query.filter_by(acronym=dest_dept).first() is None
|
||||
):
|
||||
return error_response(404, "user_edit: departement inexistant")
|
||||
user.dept = dest_dept
|
||||
|
||||
user.nom = data.get("nom", user.nom)
|
||||
user.prenom = data.get("prenom", user.prenom)
|
||||
user.active = scu.to_bool(data.get("active", user.active))
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return jsonify(user.to_dict())
|
||||
|
||||
|
||||
@bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"])
|
||||
@api_web_bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"])
|
||||
@bp.route(
|
||||
"/user/<int:uid>/role/<string:role_name>/add/departement/<string:dept>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@api_web_bp.route(
|
||||
"/user/<int:uid>/role/<string:role_name>/add/departement/<string:dept>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
def user_role_add(uid: int, role_name: str, dept: str = None):
|
||||
"""Add a role to the user"""
|
||||
user: User = User.query.get_or_404(uid)
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
if dept is not None: # check
|
||||
_ = Departement.query.filter_by(acronym=dept).first_or_404()
|
||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoSuperAdmin)
|
||||
if (None not in allowed_depts) and (dept not in allowed_depts):
|
||||
return error_response(403, "user_role_add: departement non autorise")
|
||||
user.add_role(role, dept)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return jsonify(user.to_dict())
|
||||
|
||||
|
||||
@bp.route("/user/<int:uid>/role/<string:role_name>/remove", methods=["POST"])
|
||||
@api_web_bp.route("/user/<int:uid>/role/<string:role_name>/remove", methods=["POST"])
|
||||
@bp.route(
|
||||
"/user/<int:uid>/role/<string:role_name>/remove/departement/<string:dept>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@api_web_bp.route(
|
||||
"/user/<int:uid>/role/<string:role_name>/remove/departement/<string:dept>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
def user_role_remove(uid: int, role_name: str, dept: str = None):
|
||||
"""Remove the role from the user"""
|
||||
user: User = User.query.get_or_404(uid)
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
if dept is not None: # check
|
||||
_ = Departement.query.filter_by(acronym=dept).first_or_404()
|
||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoSuperAdmin)
|
||||
if (None not in allowed_depts) and (dept not in allowed_depts):
|
||||
return error_response(403, "user_role_remove: departement non autorise")
|
||||
|
||||
query = UserRole.query.filter(UserRole.role == role, UserRole.user == user)
|
||||
if dept is not None:
|
||||
query = query.filter(UserRole.dept == dept)
|
||||
user_role = query.first()
|
||||
if user_role:
|
||||
db.session.delete(user_role)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return jsonify(user.to_dict())
|
||||
|
||||
|
||||
@bp.route("/roles")
|
||||
@api_web_bp.route("/roles")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def list_roles():
|
||||
"""Tous les rôles définis"""
|
||||
return jsonify([role.to_dict() for role in Role.query])
|
||||
|
||||
|
||||
@bp.route("/permissions")
|
||||
@api_web_bp.route("/permissions")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def list_permissions():
|
||||
"""Liste des noms de permissions définies"""
|
||||
return jsonify(list(Permission.permission_by_name.keys()))
|
||||
|
@ -428,6 +428,14 @@ class Role(db.Model):
|
||||
def __str__(self):
|
||||
return f"{self.name}: perm={', '.join(Permission.permissions_names(self.permissions))}"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"As dict. Convert permissions to names."
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"permissions": Permission.permissions_names(self.permissions),
|
||||
}
|
||||
|
||||
def add_permission(self, perm):
|
||||
self.permissions |= perm
|
||||
|
||||
|
@ -600,22 +600,6 @@ def float_null_is_null(x):
|
||||
return float(x)
|
||||
|
||||
|
||||
BOOL_STR = {
|
||||
"": False,
|
||||
"false": False,
|
||||
"0": False,
|
||||
"1": True,
|
||||
"true": True,
|
||||
}
|
||||
|
||||
|
||||
def bool_or_str(x) -> bool:
|
||||
"""a boolean, may also be encoded as a string "0", "False", "1", "True" """
|
||||
if isinstance(x, str):
|
||||
return BOOL_STR[x.lower()]
|
||||
return bool(x)
|
||||
|
||||
|
||||
# post filtering
|
||||
#
|
||||
def UniqListofDicts(L, key):
|
||||
|
@ -42,7 +42,6 @@ from app.scodoc.scolog import logdb
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_find_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
@ -807,7 +806,7 @@ def ListeAbsEtud(
|
||||
sco_year: année scolaire à utiliser. Si non spécifier, utilie l'année en cours. e.g. "2005"
|
||||
"""
|
||||
# si absjust_only, table absjust seule (export xls ou pdf)
|
||||
absjust_only = ndb.bool_or_str(absjust_only)
|
||||
absjust_only = scu.to_bool(absjust_only)
|
||||
datedebut = "%s-08-01" % scu.AnneeScolaire(sco_year=sco_year)
|
||||
etudid = etudid or False
|
||||
etuds = sco_etud.get_etud_info(etudid=etudid, code_nip=code_nip, filled=True)
|
||||
|
@ -91,7 +91,7 @@ _ueEditor = ndb.EditableTable(
|
||||
sortkey="numero",
|
||||
input_formators={
|
||||
"type": ndb.int_null_is_zero,
|
||||
"is_external": ndb.bool_or_str,
|
||||
"is_external": scu.to_bool,
|
||||
"ects": ndb.float_null_is_null,
|
||||
},
|
||||
output_formators={
|
||||
|
@ -9,7 +9,7 @@
|
||||
_SCO_PERMISSIONS = (
|
||||
# permission bit, symbol, description
|
||||
# ScoSuperAdmin est utilisé pour:
|
||||
# - ZScoDoc: add/delete departments
|
||||
# - add/delete departments
|
||||
# - tous rôles lors creation utilisateurs
|
||||
(1 << 1, "ScoSuperAdmin", "Super Administrateur"),
|
||||
(1 << 2, "APIView", "Voir (obsolete, use ScoView)"), # deprecated
|
||||
|
@ -638,6 +638,22 @@ def is_valid_filename(filename):
|
||||
return VALID_EXP.match(filename)
|
||||
|
||||
|
||||
BOOL_STR = {
|
||||
"": False,
|
||||
"false": False,
|
||||
"0": False,
|
||||
"1": True,
|
||||
"true": True,
|
||||
}
|
||||
|
||||
|
||||
def to_bool(x) -> bool:
|
||||
"""a boolean, may also be encoded as a string "0", "False", "1", "True" """
|
||||
if isinstance(x, str):
|
||||
return BOOL_STR.get(x.lower().strip(), True)
|
||||
return bool(x)
|
||||
|
||||
|
||||
def bul_filename_old(sem: dict, etud: dict, format):
|
||||
"""Build a filename for this bulletin"""
|
||||
dt = time.strftime("%Y-%m-%d")
|
||||
|
@ -337,7 +337,8 @@ def user_role(username, dept_acronym=None, add_role_name=None, remove_role_name=
|
||||
user_role = UserRole.query.filter(
|
||||
UserRole.role == role, UserRole.user == user, UserRole.dept == dept_acronym
|
||||
).first()
|
||||
db.session.delete(user_role)
|
||||
if user_role:
|
||||
db.session.delete(user_role)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@ -352,8 +353,8 @@ def abort_if_false(ctx, param, value):
|
||||
is_flag=True,
|
||||
callback=abort_if_false,
|
||||
expose_value=False,
|
||||
prompt=f"""Attention: Cela va effacer toutes les données du département
|
||||
(étudiants, notes, formations, etc)
|
||||
prompt="""Attention: Cela va effacer toutes les données du département
|
||||
(étudiants, notes, formations, etc).
|
||||
Voulez-vous vraiment continuer ?
|
||||
""",
|
||||
)
|
||||
|
@ -63,3 +63,45 @@ def test_list_users(api_admin_headers):
|
||||
# (test, other et u_TAPI)
|
||||
# plus l'utilisateur de chaque département jusqu'au leur
|
||||
# (u_AA voit AA, u_BB voit AA et BB, etc)
|
||||
|
||||
|
||||
def test_edit_users(api_admin_headers):
|
||||
"""
|
||||
Routes: /user/create
|
||||
/user/edit/<int:uid>
|
||||
"""
|
||||
admin_h = api_admin_headers
|
||||
nb_users = len(GET("/users/query", headers=admin_h))
|
||||
user = POST_JSON(
|
||||
"/user/create",
|
||||
{"user_name": "toto", "nom": "Toto"},
|
||||
headers=admin_h,
|
||||
)
|
||||
assert user["user_name"] == "toto"
|
||||
assert user["dept"] is None
|
||||
assert user["active"] is True
|
||||
assert nb_users == 1 + len(GET("/users/query", headers=admin_h))
|
||||
# Change le dept et rend inactif
|
||||
user = POST_JSON(
|
||||
f"/user/edit/{user['id']}",
|
||||
{"active": False, "dept": "TAPI"},
|
||||
headers=admin_h,
|
||||
)
|
||||
assert user["dept"] is "TAPI"
|
||||
assert user["active"] is False
|
||||
|
||||
|
||||
def test_roles(api_admin_headers):
|
||||
"""
|
||||
Routes: /user/create
|
||||
/user/edit/<int:uid>
|
||||
"""
|
||||
admin_h = api_admin_headers
|
||||
user = POST_JSON(
|
||||
"/user/create",
|
||||
{"user_name": "test_roles", "nom": "Role", "prenom": "Test"},
|
||||
headers=admin_h,
|
||||
)
|
||||
uid = user["id"]
|
||||
ans = POST_JSON(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
||||
assert ans["user_name"] == "test_roles"
|
||||
|
Loading…
Reference in New Issue
Block a user