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
This commit is contained in:
Richard van der Hoff 2024-01-18 11:18:55 +00:00 committed by GitHub
parent b64d2e734a
commit b6364a4cea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 139 additions and 20 deletions

View file

@ -52,6 +52,7 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import PlatformPeg from "./PlatformPeg"; import PlatformPeg from "./PlatformPeg";
import { formatList } from "./utils/FormattingUtils"; import { formatList } from "./utils/FormattingUtils";
import SdkConfig from "./SdkConfig"; import SdkConfig from "./SdkConfig";
import { Features } from "./settings/Settings";
export interface IMatrixClientCreds { export interface IMatrixClientCreds {
homeserverUrl: string; homeserverUrl: string;
@ -301,7 +302,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
throw new Error("createClient must be called first"); 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, // 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 // so persist the setting at the device layer

View file

@ -1447,7 +1447,10 @@
"report_to_moderators": "Report to moderators", "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.", "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": "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": "Sliding Sync mode",
"sliding_sync_checking": "Checking…", "sliding_sync_checking": "Checking…",
"sliding_sync_configuration": "Sliding Sync configuration", "sliding_sync_configuration": "Sliding Sync configuration",

View file

@ -46,6 +46,7 @@ import RustCryptoSdkController from "./controllers/RustCryptoSdkController";
import ServerSupportUnstableFeatureController from "./controllers/ServerSupportUnstableFeatureController"; import ServerSupportUnstableFeatureController from "./controllers/ServerSupportUnstableFeatureController";
import { WatchManager } from "./WatchManager"; import { WatchManager } from "./WatchManager";
import { CustomTheme } from "../theme"; import { CustomTheme } from "../theme";
import SettingsStore from "./SettingsStore";
export const defaultWatchManager = new WatchManager(); export const defaultWatchManager = new WatchManager();
@ -94,6 +95,7 @@ export enum Features {
VoiceBroadcastForceSmallChunks = "feature_voice_broadcast_force_small_chunks", VoiceBroadcastForceSmallChunks = "feature_voice_broadcast_force_small_chunks",
NotificationSettings2 = "feature_notification_settings2", NotificationSettings2 = "feature_notification_settings2",
OidcNativeFlow = "feature_oidc_native_flow", OidcNativeFlow = "feature_oidc_native_flow",
RustCrypto = "feature_rust_crypto",
} }
export const labGroupNames: Record<LabGroup, TranslationKey> = { export const labGroupNames: Record<LabGroup, TranslationKey> = {
@ -480,15 +482,22 @@ export const SETTINGS: { [setting: string]: ISetting } = {
description: _td("labs|oidc_native_flow_description"), description: _td("labs|oidc_native_flow_description"),
default: false, default: false,
}, },
"feature_rust_crypto": { [Features.RustCrypto]: {
// use the rust matrix-sdk-crypto-js for crypto. // use the rust matrix-sdk-crypto-wasm for crypto.
isFeature: true, isFeature: true,
labsGroup: LabGroup.Developer, labsGroup: LabGroup.Developer,
configDisablesSetting: true, // unlike most features, `configDisablesSetting` is false here.
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
displayName: _td("labs|rust_crypto"), displayName: _td("labs|rust_crypto"),
description: _td("labs|under_active_development"), description: () => {
// shouldWarn: true, 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, default: false,
controller: new RustCryptoSdkController(), controller: new RustCryptoSdkController(),
}, },

View file

@ -15,12 +15,35 @@ limitations under the License.
*/ */
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import SettingsStore from "../SettingsStore";
import { SettingLevel } from "../SettingLevel";
import PlatformPeg from "../../PlatformPeg";
import SettingController from "./SettingController"; import SettingController from "./SettingController";
import { Features } from "../Settings";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import SdkConfig from "../../SdkConfig";
export default class RustCryptoSdkController extends SettingController { 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 { 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 if (!SettingsStore.getValueAt(SettingLevel.DEVICE, Features.RustCrypto)) {
// via labs, which will migrate their existing device to the rust-sdk implementation. // If rust crypto has not yet been enabled for this device, you can turn it on, IF YOU DARE
return _t("labs|rust_crypto_disabled_notice"); 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");
} }
} }

View file

@ -38,6 +38,9 @@ describe("MatrixClientPeg", () => {
afterEach(() => { afterEach(() => {
localStorage.clear(); localStorage.clear();
jest.restoreAllMocks(); jest.restoreAllMocks();
// some of the tests assign `MatrixClientPeg.matrixClient`: clear it, to prevent leakage between tests
peg.unset();
}); });
it("setJustRegisteredUserId", () => { it("setJustRegisteredUserId", () => {

View file

@ -15,15 +15,14 @@ limitations under the License.
*/ */
import React from "react"; 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 LabsUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/LabsUserSettingsTab";
import SettingsStore from "../../../../../../src/settings/SettingsStore"; import SettingsStore from "../../../../../../src/settings/SettingsStore";
import SdkConfig from "../../../../../../src/SdkConfig"; import SdkConfig from "../../../../../../src/SdkConfig";
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
describe("<LabsUserSettingsTab />", () => { describe("<LabsUserSettingsTab />", () => {
const sdkConfigSpy = jest.spyOn(SdkConfig, "get");
const defaultProps = { const defaultProps = {
closeSettingsFn: jest.fn(), closeSettingsFn: jest.fn(),
}; };
@ -34,7 +33,9 @@ describe("<LabsUserSettingsTab />", () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
settingsValueSpy.mockReturnValue(false); settingsValueSpy.mockReturnValue(false);
sdkConfigSpy.mockReturnValue(false); SdkConfig.reset();
SdkConfig.add({ brand: "BrandedClient" });
localStorage.clear();
}); });
it("renders settings marked as beta as beta cards", () => { it("renders settings marked as beta as beta cards", () => {
@ -43,6 +44,7 @@ describe("<LabsUserSettingsTab />", () => {
}); });
it("does not render non-beta labs settings when disabled in config", () => { it("does not render non-beta labs settings when disabled in config", () => {
const sdkConfigSpy = jest.spyOn(SdkConfig, "get");
render(getComponent()); render(getComponent());
expect(sdkConfigSpy).toHaveBeenCalledWith("show_labs_settings"); expect(sdkConfigSpy).toHaveBeenCalledWith("show_labs_settings");
@ -52,7 +54,7 @@ describe("<LabsUserSettingsTab />", () => {
it("renders non-beta labs settings when enabled in config", () => { it("renders non-beta labs settings when enabled in config", () => {
// enable labs // enable labs
sdkConfigSpy.mockImplementation((configName) => configName === "show_labs_settings"); SdkConfig.add({ show_labs_settings: true });
const { container } = render(getComponent()); const { container } = render(getComponent());
// non-beta labs section // non-beta labs section
@ -60,4 +62,82 @@ describe("<LabsUserSettingsTab />", () => {
const labsSections = container.getElementsByClassName("mx_SettingsSubsection"); const labsSections = container.getElementsByClassName("mx_SettingsSubsection");
expect(labsSections).toHaveLength(9); 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",
);
});
});
});
}); });

View file

@ -15,7 +15,7 @@ exports[`<LabsUserSettingsTab /> renders settings marked as beta as beta cards 1
<div <div
class="mx_SettingsSubsection_text" class="mx_SettingsSubsection_text"
> >
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.
</div> </div>
<div <div
class="mx_BetaCard" class="mx_BetaCard"
@ -42,10 +42,10 @@ exports[`<LabsUserSettingsTab /> renders settings marked as beta as beta cards 1
class="mx_BetaCard_caption" class="mx_BetaCard_caption"
> >
<p> <p>
A new way to chat over voice and video in . A new way to chat over voice and video in BrandedClient.
</p> </p>
<p> <p>
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.
</p> </p>
</div> </div>
<div <div
@ -62,7 +62,7 @@ exports[`<LabsUserSettingsTab /> renders settings marked as beta as beta cards 1
<div <div
class="mx_BetaCard_refreshWarning" class="mx_BetaCard_refreshWarning"
> >
Joining the beta will reload . Joining the beta will reload BrandedClient.
</div> </div>
<div <div
class="mx_BetaCard_faq" class="mx_BetaCard_faq"
@ -104,7 +104,7 @@ exports[`<LabsUserSettingsTab /> renders settings marked as beta as beta cards 1
class="mx_BetaCard_caption" class="mx_BetaCard_caption"
> >
<p> <p>
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.
</p> </p>
</div> </div>
<div <div