Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,33 +244,31 @@ def from_domain_model(
GroupGet.from_domain_model(*gi) for gi in groups_by_type.standard
],
all=GroupGet.from_domain_model(*groups_by_type.everyone),
product=GroupGet.from_domain_model(*my_product_group)
if my_product_group
else None,
product=(
GroupGet.from_domain_model(*my_product_group)
if my_product_group
else None
),
)


class GroupUserGet(OutputSchemaWithoutCamelCase):

# Identifiers
id: Annotated[UserID | None, Field(description="the user's id")] = None
user_name: Annotated[UserNameID, Field(alias="userName")]
user_name: Annotated[
UserNameID | None, Field(alias="userName", description="None if private")
] = None
gid: Annotated[
GroupID | None,
Field(description="the user primary gid"),
] = None

# Private Profile
login: Annotated[
LowerCaseEmailStr | None,
Field(description="the user's email, if privacy settings allows"),
] = None
first_name: Annotated[
str | None, Field(description="If privacy settings allows")
] = None
last_name: Annotated[
str | None, Field(description="If privacy settings allows")
Field(description="the user's email or None if private"),
] = None
first_name: Annotated[str | None, Field(description="None if private")] = None
last_name: Annotated[str | None, Field(description="None if private")] = None
gravatar_id: Annotated[
str | None, Field(description="the user gravatar id hash", deprecated=True)
] = None
Expand Down Expand Up @@ -309,6 +307,11 @@ class GroupUserGet(OutputSchemaWithoutCamelCase):
"userName": "mrprivate",
"gid": "55",
},
# very private user
{
"id": "6",
"gid": "55",
},
{
"id": "56",
"userName": "mrpublic",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ class UserGet(OutputSchema):
# Public profile of a user subject to its privacy settings
user_id: UserID
group_id: GroupID
user_name: UserNameID
user_name: UserNameID | None = None
first_name: str | None = None
last_name: str | None = None
email: EmailStr | None = None
Expand Down
5 changes: 2 additions & 3 deletions packages/models-library/src/models_library/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
TypedDict,
)

from .basic_types import IDStr
from .users import UserID
from .users import UserID, UserNameID
from .utils.common_validators import create_enums_pre_validator

EVERYONE_GROUP_ID: Final[int] = 1
Expand Down Expand Up @@ -99,10 +98,10 @@ class GroupsByTypeTuple(NamedTuple):
class GroupMember(BaseModel):
# identifiers
id: UserID
name: IDStr
primary_gid: GroupID

# private profile
name: UserNameID | None
email: EmailStr | None
first_name: str | None
last_name: str | None
Expand Down
9 changes: 7 additions & 2 deletions packages/models-library/src/models_library/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@


class PrivacyDict(TypedDict):
hide_username: bool
hide_fullname: bool
hide_email: bool


class MyProfile(BaseModel):
id: UserID
user_name: IDStr
user_name: UserNameID
first_name: str | None
last_name: str | None
email: LowerCaseEmailStr
Expand All @@ -50,7 +51,11 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
"first_name": "PtN5Ab0uv",
"last_name": "",
"role": "GUEST",
"privacy": {"hide_email": True, "hide_fullname": False},
"privacy": {
"hide_email": True,
"hide_fullname": False,
"hide_username": False,
},
}
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""new users.privacy_hide_username column

Revision ID: 8403acca8759
Revises: f7f3c835f38a
Create Date: 2025-03-20 14:08:48.321587+00:00

"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "8403acca8759"
down_revision = "f7f3c835f38a"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"users",
sa.Column(
"privacy_hide_username",
sa.Boolean(),
server_default=sa.text("false"),
nullable=False,
),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("users", "privacy_hide_username")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@
#
# User Privacy Rules ------------------
#
sa.Column(
"privacy_hide_username",
sa.Boolean,
nullable=False,
server_default=expression.false(),
doc="If true, it hides users.name to others",
),
sa.Column(
"privacy_hide_fullname",
sa.Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,16 @@ def is_public(hide_attribute: Column, caller_id: int):
return hide_attribute.is_(False) | (users.c.id == caller_id)


def visible_user_profile_cols(caller_id: int):
def visible_user_profile_cols(caller_id: int, *, username_label: str):
"""Returns user profile columns with visibility constraints applied based on privacy settings."""
return (
sa.case(
(
is_private(users.c.privacy_hide_username, caller_id),
None,
),
else_=users.c.name,
).label(username_label),
sa.case(
(
is_private(users.c.privacy_hide_email, caller_id),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import contextlib
import re
from collections.abc import AsyncIterator
from datetime import datetime
from typing import Any, TypedDict

Expand Down Expand Up @@ -186,6 +188,30 @@ async def __aexit__(self, *args):
return await super().__aexit__(*args)


@contextlib.asynccontextmanager
async def switch_client_session_to(
client: TestClient, user: UserInfoDict
) -> AsyncIterator[TestClient]:
assert client.app

await client.post(f'{client.app.router["auth_logout"].url_for()}')
# sometimes 4xx if user already logged out. Ignore

resp = await client.post(
f'{client.app.router["auth_login"].url_for()}',
json={
"email": user["email"],
"password": user["raw_password"],
},
)
await assert_status(resp, status.HTTP_200_OK)

yield client

resp = await client.post(f'{client.app.router["auth_logout"].url_for()}')
await assert_status(resp, status.HTTP_200_OK)


class NewInvitation(NewUser):
def __init__(
self,
Expand Down
2 changes: 1 addition & 1 deletion services/web/server/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.61.2
0.61.3
2 changes: 1 addition & 1 deletion services/web/server/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.61.2
current_version = 0.61.3
commit = True
message = services/webserver api version: {current_version} → {new_version}
tag = False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
openapi: 3.1.0
info:
title: simcore-service-webserver
description: Main service with an interface (http-API & websockets) to the web front-end
version: 0.61.2
version: 0.61.3
servers:
- url: ''
description: webserver
Expand Down Expand Up @@ -11001,10 +11001,13 @@
title: Id
description: the user's id
userName:
type: string
maxLength: 100
minLength: 1
anyOf:
- type: string
maxLength: 100
minLength: 1
- type: 'null'
title: Username
description: None if private
gid:
anyOf:
- type: integer
Expand All @@ -11019,19 +11022,19 @@
format: email
- type: 'null'
title: Login
description: the user's email, if privacy settings allows
description: the user's email or None if private
first_name:
anyOf:
- type: string
- type: 'null'
title: First Name
description: If privacy settings allows
description: None if private
last_name:
anyOf:
- type: string
- type: 'null'
title: Last Name
description: If privacy settings allows
description: None if private
gravatar_id:
anyOf:
- type: string
Expand All @@ -11046,8 +11049,6 @@
description: If group is standard, these are these are the access rights
of the user to it.None if primary group.
type: object
required:
- userName
title: GroupUserGet
example:
accessRights:
Expand Down Expand Up @@ -15579,9 +15580,11 @@
title: Groupid
minimum: 0
userName:
type: string
maxLength: 100
minLength: 1
anyOf:
- type: string
maxLength: 100
minLength: 1
- type: 'null'
title: Username
firstName:
anyOf:
Expand All @@ -15603,7 +15606,6 @@
required:
- userId
- groupId
- userName
title: UserGet
UserNotification:
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,7 @@ async def get_user_from_email(
def _group_user_cols(caller_id: UserID):
return (
users.c.id,
users.c.name,
*visible_user_profile_cols(caller_id),
*visible_user_profile_cols(caller_id, username_label="name"),
users.c.primary_gid,
)

Expand Down
Loading
Loading