Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC4171 Omit service members from room summary #17866

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions synapse/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ class EventTypes:

PollStart: Final = "m.poll.start"

MSC4171FunctionalMembers: Final = "io.element.functional_members"


class ToDeviceEventTypes:
RoomKeyRequest: Final = "m.room_key_request"
Expand Down
3 changes: 3 additions & 0 deletions synapse/config/experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,5 +448,8 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
# MSC4151: Report room API (Client-Server API)
self.msc4151_enabled: bool = experimental.get("msc4151_enabled", False)

# MSC4171: Service members
self.msc4171_enabled: bool = experimental.get("msc4171_enabled", False)

# MSC4210: Remove legacy mentions
self.msc4210_enabled: bool = experimental.get("msc4210_enabled", False)
36 changes: 31 additions & 5 deletions synapse/handlers/sliding_sync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def __init__(self, hs: "HomeServer"):
self.event_sources = hs.get_event_sources()
self.relations_handler = hs.get_relations_handler()
self.rooms_to_exclude_globally = hs.config.server.rooms_to_exclude_from_sync
self.hide_service_members_from_heroes = hs.config.experimental.msc4171_enabled
self.is_mine_id = hs.is_mine_id

self.connection_store = SlidingSyncConnectionStore(self.store)
Expand Down Expand Up @@ -765,15 +766,38 @@ async def get_room_sync_data(
membership_changed = False
name_changed = False
avatar_changed = False
ignore_members_for_heroes = []
if initial:
# Check whether the room has a name set
name_state_ids = await self.get_current_state_ids_at(
state_filter_types = [(EventTypes.Name, "")]

if self.hide_service_members_from_heroes:
state_filter_types.append((EventTypes.MSC4171FunctionalMembers, ""))

# Check whether the room has a name set (and fetch the service members)
state_ids = await self.get_current_state_ids_at(
room_id=room_id,
room_membership_for_user_at_to_token=room_membership_for_user_at_to_token,
state_filter=StateFilter.from_types([(EventTypes.Name, "")]),
state_filter=StateFilter.from_types(state_filter_types),
to_token=to_token,
)
name_event_id = name_state_ids.get((EventTypes.Name, ""))
name_event_id = state_ids.get((EventTypes.Name, ""))

if self.hide_service_members_from_heroes:
functional_members_id = state_ids.get(
(EventTypes.MSC4171FunctionalMembers, "")
)
if functional_members_id:
functional_members = await self.store.get_event(
functional_members_id, allow_none=True
)
# If there is a functional members event, and the service_members is an array, then apply the filter.
if functional_members and isinstance(
functional_members.content.get("service_members"), list
):
ignore_members_for_heroes = functional_members.content.get(
"service_members"
)

else:
assert from_bound is not None

Expand Down Expand Up @@ -833,7 +857,9 @@ async def get_room_sync_data(
# TODO: Reverse/rewind back to the `to_token`

hero_user_ids = extract_heroes_from_room_summary(
room_membership_summary, me=user.to_string()
room_membership_summary,
me=user.to_string(),
skip_user_ids=ignore_members_for_heroes,
)

# Fetch the membership counts for rooms we're joined to.
Expand Down
33 changes: 29 additions & 4 deletions synapse/handlers/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ def __init__(self, hs: "HomeServer"):
)

self.rooms_to_exclude_globally = hs.config.server.rooms_to_exclude_from_sync
self.hide_service_members_from_heroes = hs.config.experimental.msc4171_enabled

@overload
async def wait_for_sync_for_user(
Expand Down Expand Up @@ -1032,11 +1033,15 @@ async def compute_summary(
return None

last_event = last_events[-1]

state_filter_types = [(EventTypes.Name, ""), (EventTypes.CanonicalAlias, "")]

if self.hide_service_members_from_heroes:
state_filter_types.append((EventTypes.MSC4171FunctionalMembers, ""))

state_ids = await self._state_storage_controller.get_state_ids_for_event(
last_event.event_id,
state_filter=StateFilter.from_types(
[(EventTypes.Name, ""), (EventTypes.CanonicalAlias, "")]
),
state_filter=StateFilter.from_types(state_filter_types),
)

# this is heavily cached, thus: fast.
Expand Down Expand Up @@ -1069,6 +1074,24 @@ async def compute_summary(
if canonical_alias and canonical_alias.content.get("alias"):
return summary

ignore_members_for_heroes = []

if self.hide_service_members_from_heroes:
functional_members_id = state_ids.get(
(EventTypes.MSC4171FunctionalMembers, "")
)
if functional_members_id:
functional_members = await self.store.get_event(
functional_members_id, allow_none=True
)
# If there is a functional members event, and the service_members is an array, then apply the filter.
if functional_members and isinstance(
functional_members.content.get("service_members"), list
):
ignore_members_for_heroes = functional_members.content.get(
"service_members"
)

# FIXME: only build up a member_ids list for our heroes
member_ids = {}
for membership in (
Expand All @@ -1081,7 +1104,9 @@ async def compute_summary(
member_ids[user_id] = event_id

me = sync_config.user.to_string()
summary["m.heroes"] = extract_heroes_from_room_summary(details, me)
summary["m.heroes"] = extract_heroes_from_room_summary(
details, me, ignore_members_for_heroes
)

if not sync_config.filter_collection.lazy_load_members():
return summary
Expand Down
24 changes: 19 additions & 5 deletions synapse/storage/databases/main/roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -1754,7 +1754,9 @@ def __init__(


def extract_heroes_from_room_summary(
details: Mapping[str, MemberSummary], me: str
details: Mapping[str, MemberSummary],
me: str,
skip_user_ids: List[str] = None,
) -> List[str]:
"""Determine the users that represent a room, from the perspective of the `me` user.

Expand All @@ -1770,20 +1772,32 @@ def extract_heroes_from_room_summary(
details: Mapping from membership type to member summary. We expect
`MemberSummary.members` to already be sorted by `stream_ordering`.
me: The user for whom we are determining the heroes for.
skip_user_ids: Users to always ignore when building up a list of heros.

Returns a list (possibly empty) of heroes' mxids.
"""
empty_ms = MemberSummary([], 0)
skip_user_ids = skip_user_ids or []

joined_user_ids = [
r[0] for r in details.get(Membership.JOIN, empty_ms).members if r[0] != me
r[0]
for r in details.get(Membership.JOIN, empty_ms).members
if r[0] != me and r[0] not in skip_user_ids
]
invited_user_ids = [
r[0] for r in details.get(Membership.INVITE, empty_ms).members if r[0] != me
r[0]
for r in details.get(Membership.INVITE, empty_ms).members
if r[0] != me and r[0] not in skip_user_ids
]
gone_user_ids = [
r[0] for r in details.get(Membership.LEAVE, empty_ms).members if r[0] != me
] + [r[0] for r in details.get(Membership.BAN, empty_ms).members if r[0] != me]
r[0]
for r in details.get(Membership.LEAVE, empty_ms).members
if r[0] != me and r[0] not in skip_user_ids
] + [
r[0]
for r in details.get(Membership.BAN, empty_ms).members
if r[0] != me and r[0] not in skip_user_ids
]

# We expect `MemberSummary.members` to already be sorted by `stream_ordering`
if joined_user_ids or invited_user_ids:
Expand Down
43 changes: 43 additions & 0 deletions tests/storage/test_roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,49 @@ def test_extract_heroes_from_room_summary_first_five_joins(self) -> None:
hero_user_ids, [user1_id, user2_id, user3_id, user4_id, user5_id]
)

def test_extract_heroes_from_room_summary_exclude_service_members(self) -> None:
"""
Test that `extract_heroes_from_room_summary(...)` returns the first 5 joins.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
user3_id = self.register_user("user3", "pass")
user3_tok = self.login(user3_id, "pass")
user4_id = self.register_user("user4", "pass")
user4_tok = self.login(user4_id, "pass")
user5_id = self.register_user("user5", "pass")
user5_tok = self.login(user5_id, "pass")
user6_id = self.register_user("user6", "pass")
user6_tok = self.login(user6_id, "pass")
user7_id = self.register_user("user7", "pass")
user7_tok = self.login(user7_id, "pass")

# Setup the room (user1 is the creator and is joined to the room)
room_id = self.helper.create_room_as(user1_id, tok=user1_tok)

# User2 -> User7 joins
self.helper.join(room_id, user2_id, tok=user2_tok)
self.helper.join(room_id, user3_id, tok=user3_tok)
self.helper.join(room_id, user4_id, tok=user4_tok)
self.helper.join(room_id, user5_id, tok=user5_tok)
self.helper.join(room_id, user6_id, tok=user6_tok)
self.helper.join(room_id, user7_id, tok=user7_tok)

room_membership_summary = self.get_success(self.store.get_room_summary(room_id))

print(room_membership_summary)

hero_user_ids = extract_heroes_from_room_summary(
room_membership_summary, me="@fakuser", skip_user_ids=[user2_id, user3_id]
)

# First 5 users to join the room
self.assertListEqual(
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
hero_user_ids, [user1_id, user4_id, user5_id, user6_id, user7_id]
)

MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
def test_extract_heroes_from_room_summary_membership_order(self) -> None:
"""
Test that `extract_heroes_from_room_summary(...)` prefers joins/invites over
Expand Down
Loading