about summary refs log tree commit diff
diff options
context:
space:
mode:
authorClaude Sonnet 4.62026-08-28 19:00:00 +0000
committerFrederick Muriuki Muriithi2026-08-28 14:51:04 -0500
commit58c6f7a9eedc77c7268dc350387f78f77d619eac (patch)
treec1844964f1157f85a086402a14e343892a2be21e
parent3348d266228e1f4b374d0af6e33fe4948eb1bfaa (diff)
downloadgn-auth-58c6f7a9eedc77c7268dc350387f78f77d619eac.tar.gz
Implement assign-owner / revoke-owner in system admin resources HEAD main
The system-administrator role carries system:resource:assign-owner
(migration 20250729_02), but no endpoint used it.  Sysadmins had no API
path to bootstrap ownership on a resource that has no owner yet.

Implement two endpoints in gn_auth/auth/system/admin/resources.py
(the blueprint skeleton was already wired in a preceding commit):

  POST /auth/system/administration/resources/<resource_id>/assign-owner
    Body: {"user_id": "<uuid>"}
    Assigns the resource-owner role to the named user on the resource.

  POST /auth/system/administration/resources/<resource_id>/revoke-owner
    Body: {"user_id": "<uuid>"}
    Revokes the resource-owner role from the named user on the resource.

Both check system:resource:assign-owner on the *system* resource, so a
sysadmin can grant/revoke ownership without being resource-owner
themselves.

Reviewed-By: Frederick M. Muriithi <fredmanglis@gmail.com>
-rw-r--r--gn_auth/auth/system/admin/resources.py67
1 files changed, 65 insertions, 2 deletions
diff --git a/gn_auth/auth/system/admin/resources.py b/gn_auth/auth/system/admin/resources.py
index d3a9f54..59d7686 100644
--- a/gn_auth/auth/system/admin/resources.py
+++ b/gn_auth/auth/system/admin/resources.py
@@ -1,8 +1,71 @@
 """Administrative endpoints concerning resources."""
+from uuid import UUID
 
-from flask import Blueprint
+from flask import jsonify, Response, Blueprint, current_app as app
+
+from gn_libs import sqlite3 as db
+
+from gn_auth.auth.errors import ForbiddenAccess
+from gn_auth.auth.requests import request_json
+from gn_auth.auth.authentication.users import user_by_id
+from gn_auth.auth.authentication.oauth2.resource_server import require_oauth
+from gn_auth.auth.authorisation.roles.models import (
+    user_roles_on_resource,
+    assign_user_role_by_name,
+    unassign_user_role_by_name)
+from gn_auth.auth.authorisation.resources.system.models import system_resource
 
 resources = Blueprint("resources", __name__)
 
 
-# TODO: assign-owner and revoke-owner go here.
+def _require_assign_owner_privilege(conn, user) -> None:
+    """Raise ForbiddenAccess if user lacks system:resource:assign-owner on the system resource."""
+    _sys = system_resource(conn)
+    sys_roles = user_roles_on_resource(conn, user.user_id, _sys.resource_id)
+    sys_privs = tuple(
+        priv.privilege_id for role in sys_roles for priv in role.privileges)
+    if "system:resource:assign-owner" not in sys_privs:
+        raise ForbiddenAccess(
+            "You need the 'system:resource:assign-owner' privilege.")
+
+
+@resources.route("/<uuid:resource_id>/assign-owner", methods=["POST"])
+def assign_resource_owner(resource_id: UUID) -> Response:
+    """Assign the resource-owner role to a user on the given resource.
+
+    Only users with system:resource:assign-owner (sysadmins) may call this.
+    This is the correct path to bootstrap ownership on a resource that has
+    no owner yet.
+    """
+    with (require_oauth.acquire("profile group resource") as _token,
+          db.connection(app.config["AUTH_DB"]) as conn):
+        _require_assign_owner_privilege(conn, _token.user)
+        form = request_json()
+        target = user_by_id(conn, UUID(form["user_id"]))
+        with db.cursor(conn) as cursor:
+            assign_user_role_by_name(cursor, target, resource_id, "resource-owner")
+    return jsonify({
+        "user_id": form["user_id"],
+        "resource_id": str(resource_id),
+        "role_name": "resource-owner",
+    })
+
+
+@resources.route("/<uuid:resource_id>/revoke-owner", methods=["POST"])
+def revoke_resource_owner(resource_id: UUID) -> Response:
+    """Revoke the resource-owner role from a user on the given resource.
+
+    Requires the same system:resource:assign-owner privilege as assign-owner.
+    """
+    with (require_oauth.acquire("profile group resource") as _token,
+          db.connection(app.config["AUTH_DB"]) as conn):
+        _require_assign_owner_privilege(conn, _token.user)
+        form = request_json()
+        target = user_by_id(conn, UUID(form["user_id"]))
+        with db.cursor(conn) as cursor:
+            unassign_user_role_by_name(cursor, target, resource_id, "resource-owner")
+    return jsonify({
+        "user_id": form["user_id"],
+        "resource_id": str(resource_id),
+        "role_name": "resource-owner",
+    })