From c25c1878b884d04e832c047c90d24acc3212a26a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 23 Jan 2020 13:54:28 +0000 Subject: [PATCH 1/3] Move control of room initial state into createRoom This changes `createRoom` so it has more control of the room's initial state, and appends state for different features, rather resetting the entire state array. This makes room for also controlling encryption state in the next change. --- src/components/structures/MatrixChat.js | 4 ++-- src/components/views/dialogs/CreateRoomDialog.js | 9 +++++---- src/createRoom.js | 14 +++++++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3ac8a93e3d..9afc79811d 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -961,9 +961,9 @@ export default createReactClass({ const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog'); const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog); - const [shouldCreate, createOpts] = await modal.finished; + const [shouldCreate, opts] = await modal.finished; if (shouldCreate) { - createRoom({createOpts}); + createRoom(opts); } }, diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 288074a891..9380226381 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -1,5 +1,6 @@ /* 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"); you may not use this file except in compliance with the License. @@ -44,13 +45,13 @@ export default createReactClass({ }, _roomCreateOptions() { - const createOpts = {}; + const opts = {}; + const createOpts = opts.createOpts = {}; createOpts.name = this.state.name; if (this.state.isPublic) { createOpts.visibility = "public"; createOpts.preset = "public_chat"; - // to prevent createRoom from enabling guest access - createOpts['initial_state'] = []; + opts.guestAccess = false; const {alias} = this.state; const localPart = alias.substr(1, alias.indexOf(":") - 1); createOpts['room_alias_name'] = localPart; @@ -61,7 +62,7 @@ export default createReactClass({ if (this.state.noFederate) { createOpts.creation_content = {'m.federate': false}; } - return createOpts; + return opts; }, componentDidMount() { diff --git a/src/createRoom.js b/src/createRoom.js index cde9e8b03e..0fa8b1b241 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -1,6 +1,6 @@ /* 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"); you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ import {getAddressType} from "./UserAddress"; * @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. * Default: True + * @param {bool=} opts.guestAccess Whether to enable guest access. + * Default: True * * @returns {Promise} which resolves to the room id, or null if the * action was aborted or failed. @@ -39,6 +41,7 @@ import {getAddressType} from "./UserAddress"; export default function createRoom(opts) { opts = opts || {}; if (opts.spinner === undefined) opts.spinner = true; + if (opts.guestAccess === undefined) opts.guestAccess = true; const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const Loader = sdk.getComponent("elements.Spinner"); @@ -80,15 +83,16 @@ export default function createRoom(opts) { // Allow guests by default since the room is private and they'd // need an invite. This means clicking on a 3pid invite email can // actually drop you right in to a chat. - createOpts.initial_state = createOpts.initial_state || [ - { + createOpts.initial_state = createOpts.initial_state || []; + if (opts.guestAccess) { + createOpts.initial_state.push({ content: { guest_access: 'can_join', }, type: 'm.room.guest_access', state_key: '', - }, - ]; + }); + } let modal; if (opts.spinner) modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); From 2b16b650fed350ccfca1730a4407d7100d88e408 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 23 Jan 2020 14:05:38 +0000 Subject: [PATCH 2/3] Add encryption option to createRoom --- src/createRoom.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/createRoom.js b/src/createRoom.js index 0fa8b1b241..c25b618dc6 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -34,6 +34,8 @@ import {getAddressType} from "./UserAddress"; * 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 * action was aborted or failed. @@ -42,6 +44,7 @@ export default function createRoom(opts) { opts = opts || {}; 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 Loader = sdk.getComponent("elements.Spinner"); @@ -80,17 +83,28 @@ export default function createRoom(opts) { opts.andView = true; } + createOpts.initial_state = createOpts.initial_state || []; + // Allow guests by default since the room is private and they'd // need an invite. This means clicking on a 3pid invite email can // 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: { guest_access: 'can_join', }, - type: 'm.room.guest_access', + }); + } + + if (opts.encryption) { + createOpts.initial_state.push({ + type: 'm.room.encryption', state_key: '', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + }, }); } From 1e25b32ba395f3bf714241f6f5eab202a2a49c31 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 23 Jan 2020 16:00:55 +0000 Subject: [PATCH 3/3] Enable encryption in DMs with device keys When the cross-signing lab is enabled, this changes DMs to use encryption as long as all invited users have uploaded device keys (which we're using as a proxy for "has some client that understands E2E"). Fixes https://github.com/vector-im/riot-web/issues/12005 --- src/components/views/dialogs/InviteDialog.js | 24 +++++++++++++++++--- src/components/views/right_panel/UserInfo.js | 24 +++++++++++++++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 703b0b5121..fde21791e7 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -33,6 +33,7 @@ import Modal from "../../../Modal"; import {humanizeTime} from "../../../utils/humanize"; import createRoom from "../../../createRoom"; import {inviteMultipleToRoom} from "../../../RoomInvite"; +import SettingsStore from '../../../settings/SettingsStore'; export const KIND_DM = "dm"; export const KIND_INVITE = "invite"; @@ -493,7 +494,7 @@ export default class InviteDialog extends React.PureComponent { return false; } - _startDm = () => { + _startDm = async () => { this.setState({busy: true}); const targetIds = this.state.targets.map(t => t.userId); @@ -510,14 +511,31 @@ export default class InviteDialog extends React.PureComponent { 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. // TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM let createRoomPromise = Promise.resolve(); if (targetIds.length === 1) { - createRoomPromise = createRoom({dmUserId: targetIds[0]}); + createRoomOptions.dmUserId = targetIds[0]; + createRoomPromise = createRoom(createRoomOptions); } else { // Create a boring room and try to invite the targets manually. - createRoomPromise = createRoom().then(roomId => { + createRoomPromise = createRoom(createRoomOptions).then(roomId => { return inviteMultipleToRoom(roomId, targetIds); }).then(result => { if (this._shouldAbortAfterInviteError(result)) { diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index b08f07ace4..01d0002801 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -82,7 +82,7 @@ const _getE2EStatus = (cli, userId, devices) => { return "warning"; }; -function openDMForUser(matrixClient, userId) { +async function openDMForUser(matrixClient, userId) { const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId); const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => { const room = matrixClient.getRoom(roomId); @@ -100,9 +100,27 @@ function openDMForUser(matrixClient, userId) { action: 'view_room', room_id: lastActiveRoom.roomId, }); - } else { - createRoom({dmUserId: userId}); + return; } + + 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) {