diff options
Diffstat (limited to 'gn_auth/auth/authorisation/resources/views.py')
| -rw-r--r-- | gn_auth/auth/authorisation/resources/views.py | 125 |
1 files changed, 109 insertions, 16 deletions
diff --git a/gn_auth/auth/authorisation/resources/views.py b/gn_auth/auth/authorisation/resources/views.py index 0a68927..f114476 100644 --- a/gn_auth/auth/authorisation/resources/views.py +++ b/gn_auth/auth/authorisation/resources/views.py @@ -1,9 +1,10 @@ """The views/routes for the resources package""" -from uuid import UUID, uuid4 +import time import json +import logging import operator import sqlite3 -import time +from uuid import UUID, uuid4 from dataclasses import asdict from functools import reduce @@ -13,6 +14,7 @@ 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) +import gn_libs.privileges.resources from gn_auth.auth.requests import request_json @@ -39,18 +41,22 @@ from gn_auth.auth.authorisation.roles.models import ( 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 .system.models import system_resource from .groups.models import Group, user_group +from .checks import can_delete, authorised_for 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) + get_resource_id, delete_resource as _delete_resource, + edit_resource as _edit_resource) + +logger = logging.getLogger(__name__) resources = Blueprint("resources", __name__) resources.register_blueprint(popbp, url_prefix="/") @@ -75,8 +81,7 @@ def create_resource() -> Response: 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, - db.cursor(conn) as cursor): + with db.connection(db_uri) as conn: try: group = user_group(conn, the_token.user).maybe( False, lambda grp: grp)# type: ignore[misc, arg-type] @@ -84,7 +89,7 @@ def create_resource() -> Response: raise MissingGroupError(# Not all resources require an owner group "User with no group cannot create a resource.") resource = _create_resource( - cursor, + conn, resource_name, resource_category_by_id(conn, resource_category_id), the_token.user, @@ -96,11 +101,12 @@ def create_resource() -> Response: "resources.resource_name"): raise InconsistencyError( "You cannot have duplicate resource names.") from sql3ie - app.logger.debug( - f"{type(sql3ie)=}: {sql3ie=}") + logger.debug("type(sql3ie)=%s: sql3ie=%s", type(sql3ie), sql3ie) raise + @resources.route("/view/<uuid:resource_id>") +@resources.route("/<uuid:resource_id>/view") @require_oauth("profile group resource") def view_resource(resource_id: UUID) -> Response: """View a particular resource's details.""" @@ -113,6 +119,49 @@ def view_resource(resource_id: UUID) -> Response: ) ) + +@resources.route("/<uuid:resource_id>/edit", methods=["POST"]) +@require_oauth("profile group resource") +def edit_resource(resource_id: UUID) -> Response: + """Update/edit basic details regarding a resource.""" + db_uri = app.config["AUTH_DB"] + with (require_oauth.acquire("profile group resource") as _token, + db.connection(db_uri) as conn): + def __extract_privileges__(roles: tuple[Role, ...]) -> tuple[str, ...]: + return tuple( + priv.privilege_id for role in roles + for priv in role.privileges) + + _sys_resource = system_resource(conn) + _privileges = { + ("system_privileges" + if _rid == _sys_resource.resource_id + else "resource_privileges"): __extract_privileges__(_rroles) + for _rid, _rroles in user_roles_on_resources( + conn, + _token.user, + (resource_id, _sys_resource.resource_id) + ).items() + } + if not gn_libs.privileges.resources.can_edit(**_privileges): + return make_response(jsonify({ + "error": "AuthorisationError", + "error_description": "You are not allowed to edit this resource." + }), 401) + + name = (request_json().get("resource_name") or "").strip() + if bool(name): + return jsonify({ + "resource": asdict(_edit_resource(conn, resource_id, name)), + "message": "Resource updated successfully", + "status": "success" + }) + + return make_response(jsonify({ + "error_description": "Expected `resource_name` to be provided.", + "error": "InvalidInput" + }), 400) + def __safe_get_requests_page__(key: str = "page") -> int: """Get the results page if it exists or default to the first page.""" try: @@ -231,9 +280,11 @@ def resource_users(resource_id: UUID): **users_n_roles, user_id: { "user": user, - "user_group": Group( - UUID(row["group_id"]), row["group_name"], - json.loads(row["group_metadata"])), + "user_group": ( + Group(UUID(row["group_id"]), + row["group_name"], + json.loads(row["group_metadata"])) + if bool(row["group_id"]) else False) , "roles": users_n_roles.get( user_id, {}).get("roles", tuple()) + (role,) } @@ -241,7 +292,7 @@ def resource_users(resource_id: UUID): cursor.execute( "SELECT g.*, u.*, r.* " "FROM groups AS g INNER JOIN group_users AS gu " - "ON g.group_id=gu.group_id INNER JOIN users AS u " + "ON g.group_id=gu.group_id RIGHT JOIN users AS u " "ON gu.user_id=u.user_id INNER JOIN user_roles AS ur " "ON u.user_id=ur.user_id INNER JOIN roles AS r " "ON ur.role_id=r.role_id " @@ -254,7 +305,8 @@ def resource_users(resource_id: UUID): results = ( { "user": asdict(row["user"]), - "user_group": asdict(row["user_group"]), + "user_group": ( + asdict(row["user_group"]) if row["user_group"] else False), "roles": tuple(asdict(role) for role in row["roles"]) } for row in ( user_row for user_id, user_row @@ -467,7 +519,7 @@ def resources_authorisation(): }) resp.status_code = 400 except Exception as _exc:#pylint: disable=[broad-except] - app.logger.debug("Generic exception.", exc_info=True) + logger.debug("Generic exception.", exc_info=True) resp = jsonify({ "status": "general-exception", "error_description": ( @@ -505,7 +557,6 @@ def get_user_roles_on_resource(name) -> Response: response = make_response({ # Flatten this list "roles": roles, - "silly": "ausah", }) iat = int(time.time()) jose_header = { @@ -673,3 +724,45 @@ def user_resource_roles(resource_id: UUID, user_id: UUID): return jsonify([asdict(role) for role in _user_resource_roles(conn, _token.user, _resource)]) + + +@resources.route("/delete", methods=["POST"]) +@require_oauth("profile group resource") +def delete_resource(): + """Delete the specified resource, if possible.""" + with (require_oauth.acquire("profile group resource") as the_token, + db.connection(app.config["AUTH_DB"]) as conn): + form = request_json() + try: + resource_id = UUID(form.get("resource_id")) + if not can_delete(conn, the_token.user.user_id, resource_id): + raise AuthorisationError( + "You are not allowed to delete this resource.") + + data = resource_data( + conn, + resource_by_id(conn, the_token.user, resource_id), + 0, + 10) + if bool(data): + return jsonify({ + "error": "NonEmptyResouce", + "error-description": "Cannot delete a resource with linked data" + }), 400 + + _delete_resource(conn, resource_id) + return jsonify({ + "description": f"Successfully deleted resource with ID '{resource_id}'." + }) + except ValueError as _verr: + logger.debug("Error!", exc_info=True) + return jsonify({ + "error": "ValueError", + "error-description": "An invalid identifier was provided" + }), 400 + except TypeError as _terr: + logger.debug("Error!", exc_info=True) + return jsonify({ + "error": "TypeError", + "error-description": "An invalid identifier was provided" + }), 400 |
