about summary refs log tree commit diff
path: root/gn_auth/auth/authorisation/resources/groups/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'gn_auth/auth/authorisation/resources/groups/views.py')
-rw-r--r--gn_auth/auth/authorisation/resources/groups/views.py269
1 files changed, 174 insertions, 95 deletions
diff --git a/gn_auth/auth/authorisation/resources/groups/views.py b/gn_auth/auth/authorisation/resources/groups/views.py
index ef6bb0d..2aa115a 100644
--- a/gn_auth/auth/authorisation/resources/groups/views.py
+++ b/gn_auth/auth/authorisation/resources/groups/views.py
@@ -3,35 +3,47 @@ The views/routes for the `gn3.auth.authorisation.resources.groups` package.
 """
 import uuid
 import datetime
-import warnings
-from typing import Iterable
 from functools import partial
 from dataclasses import asdict
 
+import sqlite3
 from MySQLdb.cursors import DictCursor
-from flask import request, jsonify, Response, Blueprint, current_app
+from flask import jsonify, Response, Blueprint, current_app
 
+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.roles.models import Role
-from gn_auth.auth.authorisation.roles.models import user_roles
-
-from gn_auth.auth.authorisation.checks import authorised_p
-from gn_auth.auth.authorisation.privileges import Privilege, privileges_by_ids
+from gn_auth.auth.authorisation.privileges import privileges_by_ids
 from gn_auth.auth.errors import InvalidData, NotFoundError, AuthorisationError
 
-from gn_auth.auth.authentication.users import User
+from gn_auth.auth.authentication.users import User, user_by_id
 from gn_auth.auth.authentication.oauth2.resource_server import require_oauth
 
+from gn_auth.auth.authorisation.resources.checks import authorised_for_spec
+from gn_auth.auth.authorisation.resources.groups.models import (resource_from_group,
+                                                                remove_user_from_group)
+
 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, create_group_role as _create_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_group as _delete_group,
+                     delete_privilege_from_group_role)
 
 groups = Blueprint("groups", __name__)
 
@@ -39,26 +51,48 @@ 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")
 def create_group():
     """Create a new group."""
     with require_oauth.acquire("profile group") as the_token:
-        group_name=request.form.get("group_name", "").strip()
+        group_name=request_json().get("group_name", "").strip()
         if not bool(group_name):
-            raise GroupCreationError("Could not create the group.")
+            raise GroupCreationError(
+                "Could not create the group. Invalid Group name provided was "
+                f"`{group_name}`")
 
         db_uri = current_app.config["AUTH_DB"]
         with db.connection(db_uri) as conn:
             user = the_token.user
             new_group = _create_group(
-                conn, group_name, user, request.form.get("group_description"))
+                conn, group_name, user, request_json().get("group_description"))
             return jsonify({
                 **asdict(new_group), "group_leader": asdict(user)
             })
@@ -107,7 +141,7 @@ def request_to_join(group_id: uuid.UUID) -> Response:
             }
 
     with require_oauth.acquire("profile group") as the_token:
-        form = request.form
+        form = request_json()
         results = with_db_connection(partial(
             __request__, user=the_token.user, group_id=group_id, message=form.get(
                 "message", "I hereby request that you add me to your group.")))
@@ -126,7 +160,7 @@ def list_join_requests() -> Response:
 def accept_join_requests() -> Response:
     """Accept a join request."""
     with require_oauth.acquire("profile group") as the_token:
-        form = request.form
+        form = request_json()
         request_id = uuid.UUID(form.get("request_id"))
         return jsonify(with_db_connection(partial(
             accept_reject_join_request, request_id=request_id,
@@ -137,7 +171,7 @@ def accept_join_requests() -> Response:
 def reject_join_requests() -> Response:
     """Reject a join request."""
     with require_oauth.acquire("profile group") as the_token:
-        form = request.form
+        form = request_json()
         request_id = uuid.UUID(form.get("request_id"))
         return jsonify(with_db_connection(partial(
             accept_reject_join_request, request_id=request_id,
@@ -171,7 +205,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
@@ -237,7 +271,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:
@@ -255,7 +289,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](
@@ -268,9 +303,9 @@ def unlinked_data(resource_type: str) -> Response:
 def link_data() -> Response:
     """Link selected data to specified group."""
     with require_oauth.acquire("profile group resource") as _the_token:
-        form = request.form
+        form = request_json()
         group_id = uuid.UUID(form["group_id"])
-        dataset_ids = form.getlist("dataset_ids")
+        dataset_ids = form.get("dataset_ids")
         dataset_type = form.get("dataset_type")
         if dataset_type not in ("mrna", "genotype", "phenotype"):
             raise InvalidData("Unexpected dataset type requested!")
@@ -282,70 +317,6 @@ def link_data() -> Response:
 
         return jsonify(with_db_connection(__link__))
 
-@groups.route("/privileges", methods=["GET"])
-@require_oauth("profile group")
-def group_privileges():
-    """Return a list of all available group roles."""
-    with require_oauth.acquire("profile group role") as the_token:
-        def __list_privileges__(conn: db.DbConnection) -> Iterable[Privilege]:
-            ## TODO: Check that user has appropriate privileges
-            this_user_roles = user_roles(conn, the_token.user)
-            with db.cursor(conn) as cursor:
-                cursor.execute("SELECT * FROM privileges "
-                               "WHERE privilege_id LIKE 'group:%'")
-                group_level_roles = tuple(
-                    Privilege(row["privilege_id"], row["privilege_description"])
-                    for row in cursor.fetchall())
-
-            ## the `user_roles(...)` function changed thus this entire function
-            ## needs to change or be obsoleted -- also remove the ignore below
-            return tuple(
-                privilege for arole in this_user_roles["roles"]
-                for privilege in arole.privileges) + group_level_roles #type: ignore[attr-defined]
-        warnings.warn(
-            (f"The `{__name__}.group_privileges` function is broken and will "
-             "be deleted."),
-            PendingDeprecationWarning)
-        return jsonify(tuple(
-            asdict(priv) for priv in with_db_connection(__list_privileges__)))
-
-
-
-@groups.route("/role/create", methods=["POST"])
-@require_oauth("profile group")
-def create_group_role():
-    """Create a new group role."""
-    with require_oauth.acquire("profile group role") as the_token:
-        ## TODO: Check that user has appropriate privileges
-        @authorised_p(("group:role:create-role",),
-                      "You do not have the privilege to create new roles",
-                      oauth2_scope="profile group role")
-        def __create__(conn: db.DbConnection) -> GroupRole:
-            ## TODO: Check user cannot assign any privilege they don't have.
-            form = request.form
-            role_name = form.get("role_name", "").strip()
-            privileges_ids = form.getlist("privileges[]")
-            if len(role_name) == 0:
-                raise InvalidData("Role name not provided!")
-            if len(privileges_ids) == 0:
-                raise InvalidData(
-                    "At least one privilege needs to be provided.")
-
-            group = user_group(conn, the_token.user).maybe(# type: ignore[misc]
-                    DUMMY_GROUP, lambda grp: grp)
-
-            if group == DUMMY_GROUP:
-                raise AuthorisationError(
-                    "A user without a group cannot create a new role.")
-            privileges = privileges_by_ids(conn, tuple(privileges_ids))
-            if len(privileges_ids) != len(privileges):
-                raise InvalidData(
-                    f"{len(privileges_ids) - len(privileges)} of the selected "
-                    "privileges were not found in the database.")
-
-            return _create_group_role(conn, group, role_name, privileges)
-
-        return jsonify(with_db_connection(__create__))
 
 @groups.route("/role/<uuid:group_role_id>", methods=["GET"])
 @require_oauth("profile group")
@@ -374,7 +345,7 @@ def __add_remove_priv_to_from_role__(conn: db.DbConnection,
         raise AuthorisationError(
             "You need to be a member of a group to edit roles.")
     try:
-        privilege_id = request.form.get("privilege_id", "")
+        privilege_id = request_json().get("privilege_id", "")
         assert bool(privilege_id), "Privilege to add must be provided."
         privileges = privileges_by_ids(conn, (privilege_id,))
         if len(privileges) == 0:
@@ -413,3 +384,111 @@ 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)))
+
+
+@groups.route("/<uuid:group_id>/remove-member", methods=["POST"])
+@require_oauth("profile group")
+def remove_group_member(group_id: uuid.UUID):
+    """Remove a user as member of this group."""
+    with (require_oauth.acquire("profile group") as _token,
+          db.connection(current_app.config["AUTH_DB"]) as conn):
+        group = group_by_id(conn, group_id)
+        grp_resource = resource_from_group(conn, group)
+        if not authorised_for_spec(
+                conn,
+                _token.user.user_id,
+                grp_resource.resource_id,
+                "(OR group:user:remove-group-member system:group:remove-group-member)"):
+            raise AuthorisationError(
+                "You do not have appropriate privileges to remove a user from this "
+                "group.")
+
+        form = request_json()
+        if not bool(form.get("user_id")):
+            response = jsonify({
+                "error": "MissingUserId",
+                "error-description": (
+                    "Expected 'user_id' value/parameter was not provided.")
+            })
+            response.status_code = 400
+            return response
+
+        try:
+            user = user_by_id(conn, uuid.UUID(form["user_id"]))
+            remove_user_from_group(conn, group, user, grp_resource)
+            success_msg = (
+                f"User '{user.name} ({user.email})' is no longer a member of "
+                f"group '{group.group_name}'.\n"
+                "They could, however, still have access to resources owned by "
+                "the group.")
+            return jsonify({
+                "description": success_msg,
+                "message": success_msg
+            })
+        except ValueError as _verr:
+            response = jsonify({
+                "error": "InvalidUserId",
+                "error-description": "The 'user_id' provided was invalid"
+            })
+            response.status_code = 400
+            return response
+
+
+@groups.route("/<uuid:group_id>/delete", methods=["DELETE"])
+@require_oauth("profile group")
+def delete_group(group_id: uuid.UUID) -> Response:
+    """Delete group with the specified `group_id`."""
+    with (require_oauth.acquire("profile group") as _token,
+          db.connection(current_app.config["AUTH_DB"]) as conn):
+        group = group_by_id(conn, group_id)
+        grp_resource = resource_from_group(conn, group)
+        if not authorised_for_spec(
+                conn,
+                _token.user.user_id,
+                grp_resource.resource_id,
+                "(AND system:group:delete-group)"):
+            raise AuthorisationError(
+                "You do not have appropriate privileges to delete this group.")
+        try:
+            _delete_group(conn, group.group_id)
+            return Response(status=204)
+        except sqlite3.IntegrityError as _s3ie:
+            response = jsonify({
+                "error": "IntegrityError",
+                "error-description": (
+                    "A group that has members, linked resources, or both, "
+                    "cannot be deleted from the system. Remove any members and "
+                    "unlink any linked resources, and try again.")
+            })
+            response.status_code = 400
+            return response