0
0
Fork 1
mirror of https://mau.dev/maunium/synapse.git synced 2024-11-14 14:01:59 +01:00

Fix filtering room types on remote rooms (#17434)

We can only fetch room types for rooms the server is in, so we need to
only filter rooms that we're joined to.

Also includes a perf fix to bulk fetch room types.
This commit is contained in:
Erik Johnston 2024-07-11 16:00:44 +01:00 committed by GitHub
parent 677142b6a9
commit 606da398fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 130 additions and 13 deletions

1
changelog.d/17434.bugfix Normal file
View file

@ -0,0 +1 @@
Fix bug in experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint when using room type filters and the user has one or more remote invites.

View file

@ -24,13 +24,7 @@ from typing import TYPE_CHECKING, Any, Dict, Final, List, Optional, Set, Tuple
import attr import attr
from immutabledict import immutabledict from immutabledict import immutabledict
from synapse.api.constants import ( from synapse.api.constants import AccountDataTypes, Direction, EventTypes, Membership
AccountDataTypes,
Direction,
EventContentFields,
EventTypes,
Membership,
)
from synapse.events import EventBase from synapse.events import EventBase
from synapse.events.utils import strip_event from synapse.events.utils import strip_event
from synapse.handlers.relations import BundledAggregations from synapse.handlers.relations import BundledAggregations
@ -959,11 +953,15 @@ class SlidingSyncHandler:
# provided in the list. `None` is a valid type for rooms which do not have a # provided in the list. `None` is a valid type for rooms which do not have a
# room type. # room type.
if filters.room_types is not None or filters.not_room_types is not None: if filters.room_types is not None or filters.not_room_types is not None:
# Make a copy so we don't run into an error: `Set changed size during room_to_type = await self.store.bulk_get_room_type(
# iteration`, when we filter out and remove items {
for room_id in filtered_room_id_set.copy(): room_id
create_event = await self.store.get_create_event_for_room(room_id) for room_id in filtered_room_id_set
room_type = create_event.content.get(EventContentFields.ROOM_TYPE) # We only know the room types for joined rooms
if sync_room_map[room_id].membership == Membership.JOIN
}
)
for room_id, room_type in room_to_type.items():
if ( if (
filters.room_types is not None filters.room_types is not None
and room_type not in filters.room_types and room_type not in filters.room_types

View file

@ -41,7 +41,7 @@ from typing import (
import attr import attr
from synapse.api.constants import EventTypes, Membership from synapse.api.constants import EventContentFields, EventTypes, Membership
from synapse.api.errors import NotFoundError, UnsupportedRoomVersionError from synapse.api.errors import NotFoundError, UnsupportedRoomVersionError
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
from synapse.events import EventBase from synapse.events import EventBase
@ -298,6 +298,56 @@ class StateGroupWorkerStore(EventsWorkerStore, SQLBaseStore):
create_event = await self.get_event(create_id) create_event = await self.get_event(create_id)
return create_event return create_event
@cached(max_entries=10000)
async def get_room_type(self, room_id: str) -> Optional[str]:
"""Get the room type for a given room. The server must be joined to the
given room.
"""
row = await self.db_pool.simple_select_one(
table="room_stats_state",
keyvalues={"room_id": room_id},
retcols=("room_type",),
allow_none=True,
desc="get_room_type",
)
if row is not None:
return row[0]
# If we haven't updated `room_stats_state` with the room yet, query the
# create event directly.
create_event = await self.get_create_event_for_room(room_id)
room_type = create_event.content.get(EventContentFields.ROOM_TYPE)
return room_type
@cachedList(cached_method_name="get_room_type", list_name="room_ids")
async def bulk_get_room_type(
self, room_ids: Set[str]
) -> Mapping[str, Optional[str]]:
"""Bulk fetch room types for the given rooms, the server must be in all
the rooms given.
"""
rows = await self.db_pool.simple_select_many_batch(
table="room_stats_state",
column="room_id",
iterable=room_ids,
retcols=("room_id", "room_type"),
desc="bulk_get_room_type",
)
# If we haven't updated `room_stats_state` with the room yet, query the
# create events directly. This should happen only rarely so we don't
# mind if we do this in a loop.
results = dict(rows)
for room_id in room_ids - results.keys():
create_event = await self.get_create_event_for_room(room_id)
room_type = create_event.content.get(EventContentFields.ROOM_TYPE)
results[room_id] = room_type
return results
@cached(max_entries=100000, iterable=True) @cached(max_entries=100000, iterable=True)
async def get_partial_current_state_ids(self, room_id: str) -> StateMap[str]: async def get_partial_current_state_ids(self, room_id: str) -> StateMap[str]:
"""Get the current state event ids for a room based on the """Get the current state event ids for a room based on the

View file

@ -35,6 +35,8 @@ from synapse.api.constants import (
RoomTypes, RoomTypes,
) )
from synapse.api.room_versions import RoomVersions from synapse.api.room_versions import RoomVersions
from synapse.events import make_event_from_dict
from synapse.events.snapshot import EventContext
from synapse.handlers.sliding_sync import RoomSyncConfig, StateValues from synapse.handlers.sliding_sync import RoomSyncConfig, StateValues
from synapse.rest import admin from synapse.rest import admin
from synapse.rest.client import knock, login, room from synapse.rest.client import knock, login, room
@ -2791,6 +2793,72 @@ class FilterRoomsTestCase(HomeserverTestCase):
self.assertEqual(filtered_room_map.keys(), {space_room_id}) self.assertEqual(filtered_room_map.keys(), {space_room_id})
def test_filter_room_types_with_invite_remote_room(self) -> None:
"""Test that we can apply a room type filter, even if we have an invite
for a remote room.
This is a regression test.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
# Create a fake remote invite and persist it.
invite_room_id = "!some:room"
invite_event = make_event_from_dict(
{
"room_id": invite_room_id,
"sender": "@user:test.serv",
"state_key": user1_id,
"depth": 1,
"origin_server_ts": 1,
"type": EventTypes.Member,
"content": {"membership": Membership.INVITE},
"auth_events": [],
"prev_events": [],
},
room_version=RoomVersions.V10,
)
invite_event.internal_metadata.outlier = True
invite_event.internal_metadata.out_of_band_membership = True
self.get_success(
self.store.maybe_store_room_on_outlier_membership(
room_id=invite_room_id, room_version=invite_event.room_version
)
)
context = EventContext.for_outlier(self.hs.get_storage_controllers())
persist_controller = self.hs.get_storage_controllers().persistence
assert persist_controller is not None
self.get_success(persist_controller.persist_event(invite_event, context))
# Create a normal room (no room type)
room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
after_rooms_token = self.event_sources.get_current_token()
# Get the rooms the user should be syncing with
sync_room_map = self.get_success(
self.sliding_sync_handler.get_sync_room_ids_for_user(
UserID.from_string(user1_id),
from_token=None,
to_token=after_rooms_token,
)
)
filtered_room_map = self.get_success(
self.sliding_sync_handler.filter_rooms(
UserID.from_string(user1_id),
sync_room_map,
SlidingSyncConfig.SlidingSyncList.Filters(
room_types=[None, RoomTypes.SPACE],
),
after_rooms_token,
)
)
self.assertEqual(filtered_room_map.keys(), {room_id, invite_room_id})
class SortRoomsTestCase(HomeserverTestCase): class SortRoomsTestCase(HomeserverTestCase):
""" """