Skip to content

Commit d04b510

Browse files
committed
Merge branch 'master' into 1912-increase-api-server-polling-time
2 parents a767524 + 23a82ba commit d04b510

File tree

25 files changed

+836
-186
lines changed

25 files changed

+836
-186
lines changed

.github/workflows/ci-testing-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2656,7 +2656,7 @@ jobs:
26562656
if: ${{ always() }}
26572657
needs:
26582658
[
2659-
# system-test-e2e, NOTE: not required, until Odei will have a look
2659+
system-test-e2e,
26602660
system-test-e2e-playwright,
26612661
system-test-environment-setup,
26622662
system-test-public-api,

packages/models-library/src/models_library/functions.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from models_library.products import ProductName
1212
from models_library.services_types import ServiceKey, ServiceVersion
1313
from models_library.users import UserID
14+
from models_library.utils.enums import StrAutoEnum
1415
from pydantic import BaseModel, ConfigDict, Field
1516

1617
from .projects import ProjectID
@@ -301,6 +302,25 @@ class FunctionAccessRightsDB(BaseModel):
301302
)
302303

303304

305+
class FunctionUserApiAccessRights(BaseModel):
306+
user_id: UserID
307+
read_functions: bool = False
308+
write_functions: bool = False
309+
execute_functions: bool = False
310+
read_function_jobs: bool = False
311+
write_function_jobs: bool = False
312+
execute_function_jobs: bool = False
313+
read_function_job_collections: bool = False
314+
write_function_job_collections: bool = False
315+
execute_function_job_collections: bool = False
316+
317+
model_config = ConfigDict(
318+
alias_generator=snake_to_camel,
319+
populate_by_name=True,
320+
extra="forbid",
321+
)
322+
323+
304324
FunctionJobAccessRights: TypeAlias = FunctionAccessRights
305325
FunctionJobAccessRightsDB: TypeAlias = FunctionAccessRightsDB
306326
FunctionJobUserAccessRights: TypeAlias = FunctionUserAccessRights
@@ -310,3 +330,15 @@ class FunctionAccessRightsDB(BaseModel):
310330
FunctionJobCollectionAccessRightsDB: TypeAlias = FunctionAccessRightsDB
311331
FunctionJobCollectionUserAccessRights: TypeAlias = FunctionUserAccessRights
312332
FunctionJobCollectionGroupAccessRights: TypeAlias = FunctionGroupAccessRights
333+
334+
335+
class FunctionsApiAccessRights(StrAutoEnum):
336+
READ_FUNCTIONS = "read_functions"
337+
WRITE_FUNCTIONS = "write_functions"
338+
EXECUTE_FUNCTIONS = "execute_functions"
339+
READ_FUNCTION_JOBS = "read_function_jobs"
340+
WRITE_FUNCTION_JOBS = "write_function_jobs"
341+
EXECUTE_FUNCTION_JOBS = "execute_function_jobs"
342+
READ_FUNCTION_JOB_COLLECTIONS = "read_function_job_collections"
343+
WRITE_FUNCTION_JOB_COLLECTIONS = "write_function_job_collections"
344+
EXECUTE_FUNCTION_JOB_COLLECTIONS = "execute_function_job_collections"

packages/models-library/src/models_library/functions_errors.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,62 @@ class FunctionJobCollectionExecuteAccessDeniedError(FunctionBaseError):
9999
"Function job collection {function_job_collection_id} execute access denied for user {user_id}"
100100
)
101101
status_code: int = 403 # Forbidden
102+
103+
104+
class FunctionsReadApiAccessDeniedError(FunctionBaseError):
105+
msg_template: str = "User {user_id} does not have the permission to read functions"
106+
status_code: int = 403 # Forbidden
107+
108+
109+
class FunctionsWriteApiAccessDeniedError(FunctionBaseError):
110+
msg_template: str = "User {user_id} does not have the permission to write functions"
111+
status_code: int = 403 # Forbidden
112+
113+
114+
class FunctionsExecuteApiAccessDeniedError(FunctionBaseError):
115+
msg_template: str = (
116+
"User {user_id} does not have the permission to execute functions"
117+
)
118+
status_code: int = 403 # Forbidden
119+
120+
121+
class FunctionJobsReadApiAccessDeniedError(FunctionBaseError):
122+
msg_template: str = (
123+
"User {user_id} does not have the permission to read function jobs"
124+
)
125+
status_code: int = 403 # Forbidden
126+
127+
128+
class FunctionJobsWriteApiAccessDeniedError(FunctionBaseError):
129+
msg_template: str = (
130+
"User {user_id} does not have the permission to write function jobs"
131+
)
132+
status_code: int = 403 # Forbidden
133+
134+
135+
class FunctionJobsExecuteApiAccessDeniedError(FunctionBaseError):
136+
msg_template: str = (
137+
"User {user_id} does not have the permission to execute function jobs"
138+
)
139+
status_code: int = 403 # Forbidden
140+
141+
142+
class FunctionJobCollectionsReadApiAccessDeniedError(FunctionBaseError):
143+
msg_template: str = (
144+
"User {user_id} does not have the permission to read function job collections"
145+
)
146+
status_code: int = 403 # Forbidden
147+
148+
149+
class FunctionJobCollectionsWriteApiAccessDeniedError(FunctionBaseError):
150+
msg_template: str = (
151+
"User {user_id} does not have the permission to write function job collections"
152+
)
153+
status_code: int = 403 # Forbidden
154+
155+
156+
class FunctionJobCollectionsExecuteApiAccessDeniedError(FunctionBaseError):
157+
msg_template: str = (
158+
"User {user_id} does not have the permission to execute function job collections"
159+
)
160+
status_code: int = 403 # Forbidden
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Add functions api access rights
2+
3+
Revision ID: 4f6fd2586491
4+
Revises: afb1ba08f3c2
5+
Create Date: 2025-06-13 12:14:59.317685+00:00
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "4f6fd2586491"
14+
down_revision = "afb1ba08f3c2"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table(
22+
"funcapi_group_api_access_rights",
23+
sa.Column("group_id", sa.BigInteger(), nullable=False),
24+
sa.Column("product_name", sa.String(), nullable=False),
25+
sa.Column("read_functions", sa.Boolean(), nullable=True),
26+
sa.Column("write_functions", sa.Boolean(), nullable=True),
27+
sa.Column("execute_functions", sa.Boolean(), nullable=True),
28+
sa.Column("read_function_jobs", sa.Boolean(), nullable=True),
29+
sa.Column("write_function_jobs", sa.Boolean(), nullable=True),
30+
sa.Column("execute_function_jobs", sa.Boolean(), nullable=True),
31+
sa.Column("read_function_job_collections", sa.Boolean(), nullable=True),
32+
sa.Column("write_function_job_collections", sa.Boolean(), nullable=True),
33+
sa.Column("execute_function_job_collections", sa.Boolean(), nullable=True),
34+
sa.Column(
35+
"created",
36+
sa.DateTime(timezone=True),
37+
server_default=sa.text("now()"),
38+
nullable=False,
39+
),
40+
sa.Column(
41+
"modified",
42+
sa.DateTime(timezone=True),
43+
server_default=sa.text("now()"),
44+
nullable=False,
45+
),
46+
sa.ForeignKeyConstraint(
47+
["group_id"],
48+
["groups.gid"],
49+
name="fk_func_access_to_groups_group_id",
50+
onupdate="CASCADE",
51+
ondelete="CASCADE",
52+
),
53+
sa.ForeignKeyConstraint(
54+
["product_name"],
55+
["products.name"],
56+
name="fk_func_access_to_products_product_name",
57+
onupdate="CASCADE",
58+
ondelete="CASCADE",
59+
),
60+
sa.PrimaryKeyConstraint(
61+
"group_id",
62+
"product_name",
63+
name="pk_func_group_product_name_to_api_access_rights",
64+
),
65+
)
66+
# ### end Alembic commands ###
67+
68+
69+
def downgrade():
70+
# ### commands auto generated by Alembic - please adjust! ###
71+
op.drop_table("funcapi_group_api_access_rights")
72+
# ### end Alembic commands ###
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Function api access rights of groups (read, write, execute)"""
2+
3+
import sqlalchemy as sa
4+
from simcore_postgres_database.models._common import (
5+
RefActions,
6+
column_created_datetime,
7+
column_modified_datetime,
8+
)
9+
10+
from .base import metadata
11+
12+
funcapi_api_access_rights_table = sa.Table(
13+
"funcapi_group_api_access_rights",
14+
metadata,
15+
sa.Column(
16+
"group_id",
17+
sa.ForeignKey(
18+
"groups.gid",
19+
name="fk_func_access_to_groups_group_id",
20+
onupdate=RefActions.CASCADE,
21+
ondelete=RefActions.CASCADE,
22+
),
23+
nullable=False,
24+
),
25+
sa.Column(
26+
"product_name",
27+
sa.ForeignKey(
28+
"products.name",
29+
name="fk_func_access_to_products_product_name",
30+
onupdate=RefActions.CASCADE,
31+
ondelete=RefActions.CASCADE,
32+
),
33+
nullable=False,
34+
),
35+
sa.Column(
36+
"read_functions",
37+
sa.Boolean,
38+
default=False,
39+
),
40+
sa.Column(
41+
"write_functions",
42+
sa.Boolean,
43+
default=False,
44+
),
45+
sa.Column(
46+
"execute_functions",
47+
sa.Boolean,
48+
default=False,
49+
),
50+
sa.Column(
51+
"read_function_jobs",
52+
sa.Boolean,
53+
default=False,
54+
),
55+
sa.Column(
56+
"write_function_jobs",
57+
sa.Boolean,
58+
default=False,
59+
),
60+
sa.Column(
61+
"execute_function_jobs",
62+
sa.Boolean,
63+
default=False,
64+
),
65+
sa.Column(
66+
"read_function_job_collections",
67+
sa.Boolean,
68+
default=False,
69+
),
70+
sa.Column(
71+
"write_function_job_collections",
72+
sa.Boolean,
73+
default=False,
74+
),
75+
sa.Column(
76+
"execute_function_job_collections",
77+
sa.Boolean,
78+
default=False,
79+
),
80+
column_created_datetime(),
81+
column_modified_datetime(),
82+
sa.PrimaryKeyConstraint(
83+
"group_id",
84+
"product_name",
85+
name="pk_func_group_product_name_to_api_access_rights",
86+
),
87+
)

packages/postgres-database/src/simcore_postgres_database/models/funcapi_functions_access_rights_table.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
ondelete=RefActions.CASCADE,
2323
),
2424
nullable=False,
25-
doc="Unique identifier of the function",
2625
),
2726
sa.Column(
2827
"group_id",
@@ -33,7 +32,6 @@
3332
ondelete=RefActions.CASCADE,
3433
),
3534
nullable=False,
36-
doc="Group id",
3735
),
3836
sa.Column(
3937
"product_name",
@@ -44,25 +42,21 @@
4442
ondelete=RefActions.CASCADE,
4543
),
4644
nullable=False,
47-
doc="Name of the product",
4845
),
4946
sa.Column(
5047
"read",
5148
sa.Boolean,
5249
default=False,
53-
doc="Read access right for the function",
5450
),
5551
sa.Column(
5652
"write",
5753
sa.Boolean,
5854
default=False,
59-
doc="Write access right for the function",
6055
),
6156
sa.Column(
6257
"execute",
6358
sa.Boolean,
6459
default=False,
65-
doc="Execute access right for the function",
6660
),
6761
column_created_datetime(),
6862
column_modified_datetime(),

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/functions/functions_rpc_interface.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
RegisteredFunctionJob,
1717
RegisteredFunctionJobCollection,
1818
)
19-
from models_library.functions import FunctionUserAccessRights
19+
from models_library.functions import (
20+
FunctionUserAccessRights,
21+
FunctionUserApiAccessRights,
22+
)
2023
from models_library.products import ProductName
2124
from models_library.rabbitmq_basic_types import RPCMethodName
2225
from models_library.rest_pagination import PageMetaInfoLimitOffset
@@ -407,3 +410,21 @@ async def get_function_user_permissions(
407410
product_name=product_name,
408411
)
409412
return TypeAdapter(FunctionUserAccessRights).validate_python(result)
413+
414+
415+
@log_decorator(_logger, level=logging.DEBUG)
416+
async def get_functions_user_api_access_rights(
417+
rabbitmq_rpc_client: RabbitMQRPCClient,
418+
*,
419+
user_id: UserID,
420+
product_name: ProductName,
421+
) -> FunctionUserApiAccessRights:
422+
result = await rabbitmq_rpc_client.request(
423+
WEBSERVER_RPC_NAMESPACE,
424+
TypeAdapter(RPCMethodName).validate_python(
425+
"get_functions_user_api_access_rights"
426+
),
427+
user_id=user_id,
428+
product_name=product_name,
429+
)
430+
return TypeAdapter(FunctionUserApiAccessRights).validate_python(result)

services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from models_library.functions_errors import (
2626
FunctionExecuteAccessDeniedError,
2727
FunctionInputsValidationError,
28-
FunctionReadAccessDeniedError,
28+
FunctionsExecuteApiAccessDeniedError,
2929
UnsupportedFunctionClassError,
3030
)
3131
from models_library.products import ProductName
@@ -374,16 +374,23 @@ async def run_function( # noqa: PLR0913
374374
job_service: Annotated[JobService, Depends(get_job_service)],
375375
) -> RegisteredFunctionJob:
376376

377+
# Make sure the user is allowed to execute any function
378+
# (read/write right is checked in the other endpoint called in this method)
379+
user_api_access_rights = await wb_api_rpc.get_functions_user_api_access_rights(
380+
user_id=user_id, product_name=product_name
381+
)
382+
if not user_api_access_rights.execute_functions:
383+
raise FunctionsExecuteApiAccessDeniedError(
384+
user_id=user_id,
385+
function_id=function_id,
386+
)
387+
# Make sure the user is allowed to execute this particular function
388+
# (read/write right is checked in the other endpoint called in this method)
377389
user_permissions: FunctionUserAccessRights = (
378390
await wb_api_rpc.get_function_user_permissions(
379391
function_id=function_id, user_id=user_id, product_name=product_name
380392
)
381393
)
382-
if not user_permissions.read:
383-
raise FunctionReadAccessDeniedError(
384-
user_id=user_id,
385-
function_id=function_id,
386-
)
387394
if not user_permissions.execute:
388395
raise FunctionExecuteAccessDeniedError(
389396
user_id=user_id,

0 commit comments

Comments
 (0)