aboutsummaryrefslogtreecommitdiff
path: root/gn_auth/auth/authorisation/resources
diff options
context:
space:
mode:
Diffstat (limited to 'gn_auth/auth/authorisation/resources')
-rw-r--r--gn_auth/auth/authorisation/resources/checks.py37
-rw-r--r--gn_auth/auth/authorisation/resources/genotypes/models.py11
-rw-r--r--gn_auth/auth/authorisation/resources/groups/data.py12
-rw-r--r--gn_auth/auth/authorisation/resources/groups/models.py55
-rw-r--r--gn_auth/auth/authorisation/resources/groups/views.py11
-rw-r--r--gn_auth/auth/authorisation/resources/inbredset/models.py2
-rw-r--r--gn_auth/auth/authorisation/resources/inbredset/views.py11
-rw-r--r--gn_auth/auth/authorisation/resources/models.py20
-rw-r--r--gn_auth/auth/authorisation/resources/mrna.py9
-rw-r--r--gn_auth/auth/authorisation/resources/phenotype.py68
-rw-r--r--gn_auth/auth/authorisation/resources/phenotypes/__init__.py1
-rw-r--r--gn_auth/auth/authorisation/resources/phenotypes/models.py143
-rw-r--r--gn_auth/auth/authorisation/resources/phenotypes/views.py77
-rw-r--r--gn_auth/auth/authorisation/resources/system/models.py21
-rw-r--r--gn_auth/auth/authorisation/resources/views.py26
15 files changed, 395 insertions, 109 deletions
diff --git a/gn_auth/auth/authorisation/resources/checks.py b/gn_auth/auth/authorisation/resources/checks.py
index d8e3a9f..5484dbf 100644
--- a/gn_auth/auth/authorisation/resources/checks.py
+++ b/gn_auth/auth/authorisation/resources/checks.py
@@ -3,9 +3,13 @@ from uuid import UUID
from functools import reduce
from typing import Sequence
+from .base import Resource
+
from ...db import sqlite3 as db
from ...authentication.users import User
+from ..privileges.models import db_row_to_privilege
+
def __organise_privileges_by_resource_id__(rows):
def __organise__(privs, row):
resource_id = UUID(row["resource_id"])
@@ -16,6 +20,7 @@ def __organise_privileges_by_resource_id__(rows):
}
return reduce(__organise__, rows, {})
+
def authorised_for(conn: db.DbConnection,
user: User,
privileges: tuple[str, ...],
@@ -45,3 +50,35 @@ def authorised_for(conn: db.DbConnection,
resource_id: resource_id in authorised
for resource_id in resource_ids
}
+
+
+def authorised_for2(
+ conn: db.DbConnection,
+ user: User,
+ resource: Resource,
+ privileges: tuple[str, ...]
+) -> bool:
+ """
+ Check that `user` has **ALL** the specified privileges for the resource.
+ """
+ with db.cursor(conn) as cursor:
+ _query = (
+ "SELECT resources.resource_id, user_roles.user_id, roles.role_id, "
+ "privileges.* "
+ "FROM resources INNER JOIN user_roles "
+ "ON resources.resource_id=user_roles.resource_id "
+ "INNER JOIN roles ON user_roles.role_id=roles.role_id "
+ "INNER JOIN role_privileges ON roles.role_id=role_privileges.role_id "
+ "INNER JOIN privileges "
+ "ON role_privileges.privilege_id=privileges.privilege_id "
+ "WHERE resources.resource_id=? "
+ "AND user_roles.user_id=?")
+ cursor.execute(
+ _query,
+ (str(resource.resource_id), str(user.user_id)))
+ _db_privileges = tuple(
+ db_row_to_privilege(row) for row in cursor.fetchall())
+
+ str_privileges = tuple(privilege.privilege_id for privilege in _db_privileges)
+ return all((requested_privilege in str_privileges)
+ for requested_privilege in privileges)
diff --git a/gn_auth/auth/authorisation/resources/genotypes/models.py b/gn_auth/auth/authorisation/resources/genotypes/models.py
index e8dca9b..762ee7c 100644
--- a/gn_auth/auth/authorisation/resources/genotypes/models.py
+++ b/gn_auth/auth/authorisation/resources/genotypes/models.py
@@ -27,14 +27,15 @@ def resource_data(
def link_data_to_resource(
conn: db.DbConnection,
resource: Resource,
- data_link_id: uuid.UUID) -> dict:
+ data_link_ids: tuple[uuid.UUID, ...]
+) -> tuple[dict, ...]:
"""Link Genotype data with a resource using the GUI."""
with db.cursor(conn) as cursor:
- params = {
+ params = tuple({
"resource_id": str(resource.resource_id),
"data_link_id": str(data_link_id)
- }
- cursor.execute(
+ } for data_link_id in data_link_ids)
+ cursor.executemany(
"INSERT INTO genotype_resources VALUES"
"(:resource_id, :data_link_id)",
params)
@@ -68,7 +69,7 @@ def attach_resources_data(
return __attach_data__(cursor.fetchall(), resources)
-def insert_and_link_data_to_resource(# pylint: disable=[too-many-arguments]
+def insert_and_link_data_to_resource(# pylint: disable=[too-many-arguments, too-many-positional-arguments]
cursor,
resource_id: uuid.UUID,
group_id: uuid.UUID,
diff --git a/gn_auth/auth/authorisation/resources/groups/data.py b/gn_auth/auth/authorisation/resources/groups/data.py
index 702955d..ad0dfba 100644
--- a/gn_auth/auth/authorisation/resources/groups/data.py
+++ b/gn_auth/auth/authorisation/resources/groups/data.py
@@ -1,7 +1,7 @@
"""Handles the resource objects' data."""
+from gn_libs import mysqldb as gn3db
from MySQLdb.cursors import DictCursor
-from gn_auth.auth.db import mariadb as gn3db
from gn_auth.auth.db import sqlite3 as authdb
from gn_auth.auth.errors import NotFoundError
@@ -9,7 +9,7 @@ from gn_auth.auth.authorisation.checks import authorised_p
from gn_auth.auth.authorisation.resources.groups import Group
def __fetch_mrna_data_by_ids__(
- conn: gn3db.DbConnection, dataset_ids: tuple[str, ...]) -> tuple[
+ conn: gn3db.Connection, dataset_ids: tuple[str, ...]) -> tuple[
dict, ...]:
"""Fetch mRNA Assay data by ID."""
with conn.cursor(DictCursor) as cursor:
@@ -27,7 +27,7 @@ def __fetch_mrna_data_by_ids__(
raise NotFoundError("Could not find mRNA Assay data with the given ID.")
def __fetch_geno_data_by_ids__(
- conn: gn3db.DbConnection, dataset_ids: tuple[str, ...]) -> tuple[
+ conn: gn3db.Connection, dataset_ids: tuple[str, ...]) -> tuple[
dict, ...]:
"""Fetch genotype data by ID."""
with conn.cursor(DictCursor) as cursor:
@@ -45,7 +45,7 @@ def __fetch_geno_data_by_ids__(
raise NotFoundError("Could not find Genotype data with the given ID.")
def __fetch_pheno_data_by_ids__(
- conn: gn3db.DbConnection, dataset_ids: tuple[str, ...]) -> tuple[
+ conn: gn3db.Connection, dataset_ids: tuple[str, ...]) -> tuple[
dict, ...]:
"""Fetch phenotype data by ID."""
with conn.cursor(DictCursor) as cursor:
@@ -67,7 +67,7 @@ def __fetch_pheno_data_by_ids__(
"Could not find Phenotype/Publish data with the given IDs.")
def __fetch_data_by_id(
- conn: gn3db.DbConnection, dataset_type: str,
+ conn: gn3db.Connection, dataset_type: str,
dataset_ids: tuple[str, ...]) -> tuple[dict, ...]:
"""Fetch data from MySQL by IDs."""
fetch_fns = {
@@ -83,7 +83,7 @@ def __fetch_data_by_id(
"group(s)."),
oauth2_scope="profile group resource")
def link_data_to_group(
- authconn: authdb.DbConnection, gn3conn: gn3db.DbConnection,
+ authconn: authdb.DbConnection, gn3conn: gn3db.Connection,
dataset_type: str, dataset_ids: tuple[str, ...], group: Group) -> tuple[
dict, ...]:
"""Link the given data to the specified group."""
diff --git a/gn_auth/auth/authorisation/resources/groups/models.py b/gn_auth/auth/authorisation/resources/groups/models.py
index 3263e37..2df5f04 100644
--- a/gn_auth/auth/authorisation/resources/groups/models.py
+++ b/gn_auth/auth/authorisation/resources/groups/models.py
@@ -8,14 +8,18 @@ from typing import Any, Sequence, Iterable, Optional
import sqlite3
from flask import g
from pymonad.maybe import Just, Maybe, Nothing
+from pymonad.either import Left, Right, Either
+from pymonad.tools import monad_from_none_or_value
from gn_auth.auth.db import sqlite3 as db
from gn_auth.auth.authentication.users import User, user_by_id
from gn_auth.auth.authorisation.checks import authorised_p
from gn_auth.auth.authorisation.privileges import Privilege
-from gn_auth.auth.authorisation.resources.base import Resource
from gn_auth.auth.authorisation.resources.errors import MissingGroupError
+from gn_auth.auth.authorisation.resources.base import (
+ Resource,
+ resource_from_dbrow)
from gn_auth.auth.errors import (
NotFoundError, AuthorisationError, InconsistencyError)
from gn_auth.auth.authorisation.roles.models import (
@@ -118,7 +122,7 @@ def create_group(
cursor, group_name, (
{"group_description": group_description}
if group_description else {}))
- group_resource = {
+ _group_resource = {
"group_id": str(new_group.group_id),
"resource_id": str(uuid4()),
"resource_name": group_name,
@@ -131,17 +135,17 @@ def create_group(
cursor.execute(
"INSERT INTO resources VALUES "
"(:resource_id, :resource_name, :resource_category_id, :public)",
- group_resource)
+ _group_resource)
cursor.execute(
"INSERT INTO group_resources(resource_id, group_id) "
"VALUES(:resource_id, :group_id)",
- group_resource)
+ _group_resource)
add_user_to_group(cursor, new_group, group_leader)
revoke_user_role_by_name(cursor, group_leader, "group-creator")
assign_user_role_by_name(
cursor,
group_leader,
- UUID(str(group_resource["resource_id"])),
+ UUID(str(_group_resource["resource_id"])),
"group-leader")
return new_group
@@ -497,3 +501,44 @@ def add_resources_to_group(conn: db.DbConnection,
"group_id": str(group.group_id),
"resource_id": str(rsc.resource_id)
} for rsc in resources))
+
+
+def admin_group(conn: db.DbConnection) -> Either:
+ """Return a group where at least one system admin is a member."""
+ query = (
+ "SELECT DISTINCT g.group_id, g.group_name, g.group_metadata "
+ "FROM roles AS r INNER JOIN user_roles AS ur ON r.role_id=ur.role_id "
+ "INNER JOIN group_users AS gu ON ur.user_id=gu.user_id "
+ "INNER JOIN groups AS g ON gu.group_id=g.group_id "
+ "WHERE role_name='system-administrator'")
+ with db.cursor(conn) as cursor:
+ cursor.execute(query)
+ return monad_from_none_or_value(
+ Left("There is no group of which the system admininstrator is a "
+ "member."),
+ lambda row: Right(Group(
+ UUID(row["group_id"]),
+ row["group_name"],
+ json.loads(row["group_metadata"]))),
+ cursor.fetchone())
+
+
+def group_resource(conn: db.DbConnection, group_id: UUID) -> Resource:
+ """Retrieve the system resource."""
+ with db.cursor(conn) as cursor:
+ cursor.execute(
+ "SELECT group_resources.group_id, resource_categories.*, "
+ "resources.resource_id, resources.resource_name, resources.public "
+ "FROM group_resources INNER JOIN resources "
+ "ON group_resources.resource_id=resources.resource_id "
+ "INNER JOIN resource_categories "
+ "ON resources.resource_category_id=resource_categories.resource_category_id "
+ "WHERE group_resources.group_id=? "
+ "AND resource_categories.resource_category_key='group'",
+ (str(group_id),))
+ row = cursor.fetchone()
+ if row:
+ return resource_from_dbrow(row)
+
+ raise NotFoundError("Could not find a resource for group with ID "
+ f"{group_id}")
diff --git a/gn_auth/auth/authorisation/resources/groups/views.py b/gn_auth/auth/authorisation/resources/groups/views.py
index 920f504..746e23c 100644
--- a/gn_auth/auth/authorisation/resources/groups/views.py
+++ b/gn_auth/auth/authorisation/resources/groups/views.py
@@ -9,10 +9,10 @@ from dataclasses import asdict
from MySQLdb.cursors import DictCursor
from flask import jsonify, Response, Blueprint, current_app
-from gn_auth.auth.requests import request_json
+from gn_libs import mysqldb as gn3db
+from gn_auth.auth.requests import request_json
from gn_auth.auth.db import sqlite3 as db
-from gn_auth.auth.db import mariadb as gn3db
from gn_auth.auth.db.sqlite3 import with_db_connection
from gn_auth.auth.authorisation.privileges import privileges_by_ids
@@ -169,7 +169,7 @@ def unlinked_genotype_data(
return tuple(dict(row) for row in cursor.fetchall())
def unlinked_phenotype_data(
- authconn: db.DbConnection, gn3conn: gn3db.DbConnection,
+ authconn: db.DbConnection, gn3conn: gn3db.Connection,
group: Group) -> tuple[dict, ...]:
"""
Retrieve all phenotype data linked to a group but not linked to any
@@ -235,7 +235,7 @@ def unlinked_data(resource_type: str) -> Response:
if resource_type in ("system", "group"):
return jsonify(tuple())
- if resource_type not in ("all", "mrna", "genotype", "phenotype"):
+ if resource_type not in ("all", "mrna", "genotype", "phenotype", "inbredset-group"):
raise AuthorisationError(f"Invalid resource type {resource_type}")
with require_oauth.acquire("profile group resource") as the_token:
@@ -253,7 +253,8 @@ def unlinked_data(resource_type: str) -> Response:
"genotype": unlinked_genotype_data,
"phenotype": lambda conn, grp: partial(
unlinked_phenotype_data, gn3conn=gn3conn)(
- authconn=conn, group=grp)
+ authconn=conn, group=grp),
+ "inbredset-group": lambda authconn, ugroup: [] # Still need to implement this
}
return jsonify(tuple(
dict(row) for row in unlinked_fns[resource_type](
diff --git a/gn_auth/auth/authorisation/resources/inbredset/models.py b/gn_auth/auth/authorisation/resources/inbredset/models.py
index de1c18a..64d41e3 100644
--- a/gn_auth/auth/authorisation/resources/inbredset/models.py
+++ b/gn_auth/auth/authorisation/resources/inbredset/models.py
@@ -62,7 +62,7 @@ def assign_inbredset_group_owner_role(
return resource
-def link_data_to_resource(# pylint: disable=[too-many-arguments]
+def link_data_to_resource(# pylint: disable=[too-many-arguments, too-many-positional-arguments]
cursor: sqlite3.Cursor,
resource_id: UUID,
species_id: int,
diff --git a/gn_auth/auth/authorisation/resources/inbredset/views.py b/gn_auth/auth/authorisation/resources/inbredset/views.py
index b559105..40dd38d 100644
--- a/gn_auth/auth/authorisation/resources/inbredset/views.py
+++ b/gn_auth/auth/authorisation/resources/inbredset/views.py
@@ -7,7 +7,7 @@ from gn_auth.auth.db import sqlite3 as db
from gn_auth.auth.requests import request_json
from gn_auth.auth.db.sqlite3 import with_db_connection
from gn_auth.auth.authentication.oauth2.resource_server import require_oauth
-from gn_auth.auth.authorisation.resources.groups.models import user_group
+from gn_auth.auth.authorisation.resources.groups.models import user_group, admin_group
from .models import (create_resource,
link_data_to_resource,
@@ -83,7 +83,14 @@ def create_population_resource():
return Right({"formdata": form, "group": usergroup})
- return user_group(conn, _token.user).then(
+ def __default_group_if_none__(group) -> Either:
+ if group.is_nothing():
+ return admin_group(conn)
+ return Right(group.value)
+
+ return __default_group_if_none__(
+ user_group(conn, _token.user)
+ ).then(
lambda group: __check_form__(request_json(), group)
).then(
lambda formdata: {
diff --git a/gn_auth/auth/authorisation/resources/models.py b/gn_auth/auth/authorisation/resources/models.py
index d3df6dc..e538a87 100644
--- a/gn_auth/auth/authorisation/resources/models.py
+++ b/gn_auth/auth/authorisation/resources/models.py
@@ -29,7 +29,7 @@ from .genotypes.models import (
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,
@@ -39,7 +39,7 @@ from .phenotype import (
@authorised_p(("group:resource:create-resource",),
error_description="Insufficient privileges to create a resource",
oauth2_scope="profile resource")
-def create_resource(# pylint: disable=[too-many-arguments]
+def create_resource(# pylint: disable=[too-many-arguments, too-many-positional-arguments]
cursor: sqlite3.Cursor,
resource_name: str,
resource_category: ResourceCategory,
@@ -133,8 +133,10 @@ def user_resources(conn: db.DbConnection, user: User) -> Sequence[Resource]:
"""List the resources available to the user"""
with db.cursor(conn) as cursor:
cursor.execute(
- ("SELECT r.*, rc.resource_category_key, "
- "rc.resource_category_description FROM user_roles AS ur "
+ ("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 "
@@ -205,8 +207,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",),
@@ -221,7 +227,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):
diff --git a/gn_auth/auth/authorisation/resources/mrna.py b/gn_auth/auth/authorisation/resources/mrna.py
index 7fce227..66f8824 100644
--- a/gn_auth/auth/authorisation/resources/mrna.py
+++ b/gn_auth/auth/authorisation/resources/mrna.py
@@ -26,14 +26,15 @@ def resource_data(cursor: db.DbCursor,
def link_data_to_resource(
conn: db.DbConnection,
resource: Resource,
- data_link_id: uuid.UUID) -> dict:
+ data_link_ids: tuple[uuid.UUID, ...]
+) -> tuple[dict, ...]:
"""Link mRNA Assay data with a resource."""
with db.cursor(conn) as cursor:
- params = {
+ params = tuple({
"resource_id": str(resource.resource_id),
"data_link_id": str(data_link_id)
- }
- cursor.execute(
+ } for data_link_id in data_link_ids)
+ cursor.executemany(
"INSERT INTO mrna_resources VALUES"
"(:resource_id, :data_link_id)",
params)
diff --git a/gn_auth/auth/authorisation/resources/phenotype.py b/gn_auth/auth/authorisation/resources/phenotype.py
deleted file mode 100644
index 7005db3..0000000
--- a/gn_auth/auth/authorisation/resources/phenotype.py
+++ /dev/null
@@ -1,68 +0,0 @@
-"""Phenotype data resources functions and utilities."""
-import uuid
-from typing import Optional, Sequence
-
-import sqlite3
-
-import gn_auth.auth.db.sqlite3 as db
-
-from .base import Resource
-from .data import __attach_data__
-
-def resource_data(
- cursor: db.DbCursor,
- resource_id: uuid.UUID,
- offset: int = 0,
- limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
- """Fetch data linked to a Phenotype resource"""
- cursor.execute(
- ("SELECT * FROM phenotype_resources AS pr "
- "INNER JOIN linked_phenotype_data AS lpd "
- "ON pr.data_link_id=lpd.data_link_id "
- "WHERE pr.resource_id=?") + (
- f" LIMIT {limit} OFFSET {offset}" if bool(limit) else ""),
- (str(resource_id),))
- return cursor.fetchall()
-
-def link_data_to_resource(
- conn: db.DbConnection,
- resource: Resource,
- data_link_id: uuid.UUID) -> dict:
- """Link Phenotype data with a resource."""
- with db.cursor(conn) as cursor:
- params = {
- "resource_id": str(resource.resource_id),
- "data_link_id": str(data_link_id)
- }
- cursor.execute(
- "INSERT INTO phenotype_resources VALUES"
- "(:resource_id, :data_link_id)",
- params)
- return params
-
-def unlink_data_from_resource(
- conn: db.DbConnection,
- resource: Resource,
- data_link_id: uuid.UUID) -> dict:
- """Unlink data from Phenotype resources"""
- with db.cursor(conn) as cursor:
- cursor.execute("DELETE FROM phenotype_resources "
- "WHERE resource_id=? AND data_link_id=?",
- (str(resource.resource_id), str(data_link_id)))
- return {
- "resource_id": str(resource.resource_id),
- "dataset_type": resource.resource_category.resource_category_key,
- "data_link_id": str(data_link_id)
- }
-
-def attach_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_phenotype_data AS lpd "
- "ON pr.data_link_id=lpd.data_link_id "
- f"WHERE pr.resource_id IN ({placeholders})",
- tuple(str(resource.resource_id) for resource in resources))
- return __attach_data__(cursor.fetchall(), resources)
diff --git a/gn_auth/auth/authorisation/resources/phenotypes/__init__.py b/gn_auth/auth/authorisation/resources/phenotypes/__init__.py
new file mode 100644
index 0000000..0d4dbfa
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/phenotypes/__init__.py
@@ -0,0 +1 @@
+"""The phenotypes package."""
diff --git a/gn_auth/auth/authorisation/resources/phenotypes/models.py b/gn_auth/auth/authorisation/resources/phenotypes/models.py
new file mode 100644
index 0000000..0ef91ab
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/phenotypes/models.py
@@ -0,0 +1,143 @@
+"""Phenotype data resources functions and utilities."""
+import uuid
+from functools import reduce
+from typing import Optional, Sequence
+
+import sqlite3
+from pymonad.maybe import Just, Maybe, Nothing
+from pymonad.tools import monad_from_none_or_value
+
+import gn_auth.auth.db.sqlite3 as db
+from gn_auth.auth.authorisation.resources.data import __attach_data__
+from gn_auth.auth.authorisation.resources.base import Resource, resource_from_dbrow
+
+def resource_data(
+ cursor: db.DbCursor,
+ resource_id: uuid.UUID,
+ offset: int = 0,
+ limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
+ """Fetch data linked to a Phenotype resource"""
+ cursor.execute(
+ ("SELECT * FROM phenotype_resources AS pr "
+ "INNER JOIN linked_phenotype_data AS lpd "
+ "ON pr.data_link_id=lpd.data_link_id "
+ "WHERE pr.resource_id=?") + (
+ f" LIMIT {limit} OFFSET {offset}" if bool(limit) else ""),
+ (str(resource_id),))
+ return cursor.fetchall()
+
+def link_data_to_resource(
+ conn: db.DbConnection,
+ resource: Resource,
+ data_link_ids: tuple[uuid.UUID, ...]
+) -> tuple[dict, ...]:
+ """Link Phenotype data with a resource."""
+ with db.cursor(conn) as cursor:
+ params = tuple({
+ "resource_id": str(resource.resource_id),
+ "data_link_id": str(data_link_id)
+ } for data_link_id in data_link_ids)
+ cursor.executemany(
+ "INSERT INTO phenotype_resources VALUES"
+ "(:resource_id, :data_link_id)",
+ params)
+ return params
+
+def unlink_data_from_resource(
+ conn: db.DbConnection,
+ resource: Resource,
+ data_link_id: uuid.UUID) -> dict:
+ """Unlink data from Phenotype resources"""
+ with db.cursor(conn) as cursor:
+ cursor.execute("DELETE FROM phenotype_resources "
+ "WHERE resource_id=? AND data_link_id=?",
+ (str(resource.resource_id), str(data_link_id)))
+ return {
+ "resource_id": str(resource.resource_id),
+ "dataset_type": resource.resource_category.resource_category_key,
+ "data_link_id": str(data_link_id)
+ }
+
+def attach_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_phenotype_data AS lpd "
+ "ON pr.data_link_id=lpd.data_link_id "
+ f"WHERE pr.resource_id IN ({placeholders})",
+ tuple(str(resource.resource_id) for resource in resources))
+ return __attach_data__(cursor.fetchall(), resources)
+
+
+def individual_linked_resource(
+ conn: db.DbConnection,
+ species_id: int,
+ population_id: int,
+ dataset_id: int,
+ xref_id: str) -> Maybe:
+ """Given the data details, return the linked resource, if one is defined."""
+ with db.cursor(conn) as cursor:
+ cursor.execute(
+ "SELECT "
+ "rsc.*, rc.*, lpd.SpeciesId AS species_id, "
+ "lpd.InbredSetId AS population_id, lpd.PublishXRefId AS xref_id, "
+ "lpd.dataset_name, lpd.dataset_fullname, lpd.dataset_shortname "
+ "FROM linked_phenotype_data AS lpd "
+ "INNER JOIN phenotype_resources AS pr "
+ "ON lpd.data_link_id=pr.data_link_id "
+ "INNER JOIN resources AS rsc ON pr.resource_id=rsc.resource_id "
+ "INNER JOIN resource_categories AS rc "
+ "ON rsc.resource_category_id=rc.resource_category_id "
+ "WHERE "
+ "(lpd.SpeciesId, lpd.InbredSetId, lpd.PublishFreezeId, lpd.PublishXRefId) = "
+ "(?, ?, ?, ?)",
+ (species_id, population_id, dataset_id, xref_id))
+ return monad_from_none_or_value(
+ Nothing, Just, cursor.fetchone()).then(resource_from_dbrow)
+
+
+def all_linked_resources(
+ conn: db.DbConnection,
+ species_id: int,
+ population_id: int,
+ dataset_id: int) -> Maybe:
+ """Given the data details, return the linked resource, if one is defined."""
+ with db.cursor(conn) as cursor:
+ cursor.execute(
+ "SELECT rsc.*, rc.resource_category_key, "
+ "rc.resource_category_description, lpd.SpeciesId AS species_id, "
+ "lpd.InbredSetId AS population_id, lpd.PublishXRefId AS xref_id, "
+ "lpd.dataset_name, lpd.dataset_fullname, lpd.dataset_shortname "
+ "FROM linked_phenotype_data AS lpd "
+ "INNER JOIN phenotype_resources AS pr "
+ "ON lpd.data_link_id=pr.data_link_id INNER JOIN resources AS rsc "
+ "ON pr.resource_id=rsc.resource_id "
+ "INNER JOIN resource_categories AS rc "
+ "ON rsc.resource_category_id=rc.resource_category_id "
+ "WHERE "
+ "(lpd.SpeciesId, lpd.InbredSetId, lpd.PublishFreezeId) = (?, ?, ?)",
+ (species_id, population_id, dataset_id))
+
+ _rscdatakeys = (
+ "species_id", "population_id", "xref_id", "dataset_name",
+ "dataset_fullname", "dataset_shortname")
+ def __organise__(resources, row):
+ _rscid = uuid.UUID(row["resource_id"])
+ _resource = resources.get(_rscid, resource_from_dbrow(row))
+ return {
+ **resources,
+ _rscid: Resource(
+ _resource.resource_id,
+ _resource.resource_name,
+ _resource.resource_category,
+ _resource.public,
+ _resource.resource_data + (
+ {key: row[key] for key in _rscdatakeys},))
+ }
+ results: dict[uuid.UUID, Resource] = reduce(
+ __organise__, cursor.fetchall(), {})
+ if len(results) == 0:
+ return Nothing
+ return Just(tuple(results.values()))
diff --git a/gn_auth/auth/authorisation/resources/phenotypes/views.py b/gn_auth/auth/authorisation/resources/phenotypes/views.py
new file mode 100644
index 0000000..c0a5e81
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/phenotypes/views.py
@@ -0,0 +1,77 @@
+"""Views for the phenotype resources."""
+from pymonad.either import Left, Right
+from flask import jsonify, Blueprint, current_app as app
+
+from gn_auth.auth.db import sqlite3 as db
+from gn_auth.auth.requests import request_json
+from gn_auth.auth.authorisation.resources.request_utils import check_form
+from gn_auth.auth.authorisation.roles.models import user_roles_on_resource
+
+from gn_auth.auth.authentication.oauth2.resource_server import require_oauth
+
+from .models import all_linked_resources, individual_linked_resource
+
+phenobp = Blueprint("phenotypes", __name__)
+
+@phenobp.route("/phenotypes/individual/linked-resource", methods=["POST"])
+@require_oauth("profile group resource")
+def get_individual_linked_resource():
+ """Get the linked resource for a particular phenotype within the dataset.
+
+ Phenotypes are a tad tricky. Each phenotype could technically be a resource
+ on its own, and thus a user could have access to only a subset of phenotypes
+ within the entire dataset."""
+ with (require_oauth.acquire("profile group resource") as _token,
+ db.connection(app.config["AUTH_DB"]) as conn):
+ return check_form(
+ request_json(),
+ "species_id",
+ "population_id",
+ "dataset_id",
+ "xref_id"
+ ).then(
+ lambda formdata: individual_linked_resource(
+ conn,
+ int(formdata["species_id"]),
+ int(formdata["population_id"]),
+ int(formdata["dataset_id"]),
+ formdata["xref_id"]
+ ).maybe(Left("No linked resource!"),
+ lambda lrsc: Right({
+ "formdata": formdata,
+ "resource": lrsc
+ }))
+ ).then(
+ lambda fdlrsc: {
+ **fdlrsc,
+ "roles": user_roles_on_resource(
+ conn, _token.user.user_id, fdlrsc["resource"].resource_id)
+ }
+ ).either(lambda error: (jsonify(error), 400),
+ lambda res: jsonify({
+ key: value for key, value in res.items()
+ if key != "formdata"
+ }))
+
+
+@phenobp.route("/phenotypes/linked-resources", methods=["POST"])
+@require_oauth("profile group resource")
+def get_all_linked_resources():
+ """Get all the linked resources for all phenotypes within a dataset.
+
+ See `get_individual_linked_resource(…)` documentation."""
+ with (require_oauth.acquire("profile group resource") as _token,
+ db.connection(app.config["AUTH_DB"]) as conn):
+ return check_form(
+ request_json(),
+ "species_id",
+ "population_id",
+ "dataset_id"
+ ).then(
+ lambda formdata: all_linked_resources(
+ conn,
+ int(formdata["species_id"]),
+ int(formdata["population_id"]),
+ int(formdata["dataset_id"])).maybe(
+ Left("No linked resource!"), Right)
+ ).either(lambda error: (jsonify(error), 400), jsonify)
diff --git a/gn_auth/auth/authorisation/resources/system/models.py b/gn_auth/auth/authorisation/resources/system/models.py
index 7c176aa..303b0ac 100644
--- a/gn_auth/auth/authorisation/resources/system/models.py
+++ b/gn_auth/auth/authorisation/resources/system/models.py
@@ -4,11 +4,15 @@ from functools import reduce
from typing import Sequence
from gn_auth.auth.db import sqlite3 as db
+from gn_auth.auth.errors import NotFoundError
from gn_auth.auth.authentication.users import User
from gn_auth.auth.authorisation.roles import Role
from gn_auth.auth.authorisation.privileges import Privilege
+from gn_auth.auth.authorisation.resources.base import (
+ Resource,
+ resource_from_dbrow)
def __organise_privileges__(acc, row):
role_id = UUID(row["role_id"])
@@ -24,6 +28,7 @@ def __organise_privileges__(acc, row):
(Privilege(row["privilege_id"], row["privilege_description"]),)))
}
+
def user_roles_on_system(conn: db.DbConnection, user: User) -> Sequence[Role]:
"""
Retrieve all roles assigned to the `user` that act on `system` resources.
@@ -45,3 +50,19 @@ def user_roles_on_system(conn: db.DbConnection, user: User) -> Sequence[Role]:
return tuple(reduce(
__organise_privileges__, cursor.fetchall(), {}).values())
return tuple()
+
+
+def system_resource(conn: db.DbConnection) -> Resource:
+ """Retrieve the system resource."""
+ with db.cursor(conn) as cursor:
+ cursor.execute(
+ "SELECT resource_categories.*, resources.resource_id, "
+ "resources.resource_name, resources.public "
+ "FROM resource_categories INNER JOIN resources "
+ "ON resource_categories.resource_category_id=resources.resource_category_id "
+ "WHERE resource_categories.resource_category_key='system'")
+ row = cursor.fetchone()
+ if row:
+ return resource_from_dbrow(row)
+
+ raise NotFoundError("Could not find a system resource!")
diff --git a/gn_auth/auth/authorisation/resources/views.py b/gn_auth/auth/authorisation/resources/views.py
index 31421f4..0a68927 100644
--- a/gn_auth/auth/authorisation/resources/views.py
+++ b/gn_auth/auth/authorisation/resources/views.py
@@ -42,6 +42,7 @@ from gn_auth.auth.authentication.users import User, user_by_id, user_by_email
from .checks import authorised_for
from .inbredset.views import popbp
from .genotypes.views import genobp
+from .phenotypes.views import phenobp
from .errors import MissingGroupError
from .groups.models import Group, user_group
from .models import (
@@ -54,6 +55,7 @@ from .models import (
resources = Blueprint("resources", __name__)
resources.register_blueprint(popbp, url_prefix="/")
resources.register_blueprint(genobp, url_prefix="/")
+resources.register_blueprint(phenobp, url_prefix="/")
@resources.route("/categories", methods=["GET"])
@require_oauth("profile group resource")
@@ -135,7 +137,7 @@ def view_resource_data(resource_id: UUID) -> Response:
with require_oauth.acquire("profile group resource") as the_token:
db_uri = app.config["AUTH_DB"]
count_per_page = __safe_get_requests_count__("count_per_page")
- offset = (__safe_get_requests_page__("page") - 1)
+ offset = __safe_get_requests_page__("page") - 1
with db.connection(db_uri) as conn:
resource = resource_by_id(conn, the_token.user, resource_id)
return jsonify(resource_data(
@@ -151,7 +153,7 @@ def link_data():
try:
form = request_json()
assert "resource_id" in form, "Resource ID not provided."
- assert "data_link_id" in form, "Data Link ID not provided."
+ assert "data_link_ids" in form, "Data Link IDs not provided."
assert "dataset_type" in form, "Dataset type not specified"
assert form["dataset_type"].lower() in (
"mrna", "genotype", "phenotype"), "Invalid dataset type provided."
@@ -159,8 +161,11 @@ def link_data():
with require_oauth.acquire("profile group resource") as the_token:
def __link__(conn: db.DbConnection):
return link_data_to_resource(
- conn, the_token.user, UUID(form["resource_id"]),
- form["dataset_type"], UUID(form["data_link_id"]))
+ conn,
+ the_token.user,
+ UUID(form["resource_id"]),
+ form["dataset_type"],
+ tuple(UUID(dlinkid) for dlinkid in form["data_link_ids"]))
return jsonify(with_db_connection(__link__))
except AssertionError as aserr:
@@ -409,9 +414,18 @@ def resource_roles(resource_id: UUID) -> Response:
"ON rp.privilege_id=p.privilege_id "
"WHERE rr.resource_id=? AND rr.role_created_by=?",
(str(resource_id), str(_token.user.user_id)))
- results = cursor.fetchall()
+ user_created = db_rows_to_roles(cursor.fetchall())
+
+ cursor.execute(
+ "SELECT ur.user_id, ur.resource_id, 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 resource_id=? AND user_id=?",
+ (str(resource_id), str(_token.user.user_id)))
+ assigned_to_user = db_rows_to_roles(cursor.fetchall())
- return db_rows_to_roles(results)
+ return assigned_to_user + user_created
return jsonify(with_db_connection(__roles__))