Skip to content

✨ Add Controller for /me/function-permissions Endpoint in Functions Domain #7901

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 8 additions & 62 deletions api/specs/web-server/_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,23 @@
# pylint: disable=too-many-arguments


from enum import Enum
from typing import Annotated

from _common import as_query
from fastapi import APIRouter, Depends, status
from models_library.api_schemas_webserver.users import (
MyFunctionPermissionsGet,
MyPermissionGet,
MyProfileGet,
MyProfilePatch,
MyTokenCreate,
MyTokenGet,
UserAccountApprove,
UserAccountGet,
UserAccountReject,
UserAccountSearchQueryParams,
UserGet,
UsersAccountListQueryParams,
UsersSearch,
)
from models_library.api_schemas_webserver.users_preferences import PatchRequestBody
from models_library.generics import Envelope
from models_library.rest_pagination import Page
from models_library.user_preferences import PreferenceIdentifier
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.users._common.schemas import PreRegisteredUserGet
from simcore_service_webserver.users._notifications import (
UserNotification,
UserNotificationCreate,
Expand Down Expand Up @@ -128,6 +120,13 @@ async def mark_notification_as_read(
async def list_user_permissions(): ...


@router.get(
"/me/function-permissions",
response_model=Envelope[MyFunctionPermissionsGet],
)
async def list_user_functions_permissions(): ...


#
# USERS public
#
Expand All @@ -139,56 +138,3 @@ async def list_user_permissions(): ...
description="Search among users who are publicly visible to the caller (i.e., me) based on their privacy settings.",
)
async def search_users(_body: UsersSearch): ...


#
# USERS admin
#

_extra_tags: list[str | Enum] = ["admin"]


@router.get(
"/admin/user-accounts",
response_model=Page[UserAccountGet],
tags=_extra_tags,
)
async def list_users_accounts(
_query: Annotated[as_query(UsersAccountListQueryParams), Depends()],
): ...


@router.post(
"/admin/user-accounts:approve",
status_code=status.HTTP_204_NO_CONTENT,
tags=_extra_tags,
)
async def approve_user_account(_body: UserAccountApprove): ...


@router.post(
"/admin/user-accounts:reject",
status_code=status.HTTP_204_NO_CONTENT,
tags=_extra_tags,
)
async def reject_user_account(_body: UserAccountReject): ...


@router.get(
"/admin/user-accounts:search",
response_model=Envelope[list[UserAccountGet]],
tags=_extra_tags,
)
async def search_user_accounts(
_query: Annotated[UserAccountSearchQueryParams, Depends()],
):
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
...


@router.post(
"/admin/user-accounts:pre-register",
response_model=Envelope[UserAccountGet],
tags=_extra_tags,
)
async def pre_register_user_account(_body: PreRegisteredUserGet): ...
72 changes: 72 additions & 0 deletions api/specs/web-server/_users_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
# pylint: disable=unused-variable
# pylint: disable=too-many-arguments


from enum import Enum
from typing import Annotated

from _common import as_query
from fastapi import APIRouter, Depends, status
from models_library.api_schemas_webserver.users import (
UserAccountApprove,
UserAccountGet,
UserAccountReject,
UserAccountSearchQueryParams,
UsersAccountListQueryParams,
)
from models_library.generics import Envelope
from models_library.rest_pagination import Page
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.users._common.schemas import PreRegisteredUserGet

router = APIRouter(prefix=f"/{API_VTAG}", tags=["users"])

_extra_tags: list[str | Enum] = ["admin"]


@router.get(
"/admin/user-accounts",
response_model=Page[UserAccountGet],
tags=_extra_tags,
)
async def list_users_accounts(
_query: Annotated[as_query(UsersAccountListQueryParams), Depends()],
): ...


@router.post(
"/admin/user-accounts:approve",
status_code=status.HTTP_204_NO_CONTENT,
tags=_extra_tags,
)
async def approve_user_account(_body: UserAccountApprove): ...


@router.post(
"/admin/user-accounts:reject",
status_code=status.HTTP_204_NO_CONTENT,
tags=_extra_tags,
)
async def reject_user_account(_body: UserAccountReject): ...


@router.get(
"/admin/user-accounts:search",
response_model=Envelope[list[UserAccountGet]],
tags=_extra_tags,
)
async def search_user_accounts(
_query: Annotated[UserAccountSearchQueryParams, Depends()],
):
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
...


@router.post(
"/admin/user-accounts:pre-register",
response_model=Envelope[UserAccountGet],
tags=_extra_tags,
)
async def pre_register_user_account(_body: PreRegisteredUserGet): ...
1 change: 1 addition & 0 deletions api/specs/web-server/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"_tags_groups", # after _tags
"_products",
"_users",
"_users_admin", # after _users
"_wallets",
# add-ons ---
"_activity",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,7 @@ class MyPermissionGet(OutputSchema):
@classmethod
def from_domain_model(cls, permission: UserPermission) -> Self:
return cls(name=permission.name, allowed=permission.allowed)


class MyFunctionPermissionsGet(OutputSchema):
write_functions: bool
2 changes: 1 addition & 1 deletion services/web/server/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.68.1
0.69.0
2 changes: 1 addition & 1 deletion services/web/server/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.68.1
current_version = 0.69.0
commit = True
message = services/webserver api version: {current_version} → {new_version}
tag = False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ openapi: 3.1.0
info:
title: simcore-service-webserver
description: Main service with an interface (http-API & websockets) to the web front-end
version: 0.68.1
version: 0.69.0
servers:
- url: ''
description: webserver
Expand Down Expand Up @@ -1352,6 +1352,19 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Envelope_list_MyPermissionGet__'
/v0/me/function-permissions:
get:
tags:
- users
summary: List User Functions Permissions
operationId: list_user_functions_permissions
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/Envelope_MyFunctionPermissionsGet_'
/v0/users:search:
post:
tags:
Expand Down Expand Up @@ -10213,6 +10226,19 @@ components:
title: Error
type: object
title: Envelope[LoginNextPage]
Envelope_MyFunctionPermissionsGet_:
properties:
data:
anyOf:
- $ref: '#/components/schemas/MyFunctionPermissionsGet'
- type: 'null'
error:
anyOf:
- {}
- type: 'null'
title: Error
type: object
title: Envelope[MyFunctionPermissionsGet]
Envelope_MyGroupsGet_:
properties:
data:
Expand Down Expand Up @@ -12642,6 +12668,15 @@ components:
required:
- color
title: MarkerUI
MyFunctionPermissionsGet:
properties:
writeFunctions:
type: boolean
title: Writefunctions
type: object
required:
- writeFunctions
title: MyFunctionPermissionsGet
MyGroupsGet:
properties:
me:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
RegisteredFunction,
RegisteredFunctionGet,
)
from models_library.api_schemas_webserver.users import MyFunctionPermissionsGet
from pydantic import TypeAdapter
from servicelib.aiohttp import status
from servicelib.aiohttp.requests_validation import (
Expand Down Expand Up @@ -100,3 +101,29 @@ async def delete_function(request: web.Request) -> web.Response:
)

return web.json_response(status=status.HTTP_204_NO_CONTENT)


#
# /me/* endpoints
#


@routes.get(f"/{VTAG}/me/function-permissions", name="list_user_functions_permissions")
@login_required
@handle_rest_requests_exceptions
async def list_user_functions_permissions(request: web.Request) -> web.Response:
req_ctx = AuthenticatedRequestContext.model_validate(request)

function_permissions = (
await _functions_service.get_functions_user_api_access_rights(
app=request.app,
user_id=req_ctx.user_id,
product_name=req_ctx.product_name,
)
)

assert function_permissions.user_id == req_ctx.user_id # nosec

return envelope_json_response(
MyFunctionPermissionsGet(write_functions=function_permissions.write_functions)
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@


from collections.abc import AsyncIterator, Awaitable, Callable
from contextlib import AsyncExitStack
from typing import Any
from uuid import uuid4

import pytest
Expand All @@ -16,6 +18,7 @@
)
from models_library.products import ProductName
from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict
from pytest_simcore.helpers.postgres_tools import insert_and_get_row_lifespan
from pytest_simcore.helpers.typing_env import EnvVarsDict
from pytest_simcore.helpers.webserver_login import LoggedUser, UserInfoDict
from servicelib.rabbitmq import RabbitMQRPCClient
Expand Down Expand Up @@ -189,3 +192,35 @@ async def add_user_function_api_access_rights(
funcapi_api_access_rights_table.c.group_id == group_id
)
)


@pytest.fixture
async def logged_user_function_api_access_rights(
asyncpg_engine: AsyncEngine,
logged_user: UserInfoDict,
*,
expected_write_functions: bool,
) -> AsyncIterator[dict[str, Any]]:
cm = insert_and_get_row_lifespan(
asyncpg_engine,
table=funcapi_api_access_rights_table,
values={
"group_id": logged_user["primary_gid"],
"product_name": FRONTEND_APP_DEFAULT,
"read_functions": True,
"write_functions": expected_write_functions,
"execute_functions": True,
"read_function_jobs": True,
"write_function_jobs": True,
"execute_function_jobs": True,
"read_function_job_collections": True,
"write_function_job_collections": True,
"execute_function_job_collections": True,
},
pk_col=funcapi_api_access_rights_table.c.group_id,
pk_value=logged_user["primary_gid"],
)

async with AsyncExitStack() as stack:
row = await stack.enter_async_context(cm)
yield row
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
JSONFunctionOutputSchema,
RegisteredProjectFunctionGet,
)
from models_library.api_schemas_webserver.users import MyFunctionPermissionsGet
from pytest_simcore.helpers.assert_checks import assert_status
from pytest_simcore.helpers.webserver_login import UserInfoDict
from servicelib.aiohttp import status
Expand Down Expand Up @@ -111,3 +112,24 @@ async def test_register_get_delete_function(
)
response = await client.get(url)
data, error = await assert_status(response, expected_get2)


@pytest.mark.parametrize("expected_write_functions", [True, False])
async def test_list_user_functions_permissions(
client: TestClient,
logged_user: UserInfoDict,
expected_write_functions: bool,
logged_user_function_api_access_rights: dict[str, Any],
):
assert (
logged_user_function_api_access_rights["write_functions"]
== expected_write_functions
)

url = client.app.router["list_user_functions_permissions"].url_for()
response = await client.get(url)
data, error = await assert_status(response, expected_status_code=status.HTTP_200_OK)

assert not error
function_permissions = MyFunctionPermissionsGet.model_validate(data)
assert function_permissions.write_functions == expected_write_functions
Loading