forked from ScoDoc/DocScoDoc
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)
|
||||
def users_info_query():
|
||||
"""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
|
||||
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())
|
||||
|
||||
|
||||
@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")
|
||||
@api_web_bp.route("/roles")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@permission_required(Permission.ScoUsersView)
|
||||
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")
|
||||
@bp.route(
|
||||
"/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
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def list_permissions():
|
||||
"""Liste des noms de permissions définies"""
|
||||
return jsonify(list(Permission.permission_by_name.keys()))
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
def role_permission_add(role_name: str, perm_name: str):
|
||||
"""Add permission to 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_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"""
|
||||
|
||||
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)
|
||||
permissions = db.Column(db.BigInteger) # 64 bits
|
||||
users = db.relationship("User", secondary="user_role", viewonly=True)
|
||||
# __table_args__ = (db.UniqueConstraint("name", "dept", name="_rolename_dept_uc"),)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Role, self).__init__(**kwargs)
|
||||
@ -432,20 +431,34 @@ class Role(db.Model):
|
||||
"As dict. Convert permissions to names."
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"role_name": self.name, # pour être cohérent avec partion_name, etc.
|
||||
"permissions": Permission.permissions_names(self.permissions),
|
||||
}
|
||||
|
||||
def add_permission(self, perm):
|
||||
def add_permission(self, perm: int):
|
||||
"Add permission to role"
|
||||
self.permissions |= perm
|
||||
|
||||
def remove_permission(self, perm):
|
||||
def remove_permission(self, perm: int):
|
||||
"Remove permission from role"
|
||||
self.permissions = self.permissions & ~perm
|
||||
|
||||
def reset_permissions(self):
|
||||
"Remove all permissions from role"
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
|
@ -94,6 +94,8 @@ class Permission:
|
||||
def permissions_names(permissions: int) -> list[str]:
|
||||
"""From a bit field, return list of permission names"""
|
||||
names = []
|
||||
if permissions == 0:
|
||||
return []
|
||||
mask = 1 << (permissions.bit_length() - 1)
|
||||
while mask > 0:
|
||||
if mask & permissions:
|
||||
|
@ -80,14 +80,14 @@ def test_edit_users(api_admin_headers):
|
||||
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))
|
||||
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["dept"] == "TAPI"
|
||||
assert user["active"] is False
|
||||
|
||||
|
||||
@ -105,3 +105,27 @@ def test_roles(api_admin_headers):
|
||||
uid = user["id"]
|
||||
ans = POST_JSON(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
||||
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