From a96ad467073ccef2e6ff9f18f5283fd4cce59448 Mon Sep 17 00:00:00 2001 From: Thor Arne Johansen Date: Tue, 13 Feb 2024 16:49:43 +0100 Subject: [PATCH] Changed call sites from customisations/security to ModuleRunner.extensions --- src/Lifecycle.ts | 5 +- src/Login.ts | 5 +- src/MatrixClientPeg.ts | 13 +++- src/SecurityManager.ts | 15 ++-- .../security/CreateSecretStorageDialog.tsx | 7 +- src/components/structures/MatrixChat.tsx | 10 ++- src/modules/ModuleRunner.ts | 71 +++++++++++++++++++ src/toasts/SetupEncryptionToast.ts | 12 +++- 8 files changed, 117 insertions(+), 21 deletions(-) diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 1d6577ca399..4346fc5ef67 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -26,7 +26,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { MINIMUM_MATRIX_VERSION, SUPPORTED_MATRIX_VERSIONS } from "matrix-js-sdk/src/version-support"; import { IMatrixClientCreds, MatrixClientPeg } from "./MatrixClientPeg"; -import SecurityCustomisations from "./customisations/Security"; +import { ModuleRunner } from "./modules/ModuleRunner"; import EventIndexPeg from "./indexing/EventIndexPeg"; import createMatrixClient from "./utils/createMatrixClient"; import Notifier from "./Notifier"; @@ -921,7 +921,8 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise void, ): Promise { - const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.(); + // const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.(); + const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup?.getSecretStorageKey(); if (keyFromCustomisations) { - logger.log("Using key from security customisations (dehydration)"); + logger.log("CryptoSetupExtension: Using key from extension (dehydration)"); return keyFromCustomisations; } @@ -419,7 +421,8 @@ async function doAccessSecretStorage(func: () => Promise, forceReset: bool // inner operation completes. return await func(); } catch (e) { - SecurityCustomisations.catchAccessSecretStorageError?.(e); + // SecurityCustomisations.catchAccessSecretStorageError?.(e as Error); + ModuleRunner.instance.extensions.cryptoSetup?.catchAccessSecretStorageError(e as Error); logger.error(e); // Re-throw so that higher level logic can abort as needed throw e; diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx index 036fb5038b3..5f638b74521 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx @@ -41,7 +41,7 @@ import { isSecureBackupRequired, SecureBackupSetupMethod, } from "../../../../utils/WellKnownUtils"; -import SecurityCustomisations from "../../../../customisations/Security"; +import { ModuleRunner } from "../../../../modules/ModuleRunner"; import Field from "../../../../components/views/elements/Field"; import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; import Spinner from "../../../../components/views/elements/Spinner"; @@ -181,9 +181,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { if (crossSigningIsSetUp) { // if the user has previously set up cross-signing, verify this device so we can fetch the // private keys. - if (SecurityCustomisations.SHOW_ENCRYPTION_SETUP_UI === false) { - this.onLoggedIn(); + + + // if (SecurityCustomisations.SHOW_ENCRYPTION_SETUP_UI === false) { + const cryptoExtension = ModuleRunner.instance.extensions.cryptoSetup; + if (cryptoExtension !== undefined && cryptoExtension.SHOW_ENCRYPTION_SETUP_UI == false) { + this.onLoggedIn(); } else { this.setStateForNewView({ view: Views.COMPLETE_SECURITY }); } diff --git a/src/modules/ModuleRunner.ts b/src/modules/ModuleRunner.ts index 1a94eb16b38..e1abe40d35e 100644 --- a/src/modules/ModuleRunner.ts +++ b/src/modules/ModuleRunner.ts @@ -17,6 +17,10 @@ limitations under the License. import { safeSet } from "matrix-js-sdk/src/utils"; import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations"; import { AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types"; +import { AllExtensions } from "@matrix-org/react-sdk-module-api/lib/types/extensions"; +import { DefaultCryptoSetupExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions"; +import { DefaultExperimentalExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions"; +import { RuntimeModule } from "@matrix-org/react-sdk-module-api"; import { AppModule } from "./AppModule"; import { ModuleFactory } from "./ModuleFactory"; @@ -29,6 +33,13 @@ import "./ModuleComponents"; export class ModuleRunner { public static readonly instance = new ModuleRunner(); + public className: string = ModuleRunner.name; + + public extensions: AllExtensions = { + cryptoSetup: new DefaultCryptoSetupExtensions(), + experimental: new DefaultExperimentalExtensions(), + }; + private modules: AppModule[] = []; private constructor() { @@ -42,6 +53,11 @@ export class ModuleRunner { */ public reset(): void { this.modules = []; + + this.extensions = { + cryptoSetup: new DefaultCryptoSetupExtensions(), + experimental: new DefaultExperimentalExtensions(), + }; } /** @@ -66,6 +82,52 @@ export class ModuleRunner { return merged; } + + /** + * Ensure we register extensions provided by the modules + */ + private updateExtensions(): void { + const cryptoSetupExtensions: Array = []; + const experimentalExtensions: Array = []; + + this.modules.forEach((m) => { + /* Record the cryptoSetup extensions if any */ + if (m.module.extensions?.cryptoSetup) { + cryptoSetupExtensions.push(m.module); + } + + /* Record the experimantal extensions if any */ + if (m.module.extensions?.experimental) { + experimentalExtensions.push(m.module); + } + }); + + /* Enforce rule that only a single module may provide a given extension */ + if (cryptoSetupExtensions.length > 1) { + throw new Error( + `cryptoSetup extension is provided by modules ${cryptoSetupExtensions + .map((m) => m.moduleName) + .join(", ")}, but can only be provided by a single module`, + ); + } + if (experimentalExtensions.length > 1) { + throw new Error( + `experimental extension is provided by modules ${experimentalExtensions + .map((m) => m.moduleName) + .join(", ")}, but can only be provided by a single module`, + ); + } + + /* Override the default extension if extension was provided by a module */ + if (cryptoSetupExtensions.length == 1) { + this.extensions.cryptoSetup = cryptoSetupExtensions[0].extensions?.cryptoSetup; + } + + if (experimentalExtensions.length == 1) { + this.extensions.experimental = cryptoSetupExtensions[0].extensions?.experimental; + } + } + /** * Registers a factory which creates a module for later loading. The factory * will be called immediately. @@ -73,6 +135,15 @@ export class ModuleRunner { */ public registerModule(factory: ModuleFactory): void { this.modules.push(new AppModule(factory)); + + /** + * Check if the new module provides any extensions, and also ensure a given extension is only provided by a single runtime module + * Slightly inefficient to do this on each registration, but avoids changes to element-web installer code + * Also note that this require that the statement in the comment above, about immediately calling the factory, is in fact true + * (otherwise wrapped RuntimeModules will not be available) + */ + + this.updateExtensions(); } /** diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts index e55e665a27f..c0f4cc49779 100644 --- a/src/toasts/SetupEncryptionToast.ts +++ b/src/toasts/SetupEncryptionToast.ts @@ -21,7 +21,8 @@ import SetupEncryptionDialog from "../components/views/dialogs/security/SetupEnc import { accessSecretStorage } from "../SecurityManager"; import ToastStore from "../stores/ToastStore"; import GenericToast from "../components/views/toasts/GenericToast"; -import SecurityCustomisations from "../customisations/Security"; +import { ModuleRunner } from "../modules/ModuleRunner"; +import { SetupEncryptionStore } from "../stores/SetupEncryptionStore"; import Spinner from "../components/views/elements/Spinner"; const TOAST_KEY = "setupencryption"; @@ -79,7 +80,14 @@ const onReject = (): void => { }; export const showToast = (kind: Kind): void => { - if (SecurityCustomisations.setupEncryptionNeeded?.(kind)) { + // if (SecurityCustomisations.setupEncryptionNeeded?.(kind)) { + // return; + // } + + if (ModuleRunner.instance.extensions.cryptoSetup?.setupEncryptionNeeded({ + kind: kind as any, + storeProvider: { getInstance: () => SetupEncryptionStore.sharedInstance() }, + })) { return; }