Skip to content

Commit 1c6fc1e

Browse files
committed
adds support to groups
1 parent 885cfa1 commit 1c6fc1e

File tree

9 files changed

+89
-42
lines changed

9 files changed

+89
-42
lines changed

packages/models-library/src/models_library/api_schemas_webserver/groups.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def from_domain_model(
246246
cls,
247247
groups_by_type: GroupsByTypeTuple,
248248
my_product_group: tuple[Group, AccessRightsDict] | None,
249-
my_support_group: tuple[Group, AccessRightsDict] | None = None,
249+
my_support_group: tuple[Group, AccessRightsDict] | None,
250250
) -> Self:
251251
assert groups_by_type.primary # nosec
252252
assert groups_by_type.everyone # nosec

packages/models-library/src/models_library/api_schemas_webserver/users.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def from_domain_model(
132132
my_groups_by_type: GroupsByTypeTuple,
133133
my_product_group: tuple[Group, AccessRightsDict] | None,
134134
my_preferences: AggregatedPreferences,
135+
my_support_group: tuple[Group, AccessRightsDict] | None,
135136
) -> Self:
136137
data = remap_keys(
137138
my_profile.model_dump(
@@ -152,7 +153,9 @@ def from_domain_model(
152153
)
153154
return cls(
154155
**data,
155-
groups=MyGroupsGet.from_domain_model(my_groups_by_type, my_product_group),
156+
groups=MyGroupsGet.from_domain_model(
157+
my_groups_by_type, my_product_group, my_support_group
158+
),
156159
preferences=my_preferences,
157160
)
158161

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,44 +39,54 @@ class Group(BaseModel):
3939

4040
@staticmethod
4141
def _update_json_schema_extra(schema: JsonDict) -> None:
42-
everyone = {
42+
everyone: JsonDict = {
4343
"gid": 1,
4444
"name": "Everyone",
4545
"type": "everyone",
4646
"description": "all users",
4747
"thumbnail": None,
4848
}
49-
user = {
49+
user: JsonDict = {
5050
"gid": 2,
5151
"name": "User",
5252
"description": "primary group",
5353
"type": "primary",
5454
"thumbnail": None,
5555
}
56-
organization = {
56+
organization: JsonDict = {
5757
"gid": 3,
5858
"name": "Organization",
5959
"description": "standard group",
6060
"type": "standard",
6161
"thumbnail": None,
6262
"inclusionRules": {},
6363
}
64-
product = {
64+
product: JsonDict = {
6565
"gid": 4,
6666
"name": "Product",
6767
"description": "standard group for products",
6868
"type": "standard",
6969
"thumbnail": None,
7070
}
71-
support = {
71+
support: JsonDict = {
7272
"gid": 5,
7373
"name": "Support",
7474
"description": "support group",
7575
"type": "standard",
7676
"thumbnail": None,
7777
}
7878

79-
schema.update({"examples": [everyone, user, organization, product, support]})
79+
schema.update(
80+
{
81+
"examples": [
82+
everyone,
83+
user,
84+
organization,
85+
product,
86+
support,
87+
]
88+
}
89+
)
8090

8191
model_config = ConfigDict(
8292
populate_by_name=True, json_schema_extra=_update_json_schema_extra

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ async def _get_group_and_access_rights_or_raise(
108108
*,
109109
caller_id: UserID,
110110
group_id: GroupID,
111-
permission: Literal["read", "write", "delete"] | None,
111+
check_permission: Literal["read", "write", "delete"] | None,
112112
) -> Row:
113113
result = await conn.execute(
114114
sa.select(
@@ -122,8 +122,8 @@ async def _get_group_and_access_rights_or_raise(
122122
if not row:
123123
raise GroupNotFoundError(gid=group_id)
124124

125-
if permission:
126-
_check_group_permissions(row, caller_id, group_id, permission)
125+
if check_permission:
126+
_check_group_permissions(row, caller_id, group_id, check_permission)
127127

128128
return row
129129

@@ -274,29 +274,29 @@ async def get_user_group(
274274
"""
275275
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
276276
row = await _get_group_and_access_rights_or_raise(
277-
conn, caller_id=user_id, group_id=group_id, permission="read"
277+
conn, caller_id=user_id, group_id=group_id, check_permission="read"
278278
)
279279
group, access_rights = _to_group_info_tuple(row)
280280
return group, access_rights
281281

282282

283-
async def get_product_group_for_user(
283+
async def get_any_group_for_user(
284284
app: web.Application,
285285
connection: AsyncConnection | None = None,
286286
*,
287287
user_id: UserID,
288-
product_gid: GroupID,
288+
group_gid: GroupID,
289289
) -> tuple[Group, AccessRightsDict]:
290290
"""
291-
Returns product's group if user belongs to it, otherwise it
291+
Returns any group if user belongs to it (even if it has no permissions), otherwise it
292292
raises GroupNotFoundError
293293
"""
294294
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
295295
row = await _get_group_and_access_rights_or_raise(
296296
conn,
297297
caller_id=user_id,
298-
group_id=product_gid,
299-
permission=None,
298+
group_id=group_gid,
299+
check_permission=None,
300300
)
301301
group, access_rights = _to_group_info_tuple(row)
302302
return group, access_rights
@@ -363,7 +363,7 @@ async def update_standard_group(
363363

364364
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
365365
row = await _get_group_and_access_rights_or_raise(
366-
conn, caller_id=user_id, group_id=group_id, permission="write"
366+
conn, caller_id=user_id, group_id=group_id, check_permission="write"
367367
)
368368
assert row.gid == group_id # nosec
369369
# NOTE: update does not include access-rights
@@ -391,7 +391,7 @@ async def delete_standard_group(
391391
) -> None:
392392
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
393393
await _get_group_and_access_rights_or_raise(
394-
conn, caller_id=user_id, group_id=group_id, permission="delete"
394+
conn, caller_id=user_id, group_id=group_id, check_permission="delete"
395395
)
396396

397397
await conn.execute(
@@ -581,7 +581,7 @@ async def get_user_in_group(
581581
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
582582
# first check if the group exists
583583
await _get_group_and_access_rights_or_raise(
584-
conn, caller_id=caller_id, group_id=group_id, permission="read"
584+
conn, caller_id=caller_id, group_id=group_id, check_permission="read"
585585
)
586586

587587
# get the user with its permissions
@@ -611,7 +611,7 @@ async def update_user_in_group(
611611

612612
# first check if the group exists
613613
await _get_group_and_access_rights_or_raise(
614-
conn, caller_id=caller_id, group_id=group_id, permission="write"
614+
conn, caller_id=caller_id, group_id=group_id, check_permission="write"
615615
)
616616

617617
# now check the user exists
@@ -651,7 +651,7 @@ async def delete_user_from_group(
651651
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
652652
# first check if the group exists
653653
await _get_group_and_access_rights_or_raise(
654-
conn, caller_id=caller_id, group_id=group_id, permission="write"
654+
conn, caller_id=caller_id, group_id=group_id, check_permission="write"
655655
)
656656

657657
# check the user exists
@@ -714,7 +714,7 @@ async def add_new_user_in_group(
714714
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
715715
# first check if the group exists
716716
await _get_group_and_access_rights_or_raise(
717-
conn, caller_id=caller_id, group_id=group_id, permission="write"
717+
conn, caller_id=caller_id, group_id=group_id, check_permission="write"
718718
)
719719

720720
query = sa.select(users.c.id)

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ async def list_groups(request: web.Request):
5656
assert groups_by_type.primary
5757
assert groups_by_type.everyone
5858

59-
my_product_group = None
60-
6159
if product.group_id:
6260
with suppress(GroupNotFoundError):
6361
# Product is optional
@@ -66,16 +64,20 @@ async def list_groups(request: web.Request):
6664
user_id=req_ctx.user_id,
6765
product_gid=product.group_id,
6866
)
69-
70-
my_groups = MyGroupsGet(
71-
me=GroupGet.from_domain_model(*groups_by_type.primary),
72-
organizations=[
73-
GroupGet.from_domain_model(*gi) for gi in groups_by_type.standard
74-
],
75-
all=GroupGet.from_domain_model(*groups_by_type.everyone),
76-
product=(
77-
GroupGet.from_domain_model(*my_product_group) if my_product_group else None
78-
),
67+
else:
68+
my_product_group = None
69+
70+
if product.support_standard_group_id:
71+
my_support_group = await _groups_service.get_support_group_for_user_or_none(
72+
app=request.app,
73+
user_id=req_ctx.user_id,
74+
support_gid=product.support_standard_group_id,
75+
)
76+
else:
77+
my_support_group = None
78+
79+
my_groups = MyGroupsGet.from_domain_model(
80+
groups_by_type, my_product_group, my_support_group
7981
)
8082

8183
return envelope_json_response(my_groups)

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from contextlib import suppress
2+
13
from aiohttp import web
24
from models_library.basic_types import IDStr
35
from models_library.emails import LowerCaseEmailStr
@@ -16,7 +18,7 @@
1618

1719
from ..users import users_service
1820
from . import _groups_repository
19-
from .exceptions import GroupsError
21+
from .exceptions import GroupNotFoundError, GroupsError
2022

2123
#
2224
# GROUPS
@@ -71,11 +73,24 @@ async def get_product_group_for_user(
7173
Returns product's group if user belongs to it, otherwise it
7274
raises GroupNotFoundError
7375
"""
74-
return await _groups_repository.get_product_group_for_user(
75-
app, user_id=user_id, product_gid=product_gid
76+
return await _groups_repository.get_any_group_for_user(
77+
app, user_id=user_id, group_gid=product_gid
7678
)
7779

7880

81+
async def get_support_group_for_user_or_none(
82+
app: web.Application, *, user_id: UserID, support_gid: GroupID
83+
) -> tuple[Group, AccessRightsDict] | None:
84+
"""
85+
Returns support's group if user belongs to it, otherwise it
86+
"""
87+
with suppress(GroupNotFoundError):
88+
return await _groups_repository.get_any_group_for_user(
89+
app, user_id=user_id, group_gid=support_gid
90+
)
91+
return None
92+
93+
7994
#
8095
# CRUD operations on groups linked to a user
8196
#

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
auto_add_user_to_product_group,
88
get_group_from_gid,
99
get_product_group_for_user,
10+
get_support_group_for_user_or_none,
1011
is_user_by_email_in_group,
1112
list_all_user_groups_ids,
1213
list_group_members,
@@ -20,10 +21,11 @@
2021
"auto_add_user_to_product_group",
2122
"get_group_from_gid",
2223
"get_product_group_for_user",
24+
"get_support_group_for_user_or_none",
2325
"is_user_by_email_in_group",
2426
"list_all_user_groups_ids",
27+
"list_group_members",
2528
"list_user_groups_ids_with_read_access",
2629
"list_user_groups_with_read_access",
27-
"list_group_members",
2830
# nopycln: file
2931
)

services/web/server/src/simcore_service_webserver/users/_controller/rest/users_rest.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ async def get_my_profile(request: web.Request) -> web.Response:
5858
assert groups_by_type.primary
5959
assert groups_by_type.everyone
6060

61-
my_product_group = None
62-
6361
if product.group_id:
6462
with suppress(GroupNotFoundError):
6563
# Product is optional
@@ -68,13 +66,25 @@ async def get_my_profile(request: web.Request) -> web.Response:
6866
user_id=req_ctx.user_id,
6967
product_gid=product.group_id,
7068
)
69+
else:
70+
my_product_group = None
71+
72+
if product.support_standard_group_id:
73+
# Support group is optional
74+
my_support_group = await groups_service.get_support_group_for_user_or_none(
75+
app=request.app,
76+
user_id=req_ctx.user_id,
77+
support_gid=product.support_standard_group_id,
78+
)
79+
else:
80+
my_support_group = None
7181

7282
my_profile, preferences = await _users_service.get_my_profile(
7383
request.app, user_id=req_ctx.user_id, product_name=req_ctx.product_name
7484
)
7585

7686
profile = MyProfileRestGet.from_domain_model(
77-
my_profile, groups_by_type, my_product_group, preferences
87+
my_profile, groups_by_type, my_product_group, preferences, my_support_group
7888
)
7989

8090
return envelope_json_response(profile)

services/web/server/tests/unit/with_dbs/03/test_users_rest_profiles.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ async def private_user(
8888
"first_name": partial_first_name,
8989
"last_name": "Bond",
9090
"email": f"james{partial_email}",
91+
# Maximum privacy
9192
"privacy_hide_username": True,
9293
"privacy_hide_email": True,
9394
"privacy_hide_fullname": True,
@@ -108,6 +109,7 @@ async def semi_private_user(
108109
"first_name": partial_first_name,
109110
"last_name": "Maxwell",
110111
"email": "[email protected]",
112+
# Medium privacy
111113
"privacy_hide_username": False,
112114
"privacy_hide_email": True,
113115
"privacy_hide_fullname": False, # <--
@@ -128,6 +130,7 @@ async def public_user(
128130
"first_name": "Taylor",
129131
"last_name": "Swift",
130132
"email": f"taylor{partial_email}",
133+
# Fully public
131134
"privacy_hide_username": False,
132135
"privacy_hide_email": False,
133136
"privacy_hide_fullname": False,
@@ -407,6 +410,8 @@ async def test_get_profile(
407410
"thumbnail": None,
408411
}
409412

413+
assert got_profile_groups["support"] is None
414+
410415
sorted_by_group_id = functools.partial(sorted, key=lambda d: d["gid"])
411416
assert sorted_by_group_id(
412417
got_profile_groups["organizations"]

0 commit comments

Comments
 (0)