forked from ScoDoc/ScoDoc
API: gestion utilisateurs
This commit is contained in:
parent
a069280746
commit
a053afeba6
143
app/api/users.py
143
app/api/users.py
@ -51,7 +51,7 @@ def user_info(uid: int):
|
|||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def users_info_query():
|
def users_info_query():
|
||||||
"""Utilisateurs, filtrés par dept, active ou début nom
|
"""Utilisateurs, filtrés par dept, active ou début nom
|
||||||
/users/query?departement=dept_acronym&active=1&starts_with=<str:nom>
|
/users/query?departement=dept_acronym&active=1&starts_with=<string:nom>
|
||||||
|
|
||||||
Si accès via API web, seuls les utilisateurs "accessibles" (selon les
|
Si accès via API web, seuls les utilisateurs "accessibles" (selon les
|
||||||
permissions) sont retournés: le département de l'URL est ignoré, seules
|
permissions) sont retournés: le département de l'URL est ignoré, seules
|
||||||
@ -230,21 +230,148 @@ def user_role_remove(uid: int, role_name: str, dept: str = None):
|
|||||||
return jsonify(user.to_dict())
|
return jsonify(user.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/permissions")
|
||||||
|
@api_web_bp.route("/permissions")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoUsersView)
|
||||||
|
def list_permissions():
|
||||||
|
"""Liste des noms de permissions définies"""
|
||||||
|
return jsonify(list(Permission.permission_by_name.keys()))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/role/<string:role_name>")
|
||||||
|
@api_web_bp.route("/role/<string:role_name>")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoUsersView)
|
||||||
|
def list_role(role_name: str):
|
||||||
|
"""Un rôle"""
|
||||||
|
return jsonify(Role.query.filter_by(name=role_name).first_or_404().to_dict())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/roles")
|
@bp.route("/roles")
|
||||||
@api_web_bp.route("/roles")
|
@api_web_bp.route("/roles")
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoUsersView)
|
||||||
def list_roles():
|
def list_roles():
|
||||||
"""Tous les rôles définis"""
|
"""Tous les rôles définis"""
|
||||||
return jsonify([role.to_dict() for role in Role.query])
|
return jsonify([role.to_dict() for role in Role.query])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/permissions")
|
@bp.route(
|
||||||
@api_web_bp.route("/permissions")
|
"/role/<string:role_name>/add_permission/<string:perm_name>",
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/role/<string:role_name>/add_permission/<string:perm_name>",
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
def list_permissions():
|
def role_permission_add(role_name: str, perm_name: str):
|
||||||
"""Liste des noms de permissions définies"""
|
"""Add permission to role"""
|
||||||
return jsonify(list(Permission.permission_by_name.keys()))
|
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||||
|
permission = Permission.get_by_name(perm_name)
|
||||||
|
if permission is None:
|
||||||
|
return error_response(404, "role_permission_add: permission inconnue")
|
||||||
|
role.add_permission(permission)
|
||||||
|
db.session.add(role)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify(role.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route(
|
||||||
|
"/role/<string:role_name>/remove_permission/<string:perm_name>",
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/role/<string:role_name>/remove_permission/<string:perm_name>",
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
|
def role_permission_remove(role_name: str, perm_name: str):
|
||||||
|
"""Remove permission from role"""
|
||||||
|
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||||
|
permission = Permission.get_by_name(perm_name)
|
||||||
|
if permission is None:
|
||||||
|
return error_response(404, "role_permission_remove: permission inconnue")
|
||||||
|
role.remove_permission(permission)
|
||||||
|
db.session.add(role)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify(role.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/role/<string:role_name>/create", methods=["POST"])
|
||||||
|
@api_web_bp.route("/role/<string:role_name>/create", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
|
def role_create(role_name: str):
|
||||||
|
"""Create a new role with permissions.
|
||||||
|
{
|
||||||
|
"permissions" : [ 'ScoView', ... ]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
role: Role = Role.query.filter_by(name=role_name).first()
|
||||||
|
if role:
|
||||||
|
return error_response(404, "role_create: role already exists")
|
||||||
|
role = Role(name=role_name)
|
||||||
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
|
permissions = data.get("permissions")
|
||||||
|
if permissions:
|
||||||
|
try:
|
||||||
|
role.set_named_permissions(permissions)
|
||||||
|
except ScoValueError:
|
||||||
|
return error_response(404, "role_create: invalid permissions")
|
||||||
|
db.session.add(role)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify(role.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/role/<string:role_name>/edit", methods=["POST"])
|
||||||
|
@api_web_bp.route("/role/<string:role_name>/edit", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
|
def role_edit(role_name: str):
|
||||||
|
"""Edit a role. On peut spécifier un nom et/ou des permissions.
|
||||||
|
{
|
||||||
|
"name" : name
|
||||||
|
"permissions" : [ 'ScoView', ... ]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||||
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
|
permissions = data.get("permissions", False)
|
||||||
|
if permissions is not False:
|
||||||
|
try:
|
||||||
|
role.set_named_permissions(permissions)
|
||||||
|
except ScoValueError:
|
||||||
|
return error_response(404, "role_create: invalid permissions")
|
||||||
|
role_name = data.get("role_name")
|
||||||
|
if role_name and role_name != role.name:
|
||||||
|
existing_role: Role = Role.query.filter_by(name=role_name).first()
|
||||||
|
if existing_role:
|
||||||
|
return error_response(404, "role_edit: role name already exists")
|
||||||
|
role.name = role_name
|
||||||
|
db.session.add(role)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify(role.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/role/<string:role_name>/delete", methods=["POST"])
|
||||||
|
@api_web_bp.route("/role/<string:role_name>/delete", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
|
def role_delete(role_name: str):
|
||||||
|
"""Delete a role"""
|
||||||
|
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||||
|
db.session.delete(role)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({"OK": True})
|
||||||
|
@ -407,11 +407,10 @@ class Role(db.Model):
|
|||||||
"""Roles for ScoDoc"""
|
"""Roles for ScoDoc"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(64), unique=True) # TODO: , nullable=False))
|
name = db.Column(db.String(64), unique=True, nullable=False, index=True)
|
||||||
default = db.Column(db.Boolean, default=False, index=True)
|
default = db.Column(db.Boolean, default=False, index=True)
|
||||||
permissions = db.Column(db.BigInteger) # 64 bits
|
permissions = db.Column(db.BigInteger) # 64 bits
|
||||||
users = db.relationship("User", secondary="user_role", viewonly=True)
|
users = db.relationship("User", secondary="user_role", viewonly=True)
|
||||||
# __table_args__ = (db.UniqueConstraint("name", "dept", name="_rolename_dept_uc"),)
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Role, self).__init__(**kwargs)
|
super(Role, self).__init__(**kwargs)
|
||||||
@ -432,20 +431,34 @@ class Role(db.Model):
|
|||||||
"As dict. Convert permissions to names."
|
"As dict. Convert permissions to names."
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"name": self.name,
|
"role_name": self.name, # pour être cohérent avec partion_name, etc.
|
||||||
"permissions": Permission.permissions_names(self.permissions),
|
"permissions": Permission.permissions_names(self.permissions),
|
||||||
}
|
}
|
||||||
|
|
||||||
def add_permission(self, perm):
|
def add_permission(self, perm: int):
|
||||||
|
"Add permission to role"
|
||||||
self.permissions |= perm
|
self.permissions |= perm
|
||||||
|
|
||||||
def remove_permission(self, perm):
|
def remove_permission(self, perm: int):
|
||||||
|
"Remove permission from role"
|
||||||
self.permissions = self.permissions & ~perm
|
self.permissions = self.permissions & ~perm
|
||||||
|
|
||||||
def reset_permissions(self):
|
def reset_permissions(self):
|
||||||
|
"Remove all permissions from role"
|
||||||
self.permissions = 0
|
self.permissions = 0
|
||||||
|
|
||||||
def has_permission(self, perm):
|
def set_named_permissions(self, permission_names: list[str]):
|
||||||
|
"""Set permissions, given as a list of permissions names.
|
||||||
|
Raises ScoValueError if invalid permission."""
|
||||||
|
self.permissions = 0
|
||||||
|
for permission_name in permission_names:
|
||||||
|
permission = Permission.get_by_name(permission_name)
|
||||||
|
if permission is None:
|
||||||
|
raise ScoValueError("set_named_permissions: invalid permission name")
|
||||||
|
self.permissions |= permission
|
||||||
|
|
||||||
|
def has_permission(self, perm: int) -> bool:
|
||||||
|
"True if role as this permission"
|
||||||
return self.permissions & perm == perm
|
return self.permissions & perm == perm
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -94,6 +94,8 @@ class Permission:
|
|||||||
def permissions_names(permissions: int) -> list[str]:
|
def permissions_names(permissions: int) -> list[str]:
|
||||||
"""From a bit field, return list of permission names"""
|
"""From a bit field, return list of permission names"""
|
||||||
names = []
|
names = []
|
||||||
|
if permissions == 0:
|
||||||
|
return []
|
||||||
mask = 1 << (permissions.bit_length() - 1)
|
mask = 1 << (permissions.bit_length() - 1)
|
||||||
while mask > 0:
|
while mask > 0:
|
||||||
if mask & permissions:
|
if mask & permissions:
|
||||||
|
@ -80,14 +80,14 @@ def test_edit_users(api_admin_headers):
|
|||||||
assert user["user_name"] == "toto"
|
assert user["user_name"] == "toto"
|
||||||
assert user["dept"] is None
|
assert user["dept"] is None
|
||||||
assert user["active"] is True
|
assert user["active"] is True
|
||||||
assert nb_users == 1 + len(GET("/users/query", headers=admin_h))
|
assert (nb_users + 1) == len(GET("/users/query", headers=admin_h))
|
||||||
# Change le dept et rend inactif
|
# Change le dept et rend inactif
|
||||||
user = POST_JSON(
|
user = POST_JSON(
|
||||||
f"/user/edit/{user['id']}",
|
f"/user/edit/{user['id']}",
|
||||||
{"active": False, "dept": "TAPI"},
|
{"active": False, "dept": "TAPI"},
|
||||||
headers=admin_h,
|
headers=admin_h,
|
||||||
)
|
)
|
||||||
assert user["dept"] is "TAPI"
|
assert user["dept"] == "TAPI"
|
||||||
assert user["active"] is False
|
assert user["active"] is False
|
||||||
|
|
||||||
|
|
||||||
@ -105,3 +105,27 @@ def test_roles(api_admin_headers):
|
|||||||
uid = user["id"]
|
uid = user["id"]
|
||||||
ans = POST_JSON(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
ans = POST_JSON(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
||||||
assert ans["user_name"] == "test_roles"
|
assert ans["user_name"] == "test_roles"
|
||||||
|
role = POST_JSON("/role/Test_X/create", headers=admin_h)
|
||||||
|
assert role["role_name"] == "Test_X"
|
||||||
|
assert role["permissions"] == []
|
||||||
|
role = GET("/role/Test_X", headers=admin_h)
|
||||||
|
assert role["role_name"] == "Test_X"
|
||||||
|
assert role["permissions"] == []
|
||||||
|
role = POST_JSON("/role/Test_X/edit", {"role_name": "Test_Y"}, headers=admin_h)
|
||||||
|
assert role["role_name"] == "Test_Y"
|
||||||
|
role = GET("/role/Test_Y", headers=admin_h)
|
||||||
|
assert role["role_name"] == "Test_Y"
|
||||||
|
role = POST_JSON(
|
||||||
|
"/role/Test_Y/edit",
|
||||||
|
{"permissions": ["ScoView", "ScoAbsChange"]},
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
assert set(role["permissions"]) == {"ScoView", "ScoAbsChange"}
|
||||||
|
role = POST_JSON("/role/Test_Y/add_permission/ScoAbsAddBillet", headers=admin_h)
|
||||||
|
assert set(role["permissions"]) == {"ScoView", "ScoAbsChange", "ScoAbsAddBillet"}
|
||||||
|
role = GET("/role/Test_Y", headers=admin_h)
|
||||||
|
assert set(role["permissions"]) == {"ScoView", "ScoAbsChange", "ScoAbsAddBillet"}
|
||||||
|
role = POST_JSON("/role/Test_Y/remove_permission/ScoAbsChange", headers=admin_h)
|
||||||
|
assert set(role["permissions"]) == {"ScoView", "ScoAbsAddBillet"}
|
||||||
|
ans = POST_JSON("/role/Test_Y/delete", headers=admin_h)
|
||||||
|
assert ans["OK"] is True
|
||||||
|
Loading…
Reference in New Issue
Block a user