async def update_cluster_handler(request: web.Request) -> web.Response: path, _, _ = await extract_and_validate(request) user_id: UserID = request[RQT_USERID_KEY] primary_group, std_groups, all_group = await list_user_groups( request.app, user_id) body = await request.json() try: updated_cluster = ClusterPatch.parse_obj(body) clusters_repo = ClustersRepository(request) cluster = await clusters_repo.update_cluster( GroupID(primary_group["gid"]), [GroupID(g["gid"]) for g in std_groups], GroupID(all_group["gid"]), path["cluster_id"], updated_cluster, ) data = cluster.dict(by_alias=True) return web.json_response(data={"data": data}, dumps=json_dumps) except ValidationError as exc: raise web.HTTPUnprocessableEntity( reason=f"Invalid cluster definition: {exc} ") from exc except ClusterNotFoundError as exc: raise web.HTTPNotFound(reason=f"{exc}") except ClusterAccessForbidden as exc: raise web.HTTPForbidden(reason=f"{exc}")
async def test_delete_cluster( enable_dev_features: None, client: TestClient, postgres_db: sa.engine.Engine, logged_user: Dict[str, Any], second_user: Dict[str, Any], cluster: Callable[..., Coroutine[Any, Any, Cluster]], faker: Faker, user_role: UserRole, expected: ExpectedResponse, ): # deleting a non-existing cluster returns not found url = client.app.router["delete_cluster_handler"].url_for(cluster_id=f"{25}") rsp = await client.delete(f"{url}") data, error = await assert_status(rsp, expected.not_found) if error and user_role in [UserRole.ANONYMOUS, UserRole.GUEST]: return assert data is None # create our own cluster allows us to delete it admin_cluster: Cluster = await cluster(GroupID(logged_user["primary_gid"])) url = client.app.router["delete_cluster_handler"].url_for( cluster_id=f"{admin_cluster.id}" ) rsp = await client.delete(f"{url}") data, error = await assert_status(rsp, expected.no_content) assert data is None # check it was deleted result: ResultProxy = postgres_db.execute( sa.select([clusters]).where(clusters.c.id == admin_cluster.id) ) assert result.rowcount == 0
async def list_clusters_handler(request: web.Request) -> web.Response: await extract_and_validate(request) user_id: UserID = request[RQT_USERID_KEY] primary_group, std_groups, all_group = await list_user_groups( request.app, user_id) clusters_repo = ClustersRepository(request) clusters_list: List[Cluster] = await clusters_repo.list_clusters( GroupID(primary_group["gid"]), [GroupID(g["gid"]) for g in std_groups], GroupID(all_group["gid"]), ) data = [d.dict(by_alias=True) for d in clusters_list] return web.json_response(data={"data": data}, dumps=json_dumps)
async def delete_cluster_handler(request: web.Request) -> web.Response: path, _, _ = await extract_and_validate(request) user_id: UserID = request[RQT_USERID_KEY] primary_group, std_groups, all_group = await list_user_groups( request.app, user_id) clusters_repo = ClustersRepository(request) try: await clusters_repo.delete_cluster( GroupID(primary_group["gid"]), [GroupID(g["gid"]) for g in std_groups], GroupID(all_group["gid"]), path["cluster_id"], ) return web.json_response(status=web.HTTPNoContent.status_code) except ClusterNotFoundError as exc: raise web.HTTPNotFound(reason=f"{exc}") except ClusterAccessForbidden as exc: raise web.HTTPForbidden(reason=f"{exc}")
async def get_cluster_handler(request: web.Request) -> web.Response: path, _, _ = await extract_and_validate(request) user_id: UserID = request[RQT_USERID_KEY] primary_group, std_groups, all_group = await list_user_groups( request.app, user_id) clusters_repo = ClustersRepository(request) try: cluster = await clusters_repo.get_cluster( GroupID(primary_group["gid"]), [GroupID(g["gid"]) for g in std_groups], GroupID(all_group["gid"]), path["cluster_id"], ) data = cluster.dict(by_alias=True) return web.json_response(data={"data": data}, dumps=json_dumps) except ClusterNotFoundError as exc: raise web.HTTPNotFound(reason=f"{exc}") except ClusterAccessForbidden as exc: raise web.HTTPForbidden(reason=f"{exc}")
async def test_update_cluster( enable_dev_features: None, client: TestClient, logged_user: Dict[str, Any], second_user: Dict[str, Any], all_group: Dict[str, Any], cluster: Callable[..., Coroutine[Any, Any, Cluster]], user_role: UserRole, expected: ExpectedResponse, cluster_authentication: Callable[[], Dict[str, Any]], ): _PATCH_EXPORT = {"by_alias": True, "exclude_unset": True, "exclude_none": True} # check modifying invalid returns not found assert client.app url = client.app.router["update_cluster_handler"].url_for(cluster_id=f"{25}") rsp = await client.patch( f"{url}", json=ClusterPatch().dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.not_found) if error and user_role in [UserRole.ANONYMOUS, UserRole.GUEST]: return # create our own cluster, allows us to modify it admin_cluster: Cluster = await cluster(GroupID(logged_user["primary_gid"])) url = client.app.router["update_cluster_handler"].url_for( cluster_id=f"{admin_cluster.id}" ) # we can modify these simple things expected_admin_cluster = admin_cluster.copy() for cluster_patch in [ ClusterPatch(name="My patched cluster name"), ClusterPatch(description="My patched cluster description"), ClusterPatch(type=ClusterType.ON_PREMISE), ClusterPatch(thumbnail="https://placeimg.com/640/480/nature"), ClusterPatch(endpoint="https://some_other_endpoint.com"), ClusterPatch(authentication=cluster_authentication()), ]: jsonable_cluster_patch = json.loads(cluster_patch.json(**_PATCH_EXPORT)) print(f"--> patching cluster with {jsonable_cluster_patch}") rsp = await client.patch(f"{url}", json=jsonable_cluster_patch) data, error = await assert_status(rsp, expected.ok) expected_admin_cluster = expected_admin_cluster.copy( update=cluster_patch.dict(**_PATCH_EXPORT) ) assert Cluster.parse_obj(data) == expected_admin_cluster # we can change the access rights, the owner rights are always kept for rights in [ CLUSTER_ADMIN_RIGHTS, CLUSTER_MANAGER_RIGHTS, CLUSTER_USER_RIGHTS, CLUSTER_NO_RIGHTS, ]: cluster_patch = ClusterPatch(accessRights={second_user["primary_gid"]: rights}) rsp = await client.patch( f"{url}", json=cluster_patch.dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.ok) expected_admin_cluster.access_rights[second_user["primary_gid"]] = rights assert Cluster.parse_obj(data) == expected_admin_cluster # we can change the owner since we are admin cluster_patch = ClusterPatch(owner=second_user["primary_gid"]) rsp = await client.patch( f"{url}", json=cluster_patch.dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.ok) expected_admin_cluster.owner = second_user["primary_gid"] expected_admin_cluster.access_rights[ second_user["primary_gid"] ] = CLUSTER_ADMIN_RIGHTS assert Cluster.parse_obj(data) == expected_admin_cluster # we should not be able to reduce the rights of the new owner cluster_patch = ClusterPatch( accessRights={second_user["primary_gid"]: CLUSTER_NO_RIGHTS} ) rsp = await client.patch( f"{url}", json=cluster_patch.dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.forbidden) # we have a second user that creates a few clusters, some are shared with the first user a_cluster_that_may_be_administrated: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_ADMIN_RIGHTS}, ) a_cluster_that_may_be_managed: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_MANAGER_RIGHTS}, ) a_cluster_that_may_be_used: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_USER_RIGHTS}, ) a_cluster_that_is_not_shared: Cluster = await cluster( GroupID(second_user["primary_gid"]), ) a_cluster_that_may_not_be_used: Cluster = await cluster( GroupID(second_user["primary_gid"]), { GroupID(all_group["gid"]): CLUSTER_ADMIN_RIGHTS, GroupID(logged_user["primary_gid"]): CLUSTER_NO_RIGHTS, }, ) # we can manage so we shall be ok here changing the name for cl in [a_cluster_that_may_be_administrated, a_cluster_that_may_be_managed]: url = client.app.router["update_cluster_handler"].url_for(cluster_id=f"{cl.id}") rsp = await client.patch( f"{url}", json=ClusterPatch(name="I prefer this new name here").dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.ok) # we can NOT change the owner of this one url = client.app.router["update_cluster_handler"].url_for( cluster_id=f"{a_cluster_that_may_be_managed.id}" ) rsp = await client.patch( f"{url}", json=ClusterPatch(owner=logged_user["primary_gid"]).dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.forbidden) # we can NOT change ourself to become an admin url = client.app.router["update_cluster_handler"].url_for( cluster_id=f"{a_cluster_that_may_be_managed.id}" ) rsp = await client.patch( f"{url}", json=ClusterPatch( accessRights={logged_user["primary_gid"]: CLUSTER_ADMIN_RIGHTS} ).dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.forbidden) # but I can add a user url = client.app.router["update_cluster_handler"].url_for( cluster_id=f"{a_cluster_that_may_be_managed.id}" ) rsp = await client.patch( f"{url}", json=ClusterPatch( accessRights={ **a_cluster_that_may_be_managed.access_rights, **{ logged_user["primary_gid"]: CLUSTER_MANAGER_RIGHTS, all_group["gid"]: CLUSTER_USER_RIGHTS, }, }, ).dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.ok) # and I shall be able to deny a user url = client.app.router["update_cluster_handler"].url_for( cluster_id=f"{a_cluster_that_may_be_managed.id}" ) rsp = await client.patch( f"{url}", json=ClusterPatch( accessRights={ logged_user["primary_gid"]: CLUSTER_MANAGER_RIGHTS, all_group["gid"]: CLUSTER_NO_RIGHTS, } ).dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.ok) # and I shall be able to remove a user (provided that is not the owner) url = client.app.router["update_cluster_handler"].url_for( cluster_id=f"{a_cluster_that_may_be_managed.id}" ) rsp = await client.patch( f"{url}", json=ClusterPatch( accessRights={ logged_user["primary_gid"]: CLUSTER_MANAGER_RIGHTS, } ).dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.ok) # but I canNOT add a manager or an admin for rights in [CLUSTER_ADMIN_RIGHTS, CLUSTER_MANAGER_RIGHTS]: url = client.app.router["update_cluster_handler"].url_for( cluster_id=f"{a_cluster_that_may_be_managed.id}" ) rsp = await client.patch( f"{url}", json=ClusterPatch(accessRights={all_group["gid"]: rights}).dict( **_PATCH_EXPORT ), ) data, error = await assert_status(rsp, expected.forbidden) # we can NOT manage so we shall be forbidden changing the name for cl in [ a_cluster_that_may_be_used, a_cluster_that_may_not_be_used, a_cluster_that_is_not_shared, ]: url = client.app.router["update_cluster_handler"].url_for(cluster_id=f"{cl.id}") rsp = await client.patch( f"{url}", json=ClusterPatch( name="I prefer this new name here, but I am not allowed" ).dict(**_PATCH_EXPORT), ) data, error = await assert_status(rsp, expected.forbidden)
async def test_get_cluster( enable_dev_features: None, client: TestClient, logged_user: Dict[str, Any], second_user: Dict[str, Any], all_group: Dict[str, Any], cluster: Callable[..., Coroutine[Any, Any, Cluster]], user_role: UserRole, expected: ExpectedResponse, ): # check not found assert client.app url = client.app.router["get_cluster_handler"].url_for(cluster_id=f"{25}") rsp = await client.get(f"{url}") data, error = await assert_status(rsp, expected.not_found) if error and user_role in [UserRole.ANONYMOUS, UserRole.GUEST]: return assert data is None # create our own cluster, and we can get it admin_cluster: Cluster = await cluster(GroupID(logged_user["primary_gid"])) url = client.app.router["get_cluster_handler"].url_for( cluster_id=f"{admin_cluster.id}" ) rsp = await client.get(f"{url}") data, error = await assert_status(rsp, expected.ok) assert Cluster.parse_obj(data) == admin_cluster # we have a second user that creates a few clusters, some are shared with the first user a_cluster_that_may_be_administrated: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_MANAGER_RIGHTS}, ) a_cluster_that_may_be_managed: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_MANAGER_RIGHTS}, ) a_cluster_that_may_be_used: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_USER_RIGHTS}, ) a_cluster_that_is_not_shared: Cluster = await cluster( GroupID(second_user["primary_gid"]), ) a_cluster_that_may_not_be_used: Cluster = await cluster( GroupID(second_user["primary_gid"]), { GroupID(all_group["gid"]): CLUSTER_USER_RIGHTS, GroupID(logged_user["primary_gid"]): CLUSTER_NO_RIGHTS, }, ) # we should have access to that one for cl in [ a_cluster_that_may_be_administrated, a_cluster_that_may_be_managed, a_cluster_that_may_be_used, ]: url = client.app.router["get_cluster_handler"].url_for(cluster_id=f"{cl.id}") rsp = await client.get(f"{url}") data, error = await assert_status(rsp, expected.ok) assert Cluster.parse_obj(data) == cl # we should not have access to these for cl in [a_cluster_that_is_not_shared, a_cluster_that_may_not_be_used]: url = client.app.router["get_cluster_handler"].url_for(cluster_id=f"{cl.id}") rsp = await client.get(f"{url}") data, error = await assert_status(rsp, expected.forbidden)
async def test_list_clusters( enable_dev_features: None, client: TestClient, logged_user: Dict[str, Any], second_user: Dict[str, Any], all_group: Dict[str, Any], cluster: Callable[..., Coroutine[Any, Any, Cluster]], expected: ExpectedResponse, ): # check empty clusters assert client.app url = client.app.router["list_clusters_handler"].url_for() rsp = await client.get(f"{url}") data, error = await assert_status(rsp, expected.ok) if error: # we are done here, anonymous and guests cannot list return assert data == [] # create our own cluster, and check it is listed admin_cluster: Cluster = await cluster(GroupID(logged_user["primary_gid"])) rsp = await client.get(f"{url}") data, error = await assert_status(rsp, expected.ok) assert len(data) == 1 assert Cluster.parse_obj(data[0]) == admin_cluster # we have a second user that creates a few clusters, some are shared with the first user a_cluster_that_may_be_administred: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_MANAGER_RIGHTS}, ) a_cluster_that_may_be_managed: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_MANAGER_RIGHTS}, ) a_cluster_that_may_be_used: Cluster = await cluster( GroupID(second_user["primary_gid"]), {GroupID(logged_user["primary_gid"]): CLUSTER_USER_RIGHTS}, ) a_cluster_that_is_not_shared: Cluster = await cluster( GroupID(second_user["primary_gid"]), ) a_cluster_that_may_not_be_used: Cluster = await cluster( GroupID(second_user["primary_gid"]), { GroupID(all_group["gid"]): CLUSTER_USER_RIGHTS, GroupID(logged_user["primary_gid"]): CLUSTER_NO_RIGHTS, }, ) # now listing should retrieve both clusters rsp = await client.get(f"{url}") data, error = await assert_status(rsp, expected.ok) assert len(data) == (1 + 3) for d in data: assert Cluster.parse_obj(d) in [ admin_cluster, a_cluster_that_may_be_administred, a_cluster_that_may_be_managed, a_cluster_that_may_be_used, ]