Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add .well-known option to control default e2ee behaviour #4605

Merged
merged 17 commits into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
}
5 changes: 5 additions & 0 deletions res/img/feather-customised/alert-triangle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 12 additions & 4 deletions src/DeviceListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();

Expand All @@ -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
Expand All @@ -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);
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/MatrixClientPeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface IOpts {
initialSyncLimit?: number;
pendingEventOrdering?: "detached" | "chronological";
lazyLoadMembers?: boolean;
clientWellKnownPollPeriod?: number;
}

export interface IMatrixClientPeg {
Expand Down Expand Up @@ -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);
Expand Down
12 changes: 10 additions & 2 deletions src/components/views/dialogs/CreateRoomDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -36,7 +37,7 @@ export default createReactClass({
const config = SdkConfig.get();
return {
isPublic: this.props.defaultPublic || false,
isEncrypted: true,
isEncrypted: privateShouldBeEncrypted(),
name: "",
topic: "",
alias: "",
Expand Down Expand Up @@ -193,14 +194,21 @@ 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 = <React.Fragment>
<LabelledToggleSwitch
label={ _t("Enable end-to-end encryption")}
onChange={this.onEncryptedChange}
value={this.state.isEncrypted}
className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests
/>
<p>{ _t("You can’t disable this later. Bridges & most bots won’t work yet.") }</p>
<p>{ microcopy }</p>
</React.Fragment>;
}

Expand Down
20 changes: 11 additions & 9 deletions src/components/views/dialogs/InviteDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
}
}

Expand Down
22 changes: 12 additions & 10 deletions src/components/views/right_panel/UserInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 you’re logged in")}</span>
Expand Down
17 changes: 16 additions & 1 deletion src/createRoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
}
3 changes: 2 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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>.",
Expand Down Expand Up @@ -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",
Expand Down
64 changes: 26 additions & 38 deletions src/integrations/IntegrationManagers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -44,7 +42,6 @@ export class IntegrationManagers {

_managers: IntegrationManagerInstance[] = [];
_client: MatrixClient;
_wellknownRefreshTimerId: number = null;
_primaryManager: IntegrationManagerInstance;

constructor() {
Expand All @@ -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();
}

Expand All @@ -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

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

// 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");
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`);

// 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
));
}
} catch (e) {
console.error(e);
// Errors during discovery are non-fatal

this._primaryManager = null; // reset primary
} else {
console.log("Homeserver has no integration managers");
}
}

Expand Down
Loading