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
commit eccacb1bc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 136 additions and 88 deletions

View file

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

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.29 3.86002L1.82002 18C1.46466 18.6154 1.46254 19.3732 1.81445 19.9905C2.16635 20.6079 2.81943 20.9922 3.53002 21H20.47C21.1806 20.9922 21.8337 20.6079 22.1856 19.9905C22.5375 19.3732 22.5354 18.6154 22.18 18L13.71 3.86002C13.3475 3.2623 12.6991 2.89728 12 2.89728C11.3009 2.89728 10.6526 3.2623 10.29 3.86002Z" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 9V13" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="12" cy="17" r="1" fill="#2E2F32"/>
</svg>

After

Width:  |  Height:  |  Size: 665 B

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