Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Do not include rooms with an unknown room version in a sync response. #10644

Merged
merged 5 commits into from
Aug 19, 2021
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
1 change: 1 addition & 0 deletions changelog.d/10644.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rooms with unsupported room versions are no longer returned via `/sync`.
1 change: 1 addition & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ files =
tests/test_utils,
tests/handlers/test_password_providers.py,
tests/handlers/test_room_summary.py,
tests/handlers/test_sync.py,
tests/rest/client/v1/test_login.py,
tests/rest/client/v2_alpha/test_auth.py,
tests/util/test_itertools.py,
Expand Down
7 changes: 5 additions & 2 deletions synapse/handlers/sync.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Copyright 2015, 2016 OpenMarket Ltd
# Copyright 2018, 2019 New Vector Ltd
# Copyright 2015-2021 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,6 +30,7 @@

from synapse.api.constants import AccountDataTypes, EventTypes, Membership
from synapse.api.filtering import FilterCollection
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.events import EventBase
from synapse.logging.context import current_context
from synapse.logging.opentracing import SynapseTags, log_kv, set_tag, start_active_span
Expand Down Expand Up @@ -1843,6 +1843,9 @@ async def _get_all_rooms(
knocked = []

for event in room_list:
if event.room_version_id not in KNOWN_ROOM_VERSIONS:
continue

if event.membership == Membership.JOIN:
room_entries.append(
RoomSyncResultBuilder(
Expand Down
8 changes: 5 additions & 3 deletions synapse/storage/databases/main/roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,10 @@ def _get_rooms_for_local_user_where_membership_is_txn(
)

sql = """
SELECT room_id, e.sender, c.membership, event_id, e.stream_ordering
SELECT room_id, e.sender, c.membership, event_id, e.stream_ordering, r.room_version
FROM local_current_membership AS c
INNER JOIN events AS e USING (room_id, event_id)
INNER JOIN rooms AS r USING (room_id)
WHERE
user_id = ?
AND %s
Expand All @@ -397,7 +398,7 @@ def _get_rooms_for_local_user_where_membership_is_txn(
)

txn.execute(sql, (user_id, *args))
results = [RoomsForUser(**r) for r in self.db_pool.cursor_to_dict(txn)]
results = [RoomsForUser(*r) for r in txn]

return results

Expand Down Expand Up @@ -447,7 +448,8 @@ async def get_rooms_for_user_with_stream_ordering(

Returns:
Returns the rooms the user is in currently, along with the stream
ordering of the most recent join for that user and room.
ordering of the most recent join for that user and room, along with
the room version of the room.
"""
return await self.db_pool.runInteraction(
"get_rooms_for_user_with_stream_ordering",
Expand Down
1 change: 1 addition & 0 deletions synapse/storage/roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class RoomsForUser:
membership: str
event_id: str
stream_ordering: int
room_version_id: str


@attr.s(slots=True, frozen=True, weakref_slot=True, auto_attribs=True)
Expand Down
137 changes: 131 additions & 6 deletions tests/handlers/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Optional

from synapse.api.constants import EventTypes, JoinRules
from synapse.api.errors import Codes, ResourceLimitError
from synapse.api.filtering import DEFAULT_FILTER_COLLECTION
from synapse.api.room_versions import RoomVersions
from synapse.handlers.sync import SyncConfig
from synapse.rest import admin
from synapse.rest.client import knock, login, room
from synapse.server import HomeServer
from synapse.types import UserID, create_requester

import tests.unittest
Expand All @@ -24,8 +31,14 @@
class SyncTestCase(tests.unittest.HomeserverTestCase):
"""Tests Sync Handler."""

def prepare(self, reactor, clock, hs):
self.hs = hs
servlets = [
admin.register_servlets,
knock.register_servlets,
login.register_servlets,
room.register_servlets,
]

def prepare(self, reactor, clock, hs: HomeServer):
self.sync_handler = self.hs.get_sync_handler()
self.store = self.hs.get_datastore()

Expand Down Expand Up @@ -68,12 +81,124 @@ def test_wait_for_sync_for_user_auth_blocking(self):
)
self.assertEquals(e.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)

def test_unknown_room_version(self):
"""
A room with an unknown room version should not break sync (and should be excluded).
"""
inviter = self.register_user("creator", "pass", admin=True)
inviter_tok = self.login("@creator:test", "pass")

user = self.register_user("user", "pass")
tok = self.login("user", "pass")

# Do an initial sync on a different device.
requester = create_requester(user)
initial_result = self.get_success(
self.sync_handler.wait_for_sync_for_user(
requester, sync_config=generate_sync_config(user, device_id="dev")
)
)

# Create a room as the user.
joined_room = self.helper.create_room_as(user, tok=tok)

# Invite the user to the room as someone else.
invite_room = self.helper.create_room_as(inviter, tok=inviter_tok)
self.helper.invite(invite_room, targ=user, tok=inviter_tok)

knock_room = self.helper.create_room_as(
inviter, room_version=RoomVersions.V7.identifier, tok=inviter_tok
)
self.helper.send_state(
knock_room,
EventTypes.JoinRules,
{"join_rule": JoinRules.KNOCK},
tok=inviter_tok,
)
channel = self.make_request(
"POST",
"/_matrix/client/r0/knock/%s" % (knock_room,),
b"{}",
tok,
)
self.assertEquals(200, channel.code, channel.result)

# The rooms should appear in the sync response.
result = self.get_success(
self.sync_handler.wait_for_sync_for_user(
requester, sync_config=generate_sync_config(user)
)
)
self.assertIn(joined_room, [r.room_id for r in result.joined])
self.assertIn(invite_room, [r.room_id for r in result.invited])
self.assertIn(knock_room, [r.room_id for r in result.knocked])

# Test a incremental sync (by providing a since_token).
result = self.get_success(
self.sync_handler.wait_for_sync_for_user(
requester,
sync_config=generate_sync_config(user, device_id="dev"),
since_token=initial_result.next_batch,
)
)
self.assertIn(joined_room, [r.room_id for r in result.joined])
self.assertIn(invite_room, [r.room_id for r in result.invited])
self.assertIn(knock_room, [r.room_id for r in result.knocked])

# Poke the database and update the room version to an unknown one.
for room_id in (joined_room, invite_room, knock_room):
self.get_success(
self.hs.get_datastores().main.db_pool.simple_update(
"rooms",
keyvalues={"room_id": room_id},
updatevalues={"room_version": "unknown-room-version"},
desc="updated-room-version",
)
)

# Blow away caches (supported room versions can only change due to a restart).
self.get_success(
self.store.get_rooms_for_user_with_stream_ordering.invalidate_all()
)
self.store._get_event_cache.clear()

# The rooms should be excluded from the sync response.
# Get a new request key.
result = self.get_success(
self.sync_handler.wait_for_sync_for_user(
requester, sync_config=generate_sync_config(user)
)
)
self.assertNotIn(joined_room, [r.room_id for r in result.joined])
self.assertNotIn(invite_room, [r.room_id for r in result.invited])
self.assertNotIn(knock_room, [r.room_id for r in result.knocked])

# The rooms should also not be in an incremental sync.
result = self.get_success(
self.sync_handler.wait_for_sync_for_user(
requester,
sync_config=generate_sync_config(user, device_id="dev"),
since_token=initial_result.next_batch,
)
)
self.assertNotIn(joined_room, [r.room_id for r in result.joined])
self.assertNotIn(invite_room, [r.room_id for r in result.invited])
self.assertNotIn(knock_room, [r.room_id for r in result.knocked])


_request_key = 0


def generate_sync_config(user_id: str) -> SyncConfig:
def generate_sync_config(
user_id: str, device_id: Optional[str] = "device_id"
) -> SyncConfig:
"""Generate a sync config (with a unique request key)."""
global _request_key
_request_key += 1
return SyncConfig(
user=UserID(user_id.split(":")[0][1:], user_id.split(":")[1]),
user=UserID.from_string(user_id),
filter_collection=DEFAULT_FILTER_COLLECTION,
is_guest=False,
request_key="request_key",
device_id="device_id",
request_key=("request_key", _request_key),
device_id=device_id,
)
1 change: 1 addition & 0 deletions tests/replication/slave/storage/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def test_invites(self):
"invite",
event.event_id,
event.internal_metadata.stream_ordering,
RoomVersions.V1.identifier,
)
],
)
Expand Down