about summary refs log tree commit diff
path: root/gn_auth/auth/authorisation/resources/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'gn_auth/auth/authorisation/resources/models.py')
-rw-r--r--gn_auth/auth/authorisation/resources/models.py197
1 files changed, 99 insertions, 98 deletions
diff --git a/gn_auth/auth/authorisation/resources/models.py b/gn_auth/auth/authorisation/resources/models.py
index 95a7f1c..a4df363 100644
--- a/gn_auth/auth/authorisation/resources/models.py
+++ b/gn_auth/auth/authorisation/resources/models.py
@@ -2,9 +2,10 @@
 from dataclasses import asdict
 from uuid import UUID, uuid4
 from functools import reduce, partial
-from typing import Dict, Sequence, Optional
+from typing import Dict, Union, Sequence, Optional
+
+from gn_libs import sqlite3 as db
 
-from gn_auth.auth.db import sqlite3 as db
 from gn_auth.auth.authentication.users import User
 from gn_auth.auth.db.sqlite3 import with_db_connection
 
@@ -13,69 +14,42 @@ from gn_auth.auth.authorisation.privileges import Privilege
 from gn_auth.auth.authorisation.checks import authorised_p
 from gn_auth.auth.errors import NotFoundError, AuthorisationError
 
-from .checks import authorised_for
-from .base import Resource, ResourceCategory
-from .groups.models import Group, GroupRole, user_group, is_group_leader
+from .system.models import system_resource
+from .checks import authorised_for, authorised_for_spec
+from .base import Resource, ResourceCategory, resource_from_dbrow
+from .common import assign_resource_owner_role, grant_access_to_sysadmins
+from .groups.models import Group, is_group_leader
+from .inbredset.models import resource_data as inbredset_resource_data
 from .mrna import (
     resource_data as mrna_resource_data,
     attach_resources_data as mrna_attach_resources_data,
     link_data_to_resource as mrna_link_data_to_resource,
     unlink_data_from_resource as mrna_unlink_data_from_resource)
-from .genotype import (
+from .genotypes.models import (
     resource_data as genotype_resource_data,
     attach_resources_data as genotype_attach_resources_data,
     link_data_to_resource as genotype_link_data_to_resource,
     unlink_data_from_resource as genotype_unlink_data_from_resource)
-from .phenotype import (
+from .phenotypes.models import (
     resource_data as phenotype_resource_data,
     attach_resources_data as phenotype_attach_resources_data,
     link_data_to_resource as phenotype_link_data_to_resource,
     unlink_data_from_resource as phenotype_unlink_data_from_resource)
 
-from .errors import MissingGroupError
-
-def __assign_resource_owner_role__(cursor, resource, user):
-    """Assign `user` the 'Resource Owner' role for `resource`."""
-    cursor.execute(
-        "SELECT rr.* FROM resource_roles AS rr INNER JOIN roles AS r "
-        "ON rr.role_id=r.role_id WHERE r.role_name='resource-owner' "
-        "AND rr.resource_id=?",
-        (str(resource.resource_id),))
-    role = cursor.fetchone()
-    if not role:
-        cursor.execute("SELECT * FROM roles WHERE role_name='resource-owner'")
-        role = cursor.fetchone()
-        cursor.execute(
-            "INSERT INTO resource_roles(resource_id, role_created_by, role_id) "
-            "VALUES (:resource_id, :user_id, :role_id)",
-            {"resource_id": str(resource.resource_id),
-             "user_id": str(user.user_id),
-             "role_id": role["role_id"]})
-
-    cursor.execute(
-        "INSERT INTO user_roles "
-        "VALUES (:user_id, :role_id, :resource_id) "
-        "ON CONFLICT (user_id, role_id, resource_id) DO NOTHING",
-        {
-            "user_id": str(user.user_id),
-            "role_id": role["role_id"],
-            "resource_id": str(resource.resource_id)
-        })
 
 @authorised_p(("group:resource:create-resource",),
               error_description="Insufficient privileges to create a resource",
               oauth2_scope="profile resource")
-def create_resource(
-        conn: db.DbConnection, resource_name: str,
-        resource_category: ResourceCategory, user: User,
-        public: bool) -> Resource:
+def create_resource(# pylint: disable=[too-many-arguments, too-many-positional-arguments]
+        conn: Union[db.DbConnection, db.DbCursor],
+        resource_name: str,
+        resource_category: ResourceCategory,
+        user: User,
+        group: Group,
+        public: bool
+) -> Resource:
     """Create a resource item."""
-    with db.cursor(conn) as cursor:
-        group = user_group(conn, user).maybe(
-            False, lambda grp: grp)# type: ignore[misc, arg-type]
-        if not group:
-            raise MissingGroupError(# Not all resources require an owner group
-                "User with no group cannot create a resource.")
+    def __create_resource__(cursor: db.DbCursor) -> Resource:
         resource = Resource(uuid4(), resource_name, resource_category, public)
         cursor.execute(
             "INSERT INTO resources VALUES (?, ?, ?, ?)",
@@ -83,12 +57,46 @@ def create_resource(
              resource_name,
              str(resource.resource_category.resource_category_id),
              1 if resource.public else 0))
+        # TODO: @fredmanglis,@rookie101
+        # 1. Move the actions below into a (the?) hooks system
+        # 2. Do more checks: A resource can have varying hooks depending on type
+        #    e.g. if mRNA, pheno or geno resource, assign:
+        #           - "resource-owner"
+        #         if inbredset-group, assign:
+        #           - "resource-owner",
+        #           - "inbredset-group-owner" etc.
+        #         if resource is of type "group", assign:
+        #           - group-leader
         cursor.execute("INSERT INTO resource_ownership (group_id, resource_id) "
                        "VALUES (?, ?)",
                        (str(group.group_id), str(resource.resource_id)))
-        __assign_resource_owner_role__(cursor, resource, user)
+        assign_resource_owner_role(cursor, resource.resource_id, user.user_id)
+        grant_access_to_sysadmins(
+            cursor, resource.resource_id, system_resource(conn).resource_id)
+
+        return resource
+
+    if hasattr(conn, "cursor"): # This is a connection: get its cursor.
+        with db.cursor(conn) as cursor:
+            return __create_resource__(cursor)
+    else:
+        return __create_resource__(conn)
+
+
+def delete_resource(conn: db.DbConnection, resource_id: UUID):
+    """Delete a resource."""
+    with db.cursor(conn) as cursor:
+        cursor.execute("DELETE FROM user_roles WHERE resource_id=?",
+                       (str(resource_id),))
+        cursor.execute("DELETE FROM resource_roles WHERE resource_id=?",
+                       (str(resource_id),))
+        cursor.execute("DELETE FROM group_resources WHERE resource_id=?",
+                       (str(resource_id),))
+        cursor.execute("DELETE FROM resource_ownership WHERE resource_id=?",
+                       (str(resource_id),))
+        cursor.execute("DELETE FROM resources WHERE resource_id=?",
+                       (str(resource_id),))
 
-    return resource
 
 def resource_category_by_id(
         conn: db.DbConnection, category_id: UUID) -> ResourceCategory:
@@ -149,32 +157,21 @@ def group_leader_resources(
 
 def user_resources(conn: db.DbConnection, user: User) -> Sequence[Resource]:
     """List the resources available to the user"""
-    categories = { # Repeated in `public_resources` function
-        cat.resource_category_id: cat for cat in resource_categories(conn)
-    }
     with db.cursor(conn) as cursor:
-        def __all_resources__(group) -> Sequence[Resource]:
-            gl_resources = group_leader_resources(conn, user, group, categories)
+        cursor.execute(
+            ("SELECT DISTINCT(r.resource_id), r.resource_name,  "
+             "r.resource_category_id, r.public, rc.resource_category_key, "
+             "rc.resource_category_description "
+             "FROM user_roles AS ur "
+             "INNER JOIN resources AS r ON ur.resource_id=r.resource_id "
+             "INNER JOIN resource_categories AS rc "
+             "ON r.resource_category_id=rc.resource_category_id "
+             "WHERE ur.user_id=?"),
+            (str(user.user_id),))
+        rows = cursor.fetchall() or []
+
+    return tuple(resource_from_dbrow(row) for row in rows)
 
-            cursor.execute(
-                ("SELECT resources.* FROM user_roles LEFT JOIN resources "
-                 "ON user_roles.resource_id=resources.resource_id "
-                 "WHERE user_roles.user_id=?"),
-                (str(user.user_id),))
-            rows = cursor.fetchall()
-            private_res = tuple(
-                Resource(UUID(row[0]), row[1], categories[UUID(row[2])],
-                         bool(row[3]))
-                for row in rows)
-            return tuple({
-                res.resource_id: res
-                for res in
-                (private_res + gl_resources + public_resources(conn))# type: ignore[operator]
-            }.values())
-
-        # Fix the typing here
-        return user_group(conn, user).map(__all_resources__).maybe(# type: ignore[arg-type,misc]
-            public_resources(conn), lambda res: res)# type: ignore[arg-type,return-value]
 
 def resource_data(conn, resource, offset: int = 0, limit: Optional[int] = None) -> tuple[dict, ...]:
     """
@@ -188,7 +185,8 @@ def resource_data(conn, resource, offset: int = 0, limit: Optional[int] = None)
         "genotype-metadata": lambda *args: tuple(),
         "mrna-metadata": lambda *args: tuple(),
         "system": lambda *args: tuple(),
-        "group": lambda *args: tuple()
+        "group": lambda *args: tuple(),
+        "inbredset-group": inbredset_resource_data,
     }
     with db.cursor(conn) as cursor:
         return tuple(
@@ -216,9 +214,11 @@ def attach_resource_data(cursor: db.DbCursor, resource: Resource) -> Resource:
 def resource_by_id(
         conn: db.DbConnection, user: User, resource_id: UUID) -> Resource:
     """Retrieve a resource by its ID."""
-    if not authorised_for(
-            conn, user, ("group:resource:view-resource",),
-            (resource_id,))[resource_id]:
+    if not authorised_for_spec(
+            conn,
+            user.user_id,
+            resource_id,
+            "(OR group:resource:view-resource system:resource:view)"):
         raise AuthorisationError(
             "You are not authorised to access resource with id "
             f"'{resource_id}'.")
@@ -236,8 +236,12 @@ def resource_by_id(
     raise NotFoundError(f"Could not find a resource with id '{resource_id}'")
 
 def link_data_to_resource(
-        conn: db.DbConnection, user: User, resource_id: UUID, dataset_type: str,
-        data_link_id: UUID) -> dict:
+        conn: db.DbConnection,
+        user: User,
+        resource_id: UUID,
+        dataset_type: str,
+        data_link_ids: tuple[UUID, ...]
+) -> tuple[dict, ...]:
     """Link data to resource."""
     if not authorised_for(
             conn, user, ("group:resource:edit-resource",),
@@ -252,7 +256,7 @@ def link_data_to_resource(
         "mrna": mrna_link_data_to_resource,
         "genotype": genotype_link_data_to_resource,
         "phenotype": phenotype_link_data_to_resource,
-    }[dataset_type.lower()](conn, resource, data_link_id)
+    }[dataset_type.lower()](conn, resource, data_link_ids)
 
 def unlink_data_from_resource(
         conn: db.DbConnection, user: User, resource_id: UUID, data_link_id: UUID):
@@ -305,13 +309,13 @@ def attach_resources_data(
              for category, rscs in organised.items())
             for resource in categories)
 
-@authorised_p(
-    ("group:user:assign-role",),
-    "You cannot assign roles to users for this group.",
-    oauth2_scope="profile group role resource")
+
 def assign_resource_user(
-        conn: db.DbConnection, resource: Resource, user: User,
-        role: GroupRole) -> dict:
+        conn: db.DbConnection,
+        resource: Resource,
+        user: User,
+        role: Role
+) -> dict:
     """Assign `role` to `user` for the specific `resource`."""
     with db.cursor(conn) as cursor:
         cursor.execute(
@@ -319,39 +323,36 @@ def assign_resource_user(
             "VALUES (?, ?, ?) "
             "ON CONFLICT (user_id, role_id, resource_id) "
             "DO NOTHING",
-            (str(user.user_id), str(role.role.role_id),
-             str(resource.resource_id)))
+            (str(user.user_id), str(role.role_id), str(resource.resource_id)))
         return {
             "resource": asdict(resource),
             "user": asdict(user),
             "role": asdict(role),
             "description": (
                 f"The user '{user.name}'({user.email}) was assigned the "
-                f"'{role.role.role_name}' role on resource with ID "
+                f"'{role.role_name}' role on resource with ID "
                 f"'{resource.resource_id}'.")}
 
-@authorised_p(
-    ("group:user:assign-role",),
-    "You cannot assign roles to users for this group.",
-    oauth2_scope="profile group role resource")
+
 def unassign_resource_user(
-        conn: db.DbConnection, resource: Resource, user: User,
-        role: GroupRole) -> dict:
+        conn: db.DbConnection,
+        resource: Resource,
+        user: User,
+        role: Role
+) -> dict:
     """Assign `role` to `user` for the specific `resource`."""
     with db.cursor(conn) as cursor:
         cursor.execute(
             "DELETE FROM user_roles "
             "WHERE user_id=? AND role_id=? AND resource_id=?",
-            (str(user.user_id),
-             str(role.role.role_id),
-             str(resource.resource_id)))
+            (str(user.user_id), str(role.role_id), str(resource.resource_id)))
         return {
             "resource": asdict(resource),
             "user": asdict(user),
             "role": asdict(role),
             "description": (
                 f"The user '{user.name}'({user.email}) had the "
-                f"'{role.role.role_name}' role on resource with ID "
+                f"'{role.role_name}' role on resource with ID "
                 f"'{resource.resource_id}' taken away.")}
 
 def save_resource(