You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-07-28 15:22:05 +03:00
Replace SecurityCustomisations
with CryptoSetupExtension
(#12342)
* Changed call sites from customisations/security to ModuleRunner.extensions * Updated depenndecy and added tests * Fixed style and formatting with prettier * Fix according to Element PR comments * Fixing issues raised in PR review * Removed commented code. Improved encapsulation. Removed noisy logging * Improved language of comment about calling the factory * Refactor to get better encapsulation * Find a better name. Provide explicit reset function. Provide more TSDoc * Simplify mock for cryptoSetup, and add assertion for exception message. * Remove unused className property. Adjust TSDoc comments * Fix linting and code style issues * Added test to ensure we canregister anduse experimental extensions * Fix linting and code-style issues * Added test to ensure only on registration of experimental extensions * Added test toensure call to getDehydratedDeviceCallback() * Test what happens when there is no implementation * Iterating cryptoSetup tests * Lint/prettier fix * Assert both branches when checking for dehydrationkey callback * Update src/modules/ModuleRunner.ts Language and formatting Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Reset by setting a fresh ExtensionsManager Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Use regular comment instead of TSDoc style comment Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update test/MatrixClientPeg-test.ts No need to extend the base class Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Fix spelling Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Fix spelling Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Fix TSDoc formatting Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Simplify mock setup * Simplified mock and cleaned up a bit * Keeping track of extensions is an implementation detail internal to ExtensionsManager. Language and punctuation * Addressed issues and comments from PR review * Update src/modules/ModuleRunner.ts Keep the flags to track implementations as direct properties Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Fix flattening of implementation map * Update src/modules/ModuleRunner.ts Fix whitespace Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
313b556044
commit
6392759bec
@ -17,6 +17,10 @@ limitations under the License.
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import fetchMockJest from "fetch-mock-jest";
|
||||
import EventEmitter from "events";
|
||||
import {
|
||||
ProvideCryptoSetupExtensions,
|
||||
SecretStorageKeyDescription,
|
||||
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
|
||||
|
||||
import { advanceDateAndTime, stubClient } from "./test-utils";
|
||||
import { IMatrixClientPeg, MatrixClientPeg as peg } from "../src/MatrixClientPeg";
|
||||
@ -25,6 +29,7 @@ import Modal from "../src/Modal";
|
||||
import PlatformPeg from "../src/PlatformPeg";
|
||||
import { SettingLevel } from "../src/settings/SettingLevel";
|
||||
import { Features } from "../src/settings/Settings";
|
||||
import { ModuleRunner } from "../src/modules/ModuleRunner";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@ -77,6 +82,78 @@ describe("MatrixClientPeg", () => {
|
||||
expect(peg.userRegisteredWithinLastHours(24)).toBe(false);
|
||||
});
|
||||
|
||||
describe(".start extensions", () => {
|
||||
let testPeg: IMatrixClientPeg;
|
||||
|
||||
beforeEach(() => {
|
||||
// instantiate a MatrixClientPegClass instance, with a new MatrixClient
|
||||
testPeg = new PegClass();
|
||||
fetchMockJest.get("http://example.com/_matrix/client/versions", {});
|
||||
});
|
||||
|
||||
describe("cryptoSetup extension", () => {
|
||||
it("should call default cryptoSetup.getDehydrationKeyCallback", async () => {
|
||||
const mockCryptoSetup = {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn(),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn().mockReturnValue(null),
|
||||
} as ProvideCryptoSetupExtensions;
|
||||
|
||||
// Ensure we have an instance before we set up spies
|
||||
const instance = ModuleRunner.instance;
|
||||
jest.spyOn(instance.extensions, "cryptoSetup", "get").mockReturnValue(mockCryptoSetup);
|
||||
|
||||
testPeg.replaceUsingCreds({
|
||||
accessToken: "SEKRET",
|
||||
homeserverUrl: "http://example.com",
|
||||
userId: "@user:example.com",
|
||||
deviceId: "TEST_DEVICE_ID",
|
||||
});
|
||||
|
||||
expect(mockCryptoSetup.getDehydrationKeyCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call overridden cryptoSetup.getDehydrationKeyCallback", async () => {
|
||||
const mockDehydrationKeyCallback = () => Uint8Array.from([0x11, 0x22, 0x33]);
|
||||
|
||||
const mockCryptoSetup = {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn(),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn().mockReturnValue(mockDehydrationKeyCallback),
|
||||
} as ProvideCryptoSetupExtensions;
|
||||
|
||||
// Ensure we have an instance before we set up spies
|
||||
const instance = ModuleRunner.instance;
|
||||
jest.spyOn(instance.extensions, "cryptoSetup", "get").mockReturnValue(mockCryptoSetup);
|
||||
|
||||
testPeg.replaceUsingCreds({
|
||||
accessToken: "SEKRET",
|
||||
homeserverUrl: "http://example.com",
|
||||
userId: "@user:example.com",
|
||||
deviceId: "TEST_DEVICE_ID",
|
||||
});
|
||||
expect(mockCryptoSetup.getDehydrationKeyCallback).toHaveBeenCalledTimes(1);
|
||||
|
||||
const client = testPeg.get();
|
||||
const dehydrationKey = await client?.cryptoCallbacks.getDehydrationKey!(
|
||||
{} as SecretStorageKeyDescription,
|
||||
(key: Uint8Array) => true,
|
||||
);
|
||||
expect(dehydrationKey).toEqual(Uint8Array.from([0x11, 0x22, 0x33]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(".start", () => {
|
||||
let testPeg: IMatrixClientPeg;
|
||||
|
||||
|
@ -16,6 +16,9 @@ limitations under the License.
|
||||
|
||||
import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
|
||||
import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
|
||||
import { AllExtensions } from "@matrix-org/react-sdk-module-api/lib/types/extensions";
|
||||
import { ProvideCryptoSetupExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
|
||||
import { ProvideExperimentalExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
|
||||
|
||||
import { ModuleRunner } from "../../src/modules/ModuleRunner";
|
||||
|
||||
@ -29,6 +32,11 @@ export class MockModule extends RuntimeModule {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModule(): MockModule {
|
||||
let module: MockModule | undefined;
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
@ -43,3 +51,88 @@ export function registerMockModule(): MockModule {
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
class MockModuleWithCryptoSetupExtension extends RuntimeModule {
|
||||
public get apiInstance(): ModuleApi {
|
||||
return this.moduleApi;
|
||||
}
|
||||
|
||||
moduleName: string = MockModuleWithCryptoSetupExtension.name;
|
||||
|
||||
extensions: AllExtensions = {
|
||||
cryptoSetup: {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn().mockReturnValue(Uint8Array.from([0x11, 0x22, 0x99])),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn(),
|
||||
} as ProvideCryptoSetupExtensions,
|
||||
};
|
||||
|
||||
public constructor(moduleApi: ModuleApi) {
|
||||
super(moduleApi);
|
||||
}
|
||||
}
|
||||
|
||||
class MockModuleWithExperimentalExtension extends RuntimeModule {
|
||||
public get apiInstance(): ModuleApi {
|
||||
return this.moduleApi;
|
||||
}
|
||||
|
||||
moduleName: string = MockModuleWithExperimentalExtension.name;
|
||||
|
||||
extensions: AllExtensions = {
|
||||
experimental: {
|
||||
experimentalMethod: jest.fn().mockReturnValue(Uint8Array.from([0x22, 0x44, 0x88])),
|
||||
} as ProvideExperimentalExtensions,
|
||||
};
|
||||
|
||||
public constructor(moduleApi: ModuleApi) {
|
||||
super(moduleApi);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module which implements the cryptoSetup extension.
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModuleWithCryptoSetupExtension(): MockModuleWithCryptoSetupExtension {
|
||||
let module: MockModuleWithCryptoSetupExtension | undefined;
|
||||
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
if (module) {
|
||||
throw new Error("State machine error: ModuleRunner created the module twice");
|
||||
}
|
||||
module = new MockModuleWithCryptoSetupExtension(api);
|
||||
return module;
|
||||
});
|
||||
if (!module) {
|
||||
throw new Error("State machine error: ModuleRunner did not create module");
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module which implements the experimental extension.
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModuleWithExperimentalExtension(): MockModuleWithExperimentalExtension {
|
||||
let module: MockModuleWithExperimentalExtension | undefined;
|
||||
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
if (module) {
|
||||
throw new Error("State machine error: ModuleRunner created the module twice");
|
||||
}
|
||||
module = new MockModuleWithExperimentalExtension(api);
|
||||
return module;
|
||||
});
|
||||
if (!module) {
|
||||
throw new Error("State machine error: ModuleRunner did not create module");
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
@ -16,7 +16,12 @@ limitations under the License.
|
||||
|
||||
import { RoomPreviewOpts, RoomViewLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
||||
|
||||
import { MockModule, registerMockModule } from "./MockModule";
|
||||
import {
|
||||
MockModule,
|
||||
registerMockModule,
|
||||
registerMockModuleWithCryptoSetupExtension,
|
||||
registerMockModuleWithExperimentalExtension,
|
||||
} from "./MockModule";
|
||||
import { ModuleRunner } from "../../src/modules/ModuleRunner";
|
||||
|
||||
describe("ModuleRunner", () => {
|
||||
@ -49,4 +54,48 @@ describe("ModuleRunner", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extensions", () => {
|
||||
it("should return default values when no crypto-setup extensions are provided by a registered module", async () => {
|
||||
registerMockModule();
|
||||
const result = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return default values when no experimental extensions are provided by a registered module", async () => {
|
||||
registerMockModule();
|
||||
const result = ModuleRunner.instance.extensions?.experimental.experimentalMethod();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return value from crypto-setup-extensions provided by a registered module", async () => {
|
||||
registerMockModuleWithCryptoSetupExtension();
|
||||
const result = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
|
||||
expect(result).toEqual(Uint8Array.from([0x11, 0x22, 0x99]));
|
||||
});
|
||||
|
||||
it("should return value from experimental-extensions provided by a registered module", async () => {
|
||||
registerMockModuleWithExperimentalExtension();
|
||||
const result = ModuleRunner.instance.extensions.experimental.experimentalMethod();
|
||||
expect(result).toEqual(Uint8Array.from([0x22, 0x44, 0x88]));
|
||||
});
|
||||
|
||||
it("must not allow multiple modules to provide cryptoSetup extension", async () => {
|
||||
registerMockModuleWithCryptoSetupExtension();
|
||||
const t = () => registerMockModuleWithCryptoSetupExtension();
|
||||
expect(t).toThrow(Error);
|
||||
expect(t).toThrow(
|
||||
"adding cryptoSetup extension implementation from module MockModuleWithCryptoSetupExtension but an implementation was already provided",
|
||||
);
|
||||
});
|
||||
|
||||
it("must not allow multiple modules to provide experimental extension", async () => {
|
||||
registerMockModuleWithExperimentalExtension();
|
||||
const t = () => registerMockModuleWithExperimentalExtension();
|
||||
expect(t).toThrow(Error);
|
||||
expect(t).toThrow(
|
||||
"adding experimental extension implementation from module MockModuleWithExperimentalExtension but an implementation was already provided",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user