Skip to content

Commit 724b8da

Browse files
committed
♻️ Refactor access rights evaluation functions for clarity and consistency
1 parent b397622 commit 724b8da

File tree

4 files changed

+62
-34
lines changed

4 files changed

+62
-34
lines changed

services/catalog/src/simcore_service_catalog/core/background_tasks.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,13 @@ def _by_version(t: tuple[ServiceKey, ServiceVersion]) -> Version:
7676
(
7777
owner_gid,
7878
service_access_rights,
79-
) = await access_rights.evaluate_default_policy(app, service_metadata)
79+
) = await access_rights.evaluate_service_ownership_and_rights(
80+
app, service_metadata
81+
)
8082

8183
# AUTO-UPGRADE PATCH policy
8284
inherited_access_rights = await access_rights.evaluate_auto_upgrade_policy(
83-
service_metadata, services_repo
85+
service_metadata=service_metadata, services_repo=services_repo
8486
)
8587

8688
service_access_rights += inherited_access_rights

services/catalog/src/simcore_service_catalog/service/access_rights.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import arrow
1010
from fastapi import FastAPI
11+
from models_library.groups import GroupID
1112
from models_library.services import ServiceMetaDataPublished
1213
from models_library.services_types import ServiceKey, ServiceVersion
1314
from packaging.version import Version
@@ -41,21 +42,32 @@ async def _is_old_service(app: FastAPI, service: ServiceMetaDataPublished) -> bo
4142
return bool(service_build_data < _LEGACY_SERVICES_DATE)
4243

4344

44-
async def evaluate_default_policy(
45+
async def evaluate_service_ownership_and_rights(
4546
app: FastAPI, service: ServiceMetaDataPublished
46-
) -> tuple[PositiveInt | None, list[ServiceAccessRightsDB]]:
47-
"""Given a service, it returns the owner's group-id (gid) and a list of access rights following
48-
default access-rights policies
49-
50-
- DEFAULT Access Rights policies:
51-
1. All services published in osparc prior 19.08.2020 will be visible to everyone (refered as 'old service').
52-
2. Services published after 19.08.2020 will be visible ONLY to his/her owner
53-
3. Front-end services are have execute-access to everyone
47+
) -> tuple[GroupID | None, list[ServiceAccessRightsDB]]:
48+
"""Evaluates the owner (group_id) and the access rights for a service
49+
50+
This function determines:
51+
1. Who owns the service (based on contact or author email)
52+
2. Who can access the service based on the following rules:
53+
- All services published before August 19, 2020 (_LEGACY_SERVICES_DATE) are accessible to everyone
54+
- Services published after August 19, 2020 are only accessible to their owner
55+
- Frontend services are accessible to everyone regardless of publication date
56+
57+
Args:
58+
app: FastAPI application instance containing database engine and settings
59+
service: Service metadata including key, version, contact and authors information
60+
61+
Returns:
62+
A tuple containing:
63+
- The owner's group ID (gid) if found, None otherwise
64+
- A list of ServiceAccessRightsDB objects representing the default access rights
65+
for the service, including who can execute and/or modify the service
5466
5567
Raises:
56-
HTTPException: from calls to director's rest API. Maps director errors into catalog's server error
57-
SQLAlchemyError: from access to pg database
58-
ValidationError: from pydantic model errors
68+
HTTPException: If there's an error communicating with the director API
69+
SQLAlchemyError: If there's an error accessing the database
70+
ValidationError: If there's an error validating the Pydantic models
5971
"""
6072
db_engine: AsyncEngine = app.state.engine
6173

@@ -83,7 +95,7 @@ async def evaluate_default_policy(
8395
if possible_gid and not owner_gid:
8496
owner_gid = possible_gid
8597
if not owner_gid:
86-
_logger.warning("service %s:%s has no owner", service.key, service.version)
98+
_logger.warning("Service %s:%s has no owner", service.key, service.version)
8799
else:
88100
group_ids.append(owner_gid)
89101

@@ -106,16 +118,29 @@ async def evaluate_default_policy(
106118

107119

108120
async def evaluate_auto_upgrade_policy(
109-
service_metadata: ServiceMetaDataPublished, services_repo: ServicesRepository
121+
services_repo: ServicesRepository, *, service_metadata: ServiceMetaDataPublished
110122
) -> list[ServiceAccessRightsDB]:
111-
# AUTO-UPGRADE PATCH policy:
112-
#
113-
# - Any new patch released, inherits the access rights from previous compatible version
114-
# - IDEA: add as option in the publication contract, i.e. in ServiceDockerData?
115-
# - Does NOT apply to front-end services
116-
#
117-
# SEE https://github.com/ITISFoundation/osparc-simcore/issues/2244)
118-
#
123+
"""
124+
Evaluates the access rights for a service based on the auto-upgrade patch policy.
125+
126+
The AUTO-UPGRADE PATCH policy ensures that:
127+
- Any new patch release of a service automatically inherits the access rights from the previous compatible version.
128+
- This policy does NOT apply to frontend services.
129+
130+
Args:
131+
services_repo: Instance of ServicesRepository for database access.
132+
service_metadata: Metadata of the service being evaluated.
133+
134+
Returns:
135+
A list of ServiceAccessRightsDB objects representing the inherited access rights for the new patch version.
136+
Returns an empty list if the service is a frontend service or if no previous compatible version is found.
137+
138+
Notes:
139+
- The policy is described in https://github.com/ITISFoundation/osparc-simcore/issues/2244
140+
- Inheritance is only for patch releases (i.e., same major and minor version).
141+
- Future improvement: Consider making this behavior configurable in the service publication contract.
142+
143+
"""
119144
if _is_frontend_service(service_metadata):
120145
return []
121146

@@ -129,8 +154,8 @@ async def evaluate_auto_upgrade_policy(
129154

130155
previous_release = None
131156
for release in latest_releases:
132-
# NOTE: latest_release is sorted from newer to older
133-
# Here we search for the previous version patched by new-version
157+
# latest_releases is sorted from newer to older
158+
# Find the previous version that is patched by new_version
134159
if is_patch_release(new_version, release.version):
135160
previous_release = release
136161
break

services/catalog/tests/unit/with_dbs/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,13 @@ async def other_user(
168168
faker: Faker,
169169
) -> AsyncIterator[dict[str, Any]]:
170170

171-
_user = random_user(fake=faker, id=user_id + 1)
171+
_other_user = random_user(fake=faker, id=user_id + 1)
172172
async with insert_and_get_row_lifespan( # pylint:disable=contextmanager-generator-missing-cleanup
173173
sqlalchemy_async_engine,
174174
table=users,
175-
values=_user,
175+
values=_other_user,
176176
pk_col=users.c.id,
177-
pk_value=_user["id"],
177+
pk_value=_other_user["id"],
178178
) as row:
179179
yield row
180180

services/catalog/tests/unit/with_dbs/test_service_access_rights.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66

77
import simcore_service_catalog.service.access_rights
88
from fastapi import FastAPI
9-
from models_library.groups import GroupAtDB
9+
from models_library.groups import GroupAtDB, GroupID
1010
from models_library.products import ProductName
1111
from models_library.services import ServiceMetaDataPublished, ServiceVersion
1212
from pydantic import TypeAdapter
13+
from pytest_mock import MockerFixture
1314
from simcore_service_catalog.models.services_db import ServiceAccessRightsDB
1415
from simcore_service_catalog.repository.services import ServicesRepository
1516
from simcore_service_catalog.service.access_rights import (
1617
evaluate_auto_upgrade_policy,
17-
evaluate_default_policy,
18+
evaluate_service_ownership_and_rights,
1819
reduce_access_rights,
1920
)
2021
from sqlalchemy.ext.asyncio import AsyncEngine
@@ -84,12 +85,12 @@ def test_reduce_access_rights():
8485

8586
async def test_auto_upgrade_policy(
8687
sqlalchemy_async_engine: AsyncEngine,
87-
user_groups_ids: list[int],
88+
user_groups_ids: list[GroupID],
8889
target_product: ProductName,
8990
other_product: ProductName,
9091
services_db_tables_injector: Callable,
9192
create_fake_service_data: Callable,
92-
mocker,
93+
mocker: MockerFixture,
9394
):
9495
everyone_gid, user_gid, team_gid = user_groups_ids
9596

@@ -165,7 +166,7 @@ async def test_auto_upgrade_policy(
165166
services_repo = ServicesRepository(app.state.engine)
166167

167168
# DEFAULT policies
168-
owner_gid, service_access_rights = await evaluate_default_policy(
169+
owner_gid, service_access_rights = await evaluate_service_ownership_and_rights(
169170
app, new_service_metadata
170171
)
171172
assert owner_gid == user_gid

0 commit comments

Comments
 (0)