aboutsummaryrefslogtreecommitdiff
path: root/gn_auth/auth/authorisation/resources/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'gn_auth/auth/authorisation/resources/views.py')
-rw-r--r--gn_auth/auth/authorisation/resources/views.py185
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)])