From f6566c76d97cb44d47cc491f13e1342f0c2555cf Mon Sep 17 00:00:00 2001
From: Frederick Muriuki Muriithi
Date: Fri, 15 Sep 2023 08:38:47 +0300
Subject: Update `user_roles`: Return roles for user by resource.

---
 gn_auth/auth/authorisation/roles/models.py     |  68 +++++++------
 tests/unit/auth/fixtures/group_fixtures.py     |   7 +-
 tests/unit/auth/fixtures/migration_fixtures.py |   5 +
 tests/unit/auth/test_roles.py                  | 129 ++++++++++++++++---------
 4 files changed, 130 insertions(+), 79 deletions(-)

diff --git a/gn_auth/auth/authorisation/roles/models.py b/gn_auth/auth/authorisation/roles/models.py
index 579c9dc..206b05e 100644
--- a/gn_auth/auth/authorisation/roles/models.py
+++ b/gn_auth/auth/authorisation/roles/models.py
@@ -64,43 +64,51 @@ def create_role(
 
     return role
 
-def __organise_privileges__(roles_dict, privilege_row):
-    """Organise the privileges into their roles."""
-    role_id_str = privilege_row["role_id"]
-    if  role_id_str in roles_dict:
-        return {
-            **roles_dict,
-            role_id_str: Role(
-                UUID(role_id_str),
-                privilege_row["role_name"],
-                bool(int(privilege_row["user_editable"])),
-                roles_dict[role_id_str].privileges + (
-                    Privilege(privilege_row["privilege_id"],
-                              privilege_row["privilege_description"]),))
-        }
-
+def __organise_privileges__(resources, row) -> dict:
+    resource_id = UUID(row["resource_id"])
+    role_id = UUID(row["role_id"])
+    roles = resources.get(resource_id, {}).get("roles", {})
+    role = roles.get(role_id, Role(
+        role_id,
+        row["role_name"],
+        bool(int(row["user_editable"])),
+        tuple()))
     return {
-        **roles_dict,
-        role_id_str: Role(
-            UUID(role_id_str),
-            privilege_row["role_name"],
-            bool(int(privilege_row["user_editable"])),
-            (Privilege(privilege_row["privilege_id"],
-                       privilege_row["privilege_description"]),))
+        **resources,
+        resource_id: {
+            "resource_id": resource_id,
+            "user_id": UUID(row["user_id"]),
+            "roles": {
+                **roles,
+                role_id: Role(
+                    role.role_id,
+                    role.role_name,
+                    role.user_editable,
+                    role.privileges + (Privilege(
+                        row["privilege_id"],
+                        row["privilege_description"]),)
+                )
+            }
+        }
     }
 
-def user_roles(conn: db.DbConnection, user: User) -> Sequence[Role]:
-    """Retrieve non-resource roles assigned to the user."""
+def user_roles(conn: db.DbConnection, user: User) -> Sequence[dict]:
+    """Retrieve all roles (organised by resource) assigned to the user."""
     with db.cursor(conn) as cursor:
+        cursor.execute("SELECT * FROM user_roles")
         cursor.execute(
-            "SELECT r.*, p.* FROM user_roles AS ur INNER JOIN roles AS r "
-            "ON ur.role_id=r.role_id INNER JOIN role_privileges AS rp "
-            "ON r.role_id=rp.role_id INNER JOIN privileges AS p "
-            "ON rp.privilege_id=p.privilege_id WHERE ur.user_id=?",
+            "SELECT ur.resource_id, ur.user_id, r.*, p.* "
+            "FROM user_roles AS ur "
+            "INNER JOIN roles AS r ON ur.role_id=r.role_id "
+            "INNER JOIN role_privileges AS rp ON r.role_id=rp.role_id "
+            "INNER JOIN privileges AS p ON rp.privilege_id=p.privilege_id "
+            "WHERE ur.user_id=?",
             (str(user.user_id),))
 
-        return tuple(
-            reduce(__organise_privileges__, cursor.fetchall(), {}).values())
+        return tuple({
+            **row, "roles": tuple(row["roles"].values())
+        } for row in reduce(
+            __organise_privileges__, cursor.fetchall(), {}).values())
     return tuple()
 
 def user_role(conn: db.DbConnection, user: User, role_id: UUID) -> Either:
diff --git a/tests/unit/auth/fixtures/group_fixtures.py b/tests/unit/auth/fixtures/group_fixtures.py
index 66edf5e..93133d2 100644
--- a/tests/unit/auth/fixtures/group_fixtures.py
+++ b/tests/unit/auth/fixtures/group_fixtures.py
@@ -21,11 +21,14 @@ GROUP_CATEGORY = ResourceCategory(
     "A group resource.")
 GROUPS_AS_RESOURCES = tuple({
     "group_id": str(group.group_id),
-    "resource_id": str(uuid.uuid4()),
+    "resource_id": res_id,
     "resource_name": group.group_name,
     "category_id": str(GROUP_CATEGORY.resource_category_id),
     "public": "1"
-} for group in TEST_GROUPS)
+} for res_id, group in zip(
+    ("38d1807d-105f-44a7-8327-7e2d973b6d8d",
+     "89458ef6-e090-4b53-8c2c-59eaf2785f11"),
+    TEST_GROUPS))
 
 TEST_RESOURCES_GROUP_01 = (
     Resource(uuid.UUID("26ad1668-29f5-439d-b905-84d551f85955"),
diff --git a/tests/unit/auth/fixtures/migration_fixtures.py b/tests/unit/auth/fixtures/migration_fixtures.py
index 16a31cd..954b5a9 100644
--- a/tests/unit/auth/fixtures/migration_fixtures.py
+++ b/tests/unit/auth/fixtures/migration_fixtures.py
@@ -40,6 +40,11 @@ def conn_after_auth_migrations(backend, auth_testdb_path, all_migrations): # pyl
     """Run all migrations and return a connection to the database after"""
     apply_migrations(backend, all_migrations)
     with db.connection(auth_testdb_path) as conn:
+        with db.cursor(conn) as cursor:
+            cursor.execute(
+                "UPDATE resources "
+                "SET resource_id='0248b289-b277-4eaa-8c94-88a434d14b6e' "
+                "WHERE resource_name='GeneNetwork System'")
         yield conn
 
     rollback_migrations(backend, all_migrations)
diff --git a/tests/unit/auth/test_roles.py b/tests/unit/auth/test_roles.py
index 227cb9e..6c8dbae 100644
--- a/tests/unit/auth/test_roles.py
+++ b/tests/unit/auth/test_roles.py
@@ -64,53 +64,88 @@ def test_create_role_raises_exception_for_unauthorised_users(# pylint: disable=[
 @pytest.mark.parametrize(
     "user,expected",
     (zip(TEST_USERS,
-         ((Role(
-             role_id=uuid.UUID('a0e67630-d502-4b9f-b23f-6805d0f30e30'),
-             role_name='group-leader', user_editable=False,
-             privileges=(
-                 Privilege(privilege_id='group:resource:create-resource',
-                           privilege_description='Create a resource object'),
-                 Privilege(privilege_id='group:resource:delete-resource',
-                           privilege_description='Delete a resource'),
-                 Privilege(privilege_id='group:resource:edit-resource',
-                           privilege_description='edit/update a resource'),
-                 Privilege(
-                     privilege_id='group:resource:view-resource',
-                     privilege_description=(
-                         'view a resource and use it in computations')),
-                 Privilege(privilege_id='group:role:create-role',
-                           privilege_description='Create a new role'),
-                 Privilege(privilege_id='group:role:delete-role',
-                           privilege_description='Delete an existing role'),
-                 Privilege(privilege_id='group:role:edit-role',
-                           privilege_description='edit/update an existing role'),
-                 Privilege(privilege_id='group:user:add-group-member',
-                           privilege_description='Add a user to a group'),
-                 Privilege(privilege_id='group:user:assign-role',
-                           privilege_description=(
-                               'Assign a role to an existing user')),
-                 Privilege(privilege_id='group:user:remove-group-member',
-                           privilege_description='Remove a user from a group'),
-                 Privilege(privilege_id='system:group:delete-group',
-                           privilege_description='Delete a group'),
-                 Privilege(privilege_id='system:group:edit-group',
-                           privilege_description='Edit the details of a group'),
-                 Privilege(
-                     privilege_id='system:group:transfer-group-leader',
-                     privilege_description=(
-                         'Transfer leadership of the group to some other '
-                         'member')),
-                 Privilege(privilege_id='system:group:view-group',
-                           privilege_description='View the details of a group'),
-                 Privilege(privilege_id='system:user:list',
-                           privilege_description='List users in the system'))),
-           Role(
-               role_id=uuid.UUID("ade7e6b0-ba9c-4b51-87d0-2af7fe39a347"),
-               role_name="group-creator", user_editable=False,
-               privileges=(
-                   Privilege(privilege_id='system:group:create-group',
-                             privilege_description = "Create a group"),))),
-          tuple(), tuple(), tuple()))))
+         (({"resource_id": uuid.UUID("38d1807d-105f-44a7-8327-7e2d973b6d8d"),
+            "user_id": uuid.UUID("ecb52977-3004-469e-9428-2a1856725c7f"),
+            "roles": (Role(
+                role_id=uuid.UUID('a0e67630-d502-4b9f-b23f-6805d0f30e30'),
+                role_name='group-leader', user_editable=False,
+                privileges=(
+                    Privilege(
+                        privilege_id='group:resource:create-resource',
+                        privilege_description='Create a resource object'),
+                    Privilege(
+                        privilege_id='group:resource:delete-resource',
+                        privilege_description='Delete a resource'),
+                    Privilege(
+                        privilege_id='group:resource:edit-resource',
+                        privilege_description='edit/update a resource'),
+                    Privilege(
+                        privilege_id='group:resource:view-resource',
+                        privilege_description=(
+                            'view a resource and use it in computations')),
+                    Privilege(
+                        privilege_id='group:role:create-role',
+                        privilege_description='Create a new role'),
+                    Privilege(
+                        privilege_id='group:role:delete-role',
+                        privilege_description='Delete an existing role'),
+                    Privilege(
+                        privilege_id='group:role:edit-role',
+                        privilege_description='edit/update an existing role'),
+                    Privilege(
+                        privilege_id='group:user:add-group-member',
+                        privilege_description='Add a user to a group'),
+                    Privilege(
+                        privilege_id='group:user:assign-role',
+                        privilege_description=(
+                            'Assign a role to an existing user')),
+                    Privilege(
+                        privilege_id='group:user:remove-group-member',
+                        privilege_description='Remove a user from a group'),
+                    Privilege(
+                        privilege_id='system:group:delete-group',
+                        privilege_description='Delete a group'),
+                    Privilege(
+                        privilege_id='system:group:edit-group',
+                        privilege_description='Edit the details of a group'),
+                    Privilege(
+                        privilege_id='system:group:transfer-group-leader',
+                        privilege_description=(
+                            'Transfer leadership of the group to some other '
+                            'member')),
+                    Privilege(
+                        privilege_id='system:group:view-group',
+                        privilege_description='View the details of a group'),
+                    Privilege(
+                        privilege_id='system:user:list',
+                        privilege_description='List users in the system'))),)
+            },
+           {
+               "resource_id": uuid.UUID("0248b289-b277-4eaa-8c94-88a434d14b6e"),
+               "user_id": uuid.UUID("ecb52977-3004-469e-9428-2a1856725c7f"),
+               "roles": (Role(
+                   role_id=uuid.UUID("ade7e6b0-ba9c-4b51-87d0-2af7fe39a347"),
+                   role_name="group-creator",
+                   user_editable=False,
+                   privileges=(
+                       Privilege(
+                           privilege_id="system:group:create-group",
+                           privilege_description="Create a group"),)),)}),
+          ({"resource_id": uuid.UUID("2130aec0-fefd-434d-92fd-9ca342348b2d"),
+            "user_id": uuid.UUID("21351b66-8aad-475b-84ac-53ce528451e3"),
+            "roles": (Role(
+                role_id=uuid.UUID('89819f84-6346-488b-8955-86062e9eedb7'),
+                role_name='resource_editor',
+                user_editable=True,
+                privileges=(
+                    Privilege(
+                        privilege_id='group:resource:edit-resource',
+                        privilege_description='edit/update a resource'),
+                    Privilege(
+                        privilege_id='group:resource:view-resource',
+                        privilege_description='view a resource and use it in computations'))),)},),
+          tuple(),
+          tuple()))))
 def test_user_roles(fxtr_group_user_roles, user, expected):
     """
     GIVEN: an authenticated user
-- 
cgit v1.2.3