From 53371fb668d1d18ba4696b3e4739f26edd677d8d Mon Sep 17 00:00:00 2001
From: Frederick Muriuki Muriithi
Date: Wed, 11 Jan 2023 11:20:36 +0300
Subject: auth: assign default role. separate group creation from group admin

A newly registered user will have the ability to create a group.

Once a user is a member of a group, either by creating a new group, or being
added to a group, they should not be able to create any more groups, i.e. they
lose the 'create-group' (and/or equivalent) privileges.

This means that the group-administration privileges should be separated from
the group-creation privilege.

* gn3/auth/authorisation/roles.py: assign default roles to user on
  registration
* gn3/auth/authorisation/views.py: assign default roles to user on
  registration
* migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py:
  separate group-creation role from group-administration role.
* tests/unit/auth/fixtures/user_fixtures.py: Add group-creation role to test
  user
* tests/unit/auth/test_roles.py: Add the group-creation role explicitly in the
  expected results for the test
---
 gn3/auth/authorisation/roles.py                    | 13 +++++++
 gn3/auth/authorisation/views.py                    |  5 +--
 ...ove-create-group-privilege-from-group-leader.py | 40 ++++++++++++++++++++++
 tests/unit/auth/fixtures/user_fixtures.py          |  4 ++-
 tests/unit/auth/test_roles.py                      | 12 ++++---
 5 files changed, 67 insertions(+), 7 deletions(-)
 create mode 100644 migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py

diff --git a/gn3/auth/authorisation/roles.py b/gn3/auth/authorisation/roles.py
index 562d3bc..6602c9f 100644
--- a/gn3/auth/authorisation/roles.py
+++ b/gn3/auth/authorisation/roles.py
@@ -83,3 +83,16 @@ def user_roles(conn: db.DbConnection, user: User):
             return tuple(
                 reduce(__organise_privileges__, results, {}).values())
         return tuple()
+
+def assign_default_roles(cursor: db.DbCursor, user: User):
+    """Assign `user` some default roles."""
+    cursor.execute(
+        'SELECT role_id FROM roles WHERE role_name IN '
+        '("group-creator")')
+    role_ids = cursor.fetchall()
+    str_user_id = str(user.user_id)
+    params = (
+        {"user_id": str_user_id, "role_id": role_id} for role_id in role_ids)
+    cursor.executemany(
+        ("INSERT INTO user_roles VALUES (:user_id, :role_id)"),
+        params)
diff --git a/gn3/auth/authorisation/views.py b/gn3/auth/authorisation/views.py
index 6ac3be0..2c47bd9 100644
--- a/gn3/auth/authorisation/views.py
+++ b/gn3/auth/authorisation/views.py
@@ -7,10 +7,10 @@ from gn3.auth.blueprint import oauth2
 
 from .groups import user_group
 from .errors import UserRegistrationError
-from .roles import user_roles as _user_roles
+from .roles import assign_default_roles, user_roles as _user_roles
 
 from ..authentication.oauth2.resource_server import require_oauth
-from ..authentication.users import User, save_user, set_user_password
+from ..authentication.users import save_user, set_user_password
 from ..authentication.oauth2.models.oauth2token import token_by_access_token
 
 @oauth2.route("/user", methods=["GET"])
@@ -95,6 +95,7 @@ def register_user():
         with db.cursor(conn) as cursor:
             user, _hashed_password = set_user_password(
                 cursor, save_user(cursor, email, user_name), password)
+            assign_default_roles(cursor, user)
             return jsonify(
                 {
                     "user_id": user.user_id,
diff --git a/migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py b/migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py
new file mode 100644
index 0000000..7e7fda2
--- /dev/null
+++ b/migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py
@@ -0,0 +1,40 @@
+"""
+remove 'create-group' privilege from group-leader.
+"""
+
+from yoyo import step
+
+__depends__ = {'20221219_03_PcTrb-create-authorisation-code-table'}
+
+steps = [
+    step(
+        """
+        DELETE FROM role_privileges
+        WHERE role_id='a0e67630-d502-4b9f-b23f-6805d0f30e30'
+        AND privilege_id='4842e2aa-38b9-4349-805e-0a99a9cf8bff'
+        """,
+        """
+        INSERT INTO role_privileges VALUES
+        ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
+        '4842e2aa-38b9-4349-805e-0a99a9cf8bff')
+        """),
+    step(
+        """
+        INSERT INTO roles(role_id, role_name, user_editable) VALUES
+          ('ade7e6b0-ba9c-4b51-87d0-2af7fe39a347', 'group-creator', '0')
+        """,
+        """
+        DELETE FROM roles WHERE role_id='ade7e6b0-ba9c-4b51-87d0-2af7fe39a347'
+        """),
+    step(
+        """
+        INSERT INTO role_privileges VALUES
+          ('ade7e6b0-ba9c-4b51-87d0-2af7fe39a347',
+           '4842e2aa-38b9-4349-805e-0a99a9cf8bff')
+        """,
+        """
+        DELETE FROM role_privileges
+        WHERE role_id='ade7e6b0-ba9c-4b51-87d0-2af7fe39a347'
+        AND privilege_id='4842e2aa-38b9-4349-805e-0a99a9cf8bff'
+        """)
+]
diff --git a/tests/unit/auth/fixtures/user_fixtures.py b/tests/unit/auth/fixtures/user_fixtures.py
index 89b7e62..4e42abe 100644
--- a/tests/unit/auth/fixtures/user_fixtures.py
+++ b/tests/unit/auth/fixtures/user_fixtures.py
@@ -24,7 +24,9 @@ def fxtr_users(conn_after_auth_migrations):# pylint: disable=[redefined-outer-na
     query_user_roles = "INSERT INTO user_roles(user_id, role_id) VALUES (?, ?)"
     test_user_roles = (
         ("ecb52977-3004-469e-9428-2a1856725c7f",
-         "a0e67630-d502-4b9f-b23f-6805d0f30e30"),)
+         "a0e67630-d502-4b9f-b23f-6805d0f30e30"),
+        ("ecb52977-3004-469e-9428-2a1856725c7f",
+         "ade7e6b0-ba9c-4b51-87d0-2af7fe39a347"))
     with db.cursor(conn_after_auth_migrations) as cursor:
         cursor.executemany(query, (
             (str(user.user_id), user.email, user.name) for user in TEST_USERS))
diff --git a/tests/unit/auth/test_roles.py b/tests/unit/auth/test_roles.py
index 3fc146a..21d8e86 100644
--- a/tests/unit/auth/test_roles.py
+++ b/tests/unit/auth/test_roles.py
@@ -68,9 +68,6 @@ def test_create_role(# pylint: disable=[too-many-arguments]
                  Privilege(
                      privilege_id=uuid.UUID('3ebfe79c-d159-4629-8b38-772cf4bc2261'),
                      privilege_name='view-group'),
-                 Privilege(
-                     privilege_id=uuid.UUID('4842e2aa-38b9-4349-805e-0a99a9cf8bff'),
-                     privilege_name='create-group'),
                  Privilege(
                      privilege_id=uuid.UUID('5103cc68-96f8-4ebb-83a4-a31692402c9b'),
                      privilege_name='assign-role'),
@@ -97,7 +94,14 @@ def test_create_role(# pylint: disable=[too-many-arguments]
                      privilege_name='transfer-group-leadership'),
              Privilege(
                  privilege_id=uuid.UUID('f1bd3f42-567e-4965-9643-6d1a52ddee64'),
-                 privilege_name='remove-group-member'))),),
+                 privilege_name='remove-group-member'))),
+           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'),))),
           tuple(), tuple(), tuple()))))
 def test_user_roles(fxtr_group_user_roles, user, expected):
     """
-- 
cgit v1.2.3