Merge pull request #4080 from matrix-org/foldleft/12187-e2e-dm
Start verification sessions in an E2E DM where possible
This commit is contained in:
commit
27f65c17b5
3 changed files with 118 additions and 7 deletions
|
@ -31,7 +31,7 @@ import dis from "../../../dispatcher";
|
||||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import {humanizeTime} from "../../../utils/humanize";
|
import {humanizeTime} from "../../../utils/humanize";
|
||||||
import createRoom from "../../../createRoom";
|
import createRoom, {canEncryptToAllUsers} from "../../../createRoom";
|
||||||
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||||
import SettingsStore from '../../../settings/SettingsStore';
|
import SettingsStore from '../../../settings/SettingsStore';
|
||||||
|
|
||||||
|
@ -535,11 +535,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
// Check whether all users have uploaded device keys before.
|
// Check whether all users have uploaded device keys before.
|
||||||
// If so, enable encryption in the new room.
|
// If so, enable encryption in the new room.
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const usersToDevicesMap = await client.downloadKeys(targetIds);
|
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
|
||||||
const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => {
|
|
||||||
// `devices` is an object of the form { deviceId: deviceInfo, ... }.
|
|
||||||
return Object.keys(devices).length > 0;
|
|
||||||
});
|
|
||||||
if (allHaveDeviceKeys) {
|
if (allHaveDeviceKeys) {
|
||||||
createRoomOptions.encryption = true;
|
createRoomOptions.encryption = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import dis from "./dispatcher";
|
||||||
import * as Rooms from "./Rooms";
|
import * as Rooms from "./Rooms";
|
||||||
import DMRoomMap from "./utils/DMRoomMap";
|
import DMRoomMap from "./utils/DMRoomMap";
|
||||||
import {getAddressType} from "./UserAddress";
|
import {getAddressType} from "./UserAddress";
|
||||||
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new room, and switch to it.
|
* Create a new room, and switch to it.
|
||||||
|
@ -174,13 +175,55 @@ export function findDMForUser(client, userId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to ensure the user is already in the megolm session before continuing
|
||||||
|
* NOTE: this assumes you've just created the room and there's not been an opportunity
|
||||||
|
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
|
||||||
|
*/
|
||||||
|
export async function _waitForMember(client, roomId, userId, opts = { timeout: 1500 }) {
|
||||||
|
const { timeout } = opts;
|
||||||
|
let handler;
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
handler = function(_event, _roomstate, member) {
|
||||||
|
if (member.userId !== userId) return;
|
||||||
|
if (member.roomId !== roomId) return;
|
||||||
|
resolve(true);
|
||||||
|
};
|
||||||
|
client.on("RoomState.newMember", handler);
|
||||||
|
|
||||||
|
/* We don't want to hang if this goes wrong, so we proceed and hope the other
|
||||||
|
user is already in the megolm session */
|
||||||
|
setTimeout(resolve, timeout, false);
|
||||||
|
}).finally(() => {
|
||||||
|
client.removeListener("RoomState.newMember", handler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure that for every user in a room, there is at least one device that we
|
||||||
|
* can encrypt to.
|
||||||
|
*/
|
||||||
|
export async function canEncryptToAllUsers(client, userIds) {
|
||||||
|
const usersDeviceMap = await client.downloadKeys(userIds);
|
||||||
|
// { "@user:host": { "DEVICE": {...}, ... }, ... }
|
||||||
|
return Object.values(usersDeviceMap).every((userDevices) =>
|
||||||
|
// { "DEVICE": {...}, ... }
|
||||||
|
Object.keys(userDevices).length > 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function ensureDMExists(client, userId) {
|
export async function ensureDMExists(client, userId) {
|
||||||
const existingDMRoom = findDMForUser(client, userId);
|
const existingDMRoom = findDMForUser(client, userId);
|
||||||
let roomId;
|
let roomId;
|
||||||
if (existingDMRoom) {
|
if (existingDMRoom) {
|
||||||
roomId = existingDMRoom.roomId;
|
roomId = existingDMRoom.roomId;
|
||||||
} else {
|
} else {
|
||||||
roomId = await createRoom({dmUserId: userId, spinner: false, andView: false});
|
let encryption;
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||||
|
encryption = canEncryptToAllUsers(client, [userId]);
|
||||||
|
}
|
||||||
|
roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false});
|
||||||
|
await _waitForMember(client, roomId, userId);
|
||||||
}
|
}
|
||||||
return roomId;
|
return roomId;
|
||||||
}
|
}
|
||||||
|
|
72
test/createRoom-test.js
Normal file
72
test/createRoom-test.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import {_waitForMember, canEncryptToAllUsers} from '../src/createRoom';
|
||||||
|
import {EventEmitter} from 'events';
|
||||||
|
|
||||||
|
/* Shorter timeout, we've got tests to run */
|
||||||
|
const timeout = 30;
|
||||||
|
|
||||||
|
describe("waitForMember", () => {
|
||||||
|
let client;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
client = new EventEmitter();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves with false if the timeout is reached", (done) => {
|
||||||
|
_waitForMember(client, "", "", { timeout: 0 }).then((r) => {
|
||||||
|
expect(r).toBe(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves with false if the timeout is reached, even if other RoomState.newMember events fire", (done) => {
|
||||||
|
const roomId = "!roomId:domain";
|
||||||
|
const userId = "@clientId:domain";
|
||||||
|
_waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
||||||
|
expect(r).toBe(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
client.emit("RoomState.newMember", undefined, undefined, { roomId, userId: "@anotherClient:domain" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves with true if RoomState.newMember fires", (done) => {
|
||||||
|
const roomId = "!roomId:domain";
|
||||||
|
const userId = "@clientId:domain";
|
||||||
|
_waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
||||||
|
expect(r).toBe(true);
|
||||||
|
expect(client.listeners("RoomState.newMember").length).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
client.emit("RoomState.newMember", undefined, undefined, { roomId, userId });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("canEncryptToAllUsers", () => {
|
||||||
|
const trueUser = {
|
||||||
|
"@goodUser:localhost": {
|
||||||
|
"DEV1": {},
|
||||||
|
"DEV2": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const falseUser = {
|
||||||
|
"@badUser:localhost": {},
|
||||||
|
};
|
||||||
|
|
||||||
|
it("returns true if all devices have crypto", async (done) => {
|
||||||
|
const client = {
|
||||||
|
downloadKeys: async function(userIds) { return trueUser; },
|
||||||
|
};
|
||||||
|
const response = await canEncryptToAllUsers(client, ["@goodUser:localhost"]);
|
||||||
|
expect(response).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("returns false if not all users have crypto", async (done) => {
|
||||||
|
const client = {
|
||||||
|
downloadKeys: async function(userIds) { return {...trueUser, ...falseUser}; },
|
||||||
|
};
|
||||||
|
const response = await canEncryptToAllUsers(client, ["@goodUser:localhost", "@badUser:localhost"]);
|
||||||
|
expect(response).toBe(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue