aboutsummaryrefslogtreecommitdiff
path: root/tests/unit/auth/test_admin_user_roles.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/auth/test_admin_user_roles.py')
-rw-r--r--tests/unit/auth/test_admin_user_roles.py262
1 files changed, 262 insertions, 0 deletions
diff --git a/tests/unit/auth/test_admin_user_roles.py b/tests/unit/auth/test_admin_user_roles.py
new file mode 100644
index 0000000..7ce0b1f
--- /dev/null
+++ b/tests/unit/auth/test_admin_user_roles.py
@@ -0,0 +1,262 @@
+"""Tests for admin role-assignment HTTP endpoints."""
+import pytest
+
+from gn_auth.auth.db import sqlite3 as db
+from gn_auth.auth.authorisation.roles.models import assign_user_role_by_name
+
+from tests.unit.auth import conftest
+from tests.unit.auth.fixtures.resource_fixtures import SYSTEM_RESOURCE
+
+# Body used in all role-assign tests — assigning system-administrator on the
+# system resource is a real, migrations-seeded combination.
+_ASSIGN_BODY = {
+ "role_name": "system-administrator",
+ "resource_id": str(SYSTEM_RESOURCE.resource_id)
+}
+
+# Target user for assignment: unaff@iliated.user (no roles initially)
+_TARGET_USER = conftest.TEST_USERS[3]
+
+
+def _setup_admin_mock(conn, clients, mocker):
+ """Grant resource-owner role on SYSTEM_RESOURCE and mock the token.
+
+ resource-owner carries resource:user:assign-role, which is what the
+ endpoint checks. In production the caller would masquerade as the
+ resource owner; here we grant the role directly for test setup.
+ """
+ admin = conftest.TEST_USERS[4]
+ with db.cursor(conn) as cursor:
+ assign_user_role_by_name(
+ cursor, admin, SYSTEM_RESOURCE.resource_id, "resource-owner")
+ mocker.patch(
+ "gn_auth.auth.authorisation.users.views.require_oauth.acquire",
+ conftest.get_tokeniser(
+ admin,
+ tuple(c for c in clients if c.user == admin)[0]))
+ return admin
+
+
+@pytest.mark.unit_test
+def test_assign_role_no_token_returns_401(fxtr_app):
+ """
+ GIVEN: no Authorization header
+ WHEN: POST /auth/user/<uid>/roles/assign
+ THEN: 401 is returned
+ """
+ with fxtr_app.test_client() as http:
+ res = http.post(
+ f"/auth/user/{_TARGET_USER.user_id}/roles/assign",
+ json=_ASSIGN_BODY)
+ assert res.status_code == 401
+
+
+@pytest.mark.unit_test
+def test_assign_role_non_admin_returns_403(fxtr_app, mocker, fxtr_oauth2_clients):
+ """
+ GIVEN: a valid token belonging to a non-admin user
+ WHEN: POST /auth/user/<uid>/roles/assign
+ THEN: 403 is returned
+ """
+ _conn, clients = fxtr_oauth2_clients
+ user = conftest.TEST_USERS[3] # unaff@iliated.user — no privileges
+ mocker.patch(
+ "gn_auth.auth.authorisation.users.views.require_oauth.acquire",
+ conftest.get_tokeniser(
+ user,
+ tuple(c for c in clients if c.user == user)[0]))
+ with fxtr_app.test_client() as http:
+ res = http.post(
+ f"/auth/user/{_TARGET_USER.user_id}/roles/assign",
+ json=_ASSIGN_BODY,
+ headers={"Authorization": "Bearer some-mocked-token"})
+ assert res.status_code == 403
+
+
+def _revoke_assigned_role(conn):
+ """Remove the role row written by the success tests.
+
+ Keeps the DB in the state the fixtures expect — no user_roles entry for
+ _TARGET_USER — so teardown and any subsequent queries are not surprised.
+ """
+ with db.cursor(conn) as cursor:
+ cursor.execute(
+ "DELETE FROM user_roles "
+ "WHERE user_id=? "
+ "AND role_id=(SELECT role_id FROM roles WHERE role_name=?) "
+ "AND resource_id=?",
+ (str(_TARGET_USER.user_id),
+ _ASSIGN_BODY["role_name"],
+ _ASSIGN_BODY["resource_id"]))
+
+
+@pytest.mark.unit_test
+def test_assign_role_admin_returns_200(fxtr_app, mocker, fxtr_oauth2_clients):
+ """
+ GIVEN: a valid system-admin token and a valid role/resource body
+ WHEN: POST /auth/user/<uid>/roles/assign
+ THEN: 200 is returned
+ """
+ conn, clients = fxtr_oauth2_clients
+ _setup_admin_mock(conn, clients, mocker)
+ try:
+ with fxtr_app.test_client() as http:
+ res = http.post(
+ f"/auth/user/{_TARGET_USER.user_id}/roles/assign",
+ json=_ASSIGN_BODY,
+ headers={"Authorization": "Bearer some-mocked-token"})
+ assert res.status_code == 200
+ finally:
+ _revoke_assigned_role(conn)
+
+
+def _assign_target_role(conn):
+ """Pre-assign system-administrator to _TARGET_USER on SYSTEM_RESOURCE.
+
+ Required setup for revoke tests: the endpoint can only revoke what exists.
+ """
+ with db.cursor(conn) as cursor:
+ assign_user_role_by_name(
+ cursor, _TARGET_USER, SYSTEM_RESOURCE.resource_id,
+ _ASSIGN_BODY["role_name"])
+
+
+def _cleanup_target_role(conn):
+ """Remove _TARGET_USER's system-administrator row if still present.
+
+ No-op when the revoke endpoint already deleted it; guards against
+ test failures that leave the DB dirty.
+ """
+ with db.cursor(conn) as cursor:
+ cursor.execute(
+ "DELETE FROM user_roles "
+ "WHERE user_id=? "
+ "AND role_id=(SELECT role_id FROM roles WHERE role_name=?) "
+ "AND resource_id=?",
+ (str(_TARGET_USER.user_id),
+ _ASSIGN_BODY["role_name"],
+ _ASSIGN_BODY["resource_id"]))
+
+
+@pytest.mark.unit_test
+def test_assign_role_persists_to_db(fxtr_app, mocker, fxtr_oauth2_clients):
+ """
+ GIVEN: a valid system-admin token and a valid role/resource body
+ WHEN: POST /auth/user/<uid>/roles/assign
+ THEN: the user_roles row is present in the DB for that user/role/resource
+ """
+ conn, clients = fxtr_oauth2_clients
+ _setup_admin_mock(conn, clients, mocker)
+ try:
+ with fxtr_app.test_client() as http:
+ http.post(
+ f"/auth/user/{_TARGET_USER.user_id}/roles/assign",
+ json=_ASSIGN_BODY,
+ headers={"Authorization": "Bearer some-mocked-token"})
+ with db.cursor(conn) as cursor:
+ cursor.execute(
+ "SELECT COUNT(*) AS cnt FROM user_roles "
+ "INNER JOIN roles ON user_roles.role_id=roles.role_id "
+ "WHERE user_roles.user_id=? "
+ "AND roles.role_name=? "
+ "AND user_roles.resource_id=?",
+ (str(_TARGET_USER.user_id),
+ _ASSIGN_BODY["role_name"],
+ _ASSIGN_BODY["resource_id"]))
+ assert cursor.fetchone()["cnt"] == 1
+ finally:
+ _revoke_assigned_role(conn)
+
+
+# ---------------------------------------------------------------------------
+# HTTP endpoint tests: POST /auth/user/<uid>/roles/revoke
+# ---------------------------------------------------------------------------
+
+@pytest.mark.unit_test
+def test_revoke_role_no_token_returns_401(fxtr_app):
+ """
+ GIVEN: no Authorization header
+ WHEN: POST /auth/user/<uid>/roles/revoke
+ THEN: 401 is returned
+ """
+ with fxtr_app.test_client() as http:
+ res = http.post(
+ f"/auth/user/{_TARGET_USER.user_id}/roles/revoke",
+ json=_ASSIGN_BODY)
+ assert res.status_code == 401
+
+
+@pytest.mark.unit_test
+def test_revoke_role_non_admin_returns_403(fxtr_app, mocker, fxtr_oauth2_clients):
+ """
+ GIVEN: a valid token belonging to a non-admin user
+ WHEN: POST /auth/user/<uid>/roles/revoke
+ THEN: 403 is returned
+ """
+ _conn, clients = fxtr_oauth2_clients
+ user = conftest.TEST_USERS[3] # unaff@iliated.user — no privileges
+ mocker.patch(
+ "gn_auth.auth.authorisation.users.views.require_oauth.acquire",
+ conftest.get_tokeniser(
+ user,
+ tuple(c for c in clients if c.user == user)[0]))
+ with fxtr_app.test_client() as http:
+ res = http.post(
+ f"/auth/user/{_TARGET_USER.user_id}/roles/revoke",
+ json=_ASSIGN_BODY,
+ headers={"Authorization": "Bearer some-mocked-token"})
+ assert res.status_code == 403
+
+
+@pytest.mark.unit_test
+def test_revoke_role_admin_returns_200(fxtr_app, mocker, fxtr_oauth2_clients):
+ """
+ GIVEN: a valid token with resource:user:assign-role and the target user
+ holds the role on the resource
+ WHEN: POST /auth/user/<uid>/roles/revoke
+ THEN: 200 is returned
+ """
+ conn, clients = fxtr_oauth2_clients
+ _setup_admin_mock(conn, clients, mocker)
+ _assign_target_role(conn)
+ try:
+ with fxtr_app.test_client() as http:
+ res = http.post(
+ f"/auth/user/{_TARGET_USER.user_id}/roles/revoke",
+ json=_ASSIGN_BODY,
+ headers={"Authorization": "Bearer some-mocked-token"})
+ assert res.status_code == 200
+ finally:
+ _cleanup_target_role(conn)
+
+
+@pytest.mark.unit_test
+def test_revoke_role_removes_from_db(fxtr_app, mocker, fxtr_oauth2_clients):
+ """
+ GIVEN: a valid token with resource:user:assign-role and the target user
+ holds the role on the resource
+ WHEN: POST /auth/user/<uid>/roles/revoke
+ THEN: the user_roles row is absent from the DB
+ """
+ conn, clients = fxtr_oauth2_clients
+ _setup_admin_mock(conn, clients, mocker)
+ _assign_target_role(conn)
+ try:
+ with fxtr_app.test_client() as http:
+ http.post(
+ f"/auth/user/{_TARGET_USER.user_id}/roles/revoke",
+ json=_ASSIGN_BODY,
+ headers={"Authorization": "Bearer some-mocked-token"})
+ with db.cursor(conn) as cursor:
+ cursor.execute(
+ "SELECT COUNT(*) AS cnt FROM user_roles "
+ "INNER JOIN roles ON user_roles.role_id=roles.role_id "
+ "WHERE user_roles.user_id=? "
+ "AND roles.role_name=? "
+ "AND user_roles.resource_id=?",
+ (str(_TARGET_USER.user_id),
+ _ASSIGN_BODY["role_name"],
+ _ASSIGN_BODY["resource_id"]))
+ assert cursor.fetchone()["cnt"] == 0
+ finally:
+ _cleanup_target_role(conn)