diff options
Diffstat (limited to 'gn_auth/auth/authorisation/resources/views.py')
-rw-r--r-- | gn_auth/auth/authorisation/resources/views.py | 185 |
1 files changed, 144 insertions, 41 deletions
diff --git a/gn_auth/auth/authorisation/resources/views.py b/gn_auth/auth/authorisation/resources/views.py index 50f0d8e..0a68927 100644 --- a/gn_auth/auth/authorisation/resources/views.py +++ b/gn_auth/auth/authorisation/resources/views.py @@ -8,36 +8,54 @@ import time from dataclasses import asdict from functools import reduce -from authlib.integrations.flask_oauth2.errors import _HTTPException +from werkzeug.exceptions import BadRequest from authlib.jose import jwt +from authlib.integrations.flask_oauth2.errors import _HTTPException from flask import (make_response, request, jsonify, Response, Blueprint, current_app as app) +from gn_auth.auth.requests import request_json + from gn_auth.auth.db import sqlite3 as db from gn_auth.auth.db.sqlite3 import with_db_connection +from gn_auth.auth.jwks import newest_jwk, jwks_directory from gn_auth.auth.authorisation.roles import Role -from gn_auth.auth.authorisation.privileges import Privilege -from gn_auth.auth.errors import InvalidData, InconsistencyError, AuthorisationError +from gn_auth.auth.authorisation.roles.models import ( + create_role, + user_resource_roles as _user_resource_roles) +from gn_auth.auth.errors import ( + InvalidData, + InconsistencyError, + AuthorisationError) +from gn_auth.auth.authorisation.privileges import ( + privilege_by_id, + privileges_by_ids) from gn_auth.auth.authorisation.roles.models import ( role_by_id, db_rows_to_roles, - check_user_editable, delete_privilege_from_resource_role) from gn_auth.auth.authentication.oauth2.resource_server import require_oauth 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 ( Resource, resource_data, resource_by_id, public_resources, resource_categories, assign_resource_user, link_data_to_resource, unassign_resource_user, resource_category_by_id, user_roles_on_resources, unlink_data_from_resource, create_resource as _create_resource, get_resource_id) -from .groups.models import Group, resource_owner, group_role_by_id 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") @@ -53,17 +71,24 @@ def list_resource_categories() -> Response: def create_resource() -> Response: """Create a new resource""" with require_oauth.acquire("profile group resource") as the_token: - form = request.form + form = request_json() resource_name = form.get("resource_name") resource_category_id = UUID(form.get("resource_category")) db_uri = app.config["AUTH_DB"] - with db.connection(db_uri) as conn: + with (db.connection(db_uri) as conn, + db.cursor(conn) as cursor): try: + group = user_group(conn, the_token.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.") resource = _create_resource( - conn, + cursor, resource_name, resource_category_by_id(conn, resource_category_id), the_token.user, + group, (form.get("public") == "on")) return jsonify(asdict(resource)) except sqlite3.IntegrityError as sql3ie: @@ -112,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( @@ -126,9 +151,9 @@ def view_resource_data(resource_id: UUID) -> Response: def link_data(): """Link group data to a specific resource.""" try: - form = request.form + 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." @@ -136,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: @@ -150,7 +178,7 @@ def link_data(): def unlink_data(): """Unlink data bound to a specific resource.""" try: - form = request.form + form = request_json() assert "resource_id" in form, "Resource ID not provided." assert "data_link_id" in form, "Data Link ID not provided." @@ -179,7 +207,7 @@ def resource_users(resource_id: UUID): the_token.user, ("group:resource:view-resource",), (resource_id,)) - systemlevelauth = __pk__authorised_for( + systemlevelauth = authorised_for( conn, the_token.user, ("system:user:list",), @@ -237,22 +265,25 @@ def resource_users(resource_id: UUID): @require_oauth("profile group resource role") def assign_role_to_user(resource_id: UUID) -> Response: """Assign a role on the specified resource to a user.""" - with require_oauth.acquire("profile group resource role") as the_token: + with require_oauth.acquire("profile group resource role") as _token: try: - form = request.form - group_role_id = form.get("group_role_id", "") + form = request_json() + role_id = form.get("role_id", "") user_email = form.get("user_email", "") - assert bool(group_role_id), "The role must be provided." + assert bool(role_id), "The role must be provided." assert bool(user_email), "The user email must be provided." def __assign__(conn: db.DbConnection) -> dict: - resource = resource_by_id(conn, the_token.user, resource_id) + authorised_for( + conn, + _token.user, + ("resource:role:assign-role",), + (resource_id,)) + resource = resource_by_id(conn, _token.user, resource_id) user = user_by_email(conn, user_email) return assign_resource_user( conn, resource, user, - group_role_by_id(conn, - resource_owner(conn, resource), - UUID(group_role_id))) + role_by_id(conn, UUID(role_id)))# type: ignore[arg-type] except AssertionError as aserr: raise AuthorisationError(aserr.args[0]) from aserr @@ -262,21 +293,24 @@ def assign_role_to_user(resource_id: UUID) -> Response: @require_oauth("profile group resource role") def unassign_role_to_user(resource_id: UUID) -> Response: """Unassign a role on the specified resource from a user.""" - with require_oauth.acquire("profile group resource role") as the_token: + with require_oauth.acquire("profile group resource role") as _token: try: - form = request.form - group_role_id = form.get("group_role_id", "") + form = request_json() + role_id = form.get("role_id", "") user_id = form.get("user_id", "") - assert bool(group_role_id), "The role must be provided." + assert bool(role_id), "The role must be provided." assert bool(user_id), "The user id must be provided." def __assign__(conn: db.DbConnection) -> dict: - resource = resource_by_id(conn, the_token.user, resource_id) + authorised_for( + conn, + _token.user, + ("resource:role:assign-role",), + (resource_id,)) + resource = resource_by_id(conn, _token.user, resource_id) return unassign_resource_user( conn, resource, user_by_id(conn, UUID(user_id)), - group_role_by_id(conn, - resource_owner(conn, resource), - UUID(group_role_id))) + role_by_id(conn, UUID(role_id)))# type: ignore[arg-type] except AssertionError as aserr: raise AuthorisationError(aserr.args[0]) from aserr @@ -380,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()) - return db_rows_to_roles(results) + 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 assigned_to_user + user_created return jsonify(with_db_connection(__roles__)) @@ -391,7 +434,7 @@ def resource_roles(resource_id: UUID) -> Response: def resources_authorisation(): """Get user authorisations for given resource(s):""" try: - data = request.json + data = request_json() assert (data and "resource-ids" in data) resource_ids = tuple(UUID(resid) for resid in data["resource-ids"]) pubres = tuple( @@ -423,6 +466,14 @@ def resources_authorisation(): "Expected a JSON object with a 'resource-ids' key.") }) resp.status_code = 400 + except Exception as _exc:#pylint: disable=[broad-except] + app.logger.debug("Generic exception.", exc_info=True) + resp = jsonify({ + "status": "general-exception", + "error_description": ( + "Failed to fetch the user's privileges.") + }) + resp.status_code = 500 return resp @@ -475,7 +526,8 @@ def get_user_roles_on_resource(name) -> Response: "email": _token.user.email, "roles": roles, } - token = jwt.encode(jose_header, payload, app.config["SSL_PRIVATE_KEY"]) + token = jwt.encode( + jose_header, payload, newest_jwk(jwks_directory(app))) response.headers["Authorization"] = f"Bearer {token.decode('utf-8')}" return response @@ -507,7 +559,7 @@ def resource_role(resource_id: UUID, role_id: UUID): _roles = db_rows_to_roles(results) if len(_roles) > 1: - msg = f"There is data corruption in the database." + msg = "There is data corruption in the database." return jsonify({ "error": "RoleNotFound", "error_description": msg, @@ -527,7 +579,6 @@ def unassign_resource_role_privilege(resource_id: UUID, role_id: UUID): db.connection(app.config["AUTH_DB"]) as conn, db.cursor(conn) as cursor): _role = role_by_id(conn, role_id) - # check_user_editable(_role) # Check whether role is user editable _authorised = authorised_for( conn, @@ -539,14 +590,15 @@ def unassign_resource_role_privilege(resource_id: UUID, role_id: UUID): "You are not authorised to edit/update this role.") # Actually unassign the privilege from the role - privilege_id = request.json.get("privilege_id") + privilege_id = request_json().get("privilege_id") if not privilege_id: raise AuthorisationError( "You need to provide a privilege to unassign") - delete_privilege_from_resource_role(cursor, - _role, - privilege_by_id(privilege_id)) + delete_privilege_from_resource_role( + cursor, + _role,# type: ignore[arg-type] + privilege_by_id(conn, privilege_id))# type: ignore[arg-type] return jsonify({ "status": "Success", @@ -570,3 +622,54 @@ def resource_role_users(resource_id: UUID, role_id: UUID): results = cursor.fetchall() or [] return jsonify(tuple(User.from_sqlite3_row(row) for row in results)), 200 + + +@resources.route("/<uuid:resource_id>/roles/create", methods=["POST"]) +@require_oauth("profile group resource") +def create_resource_role(resource_id: UUID): + """Create a role to act upon a specific resource.""" + role_name = request_json().get("role_name", "").strip() + if not bool(role_name): + raise BadRequest("You must provide the name for the new role.") + + with (require_oauth.acquire("profile group resource") as _token, + db.connection(app.config["AUTH_DB"]) as conn, + db.cursor(conn) as cursor): + resource = resource_by_id(conn, _token.user, resource_id) + if not bool(resource): + raise BadRequest("No resource with that ID exists.") + + privileges = privileges_by_ids(conn, request_json().get("privileges", [])) + if len(privileges) == 0: + raise BadRequest( + "You must provide at least one privilege for the new role.") + role = create_role(cursor, + f"{resource.resource_name}::{role_name}", + privileges) + cursor.execute( + "INSERT INTO resource_roles(resource_id, role_created_by, role_id) " + "VALUES (:resource_id, :user_id, :role_id)", + { + "resource_id": str(resource_id), + "user_id": str(_token.user.user_id), + "role_id": str(role.role_id) + }) + + return jsonify(asdict(role)) + +@resources.route("/<uuid:resource_id>/users/<uuid:user_id>/roles", methods=["GET"]) +@require_oauth("profile group resource role") +def user_resource_roles(resource_id: UUID, user_id: UUID): + """Get a specific user's roles on a particular resource.""" + with (require_oauth.acquire("profile group resource") as _token, + db.connection(app.config["AUTH_DB"]) as conn): + if _token.user.user_id != user_id: + raise AuthorisationError( + "You are not authorised to view the roles this user has.") + + _resource = resource_by_id(conn, _token.user, resource_id) + if not bool(_resource): + raise BadRequest("No resource was found with the given ID.") + + return jsonify([asdict(role) for role in + _user_resource_roles(conn, _token.user, _resource)]) |