aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gn3/auth/authorisation/data/__init__.py0
-rw-r--r--gn3/auth/authorisation/data/views.py67
-rw-r--r--gn3/auth/authorisation/resources/models.py86
-rw-r--r--gn3/auth/views.py2
4 files changed, 154 insertions, 1 deletions
diff --git a/gn3/auth/authorisation/data/__init__.py b/gn3/auth/authorisation/data/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gn3/auth/authorisation/data/__init__.py
diff --git a/gn3/auth/authorisation/data/views.py b/gn3/auth/authorisation/data/views.py
new file mode 100644
index 0000000..76f4158
--- /dev/null
+++ b/gn3/auth/authorisation/data/views.py
@@ -0,0 +1,67 @@
+"""Handle data endpoints."""
+import json
+from functools import reduce
+
+from flask import request, Response, Blueprint, current_app as app
+from authlib.integrations.flask_oauth2.errors import _HTTPException
+
+from gn3 import db_utils as gn3db
+
+from gn3.db.traits import build_trait_name
+
+from gn3.auth import db
+from gn3.auth.authentication.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.resources.models import (
+ user_resources, public_resources, attach_resources_data)
+
+data = Blueprint("data", __name__)
+
+@data.route("/authorisation", methods=["GET"])
+def authorisation() -> Response:
+ """Retrive the authorisation level for datasets/traits for the user."""
+ db_uri = app.config["AUTH_DB"]
+ the_user = "ANONYMOUS"
+ with db.connection(db_uri) as auth_conn, gn3db.database_connection() as gn3conn:
+ try:
+ with require_oauth.acquire("profile group resource") as the_token:
+ resources = attach_resources_data(
+ auth_conn, user_resources(auth_conn, the_token.user))
+ 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))
+ else:
+ raise exc from None
+
+ # Access endpoint with somethin like:
+ # curl -X GET http://127.0.0.1:8080/api/oauth2/data/authorisation \
+ # -H "Content-Type: application/json" \
+ # -d '{"traits": ["HC_M2_0606_P::1442370_at", "BXDGeno::01.001.695", "BXDPublish::10001"]}'
+ args = request.get_json()
+ traits_names = args["traits"]
+ def __translate__(val):
+ return {
+ "ProbeSet": "mRNA",
+ "Geno": "Genotype",
+ "Publish": "Phenotype"
+ }[val]
+ traits = (
+ {
+ **{key:trait[key] for key in ("trait_fullname", "trait_name")},
+ "dataset_name": trait["db"]["dataset_name"],
+ "dataset_type": __translate__(trait["db"]["dataset_type"])
+ } for trait in
+ (build_trait_name(trait_fullname)
+ for trait_fullname in traits_names))
+ # Retrieve the dataset/trait IDs from traits above
+
+ # Compare the traits/dataset names/ids from request with resources' data
+ # - Any trait not in resources' data is marked inaccessible
+ # - Any trait in resources' data:
+ # - IF public is marked readable
+ # - IF private and user has read privilege: mark readable
+ # - ELSE mark inaccessible
+
+
+ return "NOT COMPLETED! ..."
diff --git a/gn3/auth/authorisation/resources/models.py b/gn3/auth/authorisation/resources/models.py
index 3547bc6..8940e00 100644
--- a/gn3/auth/authorisation/resources/models.py
+++ b/gn3/auth/authorisation/resources/models.py
@@ -2,7 +2,7 @@
import json
import sqlite3
from uuid import UUID, uuid4
-from functools import partial
+from functools import reduce, partial
from typing import Any, Dict, Sequence, NamedTuple
from gn3.auth import db
@@ -328,3 +328,87 @@ def link_data_to_resource(
"genotype": __link_geno_data_to_resource__,
"phenotype": __link_pheno_data_to_resource__,
}[dataset_type.lower()](conn, resource, dataset_id)
+
+def organise_resources_by_category(resources: Sequence[Resource]) -> dict[
+ ResourceCategory, tuple[Resource]]:
+ """Organise the `resources` by their categories."""
+ def __organise__(accumulator, resource):
+ category = resource.resource_category
+ return {
+ **accumulator,
+ category: accumulator.get(category, tuple()) + (resource,)
+ }
+ return reduce(__organise__, resources, {})
+
+def __attach_data__(
+ data_rows: Sequence[sqlite3.Row],
+ resources: Sequence[Resource]) -> Sequence[Resource]:
+ def __organise__(acc, row):
+ resource_id = UUID(row["resource_id"])
+ return {
+ **acc,
+ resource_id: acc.get(resource_id, tuple()) + (dict(row),)
+ }
+ organised = reduce(__organise__, data_rows, {})
+ return tuple(
+ Resource(
+ resource.group, resource.resource_id, resource.resource_name,
+ resource.resource_category, resource.public,
+ organised[resource.resource_id])
+ for resource in resources)
+
+def attach_mrna_resources_data(
+ cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
+ """Attach linked data to mRNA Assay resources"""
+ placeholders = ", ".join(["?"] * len(resources))
+ cursor.execute(
+ "SELECT * FROM mrna_resources AS mr INNER JOIN linked_group_data AS lgd"
+ " ON (mr.dataset_id=lgd.dataset_or_trait_id "
+ "AND mr.dataset_type=lgd.dataset_type) "
+ f"WHERE mr.resource_id IN ({placeholders})",
+ tuple(str(resource.resource_id) for resource in resources))
+ return __attach_data__(cursor.fetchall(), resources)
+
+def attach_genotype_resources_data(
+ cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
+ """Attach linked data to Genotype resources"""
+ placeholders = ", ".join(["?"] * len(resources))
+ cursor.execute(
+ "SELECT * FROM genotype_resources AS gr "
+ "INNER JOIN linked_group_data AS lgd "
+ "ON (gr.trait_id=lgd.dataset_or_trait_id "
+ "AND gr.dataset_type=lgd.dataset_type) "
+ f"WHERE gr.resource_id IN {placeholders}",
+ tuple(str(resource.resource_id) for resource in resources))
+ return __attach_data__(cursor.fetchall(), resources)
+
+def attach_phenotype_resources_data(
+ cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
+ """Attach linked data to Phenotype resources"""
+ placeholders = ", ".join(["?"] * len(resources))
+ cursor.execute(
+ "SELECT * FROM phenotype_resources AS pr "
+ "INNER JOIN linked_group_data AS lgd "
+ "ON (pr.trait_id=lgd.dataset_or_trait_id "
+ "AND pr.dataset_type=lgd.dataset_type) "
+ f"WHERE pr.resource_id IN {placeholders}",
+ tuple(str(resource.resource_id) for resource in resources))
+ return __attach_data__(cursor.fetchall(), resources)
+
+def attach_resources_data(
+ conn: db.DbConnection, resources: Sequence[Resource]) -> Sequence[
+ Resource]:
+ """Attach linked data for each resource in `resources`"""
+ resource_data_function = {
+ "mrna": attach_mrna_resources_data,
+ "genotype": attach_genotype_resources_data,
+ "phenotype": attach_phenotype_resources_data
+ }
+ organised = organise_resources_by_category(resources)
+ with db.cursor(conn) as cursor:
+ return tuple(
+ resource for categories in
+ (resource_data_function[category.resource_category_key](
+ cursor, rscs)
+ for category, rscs in organised.items())
+ for resource in categories)
diff --git a/gn3/auth/views.py b/gn3/auth/views.py
index 1d78fd6..4e01cc9 100644
--- a/gn3/auth/views.py
+++ b/gn3/auth/views.py
@@ -3,6 +3,7 @@ from flask import Blueprint
from .authentication.oauth2.views import auth
+from .authorisation.data.views import data
from .authorisation.users.views import users
from .authorisation.roles.views import roles
from .authorisation.groups.views import groups
@@ -11,6 +12,7 @@ from .authorisation.resources.views import resources
oauth2 = Blueprint("oauth2", __name__)
oauth2.register_blueprint(auth, url_prefix="/")
+oauth2.register_blueprint(data, url_prefix="/data")
oauth2.register_blueprint(users, url_prefix="/user")
oauth2.register_blueprint(roles, url_prefix="/role")
oauth2.register_blueprint(groups, url_prefix="/group")