Skip to content

Commit 8cbbea4

Browse files
authored
Merge branch 'main' into s7evink/eventsize
2 parents f8e4e05 + 93ccbc8 commit 8cbbea4

File tree

4 files changed

+181
-108
lines changed

4 files changed

+181
-108
lines changed

internal/client/client.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,53 @@ func (c *CSAPI) LoginUser(t *testing.T, localpart, password string, opts ...Logi
459459
return userID, accessToken, deviceID
460460
}
461461

462+
// LoginUserWithRefreshToken will log in to a homeserver, with refresh token enabled,
463+
// and create a new device on an existing user.
464+
func (c *CSAPI) LoginUserWithRefreshToken(t *testing.T, localpart, password string) (userID, accessToken, refreshToken, deviceID string, expiresInMs int64) {
465+
t.Helper()
466+
reqBody := map[string]interface{}{
467+
"identifier": map[string]interface{}{
468+
"type": "m.id.user",
469+
"user": localpart,
470+
},
471+
"password": password,
472+
"type": "m.login.password",
473+
"refresh_token": true,
474+
}
475+
res := c.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "login"}, WithJSONBody(t, reqBody))
476+
477+
body, err := ioutil.ReadAll(res.Body)
478+
if err != nil {
479+
t.Fatalf("unable to read response body: %v", err)
480+
}
481+
482+
userID = gjson.GetBytes(body, "user_id").Str
483+
accessToken = gjson.GetBytes(body, "access_token").Str
484+
deviceID = gjson.GetBytes(body, "device_id").Str
485+
refreshToken = gjson.GetBytes(body, "refresh_token").Str
486+
expiresInMs = gjson.GetBytes(body, "expires_in_ms").Int()
487+
return userID, accessToken, refreshToken, deviceID, expiresInMs
488+
}
489+
490+
// RefreshToken will consume a refresh token and return a new access token and refresh token.
491+
func (c *CSAPI) ConsumeRefreshToken(t *testing.T, refreshToken string) (newAccessToken, newRefreshToken string, expiresInMs int64) {
492+
t.Helper()
493+
reqBody := map[string]interface{}{
494+
"refresh_token": refreshToken,
495+
}
496+
res := c.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "refresh"}, WithJSONBody(t, reqBody))
497+
498+
body, err := ioutil.ReadAll(res.Body)
499+
if err != nil {
500+
t.Fatalf("unable to read response body: %v", err)
501+
}
502+
503+
newAccessToken = gjson.GetBytes(body, "access_token").Str
504+
newRefreshToken = gjson.GetBytes(body, "refresh_token").Str
505+
expiresInMs = gjson.GetBytes(body, "expires_in_ms").Int()
506+
return newAccessToken, newRefreshToken, expiresInMs
507+
}
508+
462509
// RegisterUser will register the user with given parameters and
463510
// return user ID, access token and device ID. It fails the test on network error.
464511
func (c *CSAPI) RegisterUser(t *testing.T, localpart, password string) (userID, accessToken, deviceID string) {

tests/csapi/txnid_test.go

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,9 @@ func mustHaveTransactionIDForEvent(t *testing.T, roomID, eventID, expectedTxnId
6565
})
6666
}
6767

68-
func mustNotHaveTransactionIDForEvent(t *testing.T, roomID, eventID string) client.SyncCheckOpt {
69-
return client.SyncTimelineHas(roomID, func(r gjson.Result) bool {
70-
if r.Get("event_id").Str == eventID {
71-
unsignedTxnId := r.Get("unsigned.transaction_id")
72-
if unsignedTxnId.Exists() {
73-
t.Fatalf("Event %s in room %s should NOT have a 'unsigned.transaction_id', but it did (%s)", eventID, roomID, unsignedTxnId.Str)
74-
}
75-
76-
return true
77-
}
78-
79-
return false
80-
})
81-
}
82-
83-
// TestTxnScopeOnLocalEcho tests that transaction IDs in the sync response are scoped to the "client session", not the device
68+
// TestTxnScopeOnLocalEcho tests that transaction IDs in the sync response are scoped to the device
8469
func TestTxnScopeOnLocalEcho(t *testing.T) {
85-
// Conduit scope transaction IDs to the device ID, not the access token.
86-
runtime.SkipIf(t, runtime.Conduit)
70+
runtime.SkipIf(t, runtime.Dendrite)
8771

8872
deployment := Deploy(t, b.BlueprintCleanHS)
8973
defer deployment.Destroy(t)
@@ -115,15 +99,14 @@ func TestTxnScopeOnLocalEcho(t *testing.T) {
11599
c2.UserID, c2.AccessToken, c2.DeviceID = c2.LoginUser(t, "alice", "password", client.WithDeviceID(c1.DeviceID))
116100
must.EqualStr(t, c1.DeviceID, c2.DeviceID, "Device ID should be the same")
117101

118-
// When syncing, we should find the event and it should *not* have a transaction ID on the second client.
119-
c2.MustSyncUntil(t, client.SyncReq{}, mustNotHaveTransactionIDForEvent(t, roomID, eventID))
102+
// When syncing, we should find the event and it should have the same transaction ID on the second client.
103+
c2.MustSyncUntil(t, client.SyncReq{}, mustHaveTransactionIDForEvent(t, roomID, eventID, txnId))
120104
}
121105

122-
// TestTxnIdempotencyScopedToClientSession tests that transaction IDs are scoped to a "client session"
123-
// and behave as expected across multiple clients even if they use the same device ID
124-
func TestTxnIdempotencyScopedToClientSession(t *testing.T) {
125-
// Conduit scope transaction IDs to the device ID, not the client session.
126-
runtime.SkipIf(t, runtime.Conduit)
106+
// TestTxnIdempotencyScopedToDevice tests that transaction IDs are scoped to a device
107+
// and behave as expected across multiple clients if they use the same device ID
108+
func TestTxnIdempotencyScopedToDevice(t *testing.T) {
109+
runtime.SkipIf(t, runtime.Dendrite)
127110

128111
deployment := Deploy(t, b.BlueprintCleanHS)
129112
defer deployment.Destroy(t)
@@ -156,8 +139,8 @@ func TestTxnIdempotencyScopedToClientSession(t *testing.T) {
156139
// send another event with the same txnId via the second client
157140
eventID2 := c2.SendEventUnsyncedWithTxnID(t, roomID, event, txnId)
158141

159-
// the two events should have different event IDs as they came from different clients
160-
must.NotEqualStr(t, eventID2, eventID1, "Expected eventID1 and eventID2 to be different from two clients sharing the same device ID")
142+
// the two events should have the same event IDs as they came from the same device
143+
must.EqualStr(t, eventID2, eventID1, "Expected eventID1 and eventID2 to be the same from two clients sharing the same device ID")
161144
}
162145

163146
// TestTxnIdempotency tests that PUT requests idempotency follows required semantics
@@ -213,3 +196,52 @@ func TestTxnIdempotency(t *testing.T) {
213196

214197
must.NotEqualStr(t, eventID4, eventID3, "Expected eventID4 and eventID3 to be different, but they were not")
215198
}
199+
200+
// TestTxnIdWithRefreshToken tests that when a client refreshes its access token,
201+
// it still gets back a transaction ID in the sync response and idempotency is respected.
202+
func TestTxnIdWithRefreshToken(t *testing.T) {
203+
// Dendrite and Conduit don't support refresh tokens yet.
204+
runtime.SkipIf(t, runtime.Dendrite, runtime.Conduit)
205+
206+
deployment := Deploy(t, b.BlueprintCleanHS)
207+
defer deployment.Destroy(t)
208+
209+
deployment.RegisterUser(t, "hs1", "alice", "password", false)
210+
211+
c := deployment.Client(t, "hs1", "")
212+
213+
var refreshToken string
214+
c.UserID, c.AccessToken, refreshToken, c.DeviceID, _ = c.LoginUserWithRefreshToken(t, "alice", "password")
215+
216+
// Create a room where we can send events.
217+
roomID := c.CreateRoom(t, map[string]interface{}{})
218+
219+
txnId := "abcdef"
220+
// We send an event
221+
eventID1 := c.SendEventUnsyncedWithTxnID(t, roomID, b.Event{
222+
Type: "m.room.message",
223+
Content: map[string]interface{}{
224+
"msgtype": "m.text",
225+
"body": "first",
226+
},
227+
}, txnId)
228+
229+
// Use the refresh token to get a new access token.
230+
c.AccessToken, refreshToken, _ = c.ConsumeRefreshToken(t, refreshToken)
231+
232+
// When syncing, we should find the event and it should also have the correct transaction ID even
233+
// though the access token is different.
234+
c.MustSyncUntil(t, client.SyncReq{}, mustHaveTransactionIDForEvent(t, roomID, eventID1, txnId))
235+
236+
// We try sending the event again with the same transaction ID
237+
eventID2 := c.SendEventUnsyncedWithTxnID(t, roomID, b.Event{
238+
Type: "m.room.message",
239+
Content: map[string]interface{}{
240+
"msgtype": "m.text",
241+
"body": "first",
242+
},
243+
}, txnId)
244+
245+
// The event should have been deduplicated and we should get back the same event ID
246+
must.EqualStr(t, eventID2, eventID1, "Expected eventID1 and eventID2 to be the same from a client using a refresh token")
247+
}

tests/federation_upload_keys_test.go

Lines changed: 61 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -48,77 +48,72 @@ func TestFederationKeyUploadQuery(t *testing.T) {
4848
},
4949
})
5050

51-
t.Run("Parallel", func(t *testing.T) {
52-
// sytest: Can claim remote one time key using POST
53-
t.Run("Can claim remote one time key using POST", func(t *testing.T) {
54-
t.Parallel()
55-
// check keys on remote server
56-
reqBody = client.WithJSONBody(t, map[string]interface{}{
57-
"one_time_keys": map[string]interface{}{
58-
alice.UserID: map[string]string{
59-
alice.DeviceID: "signed_curve25519",
60-
},
51+
// sytest: Can claim remote one time key using POST
52+
t.Run("Can claim remote one time key using POST", func(t *testing.T) {
53+
// check keys on remote server
54+
reqBody = client.WithJSONBody(t, map[string]interface{}{
55+
"one_time_keys": map[string]interface{}{
56+
alice.UserID: map[string]string{
57+
alice.DeviceID: "signed_curve25519",
6158
},
62-
})
63-
resp = bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "keys", "claim"}, reqBody)
64-
otksField := "one_time_keys." + client.GjsonEscape(alice.UserID) + "." + client.GjsonEscape(alice.DeviceID)
65-
must.MatchResponse(t, resp, match.HTTPResponse{
66-
StatusCode: http.StatusOK,
67-
JSON: []match.JSON{
68-
match.JSONKeyTypeEqual(otksField, gjson.JSON),
69-
match.JSONKeyEqual(otksField, oneTimeKeys),
70-
},
71-
})
72-
73-
// there should be no OTK left now
74-
resp = bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "keys", "claim"}, reqBody)
75-
must.MatchResponse(t, resp, match.HTTPResponse{
76-
StatusCode: http.StatusOK,
77-
JSON: []match.JSON{
78-
match.JSONKeyMissing("one_time_keys." + client.GjsonEscape(alice.UserID)),
79-
},
80-
})
59+
},
60+
})
61+
resp = bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "keys", "claim"}, reqBody)
62+
otksField := "one_time_keys." + client.GjsonEscape(alice.UserID) + "." + client.GjsonEscape(alice.DeviceID)
63+
must.MatchResponse(t, resp, match.HTTPResponse{
64+
StatusCode: http.StatusOK,
65+
JSON: []match.JSON{
66+
match.JSONKeyTypeEqual(otksField, gjson.JSON),
67+
match.JSONKeyEqual(otksField, oneTimeKeys),
68+
},
69+
})
70+
71+
// there should be no OTK left now
72+
resp = bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "keys", "claim"}, reqBody)
73+
must.MatchResponse(t, resp, match.HTTPResponse{
74+
StatusCode: http.StatusOK,
75+
JSON: []match.JSON{
76+
match.JSONKeyMissing("one_time_keys." + client.GjsonEscape(alice.UserID)),
77+
},
8178
})
79+
})
8280

83-
// sytest: Can query remote device keys using POST
84-
t.Run("Can query remote device keys using POST", func(t *testing.T) {
85-
t.Parallel()
86-
87-
displayName := "My new displayname"
88-
body := client.WithJSONBody(t, map[string]interface{}{
89-
"display_name": displayName,
90-
})
91-
alice.MustDoFunc(t, http.MethodPut, []string{"_matrix", "client", "v3", "devices", alice.DeviceID}, body)
92-
// wait for bob to receive the displayname change
93-
bob.MustSyncUntil(t, client.SyncReq{}, func(clientUserID string, topLevelSyncJSON gjson.Result) error {
94-
devicesChanged := topLevelSyncJSON.Get("device_lists.changed")
95-
if devicesChanged.Exists() {
96-
for _, userID := range devicesChanged.Array() {
97-
if userID.Str == alice.UserID {
98-
return nil
99-
}
81+
// sytest: Can query remote device keys using POST
82+
t.Run("Can query remote device keys using POST", func(t *testing.T) {
83+
displayName := "My new displayname"
84+
body := client.WithJSONBody(t, map[string]interface{}{
85+
"display_name": displayName,
86+
})
87+
alice.MustDoFunc(t, http.MethodPut, []string{"_matrix", "client", "v3", "devices", alice.DeviceID}, body)
88+
// wait for bob to receive the displayname change
89+
bob.MustSyncUntil(t, client.SyncReq{}, func(clientUserID string, topLevelSyncJSON gjson.Result) error {
90+
devicesChanged := topLevelSyncJSON.Get("device_lists.changed")
91+
if devicesChanged.Exists() {
92+
for _, userID := range devicesChanged.Array() {
93+
if userID.Str == alice.UserID {
94+
return nil
10095
}
10196
}
102-
return fmt.Errorf("no device_lists found")
103-
})
104-
reqBody = client.WithJSONBody(t, map[string]interface{}{
105-
"device_keys": map[string]interface{}{
106-
alice.UserID: []string{},
107-
},
108-
})
109-
resp = bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "keys", "query"}, reqBody)
110-
deviceKeysField := "device_keys." + client.GjsonEscape(alice.UserID) + "." + client.GjsonEscape(alice.DeviceID)
111-
112-
must.MatchResponse(t, resp, match.HTTPResponse{
113-
StatusCode: http.StatusOK,
114-
JSON: []match.JSON{
115-
match.JSONKeyTypeEqual(deviceKeysField, gjson.JSON),
116-
match.JSONKeyEqual(deviceKeysField+".algorithms", deviceKeys["algorithms"]),
117-
match.JSONKeyEqual(deviceKeysField+".keys", deviceKeys["keys"]),
118-
match.JSONKeyEqual(deviceKeysField+".signatures", deviceKeys["signatures"]),
119-
match.JSONKeyEqual(deviceKeysField+".unsigned.device_display_name", displayName),
120-
},
121-
})
97+
}
98+
return fmt.Errorf("no device_lists found")
99+
})
100+
reqBody = client.WithJSONBody(t, map[string]interface{}{
101+
"device_keys": map[string]interface{}{
102+
alice.UserID: []string{},
103+
},
104+
})
105+
resp = bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "keys", "query"}, reqBody)
106+
deviceKeysField := "device_keys." + client.GjsonEscape(alice.UserID) + "." + client.GjsonEscape(alice.DeviceID)
107+
108+
must.MatchResponse(t, resp, match.HTTPResponse{
109+
StatusCode: http.StatusOK,
110+
JSON: []match.JSON{
111+
match.JSONKeyTypeEqual(deviceKeysField, gjson.JSON),
112+
match.JSONKeyEqual(deviceKeysField+".algorithms", deviceKeys["algorithms"]),
113+
match.JSONKeyEqual(deviceKeysField+".keys", deviceKeys["keys"]),
114+
match.JSONKeyEqual(deviceKeysField+".signatures", deviceKeys["signatures"]),
115+
match.JSONKeyEqual(deviceKeysField+".unsigned.device_display_name", displayName),
116+
},
122117
})
123118
})
124119
}
Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
//go:build msc3787
2-
// +build msc3787
1+
//go:build !dendrite_blacklist
2+
// +build !dendrite_blacklist
33

44
// This file contains tests for a join rule which mixes concepts of restricted joins
5-
// and knocking. This is currently experimental and defined by MSC3787, found here:
6-
// https://github.com/matrix-org/matrix-spec-proposals/pull/3787
5+
// and knocking. This is implemented in room version 10.
76
//
87
// Generally, this is a combination of knocking_test and restricted_rooms_test.
98

@@ -16,26 +15,26 @@ import (
1615
)
1716

1817
var (
19-
msc3787RoomVersion = "org.matrix.msc3787"
20-
msc3787JoinRule = "knock_restricted"
18+
roomVersion = "10"
19+
joinRule = "knock_restricted"
2120
)
2221

2322
// See TestKnocking
2423
func TestKnockingInMSC3787Room(t *testing.T) {
25-
doTestKnocking(t, msc3787RoomVersion, msc3787JoinRule)
24+
doTestKnocking(t, roomVersion, joinRule)
2625
}
2726

2827
// See TestKnockRoomsInPublicRoomsDirectory
2928
func TestKnockRoomsInPublicRoomsDirectoryInMSC3787Room(t *testing.T) {
30-
doTestKnockRoomsInPublicRoomsDirectory(t, msc3787RoomVersion, msc3787JoinRule)
29+
doTestKnockRoomsInPublicRoomsDirectory(t, roomVersion, joinRule)
3130
}
3231

3332
// See TestCannotSendKnockViaSendKnock
3433
func TestCannotSendKnockViaSendKnockInMSC3787Room(t *testing.T) {
3534
testValidationForSendMembershipEndpoint(t, "/_matrix/federation/v1/send_knock", "knock",
3635
map[string]interface{}{
3736
"preset": "public_chat",
38-
"room_version": msc3787RoomVersion,
37+
"room_version": roomVersion,
3938
},
4039
)
4140
}
@@ -46,13 +45,13 @@ func TestRestrictedRoomsLocalJoinInMSC3787Room(t *testing.T) {
4645
defer deployment.Destroy(t)
4746

4847
// Setup the user, allowed room, and restricted room.
49-
alice, allowed_room, room := setupRestrictedRoom(t, deployment, msc3787RoomVersion, msc3787JoinRule)
48+
alice, allowed_room, room := setupRestrictedRoom(t, deployment, roomVersion, joinRule)
5049

5150
// Create a second user on the same homeserver.
5251
bob := deployment.Client(t, "hs1", "@bob:hs1")
5352

5453
// Execute the checks.
55-
checkRestrictedRoom(t, alice, bob, allowed_room, room, msc3787JoinRule)
54+
checkRestrictedRoom(t, alice, bob, allowed_room, room, joinRule)
5655
}
5756

5857
// See TestRestrictedRoomsRemoteJoin
@@ -61,21 +60,21 @@ func TestRestrictedRoomsRemoteJoinInMSC3787Room(t *testing.T) {
6160
defer deployment.Destroy(t)
6261

6362
// Setup the user, allowed room, and restricted room.
64-
alice, allowed_room, room := setupRestrictedRoom(t, deployment, msc3787RoomVersion, msc3787JoinRule)
63+
alice, allowed_room, room := setupRestrictedRoom(t, deployment, roomVersion, joinRule)
6564

6665
// Create a second user on a different homeserver.
6766
bob := deployment.Client(t, "hs2", "@bob:hs2")
6867

6968
// Execute the checks.
70-
checkRestrictedRoom(t, alice, bob, allowed_room, room, msc3787JoinRule)
69+
checkRestrictedRoom(t, alice, bob, allowed_room, room, joinRule)
7170
}
7271

7372
// See TestRestrictedRoomsRemoteJoinLocalUser
7473
func TestRestrictedRoomsRemoteJoinLocalUserInMSC3787Room(t *testing.T) {
75-
doTestRestrictedRoomsRemoteJoinLocalUser(t, msc3787RoomVersion, msc3787JoinRule)
74+
doTestRestrictedRoomsRemoteJoinLocalUser(t, roomVersion, joinRule)
7675
}
7776

7877
// See TestRestrictedRoomsRemoteJoinFailOver
7978
func TestRestrictedRoomsRemoteJoinFailOverInMSC3787Room(t *testing.T) {
80-
doTestRestrictedRoomsRemoteJoinFailOver(t, msc3787RoomVersion, msc3787JoinRule)
79+
doTestRestrictedRoomsRemoteJoinFailOver(t, roomVersion, joinRule)
8180
}

0 commit comments

Comments
 (0)