diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index 8700f8747d..d6466a03f9 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -63,4 +63,25 @@ limitations under the License. font-size: inherit; } } + + .mx_SecurityUserSettingsTab_warning { + color: $notice-primary-color; + position: relative; + padding-left: 40px; + margin-top: 30px; + + &::before { + mask-repeat: no-repeat; + mask-position: 0 center; + mask-size: $font-24px; + position: absolute; + width: $font-24px; + height: $font-24px; + content: ""; + top: 0; + left: 0; + background-color: $notice-primary-color; + mask-image: url('$(res)/img/feather-customised/alert-triangle.svg'); + } + } } diff --git a/res/img/feather-customised/alert-triangle.svg b/res/img/feather-customised/alert-triangle.svg new file mode 100644 index 0000000000..ceb664790f --- /dev/null +++ b/res/img/feather-customised/alert-triangle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index a5175b3220..e73b56416b 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -22,13 +22,13 @@ import { import { hideToast as hideSetupEncryptionToast, Kind as SetupKind, - Kind, showToast as showSetupEncryptionToast } from "./toasts/SetupEncryptionToast"; import { hideToast as hideUnverifiedSessionsToast, showToast as showUnverifiedSessionsToast } from "./toasts/UnverifiedSessionToast"; +import {privateShouldBeEncrypted} from "./createRoom"; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; @@ -169,6 +169,14 @@ export default class DeviceListener { return this.keyBackupInfo; } + private shouldShowSetupEncryptionToast() { + // In a default configuration, show the toasts. If the well-known config causes e2ee default to be false + // then do not show the toasts until user is in at least one encrypted room. + if (privateShouldBeEncrypted()) return true; + const cli = MatrixClientPeg.get(); + return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId)); + } + async _recheck() { const cli = MatrixClientPeg.get(); @@ -184,7 +192,7 @@ export default class DeviceListener { if (this.dismissedThisDeviceToast || crossSigningReady) { hideSetupEncryptionToast(); - } else { + } else if (this.shouldShowSetupEncryptionToast()) { // make sure our keys are finished downloading await cli.downloadKeys([cli.getUserId()]); // cross signing isn't enabled - nag to enable it @@ -196,10 +204,10 @@ export default class DeviceListener { const backupInfo = await this._getKeyBackupInfo(); if (backupInfo) { // No cross-signing on account but key backup available (upgrade encryption) - showSetupEncryptionToast(Kind.UPGRADE_ENCRYPTION); + showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION); } else { // No cross-signing or key backup on account (set up encryption) - showSetupEncryptionToast(Kind.SET_UP_ENCRYPTION); + showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION); } } } diff --git a/src/Lifecycle.js b/src/Lifecycle.js index d018ea99aa..96cefaf593 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -622,7 +622,7 @@ async function startMatrixClient(startSyncing=true) { } // Now that we have a MatrixClientPeg, update the Jitsi info - await Jitsi.getInstance().update(); + await Jitsi.getInstance().start(); // dispatch that we finished starting up to wire up any other bits // of the matrix client that cannot be set prior to starting up. diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index c6ee6c546f..bc550c1935 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -49,6 +49,7 @@ export interface IOpts { initialSyncLimit?: number; pendingEventOrdering?: "detached" | "chronological"; lazyLoadMembers?: boolean; + clientWellKnownPollPeriod?: number; } export interface IMatrixClientPeg { @@ -209,6 +210,7 @@ class _MatrixClientPeg implements IMatrixClientPeg { // the react sdk doesn't work without this, so don't allow opts.pendingEventOrdering = "detached"; opts.lazyLoadMembers = true; + opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours // Connect the matrix client to the dispatcher and setting handlers MatrixActionCreators.start(this.matrixClient); diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 87fbf3de02..ce7ac6e59c 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -24,6 +24,7 @@ import withValidation from '../elements/Validation'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {Key} from "../../../Keyboard"; +import {privateShouldBeEncrypted} from "../../../createRoom"; export default createReactClass({ displayName: 'CreateRoomDialog', @@ -36,7 +37,7 @@ export default createReactClass({ const config = SdkConfig.get(); return { isPublic: this.props.defaultPublic || false, - isEncrypted: true, + isEncrypted: privateShouldBeEncrypted(), name: "", topic: "", alias: "", @@ -193,6 +194,13 @@ export default createReactClass({ let e2eeSection; if (!this.state.isPublic) { + let microcopy; + if (privateShouldBeEncrypted()) { + microcopy = _t("You can’t disable this later. Bridges & most bots won’t work yet."); + } else { + microcopy = _t("Your server admin has disabled end-to-end encryption by default " + + "in private rooms & Direct Messages."); + } e2eeSection = -

{ _t("You can’t disable this later. Bridges & most bots won’t work yet.") }

+

{ microcopy }

; } diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index c245ba35aa..7ac9e21518 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -31,7 +31,7 @@ import dis from "../../../dispatcher/dispatcher"; import IdentityAuthClient from "../../../IdentityAuthClient"; import Modal from "../../../Modal"; import {humanizeTime} from "../../../utils/humanize"; -import createRoom, {canEncryptToAllUsers} from "../../../createRoom"; +import createRoom, {canEncryptToAllUsers, privateShouldBeEncrypted} from "../../../createRoom"; import {inviteMultipleToRoom} from "../../../RoomInvite"; import {Key} from "../../../Keyboard"; import {Action} from "../../../dispatcher/actions"; @@ -575,14 +575,16 @@ export default class InviteDialog extends React.PureComponent { const createRoomOptions = {inlineErrors: true}; - // Check whether all users have uploaded device keys before. - // If so, enable encryption in the new room. - const has3PidMembers = targets.some(t => t instanceof ThreepidMember); - if (!has3PidMembers) { - const client = MatrixClientPeg.get(); - const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds); - if (allHaveDeviceKeys) { - createRoomOptions.encryption = true; + if (privateShouldBeEncrypted()) { + // Check whether all users have uploaded device keys before. + // If so, enable encryption in the new room. + const has3PidMembers = targets.some(t => t instanceof ThreepidMember); + if (!has3PidMembers) { + const client = MatrixClientPeg.get(); + const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds); + if (allHaveDeviceKeys) { + createRoomOptions.encryption = true; + } } } diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 836e35ba22..34136b2177 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -25,7 +25,7 @@ import dis from '../../../dispatcher/dispatcher'; import Modal from '../../../Modal'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; -import createRoom from '../../../createRoom'; +import createRoom, {privateShouldBeEncrypted} from '../../../createRoom'; import DMRoomMap from '../../../utils/DMRoomMap'; import AccessibleButton from '../elements/AccessibleButton'; import SdkConfig from '../../../SdkConfig'; @@ -108,15 +108,17 @@ async function openDMForUser(matrixClient, userId) { dmUserId: userId, }; - // 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; + if (privateShouldBeEncrypted()) { + // 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); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index b3c3f63d72..952d9f1e78 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -26,6 +26,7 @@ import Modal from "../../../../../Modal"; import * as sdk from "../../../../.."; import {sleep} from "../../../../../utils/promise"; import dis from "../../../../../dispatcher/dispatcher"; +import {privateShouldBeEncrypted} from "../../../../../createRoom"; export class IgnoredUser extends React.Component { static propTypes = { @@ -317,8 +318,17 @@ export default class SecurityUserSettingsTab extends React.Component { const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); + let warning; + if (!privateShouldBeEncrypted()) { + warning =
+ { _t("Your server admin has disabled end-to-end encryption by default " + + "in private rooms & Direct Messages.") } +
; + } + return (
+ {warning}
{_t("Security & Privacy")}
{_t("Where you’re logged in")} diff --git a/src/createRoom.js b/src/createRoom.js index b5761e91c5..affdf196a7 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -24,6 +24,8 @@ import * as Rooms from "./Rooms"; import DMRoomMap from "./utils/DMRoomMap"; import {getAddressType} from "./UserAddress"; +const E2EE_WK_KEY = "im.vector.riot.e2ee"; + /** * Create a new room, and switch to it. * @@ -225,9 +227,22 @@ export async function ensureDMExists(client, userId) { if (existingDMRoom) { roomId = existingDMRoom.roomId; } else { - const encryption = canEncryptToAllUsers(client, [userId]); + let encryption; + if (privateShouldBeEncrypted()) { + encryption = canEncryptToAllUsers(client, [userId]); + } roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false}); await _waitForMember(client, roomId, userId); } return roomId; } + +export function privateShouldBeEncrypted() { + const clientWellKnown = MatrixClientPeg.get().getClientWellKnown(); + if (clientWellKnown && clientWellKnown[E2EE_WK_KEY]) { + const defaultDisabled = clientWellKnown[E2EE_WK_KEY]["default"] === false; + return !defaultDisabled; + } + + return true; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 17961d523c..eaa34278e7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -871,6 +871,7 @@ "Key backup": "Key backup", "Message search": "Message search", "Cross-signing": "Cross-signing", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", "Security & Privacy": "Security & Privacy", "Where you’re logged in": "Where you’re logged in", "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Manage the names of and sign out of your sessions below or verify them in your User Profile.", @@ -1559,8 +1560,8 @@ "Please enter a name for the room": "Please enter a name for the room", "Set a room address to easily share your room with other people.": "Set a room address to easily share your room with other people.", "This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.", - "Enable end-to-end encryption": "Enable end-to-end encryption", "You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.", + "Enable end-to-end encryption": "Enable end-to-end encryption", "Create a public room": "Create a public room", "Create a private room": "Create a private room", "Name": "Name", diff --git a/src/integrations/IntegrationManagers.js b/src/integrations/IntegrationManagers.js index 3ba1aab135..5fd28d7c54 100644 --- a/src/integrations/IntegrationManagers.js +++ b/src/integrations/IntegrationManagers.js @@ -21,10 +21,8 @@ import {IntegrationManagerInstance, KIND_ACCOUNT, KIND_CONFIG, KIND_HOMESERVER} import type {MatrixClient, MatrixEvent, Room} from "matrix-js-sdk"; import WidgetUtils from "../utils/WidgetUtils"; import {MatrixClientPeg} from "../MatrixClientPeg"; -import {AutoDiscovery} from "matrix-js-sdk"; import SettingsStore from "../settings/SettingsStore"; -const HS_MANAGERS_REFRESH_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours const KIND_PREFERENCE = [ // Ordered: first is most preferred, last is least preferred. KIND_ACCOUNT, @@ -44,7 +42,6 @@ export class IntegrationManagers { _managers: IntegrationManagerInstance[] = []; _client: MatrixClient; - _wellknownRefreshTimerId: number = null; _primaryManager: IntegrationManagerInstance; constructor() { @@ -55,20 +52,19 @@ export class IntegrationManagers { this.stopWatching(); this._client = MatrixClientPeg.get(); this._client.on("accountData", this._onAccountData); + this._client.on("WellKnown.client", this._setupHomeserverManagers); this._compileManagers(); - setInterval(() => this._setupHomeserverManagers(), HS_MANAGERS_REFRESH_INTERVAL); } stopWatching(): void { if (!this._client) return; this._client.removeListener("accountData", this._onAccountData); - if (this._wellknownRefreshTimerId !== null) clearInterval(this._wellknownRefreshTimerId); + this._client.removeListener("WellKnown.client", this._setupHomeserverManagers); } _compileManagers() { this._managers = []; this._setupConfiguredManager(); - this._setupHomeserverManagers(); this._setupAccountManagers(); } @@ -82,39 +78,31 @@ export class IntegrationManagers { } } - async _setupHomeserverManagers() { - if (!MatrixClientPeg.get()) return; - try { - console.log("Updating homeserver-configured integration managers..."); - const homeserverDomain = MatrixClientPeg.getHomeserverName(); - const discoveryResponse = await AutoDiscovery.getRawClientConfig(homeserverDomain); - if (discoveryResponse && discoveryResponse['m.integrations']) { - let managers = discoveryResponse['m.integrations']['managers']; - if (!Array.isArray(managers)) managers = []; // make it an array so we can wipe the HS managers + async _setupHomeserverManagers(discoveryResponse) { + console.log("Updating homeserver-configured integration managers..."); + if (discoveryResponse && discoveryResponse['m.integrations']) { + let managers = discoveryResponse['m.integrations']['managers']; + if (!Array.isArray(managers)) managers = []; // make it an array so we can wipe the HS managers - console.log(`Homeserver has ${managers.length} integration managers`); + console.log(`Homeserver has ${managers.length} integration managers`); - // Clear out any known managers for the homeserver - // TODO: Log out of the scalar clients - this._managers = this._managers.filter(m => m.kind !== KIND_HOMESERVER); + // Clear out any known managers for the homeserver + // TODO: Log out of the scalar clients + this._managers = this._managers.filter(m => m.kind !== KIND_HOMESERVER); - // Now add all the managers the homeserver wants us to have - for (const hsManager of managers) { - if (!hsManager["api_url"]) continue; - this._managers.push(new IntegrationManagerInstance( - KIND_HOMESERVER, - hsManager["api_url"], - hsManager["ui_url"], // optional - )); - } - - this._primaryManager = null; // reset primary - } else { - console.log("Homeserver has no integration managers"); + // Now add all the managers the homeserver wants us to have + for (const hsManager of managers) { + if (!hsManager["api_url"]) continue; + this._managers.push(new IntegrationManagerInstance( + KIND_HOMESERVER, + hsManager["api_url"], + hsManager["ui_url"], // optional + )); } - } catch (e) { - console.error(e); - // Errors during discovery are non-fatal + + this._primaryManager = null; // reset primary + } else { + console.log("Homeserver has no integration managers"); } } diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts index 15df4953aa..a52f8182aa 100644 --- a/src/widgets/Jitsi.ts +++ b/src/widgets/Jitsi.ts @@ -16,10 +16,8 @@ limitations under the License. import SdkConfig from "../SdkConfig"; import {MatrixClientPeg} from "../MatrixClientPeg"; -import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery"; const JITSI_WK_PROPERTY = "im.vector.riot.jitsi"; -const JITSI_WK_CHECK_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours, arbitrarily selected export interface JitsiWidgetData { conferenceId: string; @@ -36,39 +34,27 @@ export class Jitsi { return this.domain || 'jitsi.riot.im'; } - constructor() { - // We rely on the first call to be an .update() instead of doing one here. Doing one - // here could result in duplicate calls to the homeserver. - - // Start a timer to update the server info regularly - setInterval(() => this.update(), JITSI_WK_CHECK_INTERVAL); + public start() { + const cli = MatrixClientPeg.get(); + cli.on("WellKnown.client", this.update); + // call update initially in case we missed the first WellKnown.client event and for if no well-known present + this.update(cli.getClientWellKnown()); } - public async update(): Promise { + private update = async (discoveryResponse): Promise => { // Start with a default of the config's domain let domain = (SdkConfig.get()['jitsi'] || {})['preferredDomain'] || 'jitsi.riot.im'; - // Now request the .well-known config to see if it changed - if (MatrixClientPeg.get()) { - try { - console.log("Attempting to get Jitsi conference information from homeserver"); - - const homeserverDomain = MatrixClientPeg.getHomeserverName(); - const discoveryResponse = await AutoDiscovery.getRawClientConfig(homeserverDomain); - if (discoveryResponse && discoveryResponse[JITSI_WK_PROPERTY]) { - const wkPreferredDomain = discoveryResponse[JITSI_WK_PROPERTY]['preferredDomain']; - if (wkPreferredDomain) domain = wkPreferredDomain; - } - } catch (e) { - // These are non-fatal errors - console.error(e); - } + console.log("Attempting to get Jitsi conference information from homeserver"); + if (discoveryResponse && discoveryResponse[JITSI_WK_PROPERTY]) { + const wkPreferredDomain = discoveryResponse[JITSI_WK_PROPERTY]['preferredDomain']; + if (wkPreferredDomain) domain = wkPreferredDomain; } // Put the result into memory for us to use later this.domain = domain; console.log("Jitsi conference domain:", this.preferredDomain); - } + }; /** * Parses the given URL into the data needed for a Jitsi widget, if the widget