From 98dc0c5b1a67a7c7b97a1fa02211e9f99360edce Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 16 Jan 2023 12:14:24 +0300 Subject: auth: update privileges format Save privileges with ids of the form :: rather than using a UUID, to reduce indirection levels. * migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py: new migration to change the schema and IDs for the privileges. * Update code to use new privileges format * gn3/auth/authorisation/checks.py * gn3/auth/authorisation/groups.py * gn3/auth/authorisation/privileges.py * gn3/auth/authorisation/resources.py * gn3/auth/authorisation/roles.py * migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py * tests/unit/auth/fixtures/role_fixtures.py * tests/unit/auth/test_groups.py * tests/unit/auth/test_privileges.py * tests/unit/auth/test_roles.py --- gn3/auth/authorisation/checks.py | 2 +- gn3/auth/authorisation/groups.py | 6 +- gn3/auth/authorisation/privileges.py | 9 +- gn3/auth/authorisation/resources.py | 3 +- gn3/auth/authorisation/roles.py | 10 +- .../20230116_01_KwuJ3-rework-privileges-schema.py | 111 +++++++++++++++++++++ tests/unit/auth/fixtures/role_fixtures.py | 11 +- tests/unit/auth/test_groups.py | 6 +- tests/unit/auth/test_privileges.py | 43 ++++---- tests/unit/auth/test_roles.py | 84 +++++++--------- 10 files changed, 192 insertions(+), 93 deletions(-) create mode 100644 migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py diff --git a/gn3/auth/authorisation/checks.py b/gn3/auth/authorisation/checks.py index dd041fe..d847c1e 100644 --- a/gn3/auth/authorisation/checks.py +++ b/gn3/auth/authorisation/checks.py @@ -19,7 +19,7 @@ def authorised_p( if hasattr(g, "user") and g.user: with db.connection(app.config["AUTH_DB"]) as conn: user_privileges = tuple( - priv.privilege_name for priv in + priv.privilege_id for priv in auth_privs.user_privileges(conn, g.user)) not_assigned = [ diff --git a/gn3/auth/authorisation/groups.py b/gn3/auth/authorisation/groups.py index ff4dc80..bbabd44 100644 --- a/gn3/auth/authorisation/groups.py +++ b/gn3/auth/authorisation/groups.py @@ -49,7 +49,8 @@ def user_membership(conn: db.DbConnection, user: User) -> Sequence[Group]: return groups @authenticated_p -@authorised_p(("create-group",), error_message="Failed to create group.") +@authorised_p(("system:group:create-group",), + error_message="Failed to create group.") def create_group(conn: db.DbConnection, group_name: str, group_leader: User) -> Group: """Create a group""" @@ -69,7 +70,8 @@ def create_group(conn: db.DbConnection, group_name: str, return group @authenticated_p -@authorised_p(("create-role",), error_message="Could not create the group role") +@authorised_p(("group:role:create-role",), + error_message="Could not create the group role") def create_group_role( conn: db.DbConnection, group: Group, role_name: str, privileges: Iterable[Privilege]) -> GroupRole: diff --git a/gn3/auth/authorisation/privileges.py b/gn3/auth/authorisation/privileges.py index 9e66bda..6cfd1d8 100644 --- a/gn3/auth/authorisation/privileges.py +++ b/gn3/auth/authorisation/privileges.py @@ -1,5 +1,4 @@ """Handle privileges""" -from uuid import UUID from typing import Iterable, NamedTuple from gn3.auth import db @@ -7,14 +6,14 @@ from gn3.auth.authentication.users import User class Privilege(NamedTuple): """Class representing a privilege: creates immutable objects.""" - privilege_id: UUID - privilege_name: str + privilege_id: str + privilege_description: str def user_privileges(conn: db.DbConnection, user: User) -> Iterable[Privilege]: """Fetch the user's privileges from the database.""" with db.cursor(conn) as cursor: cursor.execute( - ("SELECT p.privilege_id, p.privilege_name " + ("SELECT p.privilege_id, p.privilege_description " "FROM user_roles AS ur " "INNER JOIN role_privileges AS rp ON ur.role_id=rp.role_id " "INNER JOIN privileges AS p ON rp.privilege_id=p.privilege_id " @@ -22,4 +21,4 @@ def user_privileges(conn: db.DbConnection, user: User) -> Iterable[Privilege]: (str(user.user_id),)) results = cursor.fetchall() - return (Privilege(UUID(row[0]), row[1]) for row in results) + return (Privilege(row[0], row[1]) for row in results) diff --git a/gn3/auth/authorisation/resources.py b/gn3/auth/authorisation/resources.py index bc6ba44..f27d61a 100644 --- a/gn3/auth/authorisation/resources.py +++ b/gn3/auth/authorisation/resources.py @@ -26,7 +26,8 @@ class Resource(NamedTuple): resource_category: ResourceCategory public: bool -@authorised_p(("create-resource",), error_message="Could not create resource") +@authorised_p(("group:resource:create-resource",), + error_message="Could not create resource") def create_resource( conn: db.DbConnection, resource_name: str, resource_category: ResourceCategory) -> Resource: diff --git a/gn3/auth/authorisation/roles.py b/gn3/auth/authorisation/roles.py index 6602c9f..606403e 100644 --- a/gn3/auth/authorisation/roles.py +++ b/gn3/auth/authorisation/roles.py @@ -17,7 +17,7 @@ class Role(NamedTuple): privileges: Iterable[Privilege] @authenticated_p -@authorised_p(("create-role",), error_message="Could not create role") +@authorised_p(("group:role:create-role",), error_message="Could not create role") def create_role( cursor: db.DbCursor, role_name: str, privileges: Iterable[Privilege]) -> Role: @@ -55,8 +55,8 @@ def __organise_privileges__(roles_dict, privilege_row): UUID(role_id_str), privilege_row["role_name"], roles_dict[role_id_str].privileges + ( - Privilege(UUID(privilege_row["privilege_id"]), - privilege_row["privilege_name"]),)) + Privilege(privilege_row["privilege_id"], + privilege_row["privilege_description"]),)) } return { @@ -64,8 +64,8 @@ def __organise_privileges__(roles_dict, privilege_row): role_id_str: Role( UUID(role_id_str), privilege_row["role_name"], - (Privilege(UUID(privilege_row["privilege_id"]), - privilege_row["privilege_name"]),)) + (Privilege(privilege_row["privilege_id"], + privilege_row["privilege_description"]),)) } def user_roles(conn: db.DbConnection, user: User): diff --git a/migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py b/migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py new file mode 100644 index 0000000..1ef5ab0 --- /dev/null +++ b/migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py @@ -0,0 +1,111 @@ +""" +rework privileges schema +""" +import contextlib + +from yoyo import step + +__depends__ = {'20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader'} + +privileges = ( # format: (original_id, original_name, new_id, category) + ("13ec2a94-4f1a-442d-aad2-936ad6dd5c57", "delete-group", + "system:group:delete-group", "group-management"), + ("1c59eff5-9336-4ed2-a166-8f70d4cb012e", "delete-role", + "group:role:delete-role", "role-management"), + ("1fe61370-cae9-4983-bd6c-ce61050c510f", "delete-any-user", + "system:user:delete-user", "user-management"), + ("221660b1-df05-4be1-b639-f010269dbda9", "create-role", + "group:role:create-role", "role-management"), + ("2f980855-959b-4339-b80e-25d1ec286e21", "edit-resource", + "group:resource:edit-resource", "resource-management"), + ("3ebfe79c-d159-4629-8b38-772cf4bc2261", "view-group", + "system:group:view-group", "group-management"), + ("4842e2aa-38b9-4349-805e-0a99a9cf8bff", "create-group", + "system:group:create-group", "group-management"), + ("5103cc68-96f8-4ebb-83a4-a31692402c9b", "assign-role", + "group:user:assign-role", "role-management"), + ("519db546-d44e-4fdc-9e4e-25aa67548ab3", "masquerade", + "system:user:masquerade", "system-admin"), + ("52576370-b3c7-4e6a-9f7e-90e9dbe24d8f", "edit-group", + "system:group:edit-group", "group-management"), + ("7bcca363-cba9-4169-9e31-26bdc6179b28", "edit-role", + "group:role:edit-role", "role-management"), + ("7f261757-3211-4f28-a43f-a09b800b164d", "view-resource", + "group:resource:view-resource", "resource-management"), + ("80f11285-5079-4ec0-907c-06509f88a364", "assign-group-leader", + "system:user:assign-group-leader", "group-management"), + ("aa25b32a-bff2-418d-b0a2-e26b4a8f089b", "create-resource", + "group:resource:create-resource", "resource-management"), + ("ae4add8c-789a-4d11-a6e9-a306470d83d9", "add-group-member", + "group:user:add-group-member", "group-management"), + ("d2a070fd-e031-42fb-ba41-d60cf19e5d6d", "delete-resource", + "group:resource:delete-resource", "resource-management"), + ("d4afe2b3-4ca0-4edd-b37d-966535b5e5bd", "transfer-group-leadership", + "system:group:transfer-group-leader", "group-management"), + ("e7252301-6ee0-43ba-93ef-73b607cf06f6", "reset-any-password", + "system:user:reset-password", "user-management"), + ("f1bd3f42-567e-4965-9643-6d1a52ddee64", "remove-group-member", + "group:user:remove-group-member", "group-management")) + +def rework_privileges_table(cursor): + "rework the schema" + cursor.executemany( + ("UPDATE privileges SET privilege_id=:id " + "WHERE privilege_id=:old_id"), + ({"id": row[2], "old_id": row[0]} for row in privileges)) + cursor.execute("ALTER TABLE privileges DROP COLUMN privilege_category") + cursor.execute("ALTER TABLE privileges DROP COLUMN privilege_name") + +def restore_privileges_table(cursor): + "restore the schema" + cursor.execute(( + "CREATE TABLE privileges_restore (" + " privilege_id TEXT PRIMARY KEY," + " privilege_name TEXT NOT NULL," + " privilege_category TEXT NOT NULL DEFAULT 'common'," + " privilege_description TEXT" + ")")) + id_dict = {row[2]: {"id": row[0], "name": row[1], "cat": row[3]} + for row in privileges} + cursor.execute( + "SELECT privilege_id, privilege_description FROM privileges") + params = ({**id_dict[row[0]], "desc": row[1]} for row in cursor.fetchall()) + cursor.executemany( + "INSERT INTO privileges_restore VALUES (:id, :name, :cat, :desc)", + params) + cursor.execute("DROP TABLE privileges") + cursor.execute("ALTER TABLE privileges_restore RENAME TO privileges") + +def update_privilege_ids_in_role_privileges(cursor): + """Update the ids to new form.""" + cursor.executemany( + ("UPDATE role_privileges SET privilege_id=:new_id " + "WHERE privilege_id=:old_id"), + ({"new_id": row[2], "old_id": row[0]} for row in privileges)) + +def restore_privilege_ids_in_role_privileges(cursor): + """Restore original ids""" + cursor.executemany( + ("UPDATE role_privileges SET privilege_id=:old_id " + "WHERE privilege_id=:new_id"), + ({"new_id": row[2], "old_id": row[0]} for row in privileges)) + +def change_schema(conn): + """Change the privileges schema and IDs""" + with contextlib.closing(conn.cursor()) as cursor: + cursor.execute("PRAGMA foreign_keys=OFF") + rework_privileges_table(cursor) + update_privilege_ids_in_role_privileges(cursor) + cursor.execute("PRAGMA foreign_keys=ON") + +def restore_schema(conn): + """Change the privileges schema and IDs""" + with contextlib.closing(conn.cursor()) as cursor: + cursor.execute("PRAGMA foreign_keys=OFF") + restore_privilege_ids_in_role_privileges(cursor) + restore_privileges_table(cursor) + cursor.execute("PRAGMA foreign_keys=ON") + +steps = [ + step(change_schema, restore_schema) +] diff --git a/tests/unit/auth/fixtures/role_fixtures.py b/tests/unit/auth/fixtures/role_fixtures.py index e1e1680..cde3d96 100644 --- a/tests/unit/auth/fixtures/role_fixtures.py +++ b/tests/unit/auth/fixtures/role_fixtures.py @@ -9,15 +9,14 @@ from gn3.auth.authorisation.privileges import Privilege RESOURCE_READER_ROLE = Role( uuid.UUID("c3ca2507-ee24-4835-9b31-8c21e1c072d3"), "resource_reader", - (Privilege(uuid.UUID("7f261757-3211-4f28-a43f-a09b800b164d"), - "view-resource"),)) + (Privilege("group:resource:view-resource", + "view a resource and use it in computations"),)) RESOURCE_EDITOR_ROLE = Role( uuid.UUID("89819f84-6346-488b-8955-86062e9eedb7"), "resource_editor", ( - Privilege(uuid.UUID("7f261757-3211-4f28-a43f-a09b800b164d"), - "view-resource"), - Privilege(uuid.UUID("2f980855-959b-4339-b80e-25d1ec286e21"), - "edit-resource"))) + Privilege("group:resource:view-resource", + "view a resource and use it in computations"), + Privilege("group:resource:edit-resource", "edit/update a resource"))) TEST_ROLES = (RESOURCE_READER_ROLE, RESOURCE_EDITOR_ROLE) diff --git a/tests/unit/auth/test_groups.py b/tests/unit/auth/test_groups.py index 2345b2a..53e0543 100644 --- a/tests/unit/auth/test_groups.py +++ b/tests/unit/auth/test_groups.py @@ -23,9 +23,9 @@ uuid_fn = lambda : UUID("d32611e3-07fc-4564-b56c-786c6db6de2b") GROUP = Group(UUID("9988c21d-f02f-4d45-8966-22c968ac2fbf"), "TheTestGroup") PRIVILEGES = ( Privilege( - UUID("7f261757-3211-4f28-a43f-a09b800b164d"), "view-resource"), - Privilege( - UUID("2f980855-959b-4339-b80e-25d1ec286e21"), "edit-resource")) + "group:resource:view-resource", + "view a resource and use it in computations"), + Privilege("group:resource:edit-resource", "edit/update a resource")) @pytest.mark.unit_test @pytest.mark.parametrize( diff --git a/tests/unit/auth/test_privileges.py b/tests/unit/auth/test_privileges.py index 1c2ba24..e6c86d8 100644 --- a/tests/unit/auth/test_privileges.py +++ b/tests/unit/auth/test_privileges.py @@ -1,6 +1,4 @@ """Test the privileges module""" -from uuid import UUID - import pytest from gn3.auth import db @@ -8,29 +6,28 @@ from gn3.auth.authorisation.privileges import Privilege, user_privileges from tests.unit.auth import conftest -SORT_KEY = lambda x: x.privilege_name +SORT_KEY = lambda x: x.privilege_id PRIVILEGES = sorted( - (Privilege(UUID("4842e2aa-38b9-4349-805e-0a99a9cf8bff"), "create-group"), - Privilege(UUID("3ebfe79c-d159-4629-8b38-772cf4bc2261"), "view-group"), - Privilege(UUID("52576370-b3c7-4e6a-9f7e-90e9dbe24d8f"), "edit-group"), - Privilege(UUID("13ec2a94-4f1a-442d-aad2-936ad6dd5c57"), "delete-group"), - Privilege(UUID("ae4add8c-789a-4d11-a6e9-a306470d83d9"), - "add-group-member"), - Privilege(UUID("f1bd3f42-567e-4965-9643-6d1a52ddee64"), - "remove-group-member"), - Privilege(UUID("d4afe2b3-4ca0-4edd-b37d-966535b5e5bd"), - "transfer-group-leadership"), - - Privilege(UUID("aa25b32a-bff2-418d-b0a2-e26b4a8f089b"), "create-resource"), - Privilege(UUID("7f261757-3211-4f28-a43f-a09b800b164d"), "view-resource"), - Privilege(UUID("2f980855-959b-4339-b80e-25d1ec286e21"), "edit-resource"), - Privilege(UUID("d2a070fd-e031-42fb-ba41-d60cf19e5d6d"), "delete-resource"), - - Privilege(UUID("221660b1-df05-4be1-b639-f010269dbda9"), "create-role"), - Privilege(UUID("7bcca363-cba9-4169-9e31-26bdc6179b28"), "edit-role"), - Privilege(UUID("5103cc68-96f8-4ebb-83a4-a31692402c9b"), "assign-role"), - Privilege(UUID("1c59eff5-9336-4ed2-a166-8f70d4cb012e"), "delete-role")), + (Privilege("system:group:create-group", "Create a group"), + Privilege("system:group:view-group", "View the details of a group"), + Privilege("system:group:edit-group", "Edit the details of a group"), + Privilege("system:group:delete-group", "Delete a group"), + Privilege("group:user:add-group-member", "Add a user to a group"), + Privilege("group:user:remove-group-member", "Remove a user from a group"), + Privilege("system:group:transfer-group-leader", + "Transfer leadership of the group to some other member"), + + Privilege("group:resource:create-resource", "Create a resource object"), + Privilege("group:resource:view-resource", + "view a resource and use it in computations"), + Privilege("group:resource:edit-resource", "edit/update a resource"), + Privilege("group:resource:delete-resource", "Delete a resource"), + + Privilege("group:role:create-role", "Create a new role"), + Privilege("group:role:edit-role", "edit/update an existing role"), + Privilege("group:user:assign-role", "Assign a role to an existing user"), + Privilege("group:role:delete-role", "Delete an existing role")), key=SORT_KEY) @pytest.mark.unit_test diff --git a/tests/unit/auth/test_roles.py b/tests/unit/auth/test_roles.py index 21d8e86..56ad39e 100644 --- a/tests/unit/auth/test_roles.py +++ b/tests/unit/auth/test_roles.py @@ -18,10 +18,9 @@ create_role_failure = { uuid_fn = lambda : uuid.UUID("d32611e3-07fc-4564-b56c-786c6db6de2b") PRIVILEGES = ( - Privilege(uuid.UUID("7f261757-3211-4f28-a43f-a09b800b164d"), - "view-resource"), - Privilege(uuid.UUID("2f980855-959b-4339-b80e-25d1ec286e21"), - "edit-resource")) + Privilege("group:resource:view-resource", + "view a resource and use it in computations"), + Privilege("group:resource:edit-resource", "edit/update a resource")) @pytest.mark.unit_test @pytest.mark.parametrize( @@ -53,55 +52,46 @@ def test_create_role(# pylint: disable=[too-many-arguments] role_id=uuid.UUID('a0e67630-d502-4b9f-b23f-6805d0f30e30'), role_name='group-leader', 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=uuid.UUID('13ec2a94-4f1a-442d-aad2-936ad6dd5c57'), - privilege_name='delete-group'), + 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=uuid.UUID('1c59eff5-9336-4ed2-a166-8f70d4cb012e'), - privilege_name='delete-role'), - Privilege( - privilege_id=uuid.UUID('221660b1-df05-4be1-b639-f010269dbda9'), - privilege_name='create-role'), - Privilege( - privilege_id=uuid.UUID('2f980855-959b-4339-b80e-25d1ec286e21'), - privilege_name='edit-resource'), - Privilege( - privilege_id=uuid.UUID('3ebfe79c-d159-4629-8b38-772cf4bc2261'), - privilege_name='view-group'), - Privilege( - privilege_id=uuid.UUID('5103cc68-96f8-4ebb-83a4-a31692402c9b'), - privilege_name='assign-role'), - Privilege( - privilege_id=uuid.UUID('52576370-b3c7-4e6a-9f7e-90e9dbe24d8f'), - privilege_name='edit-group'), - Privilege( - privilege_id=uuid.UUID('7bcca363-cba9-4169-9e31-26bdc6179b28'), - privilege_name='edit-role'), - Privilege( - privilege_id=uuid.UUID('7f261757-3211-4f28-a43f-a09b800b164d'), - privilege_name='view-resource'), - Privilege( - privilege_id=uuid.UUID('aa25b32a-bff2-418d-b0a2-e26b4a8f089b'), - privilege_name='create-resource'), - Privilege( - privilege_id=uuid.UUID('ae4add8c-789a-4d11-a6e9-a306470d83d9'), - privilege_name='add-group-member'), - Privilege( - privilege_id=uuid.UUID('d2a070fd-e031-42fb-ba41-d60cf19e5d6d'), - privilege_name='delete-resource'), - Privilege( - privilege_id=uuid.UUID('d4afe2b3-4ca0-4edd-b37d-966535b5e5bd'), - privilege_name='transfer-group-leadership'), - Privilege( - privilege_id=uuid.UUID('f1bd3f42-567e-4965-9643-6d1a52ddee64'), - privilege_name='remove-group-member'))), + 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'))), Role( role_id=uuid.UUID("ade7e6b0-ba9c-4b51-87d0-2af7fe39a347"), role_name="group-creator", privileges=( - Privilege( - privilege_id=uuid.UUID('4842e2aa-38b9-4349-805e-0a99a9cf8bff'), - privilege_name='create-group'),))), + Privilege(privilege_id='system:group:create-group', + privilege_description = "Create a group"),))), tuple(), tuple(), tuple())))) def test_user_roles(fxtr_group_user_roles, user, expected): """ -- cgit v1.2.3