diff options
-rw-r--r-- | gn3/auth/authorisation/groups.py | 41 | ||||
-rw-r--r-- | tests/unit/auth/test_groups.py | 28 |
2 files changed, 61 insertions, 8 deletions
diff --git a/gn3/auth/authorisation/groups.py b/gn3/auth/authorisation/groups.py index 6d7b885..7597a04 100644 --- a/gn3/auth/authorisation/groups.py +++ b/gn3/auth/authorisation/groups.py @@ -1,8 +1,10 @@ """Handle the management of resource/user groups.""" from uuid import UUID, uuid4 -from typing import Iterable, NamedTuple +from typing import Sequence, Iterable, NamedTuple from gn3.auth import db +from gn3.auth.authentication.users import User + from .privileges import Privilege from .roles import Role, create_role from .checks import authorised_p @@ -17,18 +19,45 @@ class GroupRole(NamedTuple): group_role_id: UUID role: Role +class MembershipError(Exception): + """Raised when there is an error with a user's membership to a group.""" + + def __init__(self, user: User, groups: Sequence[Group]): + """Initialise the `MembershipError` exception object.""" + groups_str = ", ".join(group.group_name for group in groups) + error_message = ( + f"User '{user.name} ({user.email})' is a member of {len(groups)} " + f"groups ({groups_str})") + super().__init__(f"{type(self).__name__}: {error_message}.") + +def user_membership(conn: db.DbConnection, user: User) -> Sequence[Group]: + """Returns all the groups that a member belongs to""" + query = ( + "SELECT groups.group_id, group_name FROM group_users INNER JOIN groups " + "ON group_users.group_id=groups.group_id " + "WHERE group_users.user_id=?") + with db.cursor(conn) as cursor: + cursor.execute(query, (str(user.user_id),)) + groups = tuple(Group(row[0], row[1]) for row in cursor.fetchall()) + + return groups + @authorised_p(("create-group",), error_message="Failed to create group.") -def create_group(conn: db.DbConnection, group_name: str) -> Group: +def create_group(conn: db.DbConnection, group_name: str, + group_leader: User) -> Group: """Create a group""" group = Group(uuid4(), group_name) + user_groups = user_membership(conn, group_leader) + if len(user_groups) > 0: + raise MembershipError(group_leader, user_groups) + with db.cursor(conn) as cursor: - ## 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.group_id), group_name)) - ## Maybe assign `group-leader` role to user creating the group + cursor.execute( + "INSERT INTO group_users VALUES (?, ?)", + (str(group.group_id), str(group_leader.user_id))) return group diff --git a/tests/unit/auth/test_groups.py b/tests/unit/auth/test_groups.py index 9471cac..225bb59 100644 --- a/tests/unit/auth/test_groups.py +++ b/tests/unit/auth/test_groups.py @@ -4,10 +4,11 @@ from uuid import UUID import pytest from gn3.auth import db +from gn3.auth.authentication.users import User from gn3.auth.authorisation.roles import Role from gn3.auth.authorisation.privileges import Privilege from gn3.auth.authorisation.groups import ( - Group, GroupRole, create_group, create_group_role) + Group, GroupRole, create_group, MembershipError, create_group_role) create_group_failure = { "status": "error", @@ -44,7 +45,8 @@ def test_create_group(# pylint: disable=[too-many-arguments] with test_app.app_context() as flask_context: flask_context.g.user_id = UUID(user_id) with db.connection(auth_testdb_path) as conn: - assert create_group(conn, "a_test_group") == expected + assert create_group(conn, "a_test_group", User( + UUID(user_id), "some@email.address", "a_test_user")) == expected create_role_failure = { "status": "error", @@ -76,3 +78,25 @@ def test_create_group_role(mocker, test_users_in_group, test_app, user_id, expec flask_context.g.user_id = UUID(user_id) assert create_group_role( conn, GROUP, "ResourceEditor", PRIVILEGES) == expected + +@pytest.mark.unit_test +def test_create_multiple_groups(mocker, test_app, test_users): + """ + GIVEN: An authenticated user with appropriate authorisation + WHEN: The user attempts to create a new group, while being a member of an + existing group + THEN: The system should prevent that, and respond with an appropriate error + message + """ + mocker.patch("gn3.auth.authorisation.groups.uuid4", uuid_fn) + user_id = UUID("ecb52977-3004-469e-9428-2a1856725c7f") + conn, _test_users = test_users + with test_app.app_context() as flask_context: + flask_context.g.user_id = user_id + user = User(user_id, "some@email.address", "a_test_user") + # First time, successfully creates the group + assert create_group(conn, "a_test_group", user) == Group( + UUID("d32611e3-07fc-4564-b56c-786c6db6de2b"), "a_test_group") + # subsequent attempts should fail + with pytest.raises(MembershipError): + create_group(conn, "another_test_group", user) |