mirror of
https://github.com/element-hq/element-web.git
synced 2025-09-17 11:04:05 +02:00
Compare commits
4 Commits
develop
...
valere/wid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
162c70f049 | ||
|
|
c8d7d789e8 | ||
|
|
67e84c658a | ||
|
|
41439b0875 |
@@ -515,9 +515,33 @@ export class StopGapWidget extends EventEmitter {
|
||||
};
|
||||
|
||||
private onToDeviceMessage = async (payload: ReceivedToDeviceMessage): Promise<void> => {
|
||||
// Check if the room the widget is in is end-to-end encrypted
|
||||
let acceptEncryptedTrafficOnly: boolean;
|
||||
if (this.roomId && this.client.getCrypto()) {
|
||||
acceptEncryptedTrafficOnly = await this.client.getCrypto()!.isEncryptionEnabledInRoom(this.roomId);
|
||||
} else {
|
||||
// If the widget is not in a room, default to encrypted traffic only
|
||||
acceptEncryptedTrafficOnly = true;
|
||||
}
|
||||
const { message, encryptionInfo } = payload;
|
||||
// TODO: Update the widget API to use a proper IToDeviceMessage instead of a IRoomEvent
|
||||
await this.messaging?.feedToDevice(message as IRoomEvent, encryptionInfo != null);
|
||||
|
||||
if (acceptEncryptedTrafficOnly) {
|
||||
// Only pass on to-device messages that are encrypted
|
||||
if (encryptionInfo != null) {
|
||||
// TODO: Update the widget API to use a proper IToDeviceMessage instead of a IRoomEvent
|
||||
await this.messaging?.feedToDevice(message as IRoomEvent, true);
|
||||
} else {
|
||||
logger.warn(
|
||||
`Received to-device event in clear for a widget in an e2e room (${this.roomId}), dropping.`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// Forward to the widget.
|
||||
// It is ok to send an encrypted to-device message even if the room is clear.
|
||||
// TODO: Update the widget API to use a proper IToDeviceMessage instead of a IRoomEvent
|
||||
await this.messaging?.feedToDevice(message as IRoomEvent, encryptionInfo != null);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -424,8 +424,22 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||
): Promise<void> {
|
||||
const client = MatrixClientPeg.safeGet();
|
||||
|
||||
if (encrypted) {
|
||||
const crypto = client.getCrypto();
|
||||
const crypto = client.getCrypto();
|
||||
|
||||
let forceEncryptedTraffic: boolean;
|
||||
if (crypto) {
|
||||
if (this.inRoomId) {
|
||||
forceEncryptedTraffic = await client.getCrypto()!.isEncryptionEnabledInRoom(this.inRoomId);
|
||||
} else {
|
||||
// If the widget is not in a room, we default to only encrypted traffic
|
||||
forceEncryptedTraffic = true;
|
||||
}
|
||||
} else {
|
||||
// If the client does not have crypto we default to not allowing encrypted traffic?
|
||||
forceEncryptedTraffic = false;
|
||||
}
|
||||
|
||||
if (forceEncryptedTraffic || encrypted) {
|
||||
if (!crypto) throw new Error("E2EE not enabled");
|
||||
|
||||
// attempt to re-batch these up into a single request
|
||||
|
||||
@@ -158,6 +158,7 @@ export function createTestClient(): MatrixClient {
|
||||
isSecretStorageReady: jest.fn().mockResolvedValue(false),
|
||||
deleteKeyBackupVersion: jest.fn(),
|
||||
crossSignDevice: jest.fn(),
|
||||
encryptToDeviceMessages: jest.fn(),
|
||||
}),
|
||||
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
|
||||
@@ -121,6 +121,81 @@ describe("StopGapWidget", () => {
|
||||
expect(messaging.feedToDevice).toHaveBeenCalledWith(receivedToDevice.message, true);
|
||||
});
|
||||
|
||||
it("Drop sent in clear to-device messages if room is encrypted.", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
|
||||
const clearReceivedToDevice = {
|
||||
message: {
|
||||
type: "org.example.foo",
|
||||
sender: "@alice:example.org",
|
||||
content: {
|
||||
hello: "spoofed world",
|
||||
},
|
||||
},
|
||||
encryptionInfo: null,
|
||||
};
|
||||
|
||||
client.emit(ClientEvent.ReceivedToDeviceMessage, clearReceivedToDevice);
|
||||
await Promise.resolve(); // flush promises
|
||||
expect(messaging.feedToDevice).not.toHaveBeenCalled();
|
||||
|
||||
const encryptedReceivedToDevice = {
|
||||
message: {
|
||||
type: "org.example.foo",
|
||||
sender: "@alice:example.org",
|
||||
content: {
|
||||
hello: "Hello world",
|
||||
},
|
||||
},
|
||||
encryptionInfo: {
|
||||
senderVerified: false,
|
||||
sender: "@alice:example.org",
|
||||
senderCurve25519KeyBase64: "",
|
||||
senderDevice: "ABCDEFGHI",
|
||||
},
|
||||
};
|
||||
|
||||
client.emit(ClientEvent.ReceivedToDeviceMessage, encryptedReceivedToDevice);
|
||||
await Promise.resolve(); // flush promises
|
||||
expect(messaging.feedToDevice).toHaveBeenCalledWith(encryptedReceivedToDevice.message, true);
|
||||
});
|
||||
|
||||
it("Default to only encrypted traffic if there is no room.", async () => {
|
||||
// Replace the widget with one that has no room
|
||||
// first stop messaging to clear the previous widget
|
||||
widget.stopMessaging();
|
||||
widget = new StopGapWidget({
|
||||
app: {
|
||||
id: "test",
|
||||
creatorUserId: "@alice:example.org",
|
||||
type: "example",
|
||||
url: "https://example.org?user-id=$matrix_user_id&device-id=$org.matrix.msc3819.matrix_device_id&base-url=$org.matrix.msc4039.matrix_base_url&theme=$org.matrix.msc2873.client_theme",
|
||||
},
|
||||
// no room provided
|
||||
userId: "@alice:example.org",
|
||||
creatorUserId: "@alice:example.org",
|
||||
waitForIframeLoad: true,
|
||||
userWidget: false,
|
||||
});
|
||||
// Start messaging without an iframe, since ClientWidgetApi is mocked
|
||||
widget.startMessaging(null as unknown as HTMLIFrameElement);
|
||||
|
||||
const clearReceivedToDevice = {
|
||||
message: {
|
||||
type: "org.example.foo",
|
||||
sender: "@alice:example.org",
|
||||
content: {
|
||||
hello: "spoofed world",
|
||||
},
|
||||
},
|
||||
encryptionInfo: null,
|
||||
};
|
||||
|
||||
client.emit(ClientEvent.ReceivedToDeviceMessage, clearReceivedToDevice);
|
||||
await Promise.resolve(); // flush promises
|
||||
expect(messaging.feedToDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("feeds incoming state updates to the widget", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
|
||||
@@ -190,6 +190,22 @@ describe("StopGapWidgetDriver", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
driver = mkDefaultDriver();
|
||||
|
||||
mocked(client.getCrypto()!.encryptToDeviceMessages).mockImplementation(
|
||||
async (eventType, devices, payload) => {
|
||||
return {
|
||||
eventType: "m.room.encrypted",
|
||||
batch: devices.map(({ userId, deviceId }) => ({
|
||||
userId,
|
||||
deviceId,
|
||||
payload: {
|
||||
type: "m.room.encrypted",
|
||||
content: { ciphertext: "ciphertext" },
|
||||
},
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("sends unencrypted messages", async () => {
|
||||
@@ -203,25 +219,54 @@ describe("StopGapWidgetDriver", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should force encrypted traffic if room is e2ee", async () => {
|
||||
mocked(client.getCrypto()!.isEncryptionEnabledInRoom).mockResolvedValue(true);
|
||||
|
||||
// Try to send with `encrypted: false`, but it should be forced to true
|
||||
await driver.sendToDevice("org.example.foo", false, contentMap);
|
||||
expect(client.getCrypto()!.encryptToDeviceMessages).toHaveBeenCalled();
|
||||
expect(client.queueToDevice).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
eventType: "m.room.encrypted",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("Allow to send encrypted in clear room", async () => {
|
||||
mocked(client.getCrypto()!.isEncryptionEnabledInRoom).mockResolvedValue(false);
|
||||
|
||||
await driver.sendToDevice("org.example.foo", true, contentMap);
|
||||
expect(client.getCrypto()!.encryptToDeviceMessages).toHaveBeenCalled();
|
||||
expect(client.queueToDevice).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
eventType: "m.room.encrypted",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("Should default to encrypted traffic for non-room widgets", async () => {
|
||||
const driver = new StopGapWidgetDriver(
|
||||
[],
|
||||
new Widget({
|
||||
id: "an_id",
|
||||
creatorUserId: "@alice:example.org",
|
||||
type: WidgetType.CUSTOM.preferred,
|
||||
url: "https://call.element.io",
|
||||
}),
|
||||
WidgetKind.Account,
|
||||
true,
|
||||
);
|
||||
|
||||
await driver.sendToDevice("org.example.foo", false, contentMap);
|
||||
expect(client.getCrypto()!.encryptToDeviceMessages).toHaveBeenCalled();
|
||||
expect(client.queueToDevice).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
eventType: "m.room.encrypted",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("sends encrypted messages", async () => {
|
||||
const encryptToDeviceMessages = jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(eventType, recipients: { userId: string; deviceId: string }[], content: object) => ({
|
||||
eventType: "m.room.encrypted",
|
||||
batch: recipients.map(({ userId, deviceId }) => ({
|
||||
userId,
|
||||
deviceId,
|
||||
payload: {
|
||||
eventType,
|
||||
content,
|
||||
},
|
||||
})),
|
||||
}),
|
||||
);
|
||||
|
||||
MatrixClientPeg.safeGet().getCrypto()!.encryptToDeviceMessages = encryptToDeviceMessages;
|
||||
|
||||
await driver.sendToDevice("org.example.foo", true, {
|
||||
"@alice:example.org": {
|
||||
aliceMobile: {
|
||||
@@ -235,14 +280,14 @@ describe("StopGapWidgetDriver", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(encryptToDeviceMessages).toHaveBeenCalledWith(
|
||||
expect(client.getCrypto()!.encryptToDeviceMessages).toHaveBeenCalledWith(
|
||||
"org.example.foo",
|
||||
[{ deviceId: "aliceMobile", userId: "@alice:example.org" }],
|
||||
{
|
||||
hello: "alice",
|
||||
},
|
||||
);
|
||||
expect(encryptToDeviceMessages).toHaveBeenCalledWith(
|
||||
expect(client.getCrypto()!.encryptToDeviceMessages).toHaveBeenCalledWith(
|
||||
"org.example.foo",
|
||||
[{ deviceId: "bobDesktop", userId: "@bob:example.org" }],
|
||||
{
|
||||
@@ -252,21 +297,19 @@ describe("StopGapWidgetDriver", () => {
|
||||
expect(client.queueToDevice).toHaveBeenCalledWith({
|
||||
eventType: "m.room.encrypted",
|
||||
batch: expect.arrayContaining([
|
||||
{
|
||||
expect.objectContaining({
|
||||
deviceId: "aliceMobile",
|
||||
payload: { content: { hello: "alice" }, eventType: "org.example.foo" },
|
||||
userId: "@alice:example.org",
|
||||
},
|
||||
}),
|
||||
]),
|
||||
});
|
||||
expect(client.queueToDevice).toHaveBeenCalledWith({
|
||||
eventType: "m.room.encrypted",
|
||||
batch: expect.arrayContaining([
|
||||
{
|
||||
expect.objectContaining({
|
||||
deviceId: "bobDesktop",
|
||||
payload: { content: { hello: "bob" }, eventType: "org.example.foo" },
|
||||
userId: "@bob:example.org",
|
||||
},
|
||||
}),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user