about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2023-01-03 07:22:02 +0300
committerFrederick Muriuki Muriithi2023-01-03 07:24:46 +0300
commit0a31f61ee9db84eb35087073ef6b58f352252aae (patch)
tree70d249b9a1c1c911146b4a05867a3e0d0119a7cb
parentda33d719105d67afb1ee6b040380211cfa8be23d (diff)
downloadgenenetwork3-0a31f61ee9db84eb35087073ef6b58f352252aae.tar.gz
auth: Fetch all of a user's roles.
* gn3/auth/authorisation/roles.py: Fetch roles from DB
* gn3/auth/authorisation/views.py: Provide API endpoint for user roles
* tests/unit/auth/test_roles.py: Tests to check fetching roles works correctly

Fix linting and typing issues in the following files:

* gn3/auth/authentication/oauth2/resource_server.py
* gn3/auth/authentication/oauth2/views.py
* tests/unit/auth/fixtures/oauth2_client_fixtures.py
-rw-r--r--gn3/auth/authentication/oauth2/resource_server.py4
-rw-r--r--gn3/auth/authentication/oauth2/views.py5
-rw-r--r--gn3/auth/authorisation/roles.py47
-rw-r--r--gn3/auth/authorisation/views.py15
-rw-r--r--tests/unit/auth/fixtures/oauth2_client_fixtures.py1
-rw-r--r--tests/unit/auth/test_roles.py65
6 files changed, 132 insertions, 5 deletions
diff --git a/gn3/auth/authentication/oauth2/resource_server.py b/gn3/auth/authentication/oauth2/resource_server.py
index 885cbd8..223e811 100644
--- a/gn3/auth/authentication/oauth2/resource_server.py
+++ b/gn3/auth/authentication/oauth2/resource_server.py
@@ -2,7 +2,7 @@
 
 from flask import current_app as app
 from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator
-from authlib.integrations.flask_oauth2 import ResourceProtector, current_token
+from authlib.integrations.flask_oauth2 import ResourceProtector
 
 from gn3.auth import db
 from gn3.auth.authentication.oauth2.models.oauth2token import token_by_access_token
@@ -11,7 +11,7 @@ class BearerTokenValidator(_BearerTokenValidator):
     """Extends `authlib.oauth2.rfc6750.BearerTokenValidator`"""
     def authenticate_token(self, token_string: str):
         with db.connection(app.config["AUTH_DB"]) as conn:
-            return token_by_access_token(conn, token_string).maybe(
+            return token_by_access_token(conn, token_string).maybe(# type: ignore[misc]
                 None, lambda tok: tok)
 
 require_oauth = ResourceProtector()
diff --git a/gn3/auth/authentication/oauth2/views.py b/gn3/auth/authentication/oauth2/views.py
index 0947aa2..7d0d7dd 100644
--- a/gn3/auth/authentication/oauth2/views.py
+++ b/gn3/auth/authentication/oauth2/views.py
@@ -45,8 +45,9 @@ def introspect_token():
 @oauth2.route("/user")
 @require_oauth("profile")
 def user_details():
-    with require_oauth.acquire("profile") as token:
-        user = token.user
+    """Return user's details."""
+    with require_oauth.acquire("profile") as the_token:
+        user = the_token.user
         return jsonify({
             "user_id": user.user_id,
             "email": user.email,
diff --git a/gn3/auth/authorisation/roles.py b/gn3/auth/authorisation/roles.py
index 397ad80..e71d427 100644
--- a/gn3/auth/authorisation/roles.py
+++ b/gn3/auth/authorisation/roles.py
@@ -1,8 +1,10 @@
 """Handle management of roles"""
 from uuid import UUID, uuid4
+from functools import reduce
 from typing import Iterable, NamedTuple
 
 from gn3.auth import db
+from gn3.auth.authentication.users import User
 from gn3.auth.authentication.checks import authenticated_p
 
 from .checks import authorised_p
@@ -42,3 +44,48 @@ def create_role(
               for priv in privileges))
 
     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"],
+                roles_dict[role_id_str].privileges + (
+                    Privilege(UUID(privilege_row["privilege_id"]),
+                              privilege_row["privilege_name"]),))
+        }
+
+    return {
+        **roles_dict,
+        role_id_str: Role(
+            UUID(role_id_str),
+            privilege_row["role_name"],
+            (Privilege(UUID(privilege_row["privilege_id"]),
+                       privilege_row["privilege_name"]),))
+    }
+
+def user_roles(conn: db.DbConnection, user: User):
+    """Retrieve ALL roles assigned to the user."""
+    with db.cursor(conn) as cursor:
+        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=? "
+            "UNION "
+            "SELECT r.*, p.* FROM group_user_roles_on_resources AS guror "
+            "INNER JOIN roles AS r ON guror.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 guror.user_id=?",
+            ((str(user.user_id),)*2))
+
+        results = cursor.fetchall()
+        if results:
+            return tuple(
+                reduce(__organise_privileges__, results, {}).values())
+        return tuple()
diff --git a/gn3/auth/authorisation/views.py b/gn3/auth/authorisation/views.py
new file mode 100644
index 0000000..2481633
--- /dev/null
+++ b/gn3/auth/authorisation/views.py
@@ -0,0 +1,15 @@
+"""Endpoints for the authorisation stuff."""
+from flask import jsonify, current_app
+
+from gn3.auth import db
+from .roles import user_roles as _user_roles
+from ..authentication.oauth2.views import oauth2
+from ..authentication.oauth2.resource_server import require_oauth
+
+@oauth2.route("/user-roles")
+@require_oauth
+def user_roles():
+    """Return the roles assigned to the user."""
+    with require_oauth.acquire("role") as token:
+        with db.connection(current_app.config["AUTH_DB"]) as conn:
+            return jsonify(_user_roles(conn, token.user))
diff --git a/tests/unit/auth/fixtures/oauth2_client_fixtures.py b/tests/unit/auth/fixtures/oauth2_client_fixtures.py
index 5f11e92..040da87 100644
--- a/tests/unit/auth/fixtures/oauth2_client_fixtures.py
+++ b/tests/unit/auth/fixtures/oauth2_client_fixtures.py
@@ -10,6 +10,7 @@ from gn3.auth.authentication.oauth2.models.oauth2client import OAuth2Client
 
 @pytest.fixture(autouse=True)
 def fxtr_patch_envvars(monkeypatch):
+    """Fixture: patch environment variable"""
     monkeypatch.setenv("AUTHLIB_INSECURE_TRANSPORT", "true")
 
 @pytest.fixture
diff --git a/tests/unit/auth/test_roles.py b/tests/unit/auth/test_roles.py
index 92384bb..3fc146a 100644
--- a/tests/unit/auth/test_roles.py
+++ b/tests/unit/auth/test_roles.py
@@ -5,9 +5,10 @@ import pytest
 
 from gn3.auth import db
 from gn3.auth.authorisation.privileges import Privilege
-from gn3.auth.authorisation.roles import Role, create_role
+from gn3.auth.authorisation.roles import Role, user_roles, create_role
 
 from tests.unit.auth import conftest
+from tests.unit.auth.fixtures import TEST_USERS
 
 create_role_failure = {
     "status": "error",
@@ -43,3 +44,65 @@ def test_create_role(# pylint: disable=[too-many-arguments]
         with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
             the_role = create_role(cursor, "a_test_role", PRIVILEGES)
             assert the_role == expected
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "user,expected",
+    (zip(TEST_USERS,
+         ((Role(
+             role_id=uuid.UUID('a0e67630-d502-4b9f-b23f-6805d0f30e30'),
+             role_name='group-leader',
+             privileges=(
+                 Privilege(
+                     privilege_id=uuid.UUID('13ec2a94-4f1a-442d-aad2-936ad6dd5c57'),
+                     privilege_name='delete-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('4842e2aa-38b9-4349-805e-0a99a9cf8bff'),
+                     privilege_name='create-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'))),),
+          tuple(), tuple(), tuple()))))
+def test_user_roles(fxtr_group_user_roles, user, expected):
+    """
+    GIVEN: an authenticated user
+    WHEN: we request the user's privileges
+    THEN: return **ALL** the privileges attached to the user
+    """
+    assert user_roles(fxtr_group_user_roles, user) == expected