about summary refs log tree commit diff
path: root/gn_auth/auth/authorisation
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2026-05-18 15:40:51 -0500
committerFrederick Muriuki Muriithi2026-05-18 15:40:51 -0500
commit30db52a2751f67f34fee064c2788315e50d7d0cb (patch)
tree2fc92b8491e354b57dd1d1ef7670d91930f47b87 /gn_auth/auth/authorisation
parent6e9a024234adad776913c509a63c9ac28f3aa20d (diff)
downloadgn-auth-30db52a2751f67f34fee064c2788315e50d7d0cb.tar.gz
Refactor authorisation-by-datasets-and-traits endpoint. HEAD main
Fetch resources using the dataset names (and trait names where
relevant) to simplify the code, and make it clearer what the endpoint
actually does.
Diffstat (limited to 'gn_auth/auth/authorisation')
-rw-r--r--gn_auth/auth/authorisation/data/views.py205
1 files changed, 113 insertions, 92 deletions
diff --git a/gn_auth/auth/authorisation/data/views.py b/gn_auth/auth/authorisation/data/views.py
index 1526070..f8f4033 100644
--- a/gn_auth/auth/authorisation/data/views.py
+++ b/gn_auth/auth/authorisation/data/views.py
@@ -2,9 +2,9 @@
 import sys
 import uuid
 import json
-from dataclasses import asdict
+import logging
 from typing import Any
-from functools import partial
+from functools import reduce, partial
 
 import redis
 from MySQLdb.cursors import DictCursor
@@ -13,6 +13,7 @@ from flask import request, jsonify, Response, Blueprint, current_app as app
 
 
 from gn_libs import mysqldb as gn3db
+from gn_libs import sqlite3 as db
 
 from gn_auth import jobs
 from gn_auth.commands import run_async_cmd
@@ -21,27 +22,33 @@ from gn_auth.auth.requests import request_json
 from gn_auth.auth.errors import InvalidData, NotFoundError
 from gn_auth.auth.authorisation.resources.groups.models import group_by_id
 
-from ...db import sqlite3 as db
-from ...db.sqlite3 import with_db_connection
+from gn_auth.auth.db.sqlite3 import with_db_connection # Replace this with gn_libs alternative
 
 from ..checks import require_json
 
-from ..users.models import user_resource_roles
-
-from ..resources.checks import authorised_for
-from ..resources.models import (
-    user_resources, public_resources, attach_resources_data)
-
 from ...authentication.users import User
 from ...authentication.oauth2.resource_server import require_oauth
 
-from .mrna import link_mrna_data, ungrouped_mrna_data
-from .genotypes import link_genotype_data, ungrouped_genotype_data
-from .phenotypes import phenosbp, link_phenotype_data, pheno_traits_from_db
-
+from .mrna import (
+    link_mrna_data,
+    ungrouped_mrna_data,
+    resources_by_datasets_and_traits as mrna_resources_by_datasets_and_traits)
+from .genotypes import (
+    link_genotype_data,
+    ungrouped_genotype_data,
+    resources_by_datasets_and_traits as geno_resources_by_datasets_and_traits)
+from .phenotypes import (
+    phenosbp,
+    link_phenotype_data,
+    pheno_traits_from_db,
+    resources_by_datasets_and_traits as pheno_resources_by_datasets_and_traits)
+
+
+logger = logging.getLogger(__name__)
 data = Blueprint("data", __name__)
 data.register_blueprint(phenosbp, url_prefix="/phenotypes")
 
+
 def build_trait_name(trait_fullname):
     """
     Initialises the trait's name, and other values from the search data provided
@@ -83,98 +90,112 @@ def list_species() -> Response:
 def authorisation() -> Response:
     """Retrieve the authorisation level for datasets/traits for the user."""
     # Access endpoint with something like:
-    # curl -X POST http://127.0.0.1:8080/api/oauth2/data/authorisation \
+    # curl -X POST http://127.0.0.1:8081/auth/data/authorisation \
     #    -H "Content-Type: application/json" \
     #    -d '{"traits": ["HC_M2_0606_P::1442370_at", "BXDGeno::01.001.695",
     #        "BXDPublish::10001"]}'
+    def __organise_traits__(acc, curr):
+        dset, _trt = curr
+        key = "ProbeSet"
+        if dset.endswith("Publish"):
+            key = "Publish"
+        elif dset.endswith("Geno"):
+            key="Geno"
+        elif dset.endswith("Temp"):
+            key = "Temp"
+        else:
+            key = "ProbeSet"
+
+        return {
+            **acc,
+            key: acc.get(key, tuple()) + (curr,)
+        }
+    _dset_traits: dict[str, tuple[tuple[str, str], ...]] = reduce(
+        __organise_traits__,
+        (
+            (dset.strip(), trt.strip()) for dset, trt in
+            (trtstr.split("::") for trtstr in
+             request_json().get("traits", []))),
+        {})
+
     db_uri = app.config["AUTH_DB"]
-    privileges = {}
     user = User(uuid.uuid4(), "anon@ymous.user", "Anonymous User")
-    with db.connection(db_uri) as auth_conn:
+    with (db.connection(db_uri) as authconn, db.cursor(authconn) as cursor):
+        _all_resources = {
+            _rrow["resource_id"]: _rrow
+            for _rtypes in (
+                    pheno_resources_by_datasets_and_traits(
+                        authconn, _dset_traits["Publish"]),
+                    geno_resources_by_datasets_and_traits(
+                        authconn, _dset_traits["Geno"]),
+                    mrna_resources_by_datasets_and_traits(
+                        authconn, _dset_traits["ProbeSet"]))
+            for _rrow in _rtypes
+        }
+        _resource_ids = tuple(_all_resources.keys())
+
+
+        def __explode_resource_data__(trait_fullname):
+            _dset, _trt = trait_fullname.split("::")
+            return {
+                "dataset_name": _dset,
+                "dataset_type": (
+                    "Phenotype" if _dset.endswith("Publish")
+                    else ("Genotype" if _dset.endswith("Geno")
+                          else ("Temporary" if _dset.endswith("Temp")
+                                else "mRNA"))),
+                "trait_name": _trt,
+                "trait_fullname": trait_fullname
+            }
+
+        _paramstr = ", ".join(["?"] * len(_resource_ids))
         try:
             with require_oauth.acquire("profile group resource") as _token:
                 user = _token.user
-                resources = attach_resources_data(
-                    auth_conn, user_resources(auth_conn, _token.user)[0])
-                resources_roles = user_resource_roles(auth_conn, _token.user)
-                privileges = {
-                    resource_id: tuple(
-                        privilege.privilege_id
-                        for roles in resources_roles[resource_id]
-                        for privilege in roles.privileges)#("group:resource:view-resource",)
-                    for resource_id, is_authorised
-                    in authorised_for(
-                        auth_conn, _token.user,
-                        ("group:resource:view-resource",), tuple(
-                            resource.resource_id for resource in resources)).items()
-                    if is_authorised
-                }
+                cursor.execute(
+                    "SELECT ur.resource_id, r.role_id, rp.privilege_id "
+                    "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 "
+                    "WHERE ur.user_id = ? "
+                    f"AND ur.resource_id IN ({_paramstr})",
+                    (str(user.user_id),) + _resource_ids
+                )
+                _privileges_by_resource: dict[str, tuple[str, ...]] = reduce(
+                    lambda acc, curr: {
+                        **acc,
+                        curr["resource_id"]: (
+                            acc.get(curr["resource_id"], tuple())
+                            + (curr["privilege_id"],))
+                    },
+                    cursor.fetchall(),
+                    {})
         except _HTTPException as exc:
             err_msg = json.loads(exc.body)
             if err_msg["error"] == "missing_authorization":
-                resources = attach_resources_data(
-                    auth_conn, public_resources(auth_conn))
+                cursor.execute(
+                    "SELECT rsc.resource_id "
+                    "FROM resources AS rsc "
+                    "WHERE rsc.public = '1' "
+                    f"AND rsc.resource_id IN ({_paramstr}) ",
+                    _resource_ids)
+                _privileges_by_resource = {
+                    row["resource_id"]: ('group:resource:view-resource',)
+                    for row in cursor.fetchall()
+                }
             else:
                 raise exc from None
 
-        def __gen_key__(resource, data_item):
-            if resource.resource_category.resource_category_key.lower() == "phenotype":
-                return (
-                    f"{resource.resource_category.resource_category_key.lower()}::"
-                    f"{data_item['dataset_name']}::{data_item['PublishXRefId']}")
-            return (
-                f"{resource.resource_category.resource_category_key.lower()}::"
-                f"{data_item['dataset_name']}")
-
-        data_to_resource_map = {
-            __gen_key__(resource, data_item): resource.resource_id
-            for resource in resources
-            for data_item in resource.resource_data
-        }
-        privileges = {
-            **{
-                resource.resource_id: ("system:resource:public-read",)
-                for resource in resources if resource.public
-            },
-            **privileges}
-
-        args = request.get_json()
-        traits_names = args["traits"] # type: ignore[index]
-        def __translate__(val):
-            return {
-                "Temp": "Temp",
-                "ProbeSet": "mRNA",
-                "Geno": "Genotype",
-                "Publish": "Phenotype"
-            }[val]
-
-        def __trait_key__(trait):
-            dataset_type = __translate__(trait['db']['dataset_type']).lower()
-            dataset_name = trait["db"]["dataset_name"]
-            if dataset_type == "phenotype":
-                return f"{dataset_type}::{dataset_name}::{trait['trait_name']}"
-            return f"{dataset_type}::{dataset_name}"
-
-        return jsonify(tuple(
-            {
-                "user": asdict(user),
-                **{key:trait[key] for key in ("trait_fullname", "trait_name")},
-                "dataset_name": trait["db"]["dataset_name"],
-                "dataset_type": __translate__(trait["db"]["dataset_type"]),
-                "resource_id": data_to_resource_map.get(__trait_key__(trait)),
-                "privileges": privileges.get(
-                    data_to_resource_map.get(
-                        __trait_key__(trait),
-                        uuid.UUID("4afa415e-94cb-4189-b2c6-f9ce2b6a878d")),
-                    tuple()) + (
-                        # Temporary traits do not exist in db: Set them
-                        # as public-read
-                        ("system:resource:public-read",)
-                        if trait["db"]["dataset_type"] == "Temp"
-                        else tuple())
-            } for trait in
-            (build_trait_name(trait_fullname)
-             for trait_fullname in traits_names)))
+        return jsonify({
+            "authorisation": [{
+                **resource,
+                "resource_data": [
+                    __explode_resource_data__(item)
+                            for item in resource["resource_data"]],
+                "privileges": _privileges_by_resource.get(resource["resource_id"], tuple())
+            } for resource in _all_resources.values()]
+        })
+
 
 def __search_mrna__():
     query = __request_key__("query", "")