Merge pull request #6958 from andybalaam/separate-cryptography-setting-component

Break 'Cryptography' settings into a separate component
This commit is contained in:
Andy Balaam 2021-10-18 09:34:55 +01:00 committed by GitHub
commit f05e35bd94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 179 additions and 102 deletions

View File

@ -244,6 +244,7 @@
@import "./views/rooms/_WhoIsTypingTile.scss"; @import "./views/rooms/_WhoIsTypingTile.scss";
@import "./views/settings/_AvatarSetting.scss"; @import "./views/settings/_AvatarSetting.scss";
@import "./views/settings/_CrossSigningPanel.scss"; @import "./views/settings/_CrossSigningPanel.scss";
@import "./views/settings/_CryptographyPanel.scss";
@import "./views/settings/_DevicesPanel.scss"; @import "./views/settings/_DevicesPanel.scss";
@import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_E2eAdvancedPanel.scss";
@import "./views/settings/_EmailAddresses.scss"; @import "./views/settings/_EmailAddresses.scss";

View File

@ -0,0 +1,22 @@
.mx_CryptographyPanel_sessionInfo {
display: table;
padding-left: 0;
}
.mx_CryptographyPanel_sessionInfo > li {
display: table-row;
}
.mx_CryptographyPanel_sessionInfo > li > label,
.mx_CryptographyPanel_sessionInfo > li > span {
display: table-cell;
padding-right: 1em;
}
.mx_CryptographyPanel_importExportButtons .mx_AccessibleButton {
margin-right: 10px;
}
.mx_CryptographyPanel_importExportButtons {
margin-bottom: 15px;
}

View File

@ -14,33 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.mx_SecurityUserSettingsTab_deviceInfo {
display: table;
padding-left: 0;
}
.mx_SecurityUserSettingsTab_deviceInfo > li {
display: table-row;
}
.mx_SecurityUserSettingsTab_deviceInfo > li > label,
.mx_SecurityUserSettingsTab_deviceInfo > li > span {
display: table-cell;
padding-right: 1em;
}
.mx_SecurityUserSettingsTab_importExportButtons .mx_AccessibleButton {
margin-right: 10px;
}
.mx_SecurityUserSettingsTab_bulkOptions .mx_AccessibleButton { .mx_SecurityUserSettingsTab_bulkOptions .mx_AccessibleButton {
margin-right: 10px; margin-right: 10px;
} }
.mx_SecurityUserSettingsTab_importExportButtons {
margin-bottom: 15px;
}
.mx_SecurityUserSettingsTab_ignoredUser { .mx_SecurityUserSettingsTab_ignoredUser {
margin-bottom: 5px; margin-bottom: 5px;
} }

View File

@ -0,0 +1,110 @@
/*
Copyright 2021 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import AccessibleButton from "../elements/AccessibleButton";
import * as FormattingUtils from "../../../utils/FormattingUtils";
import SettingsStore from "../../../settings/SettingsStore";
import SettingsFlag from "../elements/SettingsFlag";
import { SettingLevel } from "../../../settings/SettingLevel";
interface IProps {
}
interface IState {
}
@replaceableComponent("views.settings.CryptographyPanel")
export default class CryptographyPanel extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
}
public render(): JSX.Element {
const client = MatrixClientPeg.get();
const deviceId = client.deviceId;
let identityKey = client.getDeviceEd25519Key();
if (!identityKey) {
identityKey = _t("<not supported>");
} else {
identityKey = FormattingUtils.formatCryptoKey(identityKey);
}
let importExportButtons = null;
if (client.isCryptoEnabled()) {
importExportButtons = (
<div className='mx_CryptographyPanel_importExportButtons'>
<AccessibleButton kind='primary' onClick={this.onExportE2eKeysClicked}>
{ _t("Export E2E room keys") }
</AccessibleButton>
<AccessibleButton kind='primary' onClick={this.onImportE2eKeysClicked}>
{ _t("Import E2E room keys") }
</AccessibleButton>
</div>
);
}
let noSendUnverifiedSetting;
if (SettingsStore.isEnabled("blacklistUnverifiedDevices")) {
noSendUnverifiedSetting = <SettingsFlag
name='blacklistUnverifiedDevices'
level={SettingLevel.DEVICE}
onChange={this.updateBlacklistDevicesFlag}
/>;
}
return (
<div className='mx_SettingsTab_section mx_CryptographyPanel'>
<span className='mx_SettingsTab_subheading'>{ _t("Cryptography") }</span>
<ul className='mx_SettingsTab_subsectionText mx_CryptographyPanel_sessionInfo'>
<li>
<label>{ _t("Session ID:") }</label>
<span><code>{ deviceId }</code></span>
</li>
<li>
<label>{ _t("Session key:") }</label>
<span><code><b>{ identityKey }</b></code></span>
</li>
</ul>
{ importExportButtons }
{ noSendUnverifiedSetting }
</div>
);
}
private onExportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Export E2E Keys', '',
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
{ matrixClient: MatrixClientPeg.get() },
);
};
private onImportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Import E2E Keys', '',
import('../../../async-components/views/dialogs/security/ImportE2eKeysDialog'),
{ matrixClient: MatrixClientPeg.get() },
);
};
private updateBlacklistDevicesFlag = (checked): void => {
MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
};
}

View File

@ -21,10 +21,8 @@ import { sleep } from "matrix-js-sdk/src/utils";
import { _t } from "../../../../../languageHandler"; import { _t } from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig"; import SdkConfig from "../../../../../SdkConfig";
import { MatrixClientPeg } from "../../../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../../utils/FormattingUtils";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
import Analytics from "../../../../../Analytics"; import Analytics from "../../../../../Analytics";
import Modal from "../../../../../Modal";
import dis from "../../../../../dispatcher/dispatcher"; import dis from "../../../../../dispatcher/dispatcher";
import { privateShouldBeEncrypted } from "../../../../../createRoom"; import { privateShouldBeEncrypted } from "../../../../../createRoom";
import { SettingLevel } from "../../../../../settings/SettingLevel"; import { SettingLevel } from "../../../../../settings/SettingLevel";
@ -37,6 +35,7 @@ import { replaceableComponent } from "../../../../../utils/replaceableComponent"
import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
import { ActionPayload } from "../../../../../dispatcher/payloads"; import { ActionPayload } from "../../../../../dispatcher/payloads";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import CryptographyPanel from "../../CryptographyPanel";
import DevicesPanel from "../../DevicesPanel"; import DevicesPanel from "../../DevicesPanel";
import SettingsFlag from "../../../elements/SettingsFlag"; import SettingsFlag from "../../../elements/SettingsFlag";
import CrossSigningPanel from "../../CrossSigningPanel"; import CrossSigningPanel from "../../CrossSigningPanel";
@ -112,30 +111,12 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
} }
private updateBlacklistDevicesFlag = (checked): void => {
MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
};
private updateAnalytics = (checked: boolean): void => { private updateAnalytics = (checked: boolean): void => {
checked ? Analytics.enable() : Analytics.disable(); checked ? Analytics.enable() : Analytics.disable();
CountlyAnalytics.instance.enable(/* anonymous = */ !checked); CountlyAnalytics.instance.enable(/* anonymous = */ !checked);
PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId());
}; };
private onExportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Export E2E Keys', '',
import('../../../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
{ matrixClient: MatrixClientPeg.get() },
);
};
private onImportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Import E2E Keys', '',
import('../../../../../async-components/views/dialogs/security/ImportE2eKeysDialog'),
{ matrixClient: MatrixClientPeg.get() },
);
};
private onGoToUserProfileClick = (): void => { private onGoToUserProfileClick = (): void => {
dis.dispatch({ dis.dispatch({
action: 'view_user_info', action: 'view_user_info',
@ -211,58 +192,6 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
this.manageInvites(false); this.manageInvites(false);
}; };
private renderCurrentDeviceInfo(): JSX.Element {
const client = MatrixClientPeg.get();
const deviceId = client.deviceId;
let identityKey = client.getDeviceEd25519Key();
if (!identityKey) {
identityKey = _t("<not supported>");
} else {
identityKey = FormattingUtils.formatCryptoKey(identityKey);
}
let importExportButtons = null;
if (client.isCryptoEnabled()) {
importExportButtons = (
<div className='mx_SecurityUserSettingsTab_importExportButtons'>
<AccessibleButton kind='primary' onClick={this.onExportE2eKeysClicked}>
{ _t("Export E2E room keys") }
</AccessibleButton>
<AccessibleButton kind='primary' onClick={this.onImportE2eKeysClicked}>
{ _t("Import E2E room keys") }
</AccessibleButton>
</div>
);
}
let noSendUnverifiedSetting;
if (SettingsStore.isEnabled("blacklistUnverifiedDevices")) {
noSendUnverifiedSetting = <SettingsFlag
name='blacklistUnverifiedDevices'
level={SettingLevel.DEVICE}
onChange={this.updateBlacklistDevicesFlag}
/>;
}
return (
<div className='mx_SettingsTab_section'>
<span className='mx_SettingsTab_subheading'>{ _t("Cryptography") }</span>
<ul className='mx_SettingsTab_subsectionText mx_SecurityUserSettingsTab_deviceInfo'>
<li>
<label>{ _t("Session ID:") }</label>
<span><code>{ deviceId }</code></span>
</li>
<li>
<label>{ _t("Session key:") }</label>
<span><code><b>{ identityKey }</b></code></span>
</li>
</ul>
{ importExportButtons }
{ noSendUnverifiedSetting }
</div>
);
}
private renderIgnoredUsers(): JSX.Element { private renderIgnoredUsers(): JSX.Element {
const { waitingUnignored, ignoredUserIds } = this.state; const { waitingUnignored, ignoredUserIds } = this.state;
@ -418,7 +347,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
{ secureBackup } { secureBackup }
{ eventIndex } { eventIndex }
{ crossSigning } { crossSigning }
{ this.renderCurrentDeviceInfo() } <CryptographyPanel />
</div> </div>
{ privacySection } { privacySection }
{ advancedSection } { advancedSection }

View File

@ -1128,6 +1128,11 @@
"User signing private key:": "User signing private key:", "User signing private key:": "User signing private key:",
"Homeserver feature support:": "Homeserver feature support:", "Homeserver feature support:": "Homeserver feature support:",
"exists": "exists", "exists": "exists",
"<not supported>": "<not supported>",
"Import E2E room keys": "Import E2E room keys",
"Cryptography": "Cryptography",
"Session ID:": "Session ID:",
"Session key:": "Session key:",
"Your homeserver does not support session management.": "Your homeserver does not support session management.", "Your homeserver does not support session management.": "Your homeserver does not support session management.",
"Unable to load session list": "Unable to load session list", "Unable to load session list": "Unable to load session list",
"Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Confirm deleting these sessions by using Single Sign On to prove your identity.", "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Confirm deleting these sessions by using Single Sign On to prove your identity.",
@ -1393,11 +1398,6 @@
"Read Marker lifetime (ms)": "Read Marker lifetime (ms)", "Read Marker lifetime (ms)": "Read Marker lifetime (ms)",
"Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)",
"Unignore": "Unignore", "Unignore": "Unignore",
"<not supported>": "<not supported>",
"Import E2E room keys": "Import E2E room keys",
"Cryptography": "Cryptography",
"Session ID:": "Session ID:",
"Session key:": "Session key:",
"You have no ignored users.": "You have no ignored users.", "You have no ignored users.": "You have no ignored users.",
"Bulk options": "Bulk options", "Bulk options": "Bulk options",
"Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites",

View File

@ -0,0 +1,38 @@
import '../../../skinned-sdk';
import * as TestUtils from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import React, { ReactElement } from 'react';
import ReactDOM from 'react-dom';
import { MatrixClient } from 'matrix-js-sdk';
import CryptographyPanel from '../../../../src/components/views/settings/CryptographyPanel';
describe('CryptographyPanel', () => {
it('shows the session ID and key', () => {
const sessionId = "ABCDEFGHIJ";
const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl";
const sessionKeyFormatted = "<b>AbCD eFgh IJK7 L/m4 nOPq RSTU VW4x yzaB CDef 6gHI Jkl</b>";
TestUtils.stubClient();
const client: MatrixClient = MatrixClientPeg.get();
client.deviceId = sessionId;
client.getDeviceEd25519Key = () => sessionKey;
// When we render the CryptographyPanel
const rendered = render(<CryptographyPanel />);
// Then it displays info about the user's session
const codes = rendered.querySelectorAll("code");
expect(codes.length).toEqual(2);
expect(codes[0].innerHTML).toEqual(sessionId);
expect(codes[1].innerHTML).toEqual(sessionKeyFormatted);
});
});
function render(component: ReactElement<CryptographyPanel>): HTMLDivElement {
const parentDiv = document.createElement('div');
document.body.appendChild(parentDiv);
ReactDOM.render(component, parentDiv);
return parentDiv;
}

View File

@ -44,7 +44,7 @@ module.exports.enableLazyLoading = async function(session) {
module.exports.getE2EDeviceFromSettings = async function(session) { module.exports.getE2EDeviceFromSettings = async function(session) {
session.log.step(`gets e2e device/key from settings`); session.log.step(`gets e2e device/key from settings`);
await openSettings(session, "security"); await openSettings(session, "security");
const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_SecurityUserSettingsTab_deviceInfo code"); const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_CryptographyPanel code");
assert.equal(deviceAndKey.length, 2); assert.equal(deviceAndKey.length, 2);
const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue();
const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue();