From 1f37de222e3f93908f2db3dfef33740aea3c828c Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Tue, 15 Nov 2022 12:55:06 +0300 Subject: auth: Specify types for privileges, roles, groups Use specified types for privileges, roles and types rather than using strings to help with limiting bugs. * gn3/auth/authorisation/groups.py: Specify and use the `Group` type * gn3/auth/authorisation/privileges.py: Specify and use the `Privilege` type * gn3/auth/authorisation/roles.py: Specify the `Role` type. Add the `create_role` function. --- gn3/auth/authorisation/groups.py | 22 +++++++++++++++---- gn3/auth/authorisation/privileges.py | 14 +++++++++--- gn3/auth/authorisation/roles.py | 41 ++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 gn3/auth/authorisation/roles.py (limited to 'gn3/auth') diff --git a/gn3/auth/authorisation/groups.py b/gn3/auth/authorisation/groups.py index 1be9f61..b996d21 100644 --- a/gn3/auth/authorisation/groups.py +++ b/gn3/auth/authorisation/groups.py @@ -1,17 +1,31 @@ """Handle the management of resource/user groups.""" -import uuid +from uuid import UUID, uuid4 +from typing import Iterable, NamedTuple from gn3.auth import db +from .privileges import Privilege +from .roles import Role, create_role from .checks import authorised_p @authorised_p( ("create-group",), success_message="Successfully created group.", error_message="Failed to create group.") def create_group(conn, group_name): +class Group(NamedTuple): + """Class representing a group.""" + group_id: UUID + group_name: str + +def create_group(conn: db.DbConnection, group_name: str) -> Group: """Create a group""" + group = Group(uuid4(), group_name) with db.cursor(conn) as cursor: - group_id = uuid.uuid4() + ## Maybe check whether the user is already a member of a group + ## if they are not a member of any group, proceed to create the group + ## if they are a member of a group, then fail with an exception cursor.execute( "INSERT INTO groups(group_id, group_name) VALUES (?, ?)", - (str(group_id), group_name)) - return group_id + (str(group.group_id), group_name)) + ## Maybe assign `group-leader` role to user creating the group + + return group diff --git a/gn3/auth/authorisation/privileges.py b/gn3/auth/authorisation/privileges.py index 99b36ef..c60a58c 100644 --- a/gn3/auth/authorisation/privileges.py +++ b/gn3/auth/authorisation/privileges.py @@ -1,16 +1,24 @@ """Handle privileges""" from uuid import UUID +from typing import Iterable, NamedTuple from gn3.auth import db -def user_privileges(conn, user_id: UUID): +class Privilege(NamedTuple): + """Class representing a privilege: creates immutable objects.""" + privilege_id: UUID + privilege_name: str + +def user_privileges(conn: db.DbConnection, user_id: UUID) -> Iterable[Privilege]: """Fetch the user's privileges from the database.""" with db.cursor(conn) as cursor: cursor.execute( - ("SELECT p.privilege_name " + ("SELECT p.privilege_id, p.privilege_name " "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 " "WHERE ur.user_id=?"), (str(user_id),)) - return tuple(row[0] for row in cursor.fetchall()) + results = cursor.fetchall() + + return (Privilege(row[0], row[1]) for row in results) diff --git a/gn3/auth/authorisation/roles.py b/gn3/auth/authorisation/roles.py new file mode 100644 index 0000000..7c33ab3 --- /dev/null +++ b/gn3/auth/authorisation/roles.py @@ -0,0 +1,41 @@ +"""Handle management of roles""" +from uuid import UUID, uuid4 +from typing import Iterable, NamedTuple + +from gn3.auth import db + +from .checks import authorised_p +from .privileges import Privilege + +class Role(NamedTuple): + """Class representing a role: creates immutable objects.""" + role_id: UUID + role_name: str + privileges: Iterable[Privilege] + +@authorised_p(("create-role",), error_message="Could not create role") +def create_role( + cursor: db.DbCursor, role_name: str, + privileges: Iterable[Privilege]) -> Role: + """ + Create a new generic role. + + PARAMS: + * cursor: A database cursor object - This function could be used as part of + a transaction, hence the use of a cursor rather than a connection + object. + * role_name: The name of the role + * privileges: A 'list' of privileges to assign the new role + + RETURNS: An immutable `gn3.auth.authorisation.roles.Role` object + """ + role = Role(uuid4(), role_name, privileges) + + cursor.execute( + "INSERT INTO roles(role_id, role_name) VALUES (?, ?)", + (role.role_id, role.role_name)) + cursor.execute( + "INSERT INTO role_privileges(role_id, privilege_id) VALUES (?, ?)", + ((role.role_id, priv.privilege_id) for priv in privileges)) + + return role -- cgit v1.2.3