diff --git a/res/css/_components.scss b/res/css/_components.scss index 4abcbbc9d4..9e3988a2fe 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -244,6 +244,7 @@ @import "./views/rooms/_WhoIsTypingTile.scss"; @import "./views/settings/_AvatarSetting.scss"; @import "./views/settings/_CrossSigningPanel.scss"; +@import "./views/settings/_CryptographyPanel.scss"; @import "./views/settings/_DevicesPanel.scss"; @import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; diff --git a/res/css/views/settings/_CryptographyPanel.scss b/res/css/views/settings/_CryptographyPanel.scss new file mode 100644 index 0000000000..2290791d5d --- /dev/null +++ b/res/css/views/settings/_CryptographyPanel.scss @@ -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; +} diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index 8cc51e7e35..f1ad2352f2 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -14,33 +14,10 @@ See the License for the specific language governing permissions and 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 { margin-right: 10px; } -.mx_SecurityUserSettingsTab_importExportButtons { - margin-bottom: 15px; -} - .mx_SecurityUserSettingsTab_ignoredUser { margin-bottom: 5px; } diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx new file mode 100644 index 0000000000..1e1cb3ba68 --- /dev/null +++ b/src/components/views/settings/CryptographyPanel.tsx @@ -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 { + 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(""); + } else { + identityKey = FormattingUtils.formatCryptoKey(identityKey); + } + + let importExportButtons = null; + if (client.isCryptoEnabled()) { + importExportButtons = ( +
+ + { _t("Export E2E room keys") } + + + { _t("Import E2E room keys") } + +
+ ); + } + + let noSendUnverifiedSetting; + if (SettingsStore.isEnabled("blacklistUnverifiedDevices")) { + noSendUnverifiedSetting = ; + } + + return ( +
+ { _t("Cryptography") } +
    +
  • + + { deviceId } +
  • +
  • + + { identityKey } +
  • +
+ { importExportButtons } + { noSendUnverifiedSetting } +
+ ); + } + + 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); + }; +} diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index 6aa45d05b6..a9e02fa8a2 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -21,10 +21,8 @@ import { sleep } from "matrix-js-sdk/src/utils"; import { _t } from "../../../../../languageHandler"; import SdkConfig from "../../../../../SdkConfig"; import { MatrixClientPeg } from "../../../../../MatrixClientPeg"; -import * as FormattingUtils from "../../../../../utils/FormattingUtils"; import AccessibleButton from "../../../elements/AccessibleButton"; import Analytics from "../../../../../Analytics"; -import Modal from "../../../../../Modal"; import dis from "../../../../../dispatcher/dispatcher"; import { privateShouldBeEncrypted } from "../../../../../createRoom"; import { SettingLevel } from "../../../../../settings/SettingLevel"; @@ -37,6 +35,7 @@ import { replaceableComponent } from "../../../../../utils/replaceableComponent" import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; import { ActionPayload } from "../../../../../dispatcher/payloads"; import { Room } from "matrix-js-sdk/src/models/room"; +import CryptographyPanel from "../../CryptographyPanel"; import DevicesPanel from "../../DevicesPanel"; import SettingsFlag from "../../../elements/SettingsFlag"; import CrossSigningPanel from "../../CrossSigningPanel"; @@ -112,30 +111,12 @@ export default class SecurityUserSettingsTab extends React.Component { - MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked); - }; - private updateAnalytics = (checked: boolean): void => { checked ? Analytics.enable() : Analytics.disable(); CountlyAnalytics.instance.enable(/* anonymous = */ !checked); 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 => { dis.dispatch({ action: 'view_user_info', @@ -211,58 +192,6 @@ export default class SecurityUserSettingsTab extends React.Component"); - } else { - identityKey = FormattingUtils.formatCryptoKey(identityKey); - } - - let importExportButtons = null; - if (client.isCryptoEnabled()) { - importExportButtons = ( -
- - { _t("Export E2E room keys") } - - - { _t("Import E2E room keys") } - -
- ); - } - - let noSendUnverifiedSetting; - if (SettingsStore.isEnabled("blacklistUnverifiedDevices")) { - noSendUnverifiedSetting = ; - } - - return ( -
- { _t("Cryptography") } -
    -
  • - - { deviceId } -
  • -
  • - - { identityKey } -
  • -
- { importExportButtons } - { noSendUnverifiedSetting } -
- ); - } - private renderIgnoredUsers(): JSX.Element { const { waitingUnignored, ignoredUserIds } = this.state; @@ -418,7 +347,7 @@ export default class SecurityUserSettingsTab extends React.Component { privacySection } { advancedSection } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 55674ad553..42c840d91d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1128,6 +1128,11 @@ "User signing private key:": "User signing private key:", "Homeserver feature support:": "Homeserver feature support:", "exists": "exists", + "": "", + "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.", "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.", @@ -1393,11 +1398,6 @@ "Read Marker lifetime (ms)": "Read Marker lifetime (ms)", "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", "Unignore": "Unignore", - "": "", - "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.", "Bulk options": "Bulk options", "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", diff --git a/test/components/views/settings/CryptographyPanel-test.tsx b/test/components/views/settings/CryptographyPanel-test.tsx new file mode 100644 index 0000000000..56056b53ec --- /dev/null +++ b/test/components/views/settings/CryptographyPanel-test.tsx @@ -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 = "AbCD eFgh IJK7 L/m4 nOPq RSTU VW4x yzaB CDef 6gHI Jkl"; + + TestUtils.stubClient(); + const client: MatrixClient = MatrixClientPeg.get(); + client.deviceId = sessionId; + client.getDeviceEd25519Key = () => sessionKey; + + // When we render the CryptographyPanel + const rendered = render(); + + // 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): HTMLDivElement { + const parentDiv = document.createElement('div'); + document.body.appendChild(parentDiv); + ReactDOM.render(component, parentDiv); + return parentDiv; +} diff --git a/test/end-to-end-tests/src/usecases/settings.js b/test/end-to-end-tests/src/usecases/settings.js index 509e0b4a07..372bdead10 100644 --- a/test/end-to-end-tests/src/usecases/settings.js +++ b/test/end-to-end-tests/src/usecases/settings.js @@ -44,7 +44,7 @@ module.exports.enableLazyLoading = async function(session) { module.exports.getE2EDeviceFromSettings = async function(session) { session.log.step(`gets e2e device/key from settings`); 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); const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue();