From b6364a4cea5f68e77e0c47e03ce61383ba8fa1ab Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:18:55 +0000 Subject: [PATCH] Enable the rust-crypto labs button (#12114) * `LabsUserSettingsTab-test.tsx`: use a real `SdkConfig` ... instead of mocking it out. Doing so allows us more flexibility, and gives a more realistic test. * Enable the rust-crypto labs button * fix up a test --- src/MatrixClientPeg.ts | 3 +- src/i18n/strings/en_EN.json | 5 +- src/settings/Settings.tsx | 19 ++-- .../controllers/RustCryptoSdkController.ts | 29 +++++- test/MatrixClientPeg-test.ts | 3 + .../tabs/user/LabsUserSettingsTab-test.tsx | 90 +++++++++++++++++-- .../LabsUserSettingsTab-test.tsx.snap | 10 +-- 7 files changed, 139 insertions(+), 20 deletions(-) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index be2151044df..d09b8467fdb 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -52,6 +52,7 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import PlatformPeg from "./PlatformPeg"; import { formatList } from "./utils/FormattingUtils"; import SdkConfig from "./SdkConfig"; +import { Features } from "./settings/Settings"; export interface IMatrixClientCreds { homeserverUrl: string; @@ -301,7 +302,7 @@ class MatrixClientPegClass implements IMatrixClientPeg { throw new Error("createClient must be called first"); } - const useRustCrypto = SettingsStore.getValue("feature_rust_crypto"); + const useRustCrypto = SettingsStore.getValue(Features.RustCrypto); // we want to make sure that the same crypto implementation is used throughout the lifetime of a device, // so persist the setting at the device layer diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7dc63919823..5521b74331d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1447,7 +1447,10 @@ "report_to_moderators": "Report to moderators", "report_to_moderators_description": "In rooms that support moderation, the “Report” button will let you report abuse to room moderators.", "rust_crypto": "Rust cryptography implementation", - "rust_crypto_disabled_notice": "Can currently only be enabled via config.json", + "rust_crypto_in_config": "Rust cryptography cannot be disabled on this deployment of %(brand)s", + "rust_crypto_in_config_description": "Switching to the Rust cryptography requires a migration process that may take several minutes. It cannot be disabled; use with caution!", + "rust_crypto_optin_warning": "Switching to the Rust cryptography requires a migration process that may take several minutes. To disable you will need to log out and back in; use with caution!", + "rust_crypto_requires_logout": "Once enabled, Rust cryptography can only be disabled by logging out and in again", "sliding_sync": "Sliding Sync mode", "sliding_sync_checking": "Checking…", "sliding_sync_configuration": "Sliding Sync configuration", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 18a5ecb9ab3..3a21d6f6dda 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -46,6 +46,7 @@ import RustCryptoSdkController from "./controllers/RustCryptoSdkController"; import ServerSupportUnstableFeatureController from "./controllers/ServerSupportUnstableFeatureController"; import { WatchManager } from "./WatchManager"; import { CustomTheme } from "../theme"; +import SettingsStore from "./SettingsStore"; export const defaultWatchManager = new WatchManager(); @@ -94,6 +95,7 @@ export enum Features { VoiceBroadcastForceSmallChunks = "feature_voice_broadcast_force_small_chunks", NotificationSettings2 = "feature_notification_settings2", OidcNativeFlow = "feature_oidc_native_flow", + RustCrypto = "feature_rust_crypto", } export const labGroupNames: Record = { @@ -480,15 +482,22 @@ export const SETTINGS: { [setting: string]: ISetting } = { description: _td("labs|oidc_native_flow_description"), default: false, }, - "feature_rust_crypto": { - // use the rust matrix-sdk-crypto-js for crypto. + [Features.RustCrypto]: { + // use the rust matrix-sdk-crypto-wasm for crypto. isFeature: true, labsGroup: LabGroup.Developer, - configDisablesSetting: true, + // unlike most features, `configDisablesSetting` is false here. supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, displayName: _td("labs|rust_crypto"), - description: _td("labs|under_active_development"), - // shouldWarn: true, + description: () => { + if (SettingsStore.getValueAt(SettingLevel.CONFIG, Features.RustCrypto)) { + // It's enabled in the config, so you can't get rid of it even by logging out. + return _t("labs|rust_crypto_in_config_description"); + } else { + return _t("labs|rust_crypto_optin_warning"); + } + }, + shouldWarn: true, default: false, controller: new RustCryptoSdkController(), }, diff --git a/src/settings/controllers/RustCryptoSdkController.ts b/src/settings/controllers/RustCryptoSdkController.ts index 8de2fb3d877..3bf8526febb 100644 --- a/src/settings/controllers/RustCryptoSdkController.ts +++ b/src/settings/controllers/RustCryptoSdkController.ts @@ -15,12 +15,35 @@ limitations under the License. */ import { _t } from "../../languageHandler"; +import SettingsStore from "../SettingsStore"; +import { SettingLevel } from "../SettingLevel"; +import PlatformPeg from "../../PlatformPeg"; import SettingController from "./SettingController"; +import { Features } from "../Settings"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; +import SdkConfig from "../../SdkConfig"; export default class RustCryptoSdkController extends SettingController { + public onChange(level: SettingLevel, roomId: string | null, newValue: any): void { + // If the crypto stack has already been initialized, we'll need to reload the app to make it take effect. + if (MatrixClientPeg.get()?.getCrypto()) { + PlatformPeg.get()?.reload(); + } + } + public get settingDisabled(): boolean | string { - // Currently this can only be changed via config.json. In future, we'll allow the user to *enable* this setting - // via labs, which will migrate their existing device to the rust-sdk implementation. - return _t("labs|rust_crypto_disabled_notice"); + if (!SettingsStore.getValueAt(SettingLevel.DEVICE, Features.RustCrypto)) { + // If rust crypto has not yet been enabled for this device, you can turn it on, IF YOU DARE + return false; + } + + if (SettingsStore.getValueAt(SettingLevel.CONFIG, Features.RustCrypto)) { + // It's enabled in the config, so you can't get rid of it even by logging out. + return _t("labs|rust_crypto_in_config", { brand: SdkConfig.get().brand }); + } + + // The setting is enabled at the device level, but not mandated at the config level. + // You can only turn it off by logging out and in again. + return _t("labs|rust_crypto_requires_logout"); } } diff --git a/test/MatrixClientPeg-test.ts b/test/MatrixClientPeg-test.ts index 69306f09f36..8b137b310b5 100644 --- a/test/MatrixClientPeg-test.ts +++ b/test/MatrixClientPeg-test.ts @@ -38,6 +38,9 @@ describe("MatrixClientPeg", () => { afterEach(() => { localStorage.clear(); jest.restoreAllMocks(); + + // some of the tests assign `MatrixClientPeg.matrixClient`: clear it, to prevent leakage between tests + peg.unset(); }); it("setJustRegisteredUserId", () => { diff --git a/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx b/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx index ac832b88b21..0b34ef63438 100644 --- a/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx +++ b/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx @@ -15,15 +15,14 @@ limitations under the License. */ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import LabsUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/LabsUserSettingsTab"; import SettingsStore from "../../../../../../src/settings/SettingsStore"; import SdkConfig from "../../../../../../src/SdkConfig"; +import { SettingLevel } from "../../../../../../src/settings/SettingLevel"; describe("", () => { - const sdkConfigSpy = jest.spyOn(SdkConfig, "get"); - const defaultProps = { closeSettingsFn: jest.fn(), }; @@ -34,7 +33,9 @@ describe("", () => { beforeEach(() => { jest.clearAllMocks(); settingsValueSpy.mockReturnValue(false); - sdkConfigSpy.mockReturnValue(false); + SdkConfig.reset(); + SdkConfig.add({ brand: "BrandedClient" }); + localStorage.clear(); }); it("renders settings marked as beta as beta cards", () => { @@ -43,6 +44,7 @@ describe("", () => { }); it("does not render non-beta labs settings when disabled in config", () => { + const sdkConfigSpy = jest.spyOn(SdkConfig, "get"); render(getComponent()); expect(sdkConfigSpy).toHaveBeenCalledWith("show_labs_settings"); @@ -52,7 +54,7 @@ describe("", () => { it("renders non-beta labs settings when enabled in config", () => { // enable labs - sdkConfigSpy.mockImplementation((configName) => configName === "show_labs_settings"); + SdkConfig.add({ show_labs_settings: true }); const { container } = render(getComponent()); // non-beta labs section @@ -60,4 +62,82 @@ describe("", () => { const labsSections = container.getElementsByClassName("mx_SettingsSubsection"); expect(labsSections).toHaveLength(9); }); + + describe("Rust crypto setting", () => { + const SETTING_NAME = "Rust cryptography implementation"; + + beforeEach(() => { + SdkConfig.add({ show_labs_settings: true }); + }); + + describe("Not enabled in config", () => { + it("can be turned on if not already", async () => { + // By the time the settings panel is shown, `MatrixClientPeg.initClientCrypto` has saved the current + // value to the settings store. + await SettingsStore.setValue("feature_rust_crypto", null, SettingLevel.DEVICE, false); + + const rendered = render(getComponent()); + const toggle = rendered.getByRole("switch", { name: SETTING_NAME }); + expect(toggle.getAttribute("aria-disabled")).toEqual("false"); + expect(toggle.getAttribute("aria-checked")).toEqual("false"); + + const description = toggle.closest(".mx_SettingsFlag")?.querySelector(".mx_SettingsFlag_microcopy"); + expect(description).toHaveTextContent(/To disable you will need to log out and back in/); + }); + + it("cannot be turned off once enabled", async () => { + await SettingsStore.setValue("feature_rust_crypto", null, SettingLevel.DEVICE, true); + + const rendered = render(getComponent()); + const toggle = rendered.getByRole("switch", { name: SETTING_NAME }); + expect(toggle.getAttribute("aria-disabled")).toEqual("true"); + expect(toggle.getAttribute("aria-checked")).toEqual("true"); + + // Hover over the toggle to make it show the tooltip + fireEvent.mouseOver(toggle); + + const tooltip = rendered.getByRole("tooltip"); + expect(tooltip).toHaveTextContent( + "Once enabled, Rust cryptography can only be disabled by logging out and in again", + ); + }); + }); + + describe("Enabled in config", () => { + beforeEach(() => { + SdkConfig.add({ features: { feature_rust_crypto: true } }); + }); + + it("can be turned on if not already", async () => { + // By the time the settings panel is shown, `MatrixClientPeg.initClientCrypto` has saved the current + // value to the settings store. + await SettingsStore.setValue("feature_rust_crypto", null, SettingLevel.DEVICE, false); + + const rendered = render(getComponent()); + const toggle = rendered.getByRole("switch", { name: SETTING_NAME }); + expect(toggle.getAttribute("aria-disabled")).toEqual("false"); + expect(toggle.getAttribute("aria-checked")).toEqual("false"); + + const description = toggle.closest(".mx_SettingsFlag")?.querySelector(".mx_SettingsFlag_microcopy"); + expect(description).toHaveTextContent(/It cannot be disabled/); + }); + + it("cannot be turned off once enabled", async () => { + await SettingsStore.setValue("feature_rust_crypto", null, SettingLevel.DEVICE, true); + + const rendered = render(getComponent()); + const toggle = rendered.getByRole("switch", { name: SETTING_NAME }); + expect(toggle.getAttribute("aria-disabled")).toEqual("true"); + expect(toggle.getAttribute("aria-checked")).toEqual("true"); + + // Hover over the toggle to make it show the tooltip + fireEvent.mouseOver(toggle); + + const tooltip = rendered.getByRole("tooltip"); + expect(tooltip).toHaveTextContent( + "Rust cryptography cannot be disabled on this deployment of BrandedClient", + ); + }); + }); + }); }); diff --git a/test/components/views/settings/tabs/user/__snapshots__/LabsUserSettingsTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/LabsUserSettingsTab-test.tsx.snap index 32a8facee1d..4e65f7b61f4 100644 --- a/test/components/views/settings/tabs/user/__snapshots__/LabsUserSettingsTab-test.tsx.snap +++ b/test/components/views/settings/tabs/user/__snapshots__/LabsUserSettingsTab-test.tsx.snap @@ -15,7 +15,7 @@ exports[` renders settings marked as beta as beta cards 1
- What's next for false? Labs are the best way to get things early, test out new features and help shape them before they actually launch. + What's next for BrandedClient? Labs are the best way to get things early, test out new features and help shape them before they actually launch.
renders settings marked as beta as beta cards 1 class="mx_BetaCard_caption" >

- A new way to chat over voice and video in . + A new way to chat over voice and video in BrandedClient.

- Video rooms are always-on VoIP channels embedded within a room in . + Video rooms are always-on VoIP channels embedded within a room in BrandedClient.

renders settings marked as beta as beta cards 1
- Joining the beta will reload . + Joining the beta will reload BrandedClient.
renders settings marked as beta as beta cards 1 class="mx_BetaCard_caption" >

- Introducing a simpler way to change your notification settings. Customize your , just the way you like. + Introducing a simpler way to change your notification settings. Customize your BrandedClient, just the way you like.