diff options
Diffstat (limited to 'gn_auth/auth/authorisation/resources')
-rw-r--r-- | gn_auth/auth/authorisation/resources/checks.py | 37 | ||||
-rw-r--r-- | gn_auth/auth/authorisation/resources/genotypes/models.py | 9 | ||||
-rw-r--r-- | gn_auth/auth/authorisation/resources/groups/models.py | 118 | ||||
-rw-r--r-- | gn_auth/auth/authorisation/resources/groups/views.py | 83 | ||||
-rw-r--r-- | gn_auth/auth/authorisation/resources/models.py | 10 | ||||
-rw-r--r-- | gn_auth/auth/authorisation/resources/mrna.py | 9 | ||||
-rw-r--r-- | gn_auth/auth/authorisation/resources/phenotypes/models.py | 9 | ||||
-rw-r--r-- | gn_auth/auth/authorisation/resources/system/models.py | 21 | ||||
-rw-r--r-- | gn_auth/auth/authorisation/resources/views.py | 9 |
9 files changed, 266 insertions, 39 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 464537e..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) diff --git a/gn_auth/auth/authorisation/resources/groups/models.py b/gn_auth/auth/authorisation/resources/groups/models.py index fa25594..a4aacc7 100644 --- a/gn_auth/auth/authorisation/resources/groups/models.py +++ b/gn_auth/auth/authorisation/resources/groups/models.py @@ -16,8 +16,10 @@ 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 ( @@ -120,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, @@ -133,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 @@ -235,15 +237,56 @@ def is_group_leader(conn: db.DbConnection, user: User, group: Group) -> bool: return "group-leader" in role_names -def all_groups(conn: db.DbConnection) -> Maybe[Sequence[Group]]: +def __build_groups_list_query__( + base: str, + search: Optional[str] = None +) -> tuple[str, tuple[Optional[str], ...]]: + """Build up the query from given search terms.""" + if search is not None and search.strip() != "": + _search = search.strip() + return ((f"{base} WHERE groups.group_name LIKE ? " + "OR groups.group_metadata LIKE ?"), + (f"%{search}%", f"%{search}%")) + return base, tuple() + + +def __limit_results_length__(base: str, start: int = 0, length: int = 0) -> str: + """Add the `LIMIT … OFFSET …` clause to query `base`.""" + if length > 0: + return f"{base} LIMIT {length} OFFSET {start}" + return base + + +def all_groups( + conn: db.DbConnection, + search: Optional[str] = None, + start: int = 0, + length: int = 0 +) -> Maybe[tuple[tuple[Group, ...], int, int]]: """Retrieve all existing groups""" with db.cursor(conn) as cursor: - cursor.execute("SELECT * FROM groups") + cursor.execute("SELECT COUNT(*) FROM groups") + _groups_total_count = int(cursor.fetchone()["COUNT(*)"]) + + _qdets = __build_groups_list_query__( + "SELECT COUNT(*) FROM groups", search) + cursor.execute(*__build_groups_list_query__( + "SELECT COUNT(*) FROM groups", search)) + _filtered_total_count = int(cursor.fetchone()["COUNT(*)"]) + + _query, _params = __build_groups_list_query__( + "SELECT * FROM groups", search) + + cursor.execute(__limit_results_length__(_query, start, length), + _params) res = cursor.fetchall() if res: - return Just(tuple( - Group(row["group_id"], row["group_name"], - json.loads(row["group_metadata"])) for row in res)) + return Just(( + tuple( + Group(row["group_id"], row["group_name"], + json.loads(row["group_metadata"])) for row in res), + _groups_total_count, + _filtered_total_count)) return Nothing @@ -519,3 +562,58 @@ def admin_group(conn: db.DbConnection) -> Either: 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}") + + +def data_resources( + conn: db.DbConnection, group_id: UUID) -> Iterable[Resource]: + """Fetch a group's data resources.""" + with db.cursor(conn) as cursor: + cursor.execute( + "SELECT resource_ownership.group_id, resources.resource_id, " + "resources.resource_name, resources.public, resource_categories.* " + "FROM resource_ownership INNER JOIN resources " + "ON resource_ownership.resource_id=resources.resource_id " + "INNER JOIN resource_categories " + "ON resources.resource_category_id=resource_categories.resource_category_id " + "WHERE group_id=?", + (str(group_id),)) + yield from (resource_from_dbrow(row) for row in cursor.fetchall()) + + +def group_leaders(conn: db.DbConnection, group_id: UUID) -> Iterable[User]: + """Fetch all of a group's group leaders.""" + with db.cursor(conn) as cursor: + cursor.execute( + "SELECT users.* FROM group_users INNER JOIN group_resources " + "ON group_users.group_id=group_resources.group_id " + "INNER JOIN user_roles " + "ON group_resources.resource_id=user_roles.resource_id " + "INNER JOIN roles " + "ON user_roles.role_id=roles.role_id " + "INNER JOIN users " + "ON user_roles.user_id=users.user_id " + "WHERE group_users.group_id=? " + "AND roles.role_name='group-leader'", + (str(group_id),)) + yield from (User.from_sqlite3_row(row) for row in cursor.fetchall()) diff --git a/gn_auth/auth/authorisation/resources/groups/views.py b/gn_auth/auth/authorisation/resources/groups/views.py index 368284f..28f0645 100644 --- a/gn_auth/auth/authorisation/resources/groups/views.py +++ b/gn_auth/auth/authorisation/resources/groups/views.py @@ -22,12 +22,22 @@ from gn_auth.auth.authentication.users import User from gn_auth.auth.authentication.oauth2.resource_server import require_oauth from .data import link_data_to_group -from .models import ( - Group, user_group, all_groups, DUMMY_GROUP, GroupRole, group_by_id, - join_requests, group_role_by_id, GroupCreationError, - accept_reject_join_request, group_users as _group_users, - create_group as _create_group, add_privilege_to_group_role, - delete_privilege_from_group_role) +from .models import (Group, + GroupRole, + user_group, + all_groups, + DUMMY_GROUP, + group_by_id, + group_leaders, + join_requests, + data_resources, + group_role_by_id, + GroupCreationError, + accept_reject_join_request, + add_privilege_to_group_role, + group_users as _group_users, + create_group as _create_group, + delete_privilege_from_group_role) groups = Blueprint("groups", __name__) @@ -35,11 +45,31 @@ groups = Blueprint("groups", __name__) @require_oauth("profile group") def list_groups(): """Return the list of groups that exist.""" + _kwargs = request_json() + def __add_total_group_count__(groups_info): + return { + "groups": groups_info[0], + "total-groups": groups_info[1], + "total-filtered": groups_info[2] + } + with db.connection(current_app.config["AUTH_DB"]) as conn: - the_groups = all_groups(conn) + return jsonify(all_groups( + conn, + search=_kwargs.get("search"), + start=int(_kwargs.get("start", "0")), + length=int(_kwargs.get("length", "0")) + ).then( + __add_total_group_count__ + ).maybe( + { + "groups": [], + "message": "No groups found!", + "total-groups": 0, + "total-filtered": 0 + }, + lambda _grpdata: _grpdata)) - return jsonify(the_groups.maybe( - [], lambda grps: [asdict(grp) for grp in grps])) @groups.route("/create", methods=["POST"]) @require_oauth("profile group") @@ -235,7 +265,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 +283,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]( @@ -347,3 +378,33 @@ def delete_priv_from_role(group_role_id: uuid.UUID) -> Response: direction="DELETE", user=the_token.user))), "description": "Privilege deleted successfully" }) + + +@groups.route("/<uuid:group_id>", methods=["GET"]) +@require_oauth("profile group") +def view_group(group_id: uuid.UUID) -> Response: + """View a particular group's details.""" + # TODO: do authorisation checks here… + with (require_oauth.acquire("profile group") as _token, + db.connection(current_app.config["AUTH_DB"]) as conn): + return jsonify(group_by_id(conn, group_id)) + + +@groups.route("/<uuid:group_id>/data-resources", methods=["GET"]) +@require_oauth("profile group") +def view_group_data_resources(group_id: uuid.UUID) -> Response: + """View data resources linked to the group.""" + # TODO: do authorisation checks here… + with (require_oauth.acquire("profile group") as _token, + db.connection(current_app.config["AUTH_DB"]) as conn): + return jsonify(tuple(data_resources(conn, group_id))) + + +@groups.route("/<uuid:group_id>/leaders", methods=["GET"]) +@require_oauth("profile group") +def view_group_leaders(group_id: uuid.UUID) -> Response: + """View a group's leaders.""" + # TODO: do authorisation checks here… + with (require_oauth.acquire("profile group") as _token, + db.connection(current_app.config["AUTH_DB"]) as conn): + return jsonify(tuple(group_leaders(conn, group_id))) diff --git a/gn_auth/auth/authorisation/resources/models.py b/gn_auth/auth/authorisation/resources/models.py index d136fec..e538a87 100644 --- a/gn_auth/auth/authorisation/resources/models.py +++ b/gn_auth/auth/authorisation/resources/models.py @@ -207,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",), @@ -223,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/phenotypes/models.py b/gn_auth/auth/authorisation/resources/phenotypes/models.py index d4a516a..0ef91ab 100644 --- a/gn_auth/auth/authorisation/resources/phenotypes/models.py +++ b/gn_auth/auth/authorisation/resources/phenotypes/models.py @@ -29,14 +29,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 Phenotype 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 phenotype_resources VALUES" "(:resource_id, :data_link_id)", params) 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 29ab3ed..0a68927 100644 --- a/gn_auth/auth/authorisation/resources/views.py +++ b/gn_auth/auth/authorisation/resources/views.py @@ -153,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." @@ -161,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: |