Skip to content

Commit 0722af4

Browse files
🐛 adjust storage user project permission based on new logic with workspaces (#6337)
1 parent 5b35cfe commit 0722af4

File tree

4 files changed

+141
-32
lines changed

4 files changed

+141
-32
lines changed

services/storage/src/simcore_service_storage/db_access_layer.py

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
from models_library.users import GroupID, UserID
4848
from simcore_postgres_database.models.project_to_groups import project_to_groups
4949
from simcore_postgres_database.models.projects import projects
50+
from simcore_postgres_database.models.workspaces_access_rights import (
51+
workspaces_access_rights,
52+
)
5053
from simcore_postgres_database.storage_models import file_meta_data, user_to_groups
5154

5255
logger = logging.getLogger(__name__)
@@ -142,6 +145,26 @@ def assemble_array_groups(user_group_ids: list[GroupID]) -> str:
142145
).subquery("access_rights_subquery")
143146

144147

148+
workspace_access_rights_subquery = (
149+
sa.select(
150+
workspaces_access_rights.c.workspace_id,
151+
sa.func.jsonb_object_agg(
152+
workspaces_access_rights.c.gid,
153+
sa.func.jsonb_build_object(
154+
"read",
155+
workspaces_access_rights.c.read,
156+
"write",
157+
workspaces_access_rights.c.write,
158+
"delete",
159+
workspaces_access_rights.c.delete,
160+
),
161+
)
162+
.filter(workspaces_access_rights.c.read)
163+
.label("access_rights"),
164+
).group_by(workspaces_access_rights.c.workspace_id)
165+
).subquery("workspace_access_rights_subquery")
166+
167+
145168
async def list_projects_access_rights(
146169
conn: SAConnection, user_id: UserID
147170
) -> dict[ProjectID, AccessRights]:
@@ -151,23 +174,50 @@ async def list_projects_access_rights(
151174

152175
user_group_ids: list[GroupID] = await _get_user_groups_ids(conn, user_id)
153176

154-
query = (
177+
private_workspace_query = (
155178
sa.select(
156179
projects.c.uuid,
157180
access_rights_subquery.c.access_rights,
158181
)
159182
.select_from(projects.join(access_rights_subquery, isouter=True))
160183
.where(
161-
(projects.c.prj_owner == user_id)
162-
| sa.text(
163-
f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
184+
(
185+
(projects.c.prj_owner == user_id)
186+
| sa.text(
187+
f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
188+
)
164189
)
190+
& (projects.c.workspace_id.is_(None))
165191
)
166192
)
167193

194+
shared_workspace_query = (
195+
sa.select(
196+
projects.c.uuid,
197+
workspace_access_rights_subquery.c.access_rights,
198+
)
199+
.select_from(
200+
projects.join(
201+
workspace_access_rights_subquery,
202+
projects.c.workspace_id
203+
== workspace_access_rights_subquery.c.workspace_id,
204+
)
205+
)
206+
.where(
207+
(
208+
sa.text(
209+
f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
210+
)
211+
)
212+
& (projects.c.workspace_id.is_not(None))
213+
)
214+
)
215+
216+
combined_query = sa.union_all(private_workspace_query, shared_workspace_query)
217+
168218
projects_access_rights = {}
169219

170-
async for row in conn.execute(query):
220+
async for row in conn.execute(combined_query):
171221
assert isinstance(row.access_rights, dict) # nosec
172222
assert isinstance(row.uuid, str) # nosec
173223

@@ -193,7 +243,7 @@ async def get_project_access_rights(
193243
"""
194244
user_group_ids: list[GroupID] = await _get_user_groups_ids(conn, user_id)
195245

196-
query = (
246+
private_workspace_query = (
197247
sa.select(
198248
projects.c.prj_owner,
199249
access_rights_subquery.c.access_rights,
@@ -207,10 +257,36 @@ async def get_project_access_rights(
207257
f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
208258
)
209259
)
260+
& (projects.c.workspace_id.is_(None))
261+
)
262+
)
263+
264+
shared_workspace_query = (
265+
sa.select(
266+
projects.c.prj_owner,
267+
workspace_access_rights_subquery.c.access_rights,
268+
)
269+
.select_from(
270+
projects.join(
271+
workspace_access_rights_subquery,
272+
projects.c.workspace_id
273+
== workspace_access_rights_subquery.c.workspace_id,
274+
)
275+
)
276+
.where(
277+
(projects.c.uuid == f"{project_id}")
278+
& (
279+
sa.text(
280+
f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})"
281+
)
282+
)
283+
& (projects.c.workspace_id.is_not(None))
210284
)
211285
)
212286

213-
result: ResultProxy = await conn.execute(query)
287+
combined_query = sa.union_all(private_workspace_query, shared_workspace_query)
288+
289+
result: ResultProxy = await conn.execute(combined_query)
214290
row: RowProxy | None = await result.first()
215291

216292
if not row:

services/storage/tests/unit/test_db_access_layer.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
# pylint: disable=too-many-arguments
55

66

7+
import pytest
8+
import sqlalchemy as sa
79
from aiopg.sa.engine import Engine
810
from models_library.projects import ProjectID
911
from models_library.users import UserID
12+
from simcore_postgres_database.models.projects import projects
13+
from simcore_postgres_database.models.users import users
14+
from simcore_postgres_database.models.workspaces import workspaces
1015
from simcore_service_storage.db_access_layer import (
1116
AccessRights,
1217
get_file_access_rights,
@@ -28,3 +33,53 @@ async def test_access_rights_on_owned_project(
2833
conn, user_id, f"{project_id}/node_id/not-in-file-metadata-table.txt"
2934
)
3035
assert access == AccessRights.all()
36+
37+
38+
@pytest.fixture
39+
async def prepare_db(user_id: UserID, project_id: ProjectID, aiopg_engine: Engine):
40+
async with aiopg_engine.acquire() as conn:
41+
result = await conn.execute(
42+
sa.select(users.c.primary_gid).where(users.c.id == user_id)
43+
)
44+
row = await result.first()
45+
user_primary_id = row[0]
46+
47+
result = await conn.execute(
48+
workspaces.insert()
49+
.values(
50+
name="test",
51+
description=None,
52+
owner_primary_gid=user_primary_id,
53+
thumbnail=None,
54+
created=sa.func.now(),
55+
modified=sa.func.now(),
56+
product_name="osparc",
57+
)
58+
.returning(workspaces.c.workspace_id)
59+
)
60+
row = await result.first()
61+
workspace_id = row[0]
62+
63+
await conn.execute(
64+
projects.update()
65+
.values(workspace_id=workspace_id)
66+
.where(projects.c.uuid == f"{project_id}")
67+
)
68+
69+
yield
70+
71+
await conn.execute(workspaces.delete())
72+
73+
74+
async def test_access_rights_based_on_workspace(
75+
user_id: UserID, project_id: ProjectID, aiopg_engine: Engine, prepare_db
76+
):
77+
async with aiopg_engine.acquire() as conn:
78+
access = await get_project_access_rights(conn, user_id, project_id)
79+
assert access == AccessRights.all()
80+
81+
# still NOT registered in file_meta_data BUT with prefix {project_id} owned by user
82+
access = await get_file_access_rights(
83+
conn, user_id, f"{project_id}/node_id/not-in-file-metadata-table.txt"
84+
)
85+
assert access == AccessRights.all()

services/web/server/src/simcore_service_webserver/projects/db.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,6 @@ async def list_projects( # pylint: disable=too-many-arguments
352352

353353
async with self.engine.acquire() as conn:
354354

355-
# if workspace_is_private:
356355
access_rights_subquery = (
357356
sa.select(
358357
project_to_groups.c.project_uuid,
@@ -373,29 +372,6 @@ async def list_projects( # pylint: disable=too-many-arguments
373372
.label("access_rights"),
374373
).group_by(project_to_groups.c.project_uuid)
375374
).subquery("access_rights_subquery")
376-
# else:
377-
# access_rights_subquery = (
378-
# sa.select(
379-
# workspaces_access_rights.c.workspace_id,
380-
# sa.func.jsonb_object_agg(
381-
# workspaces_access_rights.c.gid,
382-
# sa.func.jsonb_build_object(
383-
# "read",
384-
# workspaces_access_rights.c.read,
385-
# "write",
386-
# workspaces_access_rights.c.write,
387-
# "delete",
388-
# workspaces_access_rights.c.delete,
389-
# ),
390-
# )
391-
# .filter(
392-
# workspaces_access_rights.c.read # Filters out entries where "read" is False
393-
# )
394-
# .label("access_rights"),
395-
# )
396-
# .where(workspaces_access_rights.c.workspace_id == workspace_id)
397-
# .group_by(workspaces_access_rights.c.workspace_id)
398-
# ).subquery("access_rights_subquery")
399375

400376
_join_query = (
401377
projects.join(projects_to_products, isouter=True)

services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ async def get_workspace_for_user(
175175
access_rights_subquery.c.access_rights,
176176
my_access_rights_subquery.c.my_access_rights,
177177
)
178-
.select_from(workspaces.join(my_access_rights_subquery))
178+
.select_from(
179+
workspaces.join(access_rights_subquery).join(my_access_rights_subquery)
180+
)
179181
.where(
180182
(workspaces.c.workspace_id == workspace_id)
181183
& (workspaces.c.product_name == product_name)

0 commit comments

Comments
 (0)