Skip to content

Commit 0275590

Browse files
authored
Merge branch 'master' into enh/request-services-access
2 parents e4154d5 + 975e587 commit 0275590

File tree

10 files changed

+208
-65
lines changed

10 files changed

+208
-65
lines changed

api/specs/web-server/_users.py

Lines changed: 8 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,23 @@
44
# pylint: disable=too-many-arguments
55

66

7-
from enum import Enum
87
from typing import Annotated
98

10-
from _common import as_query
119
from fastapi import APIRouter, Depends, status
1210
from models_library.api_schemas_webserver.users import (
11+
MyFunctionPermissionsGet,
1312
MyPermissionGet,
1413
MyProfileGet,
1514
MyProfilePatch,
1615
MyTokenCreate,
1716
MyTokenGet,
18-
UserAccountApprove,
19-
UserAccountGet,
20-
UserAccountReject,
21-
UserAccountSearchQueryParams,
2217
UserGet,
23-
UsersAccountListQueryParams,
2418
UsersSearch,
2519
)
2620
from models_library.api_schemas_webserver.users_preferences import PatchRequestBody
2721
from models_library.generics import Envelope
28-
from models_library.rest_pagination import Page
2922
from models_library.user_preferences import PreferenceIdentifier
3023
from simcore_service_webserver._meta import API_VTAG
31-
from simcore_service_webserver.users._common.schemas import PreRegisteredUserGet
3224
from simcore_service_webserver.users._notifications import (
3325
UserNotification,
3426
UserNotificationCreate,
@@ -128,6 +120,13 @@ async def mark_notification_as_read(
128120
async def list_user_permissions(): ...
129121

130122

123+
@router.get(
124+
"/me/function-permissions",
125+
response_model=Envelope[MyFunctionPermissionsGet],
126+
)
127+
async def list_user_functions_permissions(): ...
128+
129+
131130
#
132131
# USERS public
133132
#
@@ -139,56 +138,3 @@ async def list_user_permissions(): ...
139138
description="Search among users who are publicly visible to the caller (i.e., me) based on their privacy settings.",
140139
)
141140
async def search_users(_body: UsersSearch): ...
142-
143-
144-
#
145-
# USERS admin
146-
#
147-
148-
_extra_tags: list[str | Enum] = ["admin"]
149-
150-
151-
@router.get(
152-
"/admin/user-accounts",
153-
response_model=Page[UserAccountGet],
154-
tags=_extra_tags,
155-
)
156-
async def list_users_accounts(
157-
_query: Annotated[as_query(UsersAccountListQueryParams), Depends()],
158-
): ...
159-
160-
161-
@router.post(
162-
"/admin/user-accounts:approve",
163-
status_code=status.HTTP_204_NO_CONTENT,
164-
tags=_extra_tags,
165-
)
166-
async def approve_user_account(_body: UserAccountApprove): ...
167-
168-
169-
@router.post(
170-
"/admin/user-accounts:reject",
171-
status_code=status.HTTP_204_NO_CONTENT,
172-
tags=_extra_tags,
173-
)
174-
async def reject_user_account(_body: UserAccountReject): ...
175-
176-
177-
@router.get(
178-
"/admin/user-accounts:search",
179-
response_model=Envelope[list[UserAccountGet]],
180-
tags=_extra_tags,
181-
)
182-
async def search_user_accounts(
183-
_query: Annotated[UserAccountSearchQueryParams, Depends()],
184-
):
185-
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
186-
...
187-
188-
189-
@router.post(
190-
"/admin/user-accounts:pre-register",
191-
response_model=Envelope[UserAccountGet],
192-
tags=_extra_tags,
193-
)
194-
async def pre_register_user_account(_body: PreRegisteredUserGet): ...
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
# pylint: disable=too-many-arguments
5+
6+
7+
from enum import Enum
8+
from typing import Annotated
9+
10+
from _common import as_query
11+
from fastapi import APIRouter, Depends, status
12+
from models_library.api_schemas_webserver.users import (
13+
UserAccountApprove,
14+
UserAccountGet,
15+
UserAccountReject,
16+
UserAccountSearchQueryParams,
17+
UsersAccountListQueryParams,
18+
)
19+
from models_library.generics import Envelope
20+
from models_library.rest_pagination import Page
21+
from simcore_service_webserver._meta import API_VTAG
22+
from simcore_service_webserver.users._common.schemas import PreRegisteredUserGet
23+
24+
router = APIRouter(prefix=f"/{API_VTAG}", tags=["users"])
25+
26+
_extra_tags: list[str | Enum] = ["admin"]
27+
28+
29+
@router.get(
30+
"/admin/user-accounts",
31+
response_model=Page[UserAccountGet],
32+
tags=_extra_tags,
33+
)
34+
async def list_users_accounts(
35+
_query: Annotated[as_query(UsersAccountListQueryParams), Depends()],
36+
): ...
37+
38+
39+
@router.post(
40+
"/admin/user-accounts:approve",
41+
status_code=status.HTTP_204_NO_CONTENT,
42+
tags=_extra_tags,
43+
)
44+
async def approve_user_account(_body: UserAccountApprove): ...
45+
46+
47+
@router.post(
48+
"/admin/user-accounts:reject",
49+
status_code=status.HTTP_204_NO_CONTENT,
50+
tags=_extra_tags,
51+
)
52+
async def reject_user_account(_body: UserAccountReject): ...
53+
54+
55+
@router.get(
56+
"/admin/user-accounts:search",
57+
response_model=Envelope[list[UserAccountGet]],
58+
tags=_extra_tags,
59+
)
60+
async def search_user_accounts(
61+
_query: Annotated[UserAccountSearchQueryParams, Depends()],
62+
):
63+
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
64+
...
65+
66+
67+
@router.post(
68+
"/admin/user-accounts:pre-register",
69+
response_model=Envelope[UserAccountGet],
70+
tags=_extra_tags,
71+
)
72+
async def pre_register_user_account(_body: PreRegisteredUserGet): ...

api/specs/web-server/openapi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"_tags_groups", # after _tags
2727
"_products",
2828
"_users",
29+
"_users_admin", # after _users
2930
"_wallets",
3031
# add-ons ---
3132
"_activity",

packages/models-library/src/models_library/api_schemas_webserver/users.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,7 @@ class MyPermissionGet(OutputSchema):
377377
@classmethod
378378
def from_domain_model(cls, permission: UserPermission) -> Self:
379379
return cls(name=permission.name, allowed=permission.allowed)
380+
381+
382+
class MyFunctionPermissionsGet(OutputSchema):
383+
write_functions: bool

services/web/server/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.68.1
1+
0.69.0

services/web/server/setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.68.1
2+
current_version = 0.69.0
33
commit = True
44
message = services/webserver api version: {current_version} → {new_version}
55
tag = False

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ openapi: 3.1.0
22
info:
33
title: simcore-service-webserver
44
description: Main service with an interface (http-API & websockets) to the web front-end
5-
version: 0.68.1
5+
version: 0.69.0
66
servers:
77
- url: ''
88
description: webserver
@@ -1352,6 +1352,19 @@ paths:
13521352
application/json:
13531353
schema:
13541354
$ref: '#/components/schemas/Envelope_list_MyPermissionGet__'
1355+
/v0/me/function-permissions:
1356+
get:
1357+
tags:
1358+
- users
1359+
summary: List User Functions Permissions
1360+
operationId: list_user_functions_permissions
1361+
responses:
1362+
'200':
1363+
description: Successful Response
1364+
content:
1365+
application/json:
1366+
schema:
1367+
$ref: '#/components/schemas/Envelope_MyFunctionPermissionsGet_'
13551368
/v0/users:search:
13561369
post:
13571370
tags:
@@ -10213,6 +10226,19 @@ components:
1021310226
title: Error
1021410227
type: object
1021510228
title: Envelope[LoginNextPage]
10229+
Envelope_MyFunctionPermissionsGet_:
10230+
properties:
10231+
data:
10232+
anyOf:
10233+
- $ref: '#/components/schemas/MyFunctionPermissionsGet'
10234+
- type: 'null'
10235+
error:
10236+
anyOf:
10237+
- {}
10238+
- type: 'null'
10239+
title: Error
10240+
type: object
10241+
title: Envelope[MyFunctionPermissionsGet]
1021610242
Envelope_MyGroupsGet_:
1021710243
properties:
1021810244
data:
@@ -12642,6 +12668,15 @@ components:
1264212668
required:
1264312669
- color
1264412670
title: MarkerUI
12671+
MyFunctionPermissionsGet:
12672+
properties:
12673+
writeFunctions:
12674+
type: boolean
12675+
title: Writefunctions
12676+
type: object
12677+
required:
12678+
- writeFunctions
12679+
title: MyFunctionPermissionsGet
1264512680
MyGroupsGet:
1264612681
properties:
1264712682
me:

services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rest.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
RegisteredFunction,
66
RegisteredFunctionGet,
77
)
8+
from models_library.api_schemas_webserver.users import MyFunctionPermissionsGet
89
from pydantic import TypeAdapter
910
from servicelib.aiohttp import status
1011
from servicelib.aiohttp.requests_validation import (
@@ -100,3 +101,29 @@ async def delete_function(request: web.Request) -> web.Response:
100101
)
101102

102103
return web.json_response(status=status.HTTP_204_NO_CONTENT)
104+
105+
106+
#
107+
# /me/* endpoints
108+
#
109+
110+
111+
@routes.get(f"/{VTAG}/me/function-permissions", name="list_user_functions_permissions")
112+
@login_required
113+
@handle_rest_requests_exceptions
114+
async def list_user_functions_permissions(request: web.Request) -> web.Response:
115+
req_ctx = AuthenticatedRequestContext.model_validate(request)
116+
117+
function_permissions = (
118+
await _functions_service.get_functions_user_api_access_rights(
119+
app=request.app,
120+
user_id=req_ctx.user_id,
121+
product_name=req_ctx.product_name,
122+
)
123+
)
124+
125+
assert function_permissions.user_id == req_ctx.user_id # nosec
126+
127+
return envelope_json_response(
128+
MyFunctionPermissionsGet(write_functions=function_permissions.write_functions)
129+
)

services/web/server/tests/unit/with_dbs/04/functions_rpc/conftest.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55

66
from collections.abc import AsyncIterator, Awaitable, Callable
7+
from contextlib import AsyncExitStack
8+
from typing import Any
79
from uuid import uuid4
810

911
import pytest
@@ -16,6 +18,7 @@
1618
)
1719
from models_library.products import ProductName
1820
from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict
21+
from pytest_simcore.helpers.postgres_tools import insert_and_get_row_lifespan
1922
from pytest_simcore.helpers.typing_env import EnvVarsDict
2023
from pytest_simcore.helpers.webserver_login import LoggedUser, UserInfoDict
2124
from servicelib.rabbitmq import RabbitMQRPCClient
@@ -189,3 +192,35 @@ async def add_user_function_api_access_rights(
189192
funcapi_api_access_rights_table.c.group_id == group_id
190193
)
191194
)
195+
196+
197+
@pytest.fixture
198+
async def logged_user_function_api_access_rights(
199+
asyncpg_engine: AsyncEngine,
200+
logged_user: UserInfoDict,
201+
*,
202+
expected_write_functions: bool,
203+
) -> AsyncIterator[dict[str, Any]]:
204+
cm = insert_and_get_row_lifespan(
205+
asyncpg_engine,
206+
table=funcapi_api_access_rights_table,
207+
values={
208+
"group_id": logged_user["primary_gid"],
209+
"product_name": FRONTEND_APP_DEFAULT,
210+
"read_functions": True,
211+
"write_functions": expected_write_functions,
212+
"execute_functions": True,
213+
"read_function_jobs": True,
214+
"write_function_jobs": True,
215+
"execute_function_jobs": True,
216+
"read_function_job_collections": True,
217+
"write_function_job_collections": True,
218+
"execute_function_job_collections": True,
219+
},
220+
pk_col=funcapi_api_access_rights_table.c.group_id,
221+
pk_value=logged_user["primary_gid"],
222+
)
223+
224+
async with AsyncExitStack() as stack:
225+
row = await stack.enter_async_context(cm)
226+
yield row

services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rest.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
JSONFunctionOutputSchema,
1818
RegisteredProjectFunctionGet,
1919
)
20+
from models_library.api_schemas_webserver.users import MyFunctionPermissionsGet
2021
from pytest_simcore.helpers.assert_checks import assert_status
2122
from pytest_simcore.helpers.webserver_login import UserInfoDict
2223
from servicelib.aiohttp import status
@@ -111,3 +112,25 @@ async def test_register_get_delete_function(
111112
)
112113
response = await client.get(url)
113114
data, error = await assert_status(response, expected_get2)
115+
116+
117+
@pytest.mark.parametrize("user_role", [UserRole.USER])
118+
@pytest.mark.parametrize("expected_write_functions", [True, False])
119+
async def test_list_user_functions_permissions(
120+
client: TestClient,
121+
logged_user: UserInfoDict,
122+
expected_write_functions: bool,
123+
logged_user_function_api_access_rights: dict[str, Any],
124+
):
125+
assert (
126+
logged_user_function_api_access_rights["write_functions"]
127+
== expected_write_functions
128+
)
129+
130+
url = client.app.router["list_user_functions_permissions"].url_for()
131+
response = await client.get(url)
132+
data, error = await assert_status(response, expected_status_code=status.HTTP_200_OK)
133+
134+
assert not error
135+
function_permissions = MyFunctionPermissionsGet.model_validate(data)
136+
assert function_permissions.write_functions == expected_write_functions

0 commit comments

Comments
 (0)