Merge pull request #4605 from matrix-org/t3chguy/e2eedefault

Add .well-known option to control default e2ee behaviour
This commit is contained in:
Michael Telatynski
2020-06-03 22:12:13 +01:00
committed by GitHub
13 changed files with 136 additions and 88 deletions

View File

@@ -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);
}
}
}

View File

@@ -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.

View File

@@ -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);

View File

@@ -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 cant disable this later. Bridges & most bots wont work yet.");
} else {
microcopy = _t("Your server admin has disabled end-to-end encryption by default " +
"in private rooms & Direct Messages.");
}
e2eeSection = <React.Fragment>
<LabelledToggleSwitch
label={ _t("Enable end-to-end encryption")}
@@ -200,7 +208,7 @@ export default createReactClass({
value={this.state.isEncrypted}
className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests
/>
<p>{ _t("You cant disable this later. Bridges & most bots wont work yet.") }</p>
<p>{ microcopy }</p>
</React.Fragment>;
}

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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 = <div className="mx_SecurityUserSettingsTab_warning">
{ _t("Your server admin has disabled end-to-end encryption by default " +
"in private rooms & Direct Messages.") }
</div>;
}
return (
<div className="mx_SettingsTab mx_SecurityUserSettingsTab">
{warning}
<div className="mx_SettingsTab_heading">{_t("Security & Privacy")}</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Where youre logged in")}</span>

View File

@@ -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;
}

View File

@@ -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 youre logged in": "Where youre logged in",
"Manage the names of and sign out of your sessions below or <a>verify them in your User Profile</a>.": "Manage the names of and sign out of your sessions below or <a>verify them in your User Profile</a>.",
@@ -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 cant disable this later. Bridges & most bots wont work yet.": "You cant disable this later. Bridges & most bots wont 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",

View File

@@ -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");
}
}

View File

@@ -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<any> {
private update = async (discoveryResponse): Promise<any> => {
// 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