Skip to content

Commit 56f1541

Browse files
authored
Merge pull request #69 from matrix-org/kegan/keys-query-fails-after-room-key
Add TestToDeviceMessagesArentLostWhenKeysQueryFails
2 parents a6c91dd + 4869f6f commit 56f1541

File tree

6 files changed

+110
-0
lines changed

6 files changed

+110
-0
lines changed

internal/api/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ type Client interface {
6565
Logf(t ct.TestLike, format string, args ...interface{})
6666
// The user for this client
6767
UserID() string
68+
// The current access token for this client
69+
CurrentAccessToken(t ct.TestLike) string
6870
Type() ClientTypeLang
6971
Opts() ClientCreationOpts
7072
}
@@ -78,6 +80,13 @@ type LoggedClient struct {
7880
Client
7981
}
8082

83+
func (c *LoggedClient) CurrentAccessToken(t ct.TestLike) string {
84+
t.Helper()
85+
token := c.Client.CurrentAccessToken(t)
86+
c.Logf(t, "%s CurrentAccessToken => %s", c.logPrefix(), token)
87+
return token
88+
}
89+
8190
func (c *LoggedClient) Login(t ct.TestLike, opts ClientCreationOpts) error {
8291
t.Helper()
8392
c.Logf(t, "%s Login %+v", c.logPrefix(), opts)

internal/api/js/js.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,12 @@ func (c *JSClient) DeletePersistentStorage(t ct.TestLike) {
251251
`, indexedDBName, indexedDBCryptoName))
252252
}
253253

254+
func (c *JSClient) CurrentAccessToken(t ct.TestLike) string {
255+
token := chrome.MustRunAsyncFn[string](t, c.browser.Ctx, `
256+
return window.__client.getAccessToken();`)
257+
return *token
258+
}
259+
254260
func (c *JSClient) GetNotification(t ct.TestLike, roomID, eventID string) (*api.Notification, error) {
255261
return nil, fmt.Errorf("not implemented yet") // TODO
256262
}

internal/api/rust/rust.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ func (c *RustClient) Login(t ct.TestLike, opts api.ClientCreationOpts) error {
188188
return nil
189189
}
190190

191+
func (c *RustClient) CurrentAccessToken(t ct.TestLike) string {
192+
s, err := c.FFIClient.Session()
193+
if err != nil {
194+
ct.Fatalf(t, "error retrieving session: %s", err)
195+
}
196+
return s.AccessToken
197+
}
198+
191199
func (c *RustClient) DeletePersistentStorage(t ct.TestLike) {
192200
t.Helper()
193201
if c.persistentStoragePath != "" {

internal/deploy/rpc_client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,15 @@ func (c *RPCClient) GetNotification(t ct.TestLike, roomID, eventID string) (*api
181181
return &notification, err
182182
}
183183

184+
func (c *RPCClient) CurrentAccessToken(t ct.TestLike) string {
185+
var token string
186+
err := c.client.Call("RPCServer.CurrentAccessToken", t.Name(), &token)
187+
if err != nil {
188+
ct.Fatalf(t, "RPCServer.CurrentAccessToken: %s", err)
189+
}
190+
return token
191+
}
192+
184193
// Remove any persistent storage, if it was enabled.
185194
func (c *RPCClient) DeletePersistentStorage(t ct.TestLike) {
186195
var void int

internal/deploy/rpc_server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ func (s *RPCServer) DeletePersistentStorage(testName string, void *int) error {
100100
return nil
101101
}
102102

103+
func (s *RPCServer) CurrentAccessToken(testName string, token *string) error {
104+
defer s.keepAlive()
105+
*token = s.activeClient.CurrentAccessToken(&api.MockT{TestName: testName})
106+
return nil
107+
}
108+
103109
func (s *RPCServer) Login(opts api.ClientCreationOpts, void *int) error {
104110
defer s.keepAlive()
105111
return s.activeClient.Login(&api.MockT{}, opts)

tests/to_device_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,75 @@ func TestToDeviceMessagesAreBatched(t *testing.T) {
310310

311311
})
312312
}
313+
314+
// Regression test for https://github.com/element-hq/element-web/issues/24682
315+
//
316+
// When a to-device msg is received, the SDK may need to check that the device belongs
317+
// to the user in question. To do this, it needs an up-to-date device list. To get this,
318+
// it does a /keys/query request. If this request fails, the entire processing of the
319+
// to-device msg could fail, dropping the msg and the room key it contains.
320+
//
321+
// This test reproduces this by having an existing E2EE room between Alice and Bob, then:
322+
// - Block /keys/query requests.
323+
// - Alice logs in on a new device.
324+
// - Alice sends a message on the new device.
325+
// - Bob should get that message but may refuse to decrypt it as it cannot verify that the sender_key
326+
// belongs to Alice.
327+
// - Unblock /keys/query requests.
328+
// - Bob should eventually retry and be able to decrypt the event.
329+
func TestToDeviceMessagesArentLostWhenKeysQueryFails(t *testing.T) {
330+
ForEachClientType(t, func(t *testing.T, clientType api.ClientType) {
331+
tc := CreateTestContext(t, clientType, clientType)
332+
// get a normal E2EE room set up
333+
roomID := tc.CreateNewEncryptedRoom(t, tc.Alice, EncRoomOptions.Invite([]string{tc.Bob.UserID}))
334+
tc.Bob.MustJoinRoom(t, roomID, []string{clientType.HS})
335+
tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) {
336+
msg := "hello world"
337+
msg2 := "new device message from alice"
338+
alice.SendMessage(t, roomID, msg)
339+
bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(msg)).Waitf(t, 5*time.Second, "bob failed to see message from alice")
340+
// Block /keys/query requests
341+
waiter := helpers.NewWaiter()
342+
callbackURL, closeCallbackServer := deploy.NewCallbackServer(t, tc.Deployment, func(cd deploy.CallbackData) {
343+
t.Logf("%+v", cd)
344+
waiter.Finish()
345+
})
346+
defer closeCallbackServer()
347+
var eventID string
348+
bobAccessToken := bob.CurrentAccessToken(t)
349+
t.Logf("Bob's token => %s", bobAccessToken)
350+
tc.Deployment.WithMITMOptions(t, map[string]interface{}{
351+
"statuscode": map[string]interface{}{
352+
"return_status": http.StatusGatewayTimeout,
353+
"block_request": true,
354+
"count": 3,
355+
"filter": "~u .*/keys/query.* ~hq " + bobAccessToken,
356+
},
357+
"callback": map[string]interface{}{
358+
"callback_url": callbackURL,
359+
"filter": "~u .*/keys/query.*",
360+
},
361+
}, func() {
362+
// Alice logs in on a new device.
363+
csapiAlice2 := tc.MustRegisterNewDevice(t, tc.Alice, clientType.HS, "OTHER_DEVICE")
364+
alice2 := tc.MustLoginClient(t, csapiAlice2, clientType)
365+
defer alice2.Close(t)
366+
alice2StopSyncing := alice2.MustStartSyncing(t)
367+
defer alice2StopSyncing()
368+
// we don't know how long it will take for the device list update to be processed, so wait 1s
369+
time.Sleep(time.Second)
370+
371+
// Alice sends a message on the new device.
372+
eventID = alice2.SendMessage(t, roomID, msg2)
373+
374+
waiter.Waitf(t, 3*time.Second, "did not see /keys/query")
375+
time.Sleep(3 * time.Second) // let Bob retry /keys/query
376+
})
377+
// now we aren't blocking /keys/query anymore.
378+
// Bob should be able to decrypt this message.
379+
ev := bob.MustGetEvent(t, roomID, eventID)
380+
must.Equal(t, ev.Text, msg2, "bob failed to decrypt "+eventID)
381+
})
382+
383+
})
384+
}

0 commit comments

Comments
 (0)