Skip to content

Commit 3e9b78b

Browse files
committed
@odeimaiz review: adds username
1 parent 740c711 commit 3e9b78b

File tree

8 files changed

+66
-18
lines changed

8 files changed

+66
-18
lines changed

api/specs/web-server/_groups.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ async def add_group_user(
106106
_body: GroupUserAdd,
107107
):
108108
"""
109-
Adds a user to an organization group
109+
Adds a user to an organization group using their username, user ID, or email (subject to privacy settings
110110
"""
111111

112112

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ class GroupUserAdd(InputSchema):
288288
"""
289289

290290
uid: UserID | None = None
291+
user_name: Annotated[IDStr | None, Field(alias="userName")] = None
291292
email: Annotated[
292293
LowerCaseEmailStr | None,
293294
Field(
@@ -296,7 +297,7 @@ class GroupUserAdd(InputSchema):
296297
] = None
297298

298299
_check_uid_or_email = model_validator(mode="after")(
299-
create__check_only_one_is_set__root_validator(["uid", "email"])
300+
create__check_only_one_is_set__root_validator(["uid", "email", "user_name"])
300301
)
301302

302303
model_config = ConfigDict(

packages/models-library/src/models_library/utils/common_validators.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ def null_or_none_str_to_none_validator(value: Any):
8787
return value
8888

8989

90-
def create__check_only_one_is_set__root_validator(alternative_field_names: list[str]):
90+
def create__check_only_one_is_set__root_validator(
91+
mutually_exclusive_field_names: list[str],
92+
):
9193
"""Ensure exactly one and only one of the alternatives is set
9294
9395
NOTE: a field is considered here `unset` when it is `not None`. When None
@@ -104,17 +106,16 @@ def create__check_only_one_is_set__root_validator(alternative_field_names: list[
104106
"""
105107

106108
def _validator(cls: type[BaseModel], values):
107-
assert set(alternative_field_names).issubset(cls.model_fields) # nosec
108-
109+
assert set(mutually_exclusive_field_names).issubset( # nosec
110+
cls.model_fields
111+
), f"Invalid {mutually_exclusive_field_names=} passed in the factory arguments"
109112
got = {
110113
field_name: getattr(values, field_name)
111-
for field_name in alternative_field_names
114+
for field_name in mutually_exclusive_field_names
112115
}
113116

114117
if not functools.reduce(operator.xor, (v is not None for v in got.values())):
115-
msg = (
116-
f"Either { 'or'.join(got.keys()) } must be set, but not both. Got {got}"
117-
)
118+
msg = f"Either { ' or '.join(got.keys()) } must be set, but not both. Got {got}"
118119
raise ValueError(msg)
119120
return values
120121

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,8 @@ paths:
634634
tags:
635635
- groups
636636
summary: Add Group User
637-
description: Adds a user to an organization group
637+
description: Adds a user to an organization group using their username, user
638+
ID, or email (subject to privacy settings
638639
operationId: add_group_user
639640
parameters:
640641
- name: gid
@@ -9911,6 +9912,13 @@ components:
99119912
minimum: 0
99129913
- type: 'null'
99139914
title: Uid
9915+
userName:
9916+
anyOf:
9917+
- type: string
9918+
maxLength: 100
9919+
minLength: 1
9920+
- type: 'null'
9921+
title: Username
99149922
email:
99159923
anyOf:
99169924
- type: string

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from aiohttp import web
2+
from models_library.basic_types import IDStr
23
from models_library.emails import LowerCaseEmailStr
34
from models_library.groups import (
45
AccessRightsDict,
@@ -236,12 +237,17 @@ async def auto_add_user_to_product_group(
236237
)
237238

238239

240+
def _only_one_true(*args):
241+
return sum(bool(arg) for arg in args) == 1
242+
243+
239244
async def add_user_in_group(
240245
app: web.Application,
241246
user_id: UserID,
242247
group_id: GroupID,
243248
*,
244249
new_user_id: UserID | None = None,
250+
new_user_name: IDStr | None = None,
245251
new_user_email: EmailStr | None = None,
246252
access_rights: AccessRightsDict | None = None,
247253
) -> None:
@@ -251,9 +257,8 @@ async def add_user_in_group(
251257
UserInGroupNotFoundError
252258
GroupsException
253259
"""
254-
255-
if not new_user_id and not new_user_email:
256-
msg = "Invalid method call, missing user id or user email"
260+
if not _only_one_true(new_user_id, new_user_name, new_user_email):
261+
msg = "Invalid method call, required one of these: user id, username or user email, none provided"
257262
raise GroupsError(msg=msg)
258263

259264
if new_user_email:

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import sqlalchemy as sa
55
from aiohttp import web
6+
from models_library.basic_types import IDStr
67
from models_library.groups import (
78
AccessRightsDict,
89
Group,
@@ -589,7 +590,9 @@ async def add_new_user_in_group(
589590
*,
590591
user_id: UserID,
591592
group_id: GroupID,
592-
new_user_id: UserID,
593+
# either user_id or user_name
594+
new_user_id: UserID | None = None,
595+
new_user_name: IDStr | None = None,
593596
access_rights: AccessRightsDict | None = None,
594597
) -> None:
595598
"""
@@ -602,10 +605,17 @@ async def add_new_user_in_group(
602605
)
603606
_check_group_permissions(group, user_id, group_id, "write")
604607

608+
query = sa.select(sa.func.count())
609+
if new_user_id:
610+
query = query.where(users.c.id == new_user_id)
611+
elif new_user_name:
612+
query = query.where(users.c.name == new_user_name)
613+
else:
614+
msg = "Either user name or id but none provided"
615+
raise ValueError(msg)
616+
605617
# now check the new user exists
606-
users_count = await conn.scalar(
607-
sa.select(sa.func.count()).where(users.c.id == new_user_id)
608-
)
618+
users_count = await conn.scalar(query)
609619
if not users_count:
610620
assert new_user_id is not None # nosec
611621
raise UserInGroupNotFoundError(uid=new_user_id, gid=group_id)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ async def delete_group(request: web.Request):
163163
@login_required
164164
@permission_required("groups.*")
165165
@handle_plugin_requests_exceptions
166-
async def get_group_users(request: web.Request):
166+
async def get_all_group_users(request: web.Request):
167167
"""Gets users in organization groups"""
168168
req_ctx = GroupsRequestContext.model_validate(request)
169169
path_params = parse_request_path_parameters_as(GroupsPathParams, request)
@@ -194,6 +194,7 @@ async def add_group_user(request: web.Request):
194194
req_ctx.user_id,
195195
path_params.gid,
196196
new_user_id=added.uid,
197+
new_user_name=added.user_name,
197198
new_user_email=added.email,
198199
)
199200

services/web/server/tests/unit/isolated/test_groups_models.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import models_library.groups
2+
import pytest
23
import simcore_postgres_database.models.groups
34
from faker import Faker
45
from models_library.api_schemas_webserver._base import OutputSchema
56
from models_library.api_schemas_webserver.groups import (
67
GroupCreate,
78
GroupGet,
89
GroupUpdate,
10+
GroupUserAdd,
911
GroupUserGet,
1012
)
1113
from models_library.groups import (
@@ -17,6 +19,7 @@
1719
OrganizationUpdate,
1820
)
1921
from models_library.utils.enums import enum_to_dict
22+
from pydantic import ValidationError
2023

2124

2225
def test_models_library_and_postgress_database_enums_are_equivalent():
@@ -100,3 +103,22 @@ def test_input_schemas_to_models(faker: Faker):
100103
domain_model = input_schema.to_model()
101104
assert isinstance(domain_model, OrganizationUpdate)
102105
assert domain_model.name == input_schema.label
106+
107+
108+
def test_group_user_add_options(faker: Faker):
109+
def _only_one_true(*args):
110+
return sum(bool(arg) for arg in args) == 1
111+
112+
input_schema = GroupUserAdd(uid=faker.pyint())
113+
assert input_schema.uid
114+
assert _only_one_true(input_schema.uid, input_schema.user_name, input_schema.email)
115+
116+
input_schema = GroupUserAdd(userName=faker.user_name())
117+
assert input_schema.user_name
118+
assert _only_one_true(input_schema.uid, input_schema.user_name, input_schema.email)
119+
120+
input_schema = GroupUserAdd(email=faker.email())
121+
assert _only_one_true(input_schema.uid, input_schema.user_name, input_schema.email)
122+
123+
with pytest.raises(ValidationError):
124+
GroupUserAdd(userName=faker.user_name(), email=faker.email())

0 commit comments

Comments
 (0)