Skip to content

Commit

Permalink
Element-R: Wire up room history visibility (#3805)
Browse files Browse the repository at this point in the history
* Wire up history visibility in `RoomEncryptor.ts`

* Add more tests to history visibility conversion

* Factorize `expectSendMessage` and `expectSendMegolmMessage`

* Use correct import

* Fix overwriteRoutes

* Update comments
  • Loading branch information
florianduros authored Oct 25, 2023
1 parent 6f82f08 commit 4a4b454
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 19 deletions.
97 changes: 80 additions & 17 deletions spec/integ/crypto/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { MockResponse, MockResponseFunction } from "fetch-mock";
import Olm from "@matrix-org/olm";

import * as testUtils from "../../test-utils/test-utils";
import { CRYPTO_BACKENDS, getSyncResponse, InitCrypto, syncPromise } from "../../test-utils/test-utils";
import { CRYPTO_BACKENDS, getSyncResponse, InitCrypto, mkEventCustom, syncPromise } from "../../test-utils/test-utils";
import * as testData from "../../test-utils/test-data";
import {
BOB_SIGNED_CROSS_SIGNING_KEYS_DATA,
Expand All @@ -37,6 +37,7 @@ import {
import { TestClient } from "../../TestClient";
import { logger } from "../../../src/logger";
import {
Category,
ClientEvent,
createClient,
CryptoEvent,
Expand All @@ -53,6 +54,7 @@ import {
Room,
RoomMember,
RoomStateEvent,
HistoryVisibility,
} from "../../../src/matrix";
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
Expand Down Expand Up @@ -152,6 +154,27 @@ async function expectSendRoomKey(
});
}

/**
* Return the event received on rooms/{roomId}/send/m.room.encrypted endpoint.
* See https://spec.matrix.org/latest/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
* @returns the content of event (no decryption)
*/
function expectEncryptedSendMessage() {
return new Promise<IContent>((resolve) => {
fetchMock.putOnce(
new RegExp("/send/m.room.encrypted/"),
(url, request) => {
const content = JSON.parse(request.body as string);
resolve(content);
return { event_id: "$event_id" };
},
// append to the list of intercepts on this path (since we have some tests that call
// this function multiple times)
{ overwriteRoutes: false },
);
});
}

/**
* Expect that the client sends an encrypted event
*
Expand All @@ -165,22 +188,7 @@ async function expectSendRoomKey(
async function expectSendMegolmMessage(
inboundGroupSessionPromise: Promise<Olm.InboundGroupSession>,
): Promise<Partial<IEvent>> {
const encryptedMessageContent = await new Promise<IContent>((resolve) => {
fetchMock.putOnce(
new RegExp("/send/m.room.encrypted/"),
(url: string, opts: RequestInit): MockResponse => {
resolve(JSON.parse(opts.body as string));
return {
event_id: "$event_id",
};
},
{
// append to the list of intercepts on this path (since we have some tests that call
// this function multiple times)
overwriteRoutes: false,
},
);
});
const encryptedMessageContent = await expectEncryptedSendMessage();

// In some of the tests, the room key is sent *after* the actual event, so we may need to wait for it now.
const inboundGroupSession = await inboundGroupSessionPromise;
Expand Down Expand Up @@ -930,6 +938,61 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
});
});

newBackendOnly("should rotate the session when the history visibility changes", async () => {
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
await startClientAndAwaitFirstSync();
const p2pSession = await establishOlmSession(aliceClient, keyReceiver, syncResponder, testOlmAccount);

// Tell alice we share a room with bob
syncResponder.sendOrQueueSyncResponse(getSyncResponse(["@bob:xyz"]));
await syncPromise(aliceClient);

// Force alice to download bob keys
expectAliceKeyQuery(getTestKeysQueryResponse("@bob:xyz"));

// Send a message to bob and get the current session id
let [, , encryptedMessage] = await Promise.all([
aliceClient.sendTextMessage(ROOM_ID, "test"),
expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession),
expectEncryptedSendMessage(),
]);

// Check that the session id exists
const sessionId = encryptedMessage.session_id;
expect(sessionId).toBeDefined();

// Change history visibility in sync response
const syncResponse = getSyncResponse([]);
syncResponse.rooms[Category.Join][ROOM_ID].timeline.events.push(
mkEventCustom({
sender: TEST_USER_ID,
type: "m.room.history_visibility",
state_key: "",
content: {
history_visibility: HistoryVisibility.Invited,
},
}),
);

// Update the new visibility
syncResponder.sendOrQueueSyncResponse(syncResponse);
await syncPromise(aliceClient);

// Resend a message to bob and get the new session id
[, , encryptedMessage] = await Promise.all([
aliceClient.sendTextMessage(ROOM_ID, "test"),
expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession),
expectEncryptedSendMessage(),
]);

// Check that the new session id exists
const newSessionId = encryptedMessage.session_id;
expect(newSessionId).toBeDefined();

// Check that the session id has changed
expect(sessionId).not.toEqual(newSessionId);
});

oldBackendOnly("We should start a new megolm session when a device is blocked", async () => {
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
await startClientAndAwaitFirstSync();
Expand Down
31 changes: 31 additions & 0 deletions spec/unit/rust-crypto/RoomEncryptor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/

import { HistoryVisibility as RustHistoryVisibility } from "@matrix-org/matrix-sdk-crypto-wasm";

import { HistoryVisibility } from "../../../src";
import { toRustHistoryVisibility } from "../../../src/rust-crypto/RoomEncryptor";

it.each([
[HistoryVisibility.Invited, RustHistoryVisibility.Invited],
[HistoryVisibility.Joined, RustHistoryVisibility.Joined],
[HistoryVisibility.Shared, RustHistoryVisibility.Shared],
[HistoryVisibility.WorldReadable, RustHistoryVisibility.WorldReadable],
])("JS HistoryVisibility to Rust HistoryVisibility: converts %s to %s", (historyVisibility, expected) => {
expect(toRustHistoryVisibility(historyVisibility)).toBe(expected);
});
31 changes: 29 additions & 2 deletions src/rust-crypto/RoomEncryptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { EncryptionSettings, OlmMachine, RoomId, UserId } from "@matrix-org/matrix-sdk-crypto-wasm";
import {
EncryptionSettings,
OlmMachine,
RoomId,
UserId,
HistoryVisibility as RustHistoryVisibility,
} from "@matrix-org/matrix-sdk-crypto-wasm";

import { EventType } from "../@types/event";
import { IContent, MatrixEvent } from "../models/event";
Expand All @@ -23,6 +29,7 @@ import { Logger, logger } from "../logger";
import { KeyClaimManager } from "./KeyClaimManager";
import { RoomMember } from "../models/room-member";
import { OutgoingRequestProcessor } from "./OutgoingRequestProcessor";
import { HistoryVisibility } from "../@types/partials";

/**
* RoomEncryptor: responsible for encrypting messages to a given room
Expand Down Expand Up @@ -103,7 +110,9 @@ export class RoomEncryptor {
this.prefixedLogger.debug("Sessions for users are ready; now sharing room key");

const rustEncryptionSettings = new EncryptionSettings();
/* FIXME historyVisibility, rotation, etc */
/* FIXME rotation, etc */

rustEncryptionSettings.historyVisibility = toRustHistoryVisibility(this.room.getHistoryVisibility());

const shareMessages = await this.olmMachine.shareRoomKey(
new RoomId(this.room.roomId),
Expand Down Expand Up @@ -152,3 +161,21 @@ export class RoomEncryptor {
);
}
}

/**
* Convert a HistoryVisibility to a RustHistoryVisibility
* @param visibility - HistoryVisibility enum
* @returns a RustHistoryVisibility enum
*/
export function toRustHistoryVisibility(visibility: HistoryVisibility): RustHistoryVisibility {
switch (visibility) {
case HistoryVisibility.Invited:
return RustHistoryVisibility.Invited;
case HistoryVisibility.Joined:
return RustHistoryVisibility.Joined;
case HistoryVisibility.Shared:
return RustHistoryVisibility.Shared;
case HistoryVisibility.WorldReadable:
return RustHistoryVisibility.WorldReadable;
}
}

0 comments on commit 4a4b454

Please sign in to comment.