Skip to content

Commit

Permalink
Test device list tracking when we incorrectly believe a user is in th…
Browse files Browse the repository at this point in the history
…e room
  • Loading branch information
Sean Quah committed Sep 29, 2022
1 parent e65bbfd commit 363aea6
Showing 1 changed file with 259 additions and 0 deletions.
259 changes: 259 additions & 0 deletions tests/federation_room_join_partial_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2476,6 +2476,265 @@ func TestPartialStateJoin(t *testing.T) {
mustSyncUntilDeviceListsHas(t, alice, syncToken, "left", server.UserID("elsie"))
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
})

// setupUserIncorrectlyInRoom tricks the homeserver under test into thinking that @elsie is
// in the room when they have really been kicked. Once the partial state join completes,
// @elsie will be discovered to be no longer in the room.
setupUserIncorrectlyInRoom := func(
t *testing.T, deployment *docker.Deployment, alice *client.CSAPI,
server *federation.Server, room *federation.ServerRoom,
) (syncToken string, psjResult partialStateJoinResult) {
charlie := server.UserID("charlie")
derek := server.UserID("derek")
elsie := server.UserID("elsie")
fred := server.UserID("fred")

// The room starts with @charlie and @derek in it.
// @charlie makes @fred an admin.
// @charlie makes @derek a moderator.
var powerLevelsContent map[string]interface{}
json.Unmarshal(room.CurrentState("m.room.power_levels", "").Content(), &powerLevelsContent)
powerLevelsContent["users"].(map[string]interface{})[derek] = 50
powerLevelsContent["users"].(map[string]interface{})[fred] = 100
room.AddEvent(server.MustCreateEvent(t, room, b.Event{
Type: "m.room.power_levels",
StateKey: b.Ptr(""),
Sender: charlie,
Content: powerLevelsContent,
}))

// @fred joins and leaves the room.
fredJoinEvent := createJoinEvent(t, server, room, fred)
room.AddEvent(fredJoinEvent)
fredLeaveEvent := createLeaveEvent(t, server, room, fred)
room.AddEvent(fredLeaveEvent)

// @alice:hs1 joins the room.
psjResult = beginPartialStateJoin(t, server, room, alice)

// @elsie joins the room.
joinEvent := createJoinEvent(t, server, room, elsie)
room.AddEvent(joinEvent)
server.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{joinEvent.JSON()}, nil)
syncToken = awaitEventViaSync(t, alice, room.RoomID, joinEvent.EventID(), "")

// @fred "bans" @derek.
// This is incorrectly accepted, since the homeserver under test does not know whether
// @fred is really in the room.
// This event has to be a ban, rather than a kick, otherwise state resolution can bring
// @derek back into the room and ruin the test setup.
badKickEvent := server.MustCreateEvent(t, room, b.Event{
Type: "m.room.member",
StateKey: b.Ptr(derek),
Sender: fred,
Content: map[string]interface{}{"membership": "ban"},
AuthEvents: room.EventIDsOrReferences([]*gomatrixserverlib.Event{
room.CurrentState("m.room.create", ""),
room.CurrentState("m.room.power_levels", ""),
fredJoinEvent,
}),
})
room.Timeline = append(room.Timeline, badKickEvent)
room.Depth = badKickEvent.Depth()
room.ForwardExtremities = []string{badKickEvent.EventID()}
server.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{badKickEvent.JSON()}, nil)
syncToken = awaitEventViaSync(t, alice, room.RoomID, badKickEvent.EventID(), syncToken)

// @derek kicks @elsie.
// This is incorrectly rejected since the homeserver under test incorrectly thinks
// @derek had been kicked from the room.
kickEvent := server.MustCreateEvent(t, room, b.Event{
Type: "m.room.member",
StateKey: b.Ptr(elsie),
Sender: derek,
Content: map[string]interface{}{"membership": "leave"},
})
room.AddEvent(kickEvent)
server.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{kickEvent.JSON()}, nil)

// Ensure that the kick event has been persisted.
sentinelEvent := psjResult.CreateMessageEvent(t, "charlie", nil)
room.AddEvent(sentinelEvent)
server.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{sentinelEvent.JSON()}, nil)
syncToken = awaitEventViaSync(t, alice, room.RoomID, sentinelEvent.EventID(), syncToken)

// Check that the last kick was incorrectly rejected.
must.MatchResponse(t,
alice.DoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", room.RoomID, "event", kickEvent.EventID()}),
match.HTTPResponse{
StatusCode: 404,
JSON: []match.JSON{
match.JSONKeyEqual("errcode", "M_NOT_FOUND"),
},
},
)

return syncToken, psjResult
}

// test that device lists stop being tracked when it is discovered that a remote user is not
// in a room once a partial state join completes.
t.Run("Device list no longer tracked for user incorrectly believed to be in room", func(t *testing.T) {
alice, server, userDevicesChannel, room, _, cleanup := setupDeviceListCachingTest(t, deployment, "t36alice")
defer cleanup()

// The room starts with @charlie and @derek in it.
// @charlie leaves the room.
// @t36alice:hs1 joins the room.
// @elsie joins the room.
// @charlie "kicks" @derek, which the homeserver under test incorrectly accepts.
// @derek kicks @elsie, which the homeserver under test incorrectly rejects.
_, psjResult := setupUserIncorrectlyInRoom(t, deployment, alice, server, room)
defer psjResult.Destroy()
// @elsie is now incorrectly believed to be in the room.

// The homeserver under test incorrectly thinks it is subscribed to @elsie's device list updates.
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
mustQueryKeysWithoutFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))

// Finish the partial state join.
// The homeserver under test will discover that @elsie was actually not in the room.
psjResult.FinishStateRequest()
awaitPartialStateJoinCompletion(t, room, alice)

// @elsie's device list ought to no longer be cached.
// `device_lists.left` is not working yet: https://github.com/matrix-org/synapse/issues/13886
// mustSyncUntilDeviceListsHas(t, alice, syncToken, "left", server.UserID("elsie"))
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
})

// test that cached device lists are flushed when it is discovered that a remote user was
// not in a room the whole time once a partial state join completes.
t.Run("Device list tracking for user incorrectly believed to be in room when they rejoin before partial state join completes", func(t *testing.T) {
// Tracked in https://github.com/matrix-org/synapse/issues/13887.
t.Skip("This edge case is being ignored for now.")

alice, server, userDevicesChannel, room, _, cleanup := setupDeviceListCachingTest(t, deployment, "t37alice")
defer cleanup()

// The room starts with @charlie and @derek in it.
// @charlie leaves the room.
// @t37alice:hs1 joins the room.
// @elsie joins the room.
// @charlie "kicks" @derek, which the homeserver under test incorrectly accepts.
// @derek kicks @elsie, which the homeserver under test incorrectly rejects.
syncToken, psjResult := setupUserIncorrectlyInRoom(t, deployment, alice, server, room)
defer psjResult.Destroy()
// @elsie is now incorrectly believed to be in the room.

// The homeserver under test incorrectly thinks it is subscribed to @elsie's device list updates.
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
mustQueryKeysWithoutFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))

// @elsie rejoins the room.
joinEvent := createJoinEvent(t, server, room, server.UserID("elsie"))
room.AddEvent(joinEvent)
server.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{joinEvent.JSON()}, nil)
awaitEventViaSync(t, alice, room.RoomID, joinEvent.EventID(), syncToken)

// @elsie's device list is still cached.
mustQueryKeysWithoutFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))

// Finish the partial state join.
// The homeserver under test will discover that there was a period where @elsie was
// actually not in the room.
psjResult.FinishStateRequest()
awaitPartialStateJoinCompletion(t, room, alice)

// @elsie's device list ought to have been flushed from the cache.
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
})

// test that device lists stop being tracked when it is discovered that a remote user is not
// in a room once a partial state join completes.
// Similar to a previous test, except @elsie rejoins the room after the partial state join
// completes, so that their device list is being tracked again at the time we test the
// device list cache.
t.Run("Device list tracking for user incorrectly believed to be in room when they rejoin after partial state join completes", func(t *testing.T) {
alice, server, userDevicesChannel, room, _, cleanup := setupDeviceListCachingTest(t, deployment, "t38alice")
defer cleanup()

// The room starts with @charlie and @derek in it.
// @charlie leaves the room.
// @t38alice:hs1 joins the room.
// @elsie joins the room.
// @charlie "kicks" @derek, which the homeserver under test incorrectly accepts.
// @derek kicks @elsie, which the homeserver under test incorrectly rejects.
syncToken, psjResult := setupUserIncorrectlyInRoom(t, deployment, alice, server, room)
defer psjResult.Destroy()
// @elsie is now incorrectly believed to be in the room.

// The homeserver under test incorrectly thinks it is subscribed to @elsie's device list updates.
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
mustQueryKeysWithoutFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))

// Finish the partial state join.
// The homeserver under test will discover that @elsie was actually not in the room.
psjResult.FinishStateRequest()
awaitPartialStateJoinCompletion(t, room, alice)
// `device_lists.left` is not working yet: https://github.com/matrix-org/synapse/issues/13886
// mustSyncUntilDeviceListsHas(t, alice, syncToken, "left", server.UserID("elsie"))

// @elsie rejoins the room.
joinEvent := createJoinEvent(t, server, room, server.UserID("elsie"))
room.AddEvent(joinEvent)
server.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{joinEvent.JSON()}, nil)
awaitEventViaSync(t, alice, room.RoomID, joinEvent.EventID(), syncToken)

// @elsie's device list ought to have been flushed from the cache.
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
})

// test that cached device lists are flushed when it is discovered that a remote user did
// not share a room the whole time once a partial state join completes.
t.Run("Device list tracking for user incorrectly believed to be in room when they join another shared room before partial state join completes", func(t *testing.T) {
// Tracked in https://github.com/matrix-org/synapse/issues/13887.
t.Skip("This edge case is being ignored for now.")

alice, server, userDevicesChannel, room, _, cleanup := setupDeviceListCachingTest(t, deployment, "t39alice")
defer cleanup()

// The room starts with @charlie and @derek in it.
// @charlie leaves the room.
// @t39alice:hs1 joins the room.
// @elsie joins the room.
// @charlie "kicks" @derek, which the homeserver under test incorrectly accepts.
// @derek kicks @elsie, which the homeserver under test incorrectly rejects.
syncToken, psjResult := setupUserIncorrectlyInRoom(t, deployment, alice, server, room)
defer psjResult.Destroy()
// @elsie is now incorrectly believed to be in the room.

// The homeserver under test incorrectly thinks it is subscribed to @elsie's device list updates.
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
mustQueryKeysWithoutFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))

// @t39alice:hs1 creates a public room.
otherRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"})

// @elsie joins the room.
// The homeserver under test is now subscribed to @elsie's device list updates.
server.MustJoinRoom(t, deployment, "hs1", otherRoomID, server.UserID("elsie"))
alice.MustSyncUntil(t,
client.SyncReq{
Since: syncToken,
Filter: buildLazyLoadingSyncFilter(nil),
},
client.SyncJoinedTo(server.UserID("elsie"), otherRoomID),
)

// The cache device list for @elsie is stale, but the homeserver does not know that yet.
mustQueryKeysWithoutFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))

// Finish the partial state join.
// The homeserver under test will discover that @elsie was actually not in the room, and
// so did not share a room the whole time.
psjResult.FinishStateRequest()
awaitPartialStateJoinCompletion(t, room, alice)

// @elsie's device list ought to be evicted from the cache.
mustSyncUntilDeviceListsHas(t, alice, syncToken, "changed", server.UserID("elsie"))
mustQueryKeysWithFederationRequest(t, alice, userDevicesChannel, server.UserID("elsie"))
})
})
}

Expand Down

0 comments on commit 363aea6

Please sign in to comment.