dendrite/syncapi/internal/keychange_test.go
PiotrKozimor 7823481a0e
Merge v0.8.9 (#13)
Squashed commit of the following:

commit b5c55faf98
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Fri Jul 1 12:00:32 2022 +0100

    Version 0.8.9 (#2549)

    * Version 0.8.9

    * Update changelog

commit b50a24c666
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Fri Jul 1 10:54:07 2022 +0100

    Roomserver producers package (#2546)

    * Give the roomserver a producers package

    * Change init point

    * Populate ACLs API

    * Fix build issues

    * `RoomEventProducer` naming

commit 89cd0e8fc1
Author: Till <2353100+S7evinK@users.noreply.github.com>
Date:   Fri Jul 1 11:49:26 2022 +0200

    Try to fix backfilling (#2548)

    * Try to fix backfilling

    * Return start/end to not confuse clients

    * Update GMSL

    * Update GMSL

commit 086f182e24
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Fri Jul 1 09:50:06 2022 +0100

    Disable WebAssembly builds for now

commit 54bed4c593
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Fri Jul 1 09:37:54 2022 +0100

    Blacklist `Guest users can join guest_access rooms` test until it can be investigated

commit 561c159ad7
Author: Till <2353100+S7evinK@users.noreply.github.com>
Date:   Thu Jun 30 12:34:37 2022 +0200

    Silence presence logs (#2547)

commit 519bc1124b
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Wed Jun 29 15:29:39 2022 +0100

    Add `evacuateUser` endpoint, use it when deactivating accounts (#2545)

    * Add `evacuateUser` endpoint, use it when deactivating accounts

    * Populate the API

    * Clean up user devices when deactivating

    * Include invites, delete pushers

commit 2dea466685
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Wed Jun 29 12:32:24 2022 +0100

    Return an error if trying to invite a malformed user ID (#2543)

commit 2086992caf
Author: Till <2353100+S7evinK@users.noreply.github.com>
Date:   Wed Jun 29 10:49:12 2022 +0200

    Don't return `end` if there are not more messages (#2542)

    * Be more spec compliant

    * Move lazyLoadMembers to own method

commit 920a20821b
Author: Jean Lucas <jean@4ray.co>
Date:   Mon Jun 27 04:15:19 2022 -0400

    Fix nats.go commit (#2540)

    Signed-off-by: Jean Lucas <jean@4ray.co>

commit 7120eb6bc9
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Wed Jun 15 14:27:07 2022 +0100

    Add `InputDeviceListUpdate` to the keyserver, remove old input API (#2536)

    * Add `InputDeviceListUpdate` to the keyserver, remove old input API

    * Fix copyright

    * Log more information when a device list update fails

commit 1b90cc9536
Author: Till <2353100+S7evinK@users.noreply.github.com>
Date:   Wed Jun 15 12:50:02 2022 +0200

    Fix rare panic when returning user devices over federation (#2534)

commit 4c2a10f1a6
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Mon Jun 13 15:11:10 2022 +0100

    Handle state before, send history visibility in output (#2532)

    * Check state before event

    * Tweaks

    * Refactor a bit, include in output events

    * Don't waste time if soft failed either

    * Tweak control flow, comments, use GMSL history visibility type

commit c500958583
Author: Emanuele Aliberti <dev@mtka.eu>
Date:   Mon Jun 13 13:08:46 2022 +0200

    generic CaddyFile in front of Dendrite (monolith) (#2531)

    for Caddy 2.5.x

    Co-authored-by: emanuele.aliberti <emanuele.aliberti@mtka.eu>

commit e1136f4d3e
Author: Till Faelligen <davidf@element.io>
Date:   Mon Jun 13 11:46:59 2022 +0200

    Make the linter happy again

commit 0a7f7dc716
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Mon Jun 13 10:16:30 2022 +0100

    Add `--difference` to `resolve-state` tool

commit 89d2adadbd
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Fri Jun 10 10:58:04 2022 +0100

    Attempt to raise the file descriptor limit at startup (#2527)

commit 1030072285
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Fri Jun 10 10:18:32 2022 +0100

    Rename the page to "Optimise your installation"

commit 16ed1633b6
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Fri Jun 10 10:15:14 2022 +0100

    Highlighting in docs

commit e2a64773ce
Author: Neil Alexander <neilalexander@users.noreply.github.com>
Date:   Fri Jun 10 10:14:15 2022 +0100

    Add new next steps page to the documentation

commit 660f7839f5
Author: Till <2353100+S7evinK@users.noreply.github.com>
Date:   Thu Jun 9 18:38:07 2022 +0200

    Correctly redact events over federation (#2526)

    * Ensure we check powerlevel/origin before redacting an event

    * Add passing test

    * Use pl.UserLevel

    * Make check more readable, also check for the sender
2022-07-04 17:39:22 +02:00

432 lines
14 KiB
Go

package internal
import (
"context"
"reflect"
"sort"
"testing"
keyapi "github.com/matrix-org/dendrite/keyserver/api"
"github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/syncapi/types"
userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
)
var (
syncingUser = "@alice:localhost"
emptyToken = types.StreamPosition(0)
)
type mockKeyAPI struct{}
func (k *mockKeyAPI) PerformUploadKeys(ctx context.Context, req *keyapi.PerformUploadKeysRequest, res *keyapi.PerformUploadKeysResponse) {
}
func (k *mockKeyAPI) SetUserAPI(i userapi.UserInternalAPI) {}
// PerformClaimKeys claims one-time keys for use in pre-key messages
func (k *mockKeyAPI) PerformClaimKeys(ctx context.Context, req *keyapi.PerformClaimKeysRequest, res *keyapi.PerformClaimKeysResponse) {
}
func (k *mockKeyAPI) PerformDeleteKeys(ctx context.Context, req *keyapi.PerformDeleteKeysRequest, res *keyapi.PerformDeleteKeysResponse) {
}
func (k *mockKeyAPI) PerformUploadDeviceKeys(ctx context.Context, req *keyapi.PerformUploadDeviceKeysRequest, res *keyapi.PerformUploadDeviceKeysResponse) {
}
func (k *mockKeyAPI) PerformUploadDeviceSignatures(ctx context.Context, req *keyapi.PerformUploadDeviceSignaturesRequest, res *keyapi.PerformUploadDeviceSignaturesResponse) {
}
func (k *mockKeyAPI) QueryKeys(ctx context.Context, req *keyapi.QueryKeysRequest, res *keyapi.QueryKeysResponse) {
}
func (k *mockKeyAPI) QueryKeyChanges(ctx context.Context, req *keyapi.QueryKeyChangesRequest, res *keyapi.QueryKeyChangesResponse) {
}
func (k *mockKeyAPI) QueryOneTimeKeys(ctx context.Context, req *keyapi.QueryOneTimeKeysRequest, res *keyapi.QueryOneTimeKeysResponse) {
}
func (k *mockKeyAPI) QueryDeviceMessages(ctx context.Context, req *keyapi.QueryDeviceMessagesRequest, res *keyapi.QueryDeviceMessagesResponse) {
}
func (k *mockKeyAPI) QuerySignatures(ctx context.Context, req *keyapi.QuerySignaturesRequest, res *keyapi.QuerySignaturesResponse) {
}
type mockRoomserverAPI struct {
api.RoomserverInternalAPITrace
roomIDToJoinedMembers map[string][]string
}
// QueryRoomsForUser retrieves a list of room IDs matching the given query.
func (s *mockRoomserverAPI) QueryRoomsForUser(ctx context.Context, req *api.QueryRoomsForUserRequest, res *api.QueryRoomsForUserResponse) error {
return nil
}
// QueryBulkStateContent does a bulk query for state event content in the given rooms.
func (s *mockRoomserverAPI) QueryBulkStateContent(ctx context.Context, req *api.QueryBulkStateContentRequest, res *api.QueryBulkStateContentResponse) error {
res.Rooms = make(map[string]map[gomatrixserverlib.StateKeyTuple]string)
if req.AllowWildcards && len(req.StateTuples) == 1 && req.StateTuples[0].EventType == gomatrixserverlib.MRoomMember && req.StateTuples[0].StateKey == "*" {
for _, roomID := range req.RoomIDs {
res.Rooms[roomID] = make(map[gomatrixserverlib.StateKeyTuple]string)
for _, userID := range s.roomIDToJoinedMembers[roomID] {
res.Rooms[roomID][gomatrixserverlib.StateKeyTuple{
EventType: gomatrixserverlib.MRoomMember,
StateKey: userID,
}] = "join"
}
}
}
return nil
}
// QuerySharedUsers returns a list of users who share at least 1 room in common with the given user.
func (s *mockRoomserverAPI) QuerySharedUsers(ctx context.Context, req *api.QuerySharedUsersRequest, res *api.QuerySharedUsersResponse) error {
roomsToQuery := req.IncludeRoomIDs
for roomID, members := range s.roomIDToJoinedMembers {
exclude := false
for _, excludeRoomID := range req.ExcludeRoomIDs {
if roomID == excludeRoomID {
exclude = true
break
}
}
if exclude {
continue
}
for _, userID := range members {
if userID == req.UserID {
roomsToQuery = append(roomsToQuery, roomID)
break
}
}
}
res.UserIDsToCount = make(map[string]int)
for _, roomID := range roomsToQuery {
for _, userID := range s.roomIDToJoinedMembers[roomID] {
res.UserIDsToCount[userID]++
}
}
return nil
}
type wantCatchup struct {
hasNew bool
changed []string
left []string
}
func assertCatchup(t *testing.T, hasNew bool, syncResponse *types.Response, want wantCatchup) {
if hasNew != want.hasNew {
t.Errorf("got hasNew=%v want %v", hasNew, want.hasNew)
}
sort.Strings(syncResponse.DeviceLists.Left)
if !reflect.DeepEqual(syncResponse.DeviceLists.Left, want.left) {
t.Errorf("device_lists.left got %v want %v", syncResponse.DeviceLists.Left, want.left)
}
sort.Strings(syncResponse.DeviceLists.Changed)
if !reflect.DeepEqual(syncResponse.DeviceLists.Changed, want.changed) {
t.Errorf("device_lists.changed got %v want %v", syncResponse.DeviceLists.Changed, want.changed)
}
}
func joinResponseWithRooms(syncResponse *types.Response, userID string, roomIDs []string) *types.Response {
for _, roomID := range roomIDs {
roomEvents := []gomatrixserverlib.ClientEvent{
{
Type: "m.room.member",
StateKey: &userID,
EventID: "$something:here",
Sender: userID,
RoomID: roomID,
Content: []byte(`{"membership":"join"}`),
},
}
jr := syncResponse.Rooms.Join[roomID]
jr.State.Events = roomEvents
syncResponse.Rooms.Join[roomID] = jr
}
return syncResponse
}
func leaveResponseWithRooms(syncResponse *types.Response, userID string, roomIDs []string) *types.Response {
for _, roomID := range roomIDs {
roomEvents := []gomatrixserverlib.ClientEvent{
{
Type: "m.room.member",
StateKey: &userID,
EventID: "$something:here",
Sender: userID,
RoomID: roomID,
Content: []byte(`{"membership":"leave"}`),
},
}
lr := syncResponse.Rooms.Leave[roomID]
lr.Timeline.Events = roomEvents
syncResponse.Rooms.Leave[roomID] = lr
}
return syncResponse
}
// tests that joining a room which results in sharing a new user includes that user in `changed`
func TestKeyChangeCatchupOnJoinShareNewUser(t *testing.T) {
newShareUser := "@bill:localhost"
newlyJoinedRoom := "!TestKeyChangeCatchupOnJoinShareNewUser:bar"
syncResponse := types.NewResponse()
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
rsAPI := &mockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyJoinedRoom: {syncingUser, newShareUser},
"!another:room": {syncingUser},
},
}
_, hasNew, err := DeviceListCatchup(context.Background(), &mockKeyAPI{}, rsAPI, syncingUser, syncResponse, emptyToken, emptyToken)
if err != nil {
t.Fatalf("DeviceListCatchup returned an error: %s", err)
}
assertCatchup(t, hasNew, syncResponse, wantCatchup{
hasNew: true,
changed: []string{newShareUser},
})
}
// tests that leaving a room which results in sharing no rooms with a user includes that user in `left`
func TestKeyChangeCatchupOnLeaveShareLeftUser(t *testing.T) {
removeUser := "@bill:localhost"
newlyLeftRoom := "!TestKeyChangeCatchupOnLeaveShareLeftUser:bar"
syncResponse := types.NewResponse()
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
rsAPI := &mockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyLeftRoom: {removeUser},
"!another:room": {syncingUser},
},
}
_, hasNew, err := DeviceListCatchup(context.Background(), &mockKeyAPI{}, rsAPI, syncingUser, syncResponse, emptyToken, emptyToken)
if err != nil {
t.Fatalf("DeviceListCatchup returned an error: %s", err)
}
assertCatchup(t, hasNew, syncResponse, wantCatchup{
hasNew: true,
left: []string{removeUser},
})
}
// tests that joining a room which doesn't result in sharing a new user results in no changes.
func TestKeyChangeCatchupOnJoinShareNoNewUsers(t *testing.T) {
existingUser := "@bob:localhost"
newlyJoinedRoom := "!TestKeyChangeCatchupOnJoinShareNoNewUsers:bar"
syncResponse := types.NewResponse()
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
rsAPI := &mockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyJoinedRoom: {syncingUser, existingUser},
"!another:room": {syncingUser, existingUser},
},
}
_, hasNew, err := DeviceListCatchup(context.Background(), &mockKeyAPI{}, rsAPI, syncingUser, syncResponse, emptyToken, emptyToken)
if err != nil {
t.Fatalf("Catchup returned an error: %s", err)
}
assertCatchup(t, hasNew, syncResponse, wantCatchup{
hasNew: false,
})
}
// tests that leaving a room which doesn't result in sharing no rooms with a user results in no changes.
func TestKeyChangeCatchupOnLeaveShareNoUsers(t *testing.T) {
existingUser := "@bob:localhost"
newlyLeftRoom := "!TestKeyChangeCatchupOnLeaveShareNoUsers:bar"
syncResponse := types.NewResponse()
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
rsAPI := &mockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyLeftRoom: {existingUser},
"!another:room": {syncingUser, existingUser},
},
}
_, hasNew, err := DeviceListCatchup(context.Background(), &mockKeyAPI{}, rsAPI, syncingUser, syncResponse, emptyToken, emptyToken)
if err != nil {
t.Fatalf("DeviceListCatchup returned an error: %s", err)
}
assertCatchup(t, hasNew, syncResponse, wantCatchup{
hasNew: false,
})
}
// tests that not joining any rooms (but having messages in the response) do not result in changes.
func TestKeyChangeCatchupNoNewJoinsButMessages(t *testing.T) {
existingUser := "@bob1:localhost"
roomID := "!TestKeyChangeCatchupNoNewJoinsButMessages:bar"
syncResponse := types.NewResponse()
empty := ""
roomStateEvents := []gomatrixserverlib.ClientEvent{
{
Type: "m.room.name",
StateKey: &empty,
EventID: "$something:here",
Sender: existingUser,
RoomID: roomID,
Content: []byte(`{"name":"The Room Name"}`),
},
}
roomTimelineEvents := []gomatrixserverlib.ClientEvent{
{
Type: "m.room.message",
EventID: "$something1:here",
Sender: existingUser,
RoomID: roomID,
Content: []byte(`{"body":"Message 1"}`),
},
{
Type: "m.room.message",
EventID: "$something2:here",
Sender: syncingUser,
RoomID: roomID,
Content: []byte(`{"body":"Message 2"}`),
},
{
Type: "m.room.message",
EventID: "$something3:here",
Sender: existingUser,
RoomID: roomID,
Content: []byte(`{"body":"Message 3"}`),
},
}
jr := syncResponse.Rooms.Join[roomID]
jr.State.Events = roomStateEvents
jr.Timeline.Events = roomTimelineEvents
syncResponse.Rooms.Join[roomID] = jr
rsAPI := &mockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
roomID: {syncingUser, existingUser},
},
}
_, hasNew, err := DeviceListCatchup(context.Background(), &mockKeyAPI{}, rsAPI, syncingUser, syncResponse, emptyToken, emptyToken)
if err != nil {
t.Fatalf("DeviceListCatchup returned an error: %s", err)
}
assertCatchup(t, hasNew, syncResponse, wantCatchup{
hasNew: false,
})
}
// tests that joining/leaving multiple rooms can result in both `changed` and `left` and they are not duplicated.
func TestKeyChangeCatchupChangeAndLeft(t *testing.T) {
newShareUser := "@berta:localhost"
newShareUser2 := "@bobby:localhost"
newlyLeftUser := "@charlie:localhost"
newlyLeftUser2 := "@debra:localhost"
newlyJoinedRoom := "!join:bar"
newlyLeftRoom := "!left:bar"
syncResponse := types.NewResponse()
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
rsAPI := &mockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyJoinedRoom: {syncingUser, newShareUser, newShareUser2},
newlyLeftRoom: {newlyLeftUser, newlyLeftUser2},
"!another:room": {syncingUser},
},
}
_, hasNew, err := DeviceListCatchup(context.Background(), &mockKeyAPI{}, rsAPI, syncingUser, syncResponse, emptyToken, emptyToken)
if err != nil {
t.Fatalf("Catchup returned an error: %s", err)
}
assertCatchup(t, hasNew, syncResponse, wantCatchup{
hasNew: true,
changed: []string{newShareUser, newShareUser2},
left: []string{newlyLeftUser, newlyLeftUser2},
})
}
// tests that joining/leaving the SAME room puts users in `left` if the final state is leave.
// NB: Consider the case:
// - Alice and Bob are in a room.
// - Alice goes offline, Charlie joins, sends encrypted messages then leaves the room.
// - Alice comes back online. Technically nothing has changed in the set of users between those two points in time,
// it's still just (Alice,Bob) but then we won't be tracking Charlie -- is this okay though? It's device keys
// which are only relevant when actively sending events I think? And if Alice does need the keys she knows
// charlie's (user_id, device_id) so can just hit /keys/query - no need to keep updated about it because she
// doesn't share any rooms with him.
// Ergo, we put them in `left` as it is simpler.
func TestKeyChangeCatchupChangeAndLeftSameRoom(t *testing.T) {
newShareUser := "@berta:localhost"
newShareUser2 := "@bobby:localhost"
roomID := "!join:bar"
syncResponse := types.NewResponse()
roomEvents := []gomatrixserverlib.ClientEvent{
{
Type: "m.room.member",
StateKey: &syncingUser,
EventID: "$something:here",
Sender: syncingUser,
RoomID: roomID,
Content: []byte(`{"membership":"join"}`),
},
{
Type: "m.room.message",
EventID: "$something2:here",
Sender: syncingUser,
RoomID: roomID,
Content: []byte(`{"body":"now I leave you"}`),
},
{
Type: "m.room.member",
StateKey: &syncingUser,
EventID: "$something3:here",
Sender: syncingUser,
RoomID: roomID,
Content: []byte(`{"membership":"leave"}`),
},
{
Type: "m.room.member",
StateKey: &syncingUser,
EventID: "$something4:here",
Sender: syncingUser,
RoomID: roomID,
Content: []byte(`{"membership":"join"}`),
},
{
Type: "m.room.message",
EventID: "$something5:here",
Sender: syncingUser,
RoomID: roomID,
Content: []byte(`{"body":"now I am back, and I leave you for good"}`),
},
{
Type: "m.room.member",
StateKey: &syncingUser,
EventID: "$something6:here",
Sender: syncingUser,
RoomID: roomID,
Content: []byte(`{"membership":"leave"}`),
},
}
lr := syncResponse.Rooms.Leave[roomID]
lr.Timeline.Events = roomEvents
syncResponse.Rooms.Leave[roomID] = lr
rsAPI := &mockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
roomID: {newShareUser, newShareUser2},
"!another:room": {syncingUser},
},
}
_, hasNew, err := DeviceListCatchup(
context.Background(), &mockKeyAPI{}, rsAPI, syncingUser, syncResponse, emptyToken, emptyToken,
)
if err != nil {
t.Fatalf("DeviceListCatchup returned an error: %s", err)
}
assertCatchup(t, hasNew, syncResponse, wantCatchup{
hasNew: true,
left: []string{newShareUser, newShareUser2},
})
}