Merge pull request #3913 from matrix-org/jryans/negotiate-e2e-dms

Enable encryption in DMs with device keys
This commit is contained in:
J. Ryan Stinnett 2020-01-23 16:23:59 +00:00 committed by GitHub
commit e3027d3086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 18 deletions

View File

@ -961,9 +961,9 @@ export default createReactClass({
const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog'); const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog');
const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog); const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog);
const [shouldCreate, createOpts] = await modal.finished; const [shouldCreate, opts] = await modal.finished;
if (shouldCreate) { if (shouldCreate) {
createRoom({createOpts}); createRoom(opts);
} }
}, },

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -44,13 +45,13 @@ export default createReactClass({
}, },
_roomCreateOptions() { _roomCreateOptions() {
const createOpts = {}; const opts = {};
const createOpts = opts.createOpts = {};
createOpts.name = this.state.name; createOpts.name = this.state.name;
if (this.state.isPublic) { if (this.state.isPublic) {
createOpts.visibility = "public"; createOpts.visibility = "public";
createOpts.preset = "public_chat"; createOpts.preset = "public_chat";
// to prevent createRoom from enabling guest access opts.guestAccess = false;
createOpts['initial_state'] = [];
const {alias} = this.state; const {alias} = this.state;
const localPart = alias.substr(1, alias.indexOf(":") - 1); const localPart = alias.substr(1, alias.indexOf(":") - 1);
createOpts['room_alias_name'] = localPart; createOpts['room_alias_name'] = localPart;
@ -61,7 +62,7 @@ export default createReactClass({
if (this.state.noFederate) { if (this.state.noFederate) {
createOpts.creation_content = {'m.federate': false}; createOpts.creation_content = {'m.federate': false};
} }
return createOpts; return opts;
}, },
componentDidMount() { componentDidMount() {

View File

@ -33,6 +33,7 @@ import Modal from "../../../Modal";
import {humanizeTime} from "../../../utils/humanize"; import {humanizeTime} from "../../../utils/humanize";
import createRoom from "../../../createRoom"; import createRoom from "../../../createRoom";
import {inviteMultipleToRoom} from "../../../RoomInvite"; import {inviteMultipleToRoom} from "../../../RoomInvite";
import SettingsStore from '../../../settings/SettingsStore';
export const KIND_DM = "dm"; export const KIND_DM = "dm";
export const KIND_INVITE = "invite"; export const KIND_INVITE = "invite";
@ -493,7 +494,7 @@ export default class InviteDialog extends React.PureComponent {
return false; return false;
} }
_startDm = () => { _startDm = async () => {
this.setState({busy: true}); this.setState({busy: true});
const targetIds = this.state.targets.map(t => t.userId); const targetIds = this.state.targets.map(t => t.userId);
@ -510,14 +511,31 @@ export default class InviteDialog extends React.PureComponent {
return; return;
} }
const createRoomOptions = {};
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
// Check whether all users have uploaded device keys before.
// If so, enable encryption in the new room.
const client = MatrixClientPeg.get();
const usersToDevicesMap = await client.downloadKeys(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) {
createRoomOptions.encryption = true;
}
}
// Check if it's a traditional DM and create the room if required. // Check if it's a traditional DM and create the room if required.
// TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM // TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM
let createRoomPromise = Promise.resolve(); let createRoomPromise = Promise.resolve();
if (targetIds.length === 1) { if (targetIds.length === 1) {
createRoomPromise = createRoom({dmUserId: targetIds[0]}); createRoomOptions.dmUserId = targetIds[0];
createRoomPromise = createRoom(createRoomOptions);
} else { } else {
// Create a boring room and try to invite the targets manually. // Create a boring room and try to invite the targets manually.
createRoomPromise = createRoom().then(roomId => { createRoomPromise = createRoom(createRoomOptions).then(roomId => {
return inviteMultipleToRoom(roomId, targetIds); return inviteMultipleToRoom(roomId, targetIds);
}).then(result => { }).then(result => {
if (this._shouldAbortAfterInviteError(result)) { if (this._shouldAbortAfterInviteError(result)) {

View File

@ -82,7 +82,7 @@ const _getE2EStatus = (cli, userId, devices) => {
return "warning"; return "warning";
}; };
function openDMForUser(matrixClient, userId) { async function openDMForUser(matrixClient, userId) {
const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId); const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId);
const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => { const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => {
const room = matrixClient.getRoom(roomId); const room = matrixClient.getRoom(roomId);
@ -100,9 +100,27 @@ function openDMForUser(matrixClient, userId) {
action: 'view_room', action: 'view_room',
room_id: lastActiveRoom.roomId, room_id: lastActiveRoom.roomId,
}); });
} else { return;
createRoom({dmUserId: userId});
} }
const createRoomOptions = {
dmUserId: userId,
};
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
// Check whether all users have uploaded device keys before.
// If so, enable encryption in the new room.
const usersToDevicesMap = await matrixClient.downloadKeys([userId]);
const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => {
// `devices` is an object of the form { deviceId: deviceInfo, ... }.
return Object.keys(devices).length > 0;
});
if (allHaveDeviceKeys) {
createRoomOptions.encryption = true;
}
}
createRoom(createRoomOptions);
} }
function useIsEncrypted(cli, room) { function useIsEncrypted(cli, room) {

View File

@ -1,6 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C. Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -32,6 +32,10 @@ import {getAddressType} from "./UserAddress";
* @param {object=} opts.createOpts set of options to pass to createRoom call. * @param {object=} opts.createOpts set of options to pass to createRoom call.
* @param {bool=} opts.spinner True to show a modal spinner while the room is created. * @param {bool=} opts.spinner True to show a modal spinner while the room is created.
* Default: True * Default: True
* @param {bool=} opts.guestAccess Whether to enable guest access.
* Default: True
* @param {bool=} opts.encryption Whether to enable encryption.
* Default: False
* *
* @returns {Promise} which resolves to the room id, or null if the * @returns {Promise} which resolves to the room id, or null if the
* action was aborted or failed. * action was aborted or failed.
@ -39,6 +43,8 @@ import {getAddressType} from "./UserAddress";
export default function createRoom(opts) { export default function createRoom(opts) {
opts = opts || {}; opts = opts || {};
if (opts.spinner === undefined) opts.spinner = true; if (opts.spinner === undefined) opts.spinner = true;
if (opts.guestAccess === undefined) opts.guestAccess = true;
if (opts.encryption === undefined) opts.encryption = false;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
@ -77,18 +83,30 @@ export default function createRoom(opts) {
opts.andView = true; opts.andView = true;
} }
createOpts.initial_state = createOpts.initial_state || [];
// Allow guests by default since the room is private and they'd // Allow guests by default since the room is private and they'd
// need an invite. This means clicking on a 3pid invite email can // need an invite. This means clicking on a 3pid invite email can
// actually drop you right in to a chat. // actually drop you right in to a chat.
createOpts.initial_state = createOpts.initial_state || [ if (opts.guestAccess) {
{ createOpts.initial_state.push({
type: 'm.room.guest_access',
state_key: '',
content: { content: {
guest_access: 'can_join', guest_access: 'can_join',
}, },
type: 'm.room.guest_access', });
}
if (opts.encryption) {
createOpts.initial_state.push({
type: 'm.room.encryption',
state_key: '', state_key: '',
}, content: {
]; algorithm: 'm.megolm.v1.aes-sha2',
},
});
}
let modal; let modal;
if (opts.spinner) modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); if (opts.spinner) modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');