diff --git a/services/storage/src/simcore_service_storage/db_access_layer.py b/services/storage/src/simcore_service_storage/db_access_layer.py index ceae121ccd0..19452862de5 100644 --- a/services/storage/src/simcore_service_storage/db_access_layer.py +++ b/services/storage/src/simcore_service_storage/db_access_layer.py @@ -47,6 +47,9 @@ from models_library.users import GroupID, UserID from simcore_postgres_database.models.project_to_groups import project_to_groups from simcore_postgres_database.models.projects import projects +from simcore_postgres_database.models.workspaces_access_rights import ( + workspaces_access_rights, +) from simcore_postgres_database.storage_models import file_meta_data, user_to_groups logger = logging.getLogger(__name__) @@ -142,6 +145,26 @@ def assemble_array_groups(user_group_ids: list[GroupID]) -> str: ).subquery("access_rights_subquery") +workspace_access_rights_subquery = ( + sa.select( + workspaces_access_rights.c.workspace_id, + sa.func.jsonb_object_agg( + workspaces_access_rights.c.gid, + sa.func.jsonb_build_object( + "read", + workspaces_access_rights.c.read, + "write", + workspaces_access_rights.c.write, + "delete", + workspaces_access_rights.c.delete, + ), + ) + .filter(workspaces_access_rights.c.read) + .label("access_rights"), + ).group_by(workspaces_access_rights.c.workspace_id) +).subquery("workspace_access_rights_subquery") + + async def list_projects_access_rights( conn: SAConnection, user_id: UserID ) -> dict[ProjectID, AccessRights]: @@ -151,23 +174,50 @@ async def list_projects_access_rights( user_group_ids: list[GroupID] = await _get_user_groups_ids(conn, user_id) - query = ( + private_workspace_query = ( sa.select( projects.c.uuid, access_rights_subquery.c.access_rights, ) .select_from(projects.join(access_rights_subquery, isouter=True)) .where( - (projects.c.prj_owner == user_id) - | sa.text( - f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})" + ( + (projects.c.prj_owner == user_id) + | sa.text( + f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})" + ) ) + & (projects.c.workspace_id.is_(None)) ) ) + shared_workspace_query = ( + sa.select( + projects.c.uuid, + workspace_access_rights_subquery.c.access_rights, + ) + .select_from( + projects.join( + workspace_access_rights_subquery, + projects.c.workspace_id + == workspace_access_rights_subquery.c.workspace_id, + ) + ) + .where( + ( + sa.text( + f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})" + ) + ) + & (projects.c.workspace_id.is_not(None)) + ) + ) + + combined_query = sa.union_all(private_workspace_query, shared_workspace_query) + projects_access_rights = {} - async for row in conn.execute(query): + async for row in conn.execute(combined_query): assert isinstance(row.access_rights, dict) # nosec assert isinstance(row.uuid, str) # nosec @@ -193,7 +243,7 @@ async def get_project_access_rights( """ user_group_ids: list[GroupID] = await _get_user_groups_ids(conn, user_id) - query = ( + private_workspace_query = ( sa.select( projects.c.prj_owner, access_rights_subquery.c.access_rights, @@ -207,10 +257,36 @@ async def get_project_access_rights( f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})" ) ) + & (projects.c.workspace_id.is_(None)) + ) + ) + + shared_workspace_query = ( + sa.select( + projects.c.prj_owner, + workspace_access_rights_subquery.c.access_rights, + ) + .select_from( + projects.join( + workspace_access_rights_subquery, + projects.c.workspace_id + == workspace_access_rights_subquery.c.workspace_id, + ) + ) + .where( + (projects.c.uuid == f"{project_id}") + & ( + sa.text( + f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(user_group_ids)})" + ) + ) + & (projects.c.workspace_id.is_not(None)) ) ) - result: ResultProxy = await conn.execute(query) + combined_query = sa.union_all(private_workspace_query, shared_workspace_query) + + result: ResultProxy = await conn.execute(combined_query) row: RowProxy | None = await result.first() if not row: diff --git a/services/storage/tests/unit/test_db_access_layer.py b/services/storage/tests/unit/test_db_access_layer.py index f357b2909fc..452e09e1ead 100644 --- a/services/storage/tests/unit/test_db_access_layer.py +++ b/services/storage/tests/unit/test_db_access_layer.py @@ -4,9 +4,14 @@ # pylint: disable=too-many-arguments +import pytest +import sqlalchemy as sa from aiopg.sa.engine import Engine from models_library.projects import ProjectID from models_library.users import UserID +from simcore_postgres_database.models.projects import projects +from simcore_postgres_database.models.users import users +from simcore_postgres_database.models.workspaces import workspaces from simcore_service_storage.db_access_layer import ( AccessRights, get_file_access_rights, @@ -28,3 +33,53 @@ async def test_access_rights_on_owned_project( conn, user_id, f"{project_id}/node_id/not-in-file-metadata-table.txt" ) assert access == AccessRights.all() + + +@pytest.fixture +async def prepare_db(user_id: UserID, project_id: ProjectID, aiopg_engine: Engine): + async with aiopg_engine.acquire() as conn: + result = await conn.execute( + sa.select(users.c.primary_gid).where(users.c.id == user_id) + ) + row = await result.first() + user_primary_id = row[0] + + result = await conn.execute( + workspaces.insert() + .values( + name="test", + description=None, + owner_primary_gid=user_primary_id, + thumbnail=None, + created=sa.func.now(), + modified=sa.func.now(), + product_name="osparc", + ) + .returning(workspaces.c.workspace_id) + ) + row = await result.first() + workspace_id = row[0] + + await conn.execute( + projects.update() + .values(workspace_id=workspace_id) + .where(projects.c.uuid == f"{project_id}") + ) + + yield + + await conn.execute(workspaces.delete()) + + +async def test_access_rights_based_on_workspace( + user_id: UserID, project_id: ProjectID, aiopg_engine: Engine, prepare_db +): + async with aiopg_engine.acquire() as conn: + access = await get_project_access_rights(conn, user_id, project_id) + assert access == AccessRights.all() + + # still NOT registered in file_meta_data BUT with prefix {project_id} owned by user + access = await get_file_access_rights( + conn, user_id, f"{project_id}/node_id/not-in-file-metadata-table.txt" + ) + assert access == AccessRights.all() diff --git a/services/web/server/src/simcore_service_webserver/projects/db.py b/services/web/server/src/simcore_service_webserver/projects/db.py index ca97117b247..4ca4d36dedc 100644 --- a/services/web/server/src/simcore_service_webserver/projects/db.py +++ b/services/web/server/src/simcore_service_webserver/projects/db.py @@ -352,7 +352,6 @@ async def list_projects( # pylint: disable=too-many-arguments async with self.engine.acquire() as conn: - # if workspace_is_private: access_rights_subquery = ( sa.select( project_to_groups.c.project_uuid, @@ -373,29 +372,6 @@ async def list_projects( # pylint: disable=too-many-arguments .label("access_rights"), ).group_by(project_to_groups.c.project_uuid) ).subquery("access_rights_subquery") - # else: - # access_rights_subquery = ( - # sa.select( - # workspaces_access_rights.c.workspace_id, - # sa.func.jsonb_object_agg( - # workspaces_access_rights.c.gid, - # sa.func.jsonb_build_object( - # "read", - # workspaces_access_rights.c.read, - # "write", - # workspaces_access_rights.c.write, - # "delete", - # workspaces_access_rights.c.delete, - # ), - # ) - # .filter( - # workspaces_access_rights.c.read # Filters out entries where "read" is False - # ) - # .label("access_rights"), - # ) - # .where(workspaces_access_rights.c.workspace_id == workspace_id) - # .group_by(workspaces_access_rights.c.workspace_id) - # ).subquery("access_rights_subquery") _join_query = ( projects.join(projects_to_products, isouter=True) diff --git a/services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py b/services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py index 98ef90886ac..fd78e653d5f 100644 --- a/services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py +++ b/services/web/server/src/simcore_service_webserver/workspaces/_workspaces_db.py @@ -175,7 +175,9 @@ async def get_workspace_for_user( access_rights_subquery.c.access_rights, my_access_rights_subquery.c.my_access_rights, ) - .select_from(workspaces.join(my_access_rights_subquery)) + .select_from( + workspaces.join(access_rights_subquery).join(my_access_rights_subquery) + ) .where( (workspaces.c.workspace_id == workspace_id) & (workspaces.c.product_name == product_name)