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/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index cb4f123cea..fc31867385 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) { diff --git a/src/createRoom.js b/src/createRoom.js index cde9e8b03e..c25b618dc6 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,10 @@ 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 + * @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. @@ -39,6 +43,8 @@ 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; + if (opts.encryption === undefined) opts.encryption = false; const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const Loader = sdk.getComponent("elements.Spinner"); @@ -77,18 +83,30 @@ 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', + }, + }); + } let modal; if (opts.spinner) modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');