Skip to content

Commit b9bcbea

Browse files
🎨 sharee permission improvement (#5557)
Co-authored-by: Odei Maiz <[email protected]>
1 parent cc1695c commit b9bcbea

File tree

6 files changed

+105
-42
lines changed

6 files changed

+105
-42
lines changed

services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ qx.Class.define("osparc.share.CollaboratorsStudy", {
202202
Promise.all(promises)
203203
.then(values => {
204204
const noAccessible = values.filter(value => value["accessible"] === false);
205-
if (noAccessible.length && !osparc.product.Utils.isS4LProduct()) {
205+
if (noAccessible.length) {
206206
const shareePermissions = new osparc.share.ShareePermissions(noAccessible);
207207
const win = osparc.ui.window.Window.popUpInWindow(shareePermissions, this.tr("Sharee permissions"), 500, 500, "@FontAwesome5Solid/exclamation-triangle/14").set({
208208
clickAwayClose: false,

services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
import asyncpg.exceptions
44
from aiohttp import web
5-
from aiopg.sa.result import RowProxy
5+
from models_library.groups import Group, GroupTypeInModel
66
from models_library.users import GroupID, UserID
77
from simcore_postgres_database.errors import DatabaseError
8-
from simcore_postgres_database.models.groups import GroupType
98

109
from ..groups.api import get_group_from_gid
1110
from ..projects.db import APP_PROJECT_DBAPI, ProjectAccessRights
@@ -74,17 +73,17 @@ async def get_new_project_owner_gid(
7473
standard_groups = {} # groups of users, multiple users can be part of this
7574
primary_groups = {} # each individual user has a unique primary group
7675
for other_gid in other_users_access_rights:
77-
group: RowProxy | None = await get_group_from_gid(app=app, gid=int(other_gid))
76+
group: Group | None = await get_group_from_gid(app=app, gid=int(other_gid))
7877

7978
# only process for users and groups with write access right
8079
if group is None:
8180
continue
8281
if access_rights[other_gid]["write"] is not True:
8382
continue
8483

85-
if group.type == GroupType.STANDARD:
84+
if group.group_type == GroupTypeInModel.STANDARD:
8685
standard_groups[other_gid] = access_rights[other_gid]
87-
elif group.type == GroupType.PRIMARY:
86+
elif group.group_type == GroupTypeInModel.PRIMARY:
8887
primary_groups[other_gid] = access_rights[other_gid]
8988

9089
_logger.debug(

services/web/server/src/simcore_service_webserver/groups/_db.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sqlalchemy as sa
55
from aiopg.sa import SAConnection
66
from aiopg.sa.result import ResultProxy, RowProxy
7+
from models_library.groups import GroupAtDB
78
from models_library.users import GroupID, UserID
89
from simcore_postgres_database.utils_products import get_or_create_product_group
910
from sqlalchemy import and_, literal_column
@@ -386,6 +387,9 @@ async def delete_user_in_group(
386387
)
387388

388389

389-
async def get_group_from_gid(conn: SAConnection, gid: GroupID) -> RowProxy | None:
390-
res: ResultProxy = await conn.execute(groups.select().where(groups.c.gid == gid))
391-
return await res.first()
390+
async def get_group_from_gid(conn: SAConnection, gid: GroupID) -> GroupAtDB | None:
391+
row: ResultProxy = await conn.execute(groups.select().where(groups.c.gid == gid))
392+
result = await row.first()
393+
if result:
394+
return GroupAtDB.from_orm(result)
395+
return None

services/web/server/src/simcore_service_webserver/groups/api.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from aiohttp import web
44
from aiopg.sa.result import RowProxy
55
from models_library.emails import LowerCaseEmailStr
6+
from models_library.groups import Group
67
from models_library.users import GroupID, UserID
78

89
from ..db.plugin import get_database_engine
@@ -181,6 +182,10 @@ async def delete_user_in_group(
181182
)
182183

183184

184-
async def get_group_from_gid(app: web.Application, gid: GroupID) -> RowProxy | None:
185+
async def get_group_from_gid(app: web.Application, gid: GroupID) -> Group | None:
185186
async with get_database_engine(app).acquire() as conn:
186-
return await _db.get_group_from_gid(conn, gid=gid)
187+
group_db = await _db.get_group_from_gid(conn, gid=gid)
188+
189+
if group_db:
190+
return Group.construct(**group_db.dict())
191+
return None

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

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
NodeGetUnknown,
2020
NodeRetrieve,
2121
)
22-
from models_library.groups import EVERYONE_GROUP_ID
22+
from models_library.groups import EVERYONE_GROUP_ID, Group, GroupTypeInModel
2323
from models_library.projects import Project, ProjectID
2424
from models_library.projects_nodes import NodeID
2525
from models_library.projects_nodes_io import NodeIDStr
@@ -50,14 +50,16 @@
5050
ServiceWasNotFoundError,
5151
)
5252
from simcore_postgres_database.models.users import UserRole
53+
from simcore_service_webserver.groups.exceptions import GroupNotFoundError
5354

5455
from .._meta import API_VTAG as VTAG
5556
from ..catalog import client as catalog_client
5657
from ..director_v2 import api as director_v2_api
5758
from ..dynamic_scheduler import api as dynamic_scheduler_api
59+
from ..groups.api import get_group_from_gid, list_user_groups
5860
from ..login.decorators import login_required
5961
from ..security.decorators import permission_required
60-
from ..users.api import get_user_role
62+
from ..users.api import get_user_id_from_gid, get_user_role
6163
from ..users.exceptions import UserDefaultWalletNotFoundError
6264
from ..utils_aiohttp import envelope_json_response
6365
from ..wallets.errors import WalletNotEnoughCreditsError
@@ -88,6 +90,7 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
8890
NodeNotFoundError,
8991
UserDefaultWalletNotFoundError,
9092
DefaultPricingUnitNotFoundError,
93+
GroupNotFoundError,
9194
) as exc:
9295
raise web.HTTPNotFound(reason=f"{exc}") from exc
9396
except WalletNotEnoughCreditsError as exc:
@@ -469,23 +472,54 @@ async def get_project_services_access_for_gid(request: web.Request) -> web.Respo
469472
]
470473
)
471474

475+
# Initialize groups to compare with everyone group ID
476+
groups_to_compare = {EVERYONE_GROUP_ID}
477+
478+
# Get the group from the provided group ID
479+
_sharing_with_group: Group | None = await get_group_from_gid(
480+
app=request.app, gid=query_params.for_gid
481+
)
482+
483+
# Check if the group exists
484+
if _sharing_with_group is None:
485+
raise GroupNotFoundError(gid=query_params.for_gid)
486+
487+
# Update groups to compare based on the type of sharing group
488+
if _sharing_with_group.group_type == GroupTypeInModel.PRIMARY:
489+
_user_id = await get_user_id_from_gid(
490+
app=request.app, primary_gid=query_params.for_gid
491+
)
492+
_, _user_groups, _ = await list_user_groups(app=request.app, user_id=_user_id)
493+
groups_to_compare.update({int(item.get("gid")) for item in _user_groups})
494+
groups_to_compare.add(query_params.for_gid)
495+
elif _sharing_with_group.group_type == GroupTypeInModel.STANDARD:
496+
groups_to_compare = {query_params.for_gid}
497+
498+
# Initialize a list for inaccessible services
472499
inaccessible_services = []
500+
501+
# Check accessibility of each service
473502
for service in project_services_access_rights:
474503
service_access_rights = service.gids_with_access_rights
475504

476-
# Ignore services shared with everyone
477-
if service_access_rights.get(EVERYONE_GROUP_ID):
478-
continue
479-
480-
# Check if service is accessible to the provided group
481-
service_access_rights_for_gid = service_access_rights.get(
482-
query_params.for_gid, {}
505+
# Find common groups between service access rights and groups to compare
506+
_groups_intersection = set(service_access_rights.keys()).intersection(
507+
groups_to_compare
483508
)
484509

485-
if (
486-
not service_access_rights_for_gid
487-
or service_access_rights_for_gid.get("execute_access", False) is False
488-
):
510+
_is_service_accessible = False
511+
512+
# Iterate through common groups
513+
for group in _groups_intersection:
514+
service_access_rights_for_gid = service_access_rights.get(group)
515+
assert service_access_rights_for_gid is not None # nosec
516+
# Check if execute access is granted for the group
517+
if service_access_rights_for_gid.get("execute_access", False):
518+
_is_service_accessible = True
519+
break
520+
521+
# If service is not accessible, add it to the list of inaccessible services
522+
if not _is_service_accessible:
489523
inaccessible_services.append(service)
490524

491525
project_accessible = not inaccessible_services

services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handlers__services_access.py

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,14 @@ def mock_catalog_api_get_service_access_rights_response(mocker: MockerFixture):
9595
async def test_user_role_access(
9696
client: TestClient,
9797
user_project: ProjectDict,
98+
logged_user: dict,
9899
expected: HTTPStatus,
99100
mock_catalog_api_get_service_access_rights_response,
100101
):
101102
assert client.app
102103

103104
project_id = user_project["uuid"]
104-
for_gid = 3
105+
for_gid = logged_user["primary_gid"]
105106

106107
expected_url = client.app.router["get_project_services_access_for_gid"].url_for(
107108
project_id=project_id
@@ -119,7 +120,10 @@ async def test_user_role_access(
119120

120121
@pytest.mark.parametrize("user_role", [UserRole.USER])
121122
async def test_accessible_thanks_to_everyone_group_id(
122-
client: TestClient, user_project: ProjectDict, mocker: MockerFixture
123+
client: TestClient,
124+
user_project: ProjectDict,
125+
mocker: MockerFixture,
126+
logged_user: dict,
123127
):
124128
mocker.patch(
125129
"simcore_service_webserver.projects._nodes_handlers.catalog_client.get_service_access_rights",
@@ -152,7 +156,7 @@ async def test_accessible_thanks_to_everyone_group_id(
152156
assert client.app
153157

154158
project_id = user_project["uuid"]
155-
for_gid = 3
159+
for_gid = logged_user["primary_gid"]
156160

157161
expected_url = client.app.router["get_project_services_access_for_gid"].url_for(
158162
project_id=project_id
@@ -169,8 +173,13 @@ async def test_accessible_thanks_to_everyone_group_id(
169173

170174
@pytest.mark.parametrize("user_role", [UserRole.USER])
171175
async def test_accessible_thanks_to_concrete_group_id(
172-
client: TestClient, user_project: ProjectDict, mocker: MockerFixture
176+
client: TestClient,
177+
user_project: ProjectDict,
178+
mocker: MockerFixture,
179+
logged_user: dict,
173180
):
181+
for_gid = logged_user["primary_gid"]
182+
174183
mocker.patch(
175184
"simcore_service_webserver.projects._nodes_handlers.catalog_client.get_service_access_rights",
176185
spec=True,
@@ -179,7 +188,7 @@ async def test_accessible_thanks_to_concrete_group_id(
179188
service_key="simcore/services/comp/itis/sleeper",
180189
service_version="2.1.4",
181190
gids_with_access_rights={
182-
3: {"execute_access": True},
191+
for_gid: {"execute_access": True},
183192
5: {"execute_access": True},
184193
},
185194
),
@@ -193,15 +202,14 @@ async def test_accessible_thanks_to_concrete_group_id(
193202
ServiceAccessRightsGet(
194203
service_key="simcore/services/comp/itis/sleeper",
195204
service_version="2.1.5",
196-
gids_with_access_rights={3: {"execute_access": True}},
205+
gids_with_access_rights={for_gid: {"execute_access": True}},
197206
),
198207
],
199208
)
200209

201210
assert client.app
202211

203212
project_id = user_project["uuid"]
204-
for_gid = 3
205213

206214
expected_url = client.app.router["get_project_services_access_for_gid"].url_for(
207215
project_id=project_id
@@ -218,8 +226,13 @@ async def test_accessible_thanks_to_concrete_group_id(
218226

219227
@pytest.mark.parametrize("user_role", [UserRole.USER])
220228
async def test_not_accessible_for_one_service(
221-
client: TestClient, user_project: ProjectDict, mocker: MockerFixture
229+
client: TestClient,
230+
user_project: ProjectDict,
231+
mocker: MockerFixture,
232+
logged_user: dict,
222233
):
234+
for_gid = logged_user["primary_gid"]
235+
223236
mocker.patch(
224237
"simcore_service_webserver.projects._nodes_handlers.catalog_client.get_service_access_rights",
225238
spec=True,
@@ -236,22 +249,21 @@ async def test_not_accessible_for_one_service(
236249
service_key="simcore/services/frontend/parameter/integer",
237250
service_version="1.0.0",
238251
gids_with_access_rights={
239-
3: {"execute_access": True},
252+
for_gid: {"execute_access": True},
240253
2: {"execute_access": True},
241254
},
242255
),
243256
ServiceAccessRightsGet(
244257
service_key="simcore/services/comp/itis/sleeper",
245258
service_version="2.1.5",
246-
gids_with_access_rights={3: {"execute_access": True}},
259+
gids_with_access_rights={for_gid: {"execute_access": True}},
247260
),
248261
],
249262
)
250263

251264
assert client.app
252265

253266
project_id = user_project["uuid"]
254-
for_gid = 3
255267

256268
expected_url = client.app.router["get_project_services_access_for_gid"].url_for(
257269
project_id=project_id
@@ -274,7 +286,10 @@ async def test_not_accessible_for_one_service(
274286

275287
@pytest.mark.parametrize("user_role", [UserRole.USER])
276288
async def test_not_accessible_for_more_services(
277-
client: TestClient, user_project: ProjectDict, mocker: MockerFixture
289+
client: TestClient,
290+
user_project: ProjectDict,
291+
mocker: MockerFixture,
292+
logged_user: dict,
278293
):
279294
mocker.patch(
280295
"simcore_service_webserver.projects._nodes_handlers.catalog_client.get_service_access_rights",
@@ -310,7 +325,7 @@ async def test_not_accessible_for_more_services(
310325
assert client.app
311326

312327
project_id = user_project["uuid"]
313-
for_gid = 3
328+
for_gid = logged_user["primary_gid"]
314329

315330
expected_url = client.app.router["get_project_services_access_for_gid"].url_for(
316331
project_id=project_id
@@ -335,34 +350,40 @@ async def test_not_accessible_for_more_services(
335350

336351
@pytest.mark.parametrize("user_role", [UserRole.USER])
337352
async def test_not_accessible_for_service_because_of_execute_access_false(
338-
client: TestClient, user_project: ProjectDict, mocker: MockerFixture
353+
client: TestClient,
354+
user_project: ProjectDict,
355+
mocker: MockerFixture,
356+
logged_user: dict,
339357
):
358+
for_gid = logged_user["primary_gid"]
359+
340360
mocker.patch(
341361
"simcore_service_webserver.projects._nodes_handlers.catalog_client.get_service_access_rights",
342362
spec=True,
343363
side_effect=[
344364
ServiceAccessRightsGet(
345365
service_key="simcore/services/comp/itis/sleeper",
346366
service_version="2.1.4",
347-
gids_with_access_rights={3: {"execute_access": False}}, # <-- FALSE
367+
gids_with_access_rights={
368+
for_gid: {"execute_access": False}
369+
}, # <-- FALSE
348370
),
349371
ServiceAccessRightsGet(
350372
service_key="simcore/services/frontend/parameter/integer",
351373
service_version="1.0.0",
352-
gids_with_access_rights={3: {"execute_access": True}},
374+
gids_with_access_rights={for_gid: {"execute_access": True}},
353375
),
354376
ServiceAccessRightsGet(
355377
service_key="simcore/services/comp/itis/sleeper",
356378
service_version="2.1.5",
357-
gids_with_access_rights={3: {"execute_access": True}},
379+
gids_with_access_rights={for_gid: {"execute_access": True}},
358380
),
359381
],
360382
)
361383

362384
assert client.app
363385

364386
project_id = user_project["uuid"]
365-
for_gid = 3
366387

367388
expected_url = client.app.router["get_project_services_access_for_gid"].url_for(
368389
project_id=project_id

0 commit comments

Comments
 (0)