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

Commit 2bb4bd1

Browse files
author
David Robertson
authored
Test that bans win a join against a race when computing /sync response (#11701)
1 parent 6a04767 commit 2bb4bd1

File tree

3 files changed

+105
-3
lines changed

3 files changed

+105
-3
lines changed

changelog.d/11701.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a test for [an edge case](https://github.com/matrix-org/synapse/pull/11532#discussion_r769104461) in the `/sync` logic.

tests/handlers/test_sync.py

+94-3
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,22 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
1514
from typing import Optional
16-
from unittest.mock import Mock
15+
from unittest.mock import MagicMock, Mock, patch
1716

1817
from synapse.api.constants import EventTypes, JoinRules
1918
from synapse.api.errors import Codes, ResourceLimitError
2019
from synapse.api.filtering import Filtering
2120
from synapse.api.room_versions import RoomVersions
22-
from synapse.handlers.sync import SyncConfig
21+
from synapse.handlers.sync import SyncConfig, SyncResult
2322
from synapse.rest import admin
2423
from synapse.rest.client import knock, login, room
2524
from synapse.server import HomeServer
2625
from synapse.types import UserID, create_requester
2726

2827
import tests.unittest
2928
import tests.utils
29+
from tests.test_utils import make_awaitable
3030

3131

3232
class SyncTestCase(tests.unittest.HomeserverTestCase):
@@ -186,6 +186,97 @@ def test_unknown_room_version(self):
186186
self.assertNotIn(invite_room, [r.room_id for r in result.invited])
187187
self.assertNotIn(knock_room, [r.room_id for r in result.knocked])
188188

189+
def test_ban_wins_race_with_join(self):
190+
"""Rooms shouldn't appear under "joined" if a join loses a race to a ban.
191+
192+
A complicated edge case. Imagine the following scenario:
193+
194+
* you attempt to join a room
195+
* racing with that is a ban which comes in over federation, which ends up with
196+
an earlier stream_ordering than the join.
197+
* you get a sync response with a sync token which is _after_ the ban, but before
198+
the join
199+
* now your join lands; it is a valid event because its `prev_event`s predate the
200+
ban, but will not make it into current_state_events (because bans win over
201+
joins in state res, essentially).
202+
* When we do a sync from the incremental sync, the only event in the timeline
203+
is your join ... and yet you aren't joined.
204+
205+
The ban coming in over federation isn't crucial for this behaviour; the key
206+
requirements are:
207+
1. the homeserver generates a join event with prev_events that precede the ban
208+
(so that it passes the "are you banned" test)
209+
2. the join event has a stream_ordering after that of the ban.
210+
211+
We use monkeypatching to artificially trigger condition (1).
212+
"""
213+
# A local user Alice creates a room.
214+
owner = self.register_user("alice", "password")
215+
owner_tok = self.login(owner, "password")
216+
room_id = self.helper.create_room_as(owner, is_public=True, tok=owner_tok)
217+
218+
# Do a sync as Alice to get the latest event in the room.
219+
alice_sync_result: SyncResult = self.get_success(
220+
self.sync_handler.wait_for_sync_for_user(
221+
create_requester(owner), generate_sync_config(owner)
222+
)
223+
)
224+
self.assertEqual(len(alice_sync_result.joined), 1)
225+
self.assertEqual(alice_sync_result.joined[0].room_id, room_id)
226+
last_room_creation_event_id = (
227+
alice_sync_result.joined[0].timeline.events[-1].event_id
228+
)
229+
230+
# Eve, a ne'er-do-well, registers.
231+
eve = self.register_user("eve", "password")
232+
eve_token = self.login(eve, "password")
233+
234+
# Alice preemptively bans Eve.
235+
self.helper.ban(room_id, owner, eve, tok=owner_tok)
236+
237+
# Eve syncs.
238+
eve_requester = create_requester(eve)
239+
eve_sync_config = generate_sync_config(eve)
240+
eve_sync_after_ban: SyncResult = self.get_success(
241+
self.sync_handler.wait_for_sync_for_user(eve_requester, eve_sync_config)
242+
)
243+
244+
# Sanity check this sync result. We shouldn't be joined to the room.
245+
self.assertEqual(eve_sync_after_ban.joined, [])
246+
247+
# Eve tries to join the room. We monkey patch the internal logic which selects
248+
# the prev_events used when creating the join event, such that the ban does not
249+
# precede the join.
250+
mocked_get_prev_events = patch.object(
251+
self.hs.get_datastore(),
252+
"get_prev_events_for_room",
253+
new_callable=MagicMock,
254+
return_value=make_awaitable([last_room_creation_event_id]),
255+
)
256+
with mocked_get_prev_events:
257+
self.helper.join(room_id, eve, tok=eve_token)
258+
259+
# Eve makes a second, incremental sync.
260+
eve_incremental_sync_after_join: SyncResult = self.get_success(
261+
self.sync_handler.wait_for_sync_for_user(
262+
eve_requester,
263+
eve_sync_config,
264+
since_token=eve_sync_after_ban.next_batch,
265+
)
266+
)
267+
# Eve should not see herself as joined to the room.
268+
self.assertEqual(eve_incremental_sync_after_join.joined, [])
269+
270+
# If we did a third initial sync, we should _still_ see eve is not joined to the room.
271+
eve_initial_sync_after_join: SyncResult = self.get_success(
272+
self.sync_handler.wait_for_sync_for_user(
273+
eve_requester,
274+
eve_sync_config,
275+
since_token=None,
276+
)
277+
)
278+
self.assertEqual(eve_initial_sync_after_join.joined, [])
279+
189280

190281
_request_key = 0
191282

tests/rest/client/utils.py

+10
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,16 @@ def leave(self, room=None, user=None, expect_code=200, tok=None):
196196
expect_code=expect_code,
197197
)
198198

199+
def ban(self, room: str, src: str, targ: str, **kwargs: object):
200+
"""A convenience helper: `change_membership` with `membership` preset to "ban"."""
201+
self.change_membership(
202+
room=room,
203+
src=src,
204+
targ=targ,
205+
membership=Membership.BAN,
206+
**kwargs,
207+
)
208+
199209
def change_membership(
200210
self,
201211
room: str,

0 commit comments

Comments
 (0)