From c6a99dc7d2d9a81231fe91eeff511cdd733db4b9 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 19 Aug 2022 12:46:21 +0200 Subject: [PATCH] API users: password, plus de tests unitaires, correctifs. --- app/api/users.py | 35 ++++++++++- tests/api/exemple-api-basic.py | 1 + tests/api/setup_test_api.py | 8 ++- tests/api/test_api_users.py | 102 ++++++++++++++++++++++++++++++++- 4 files changed, 140 insertions(+), 6 deletions(-) diff --git a/app/api/users.py b/app/api/users.py index 8b34901c74..ee2cce2481 100644 --- a/app/api/users.py +++ b/app/api/users.py @@ -14,9 +14,9 @@ from flask_login import current_user, login_required from app import db, log from app.api import api_bp as bp, api_web_bp -from app.models.etudiants import Identite from app.scodoc.sco_utils import json_error from app.auth.models import User, Role, UserRole +from app.auth.models import is_valid_password from app.decorators import scodoc, permission_required from app.models import Departement from app.scodoc.sco_exceptions import ScoValueError @@ -38,7 +38,7 @@ def user_info(uid: int): return json_error(404, "user not found") if g.scodoc_dept: allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersView) - if user.dept not in allowed_depts: + if (None not in allowed_depts) and (user.dept not in allowed_depts): return json_error(404, "user not found") return jsonify(user.to_dict()) @@ -109,7 +109,7 @@ def user_create(): if dept == "@all": dept = None allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin) - if dept not in allowed_depts: + if (None not in allowed_depts) and (dept not in allowed_depts): return json_error(403, "user_create: departement non autorise") if (dept is not None) and ( Departement.query.filter_by(acronym=dept).first() is None @@ -168,6 +168,35 @@ def user_edit(uid: int): return jsonify(user.to_dict()) +@bp.route("/user//password", methods=["POST"]) +@api_web_bp.route("/user//password", methods=["POST"]) +@login_required +@scodoc +@permission_required(Permission.ScoUsersAdmin) +def user_password(uid: int): + """Modification du mot de passe d'un utilisateur + Champs modifiables: + { + "password": str + } + Si le mot de passe ne convient pas, erreur 400. + """ + data = request.get_json(force=True) # may raise 400 Bad Request + user: User = User.query.get_or_404(uid) + password = data.get("password") + if not password: + return json_error(404, "user_password: missing password") + if not is_valid_password(password): + return json_error(400, "user_password: invalid password") + allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin) + if (None not in allowed_depts) and ((user.dept not in allowed_depts)): + return json_error(403, "user_password: departement non autorise") + user.set_password(password) + db.session.add(user) + db.session.commit() + return jsonify(user.to_dict()) + + @bp.route("/user//role//add", methods=["POST"]) @api_web_bp.route("/user//role//add", methods=["POST"]) @bp.route( diff --git a/tests/api/exemple-api-basic.py b/tests/api/exemple-api-basic.py index 022065a59d..0ef58af473 100644 --- a/tests/api/exemple-api-basic.py +++ b/tests/api/exemple-api-basic.py @@ -51,6 +51,7 @@ print(f"API URL={API_URL}") HEADERS = get_auth_headers(API_USER, API_PASSWORD) +admin_h = get_auth_headers("admin_api", "admin_api") departements = GET("/departements", headers=HEADERS) pp(departements) diff --git a/tests/api/setup_test_api.py b/tests/api/setup_test_api.py index fb7517ce88..bc1a58fbc5 100644 --- a/tests/api/setup_test_api.py +++ b/tests/api/setup_test_api.py @@ -76,10 +76,14 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None): return r.json() # decode la reponse JSON -def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None): +def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None, dept=None): """Post""" + if dept: + url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path + else: + url = API_URL + path r = requests.post( - API_URL + path, + url, json=data, headers=headers or {}, verify=CHECK_CERTIFICATE, diff --git a/tests/api/test_api_users.py b/tests/api/test_api_users.py index 82c6cdefcd..e9fe5aa4b6 100644 --- a/tests/api/test_api_users.py +++ b/tests/api/test_api_users.py @@ -8,6 +8,7 @@ Utilisation : from tests.api.setup_test_api import ( API_URL, + APIError, CHECK_CERTIFICATE, GET, POST_JSON, @@ -83,7 +84,7 @@ def test_edit_users(api_admin_headers): 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']}", + f"/user/{user['id']}/edit", {"active": False, "dept": "TAPI"}, headers=admin_h, ) @@ -129,3 +130,102 @@ def test_roles(api_admin_headers): assert set(role["permissions"]) == {"ScoView", "ScoAbsAddBillet"} ans = POST_JSON("/role/Test_Y/delete", headers=admin_h) assert ans["OK"] is True + + +def test_modif_users_depts(api_admin_headers): + """ + Ce test vise à vérifier qu'un admin déclaré sur deux départements peut + bien modifier les utilisateurs de ses départements mais pas ceux des autres. + """ + admin_h = api_admin_headers + depts = GET("/departements", headers=admin_h) + assert len(depts) > 2 + dept1, dept2, dept3 = depts[:3] + # On va utiliser les 3 1er dept (TAPI, AA, BB) + # On crée un nouvel utilisateur "chef2", admin dans les 2 premiers dept + # puis un utilisateur lambda, dans le dept 2 (AA) + chef2 = POST_JSON( + "/user/create", + { + "user_name": "chef2", + "nom": "Chef", + "prenom": "Test", + "dept": dept1["acronym"], # rattaché à dept1 + }, + headers=admin_h, + ) + role_chef = POST_JSON( + "/role/create/chef", + {"permissions": ["ScoView", "ScoUsersAdmin", "ScoUsersView"]}, + headers=admin_h, + ) + _ = POST_JSON( + f"/user/{chef2['id']}/role/chef/add/departement/{dept1['acronym']}", + headers=admin_h, + ) + _ = POST_JSON( + f"/user/{chef2['id']}/role/chef/add/departement/{dept2['acronym']}", + headers=admin_h, + ) + # Un mot de passe trop simple: + ok = False + try: + _ = POST_JSON( + f"/user/{chef2['id']}/password", + {"password": "123456"}, + headers=admin_h, + ) + except APIError as exc: + if exc.args[1]["status"] == 400: + ok = True + assert ok + # Un "vrai" mot de passe: + chef2_password = "17HIOPpYhabb8qw'E:/jd7FFddjd" + _ = POST_JSON( + f"/user/{chef2['id']}/password", + {"password": chef2_password}, + headers=admin_h, + ) + # Création user lambda: + u_lambda = POST_JSON( + "/user/create", + { + "user_name": "lambda", + "nom": "Lambda", + "prenom": "Test", + "dept": dept2["acronym"], + }, + headers=admin_h, + ) + + # Le chef va modifier u_lambda: + chef_h = get_auth_headers(chef2["user_name"], chef2_password) + # on utilise une URL avec département car on n'a pas le droit sur tous: + u = POST_JSON( + f"/user/{u_lambda['id']}/edit", + {"nom": "toto"}, + headers=chef_h, + dept=dept1["acronym"], + ) + assert u["nom"] == "toto" + # Dans l'autre ? + u = POST_JSON( + f"/user/{u_lambda['id']}/edit", + {"nom": "toto"}, + headers=chef_h, + dept=dept2["acronym"], + ) + # mais pas dans le troisième: + ok = False + try: + u = POST_JSON( + f"/user/{u_lambda['id']}/edit", + {"nom": "toto"}, + headers=chef_h, + dept=dept3["acronym"], + ) + except APIError as exc: + if exc.args[1]["status"] == 401: + ok = True + assert ok + # Nettoyage: