Use CryptoApi.getKeyBackupInfo instead of deprecated MatrixClient.getKeyBackupVersion (#28450)

* Use `CryptoApi.getKeyBackupInfo` instead of deprecated `MatrixClient.getKeyBackupVersion`

* Review changes

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Florian Duros 2024-11-25 10:30:42 +01:00 committed by GitHub
parent fd7c50f61d
commit ede91bf921
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 37 additions and 40 deletions

View file

@ -230,12 +230,15 @@ export default class DeviceListener {
private async getKeyBackupInfo(): Promise<KeyBackupInfo | null> { private async getKeyBackupInfo(): Promise<KeyBackupInfo | null> {
if (!this.client) return null; if (!this.client) return null;
const now = new Date().getTime(); const now = new Date().getTime();
const crypto = this.client.getCrypto();
if (!crypto) return null;
if ( if (
!this.keyBackupInfo || !this.keyBackupInfo ||
!this.keyBackupFetchedAt || !this.keyBackupFetchedAt ||
this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL
) { ) {
this.keyBackupInfo = await this.client.getKeyBackupVersion(); this.keyBackupInfo = await crypto.getKeyBackupInfo();
this.keyBackupFetchedAt = now; this.keyBackupFetchedAt = now;
} }
return this.keyBackupInfo; return this.keyBackupInfo;

View file

@ -279,7 +279,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
if (!forceReset) { if (!forceReset) {
try { try {
this.setState({ phase: Phase.Loading }); this.setState({ phase: Phase.Loading });
backupInfo = await cli.getKeyBackupVersion(); backupInfo = await crypto.getKeyBackupInfo();
} catch (e) { } catch (e) {
logger.error("Error fetching backup data from server", e); logger.error("Error fetching backup data from server", e);
this.setState({ phase: Phase.LoadError }); this.setState({ phase: Phase.LoadError });

View file

@ -1638,7 +1638,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} else { } else {
// otherwise check the server to see if there's a new one // otherwise check the server to see if there's a new one
try { try {
newVersionInfo = await cli.getKeyBackupVersion(); newVersionInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
if (newVersionInfo !== null) haveNewVersion = true; if (newVersionInfo !== null) haveNewVersion = true;
} catch (e) { } catch (e) {
logger.error("Saw key backup error but failed to check backup version!", e); logger.error("Saw key backup error but failed to check backup version!", e);

View file

@ -109,7 +109,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
} }
// backup is not active. see if there is a backup version on the server we ought to back up to. // backup is not active. see if there is a backup version on the server we ought to back up to.
const backupInfo = await client.getKeyBackupVersion(); const backupInfo = await crypto.getKeyBackupInfo();
this.setState({ backupStatus: backupInfo ? BackupStatus.SERVER_BACKUP_BUT_DISABLED : BackupStatus.NO_BACKUP }); this.setState({ backupStatus: backupInfo ? BackupStatus.SERVER_BACKUP_BUT_DISABLED : BackupStatus.NO_BACKUP });
} }

View file

@ -258,7 +258,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
}); });
try { try {
const cli = MatrixClientPeg.safeGet(); const cli = MatrixClientPeg.safeGet();
const backupInfo = await cli.getKeyBackupVersion(); const backupInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
const has4S = await cli.secretStorage.hasKey(); const has4S = await cli.secretStorage.hasKey();
const backupKeyStored = has4S ? await cli.isKeyBackupKeyStored() : null; const backupKeyStored = has4S ? await cli.isKeyBackupKeyStored() : null;
this.setState({ this.setState({

View file

@ -118,7 +118,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
this.getUpdatedDiagnostics(); this.getUpdatedDiagnostics();
try { try {
const cli = MatrixClientPeg.safeGet(); const cli = MatrixClientPeg.safeGet();
const backupInfo = await cli.getKeyBackupVersion(); const backupInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
const backupTrustInfo = backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined; const backupTrustInfo = backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;
const activeBackupVersion = (await cli.getCrypto()?.getActiveSessionBackupVersion()) ?? null; const activeBackupVersion = (await cli.getCrypto()?.getActiveSessionBackupVersion()) ?? null;
@ -192,12 +192,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
if (!proceed) return; if (!proceed) return;
this.setState({ loading: true }); this.setState({ loading: true });
const versionToDelete = this.state.backupInfo!.version!; const versionToDelete = this.state.backupInfo!.version!;
MatrixClientPeg.safeGet() // deleteKeyBackupVersion fires a key backup status event
.getCrypto() // which will update the UI
?.deleteKeyBackupVersion(versionToDelete) MatrixClientPeg.safeGet().getCrypto()?.deleteKeyBackupVersion(versionToDelete);
.then(() => {
this.loadBackupStatus();
});
}, },
}); });
}; };

View file

@ -125,7 +125,7 @@ export class SetupEncryptionStore extends EventEmitter {
this.emit("update"); this.emit("update");
try { try {
const cli = MatrixClientPeg.safeGet(); const cli = MatrixClientPeg.safeGet();
const backupInfo = await cli.getKeyBackupVersion(); const backupInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
this.backupInfo = backupInfo; this.backupInfo = backupInfo;
this.emit("update"); this.emit("update");

View file

@ -143,7 +143,6 @@ export const mockClientMethodsCrypto = (): Partial<
> => ({ > => ({
isKeyBackupKeyStored: jest.fn(), isKeyBackupKeyStored: jest.fn(),
getCrossSigningCacheCallbacks: jest.fn().mockReturnValue({ getCrossSigningKeyCache: jest.fn() }), getCrossSigningCacheCallbacks: jest.fn().mockReturnValue({ getCrossSigningKeyCache: jest.fn() }),
getKeyBackupVersion: jest.fn().mockResolvedValue(null),
secretStorage: { hasKey: jest.fn() }, secretStorage: { hasKey: jest.fn() },
getCrypto: jest.fn().mockReturnValue({ getCrypto: jest.fn().mockReturnValue({
getUserDeviceInfo: jest.fn(), getUserDeviceInfo: jest.fn(),
@ -163,6 +162,7 @@ export const mockClientMethodsCrypto = (): Partial<
getOwnDeviceKeys: jest.fn().mockReturnValue(new Promise(() => {})), getOwnDeviceKeys: jest.fn().mockReturnValue(new Promise(() => {})),
getCrossSigningKeyId: jest.fn(), getCrossSigningKeyId: jest.fn(),
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false), isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
}), }),
}); });

View file

@ -99,7 +99,6 @@ export function createTestClient(): MatrixClient {
getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }), getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }),
getSessionId: jest.fn().mockReturnValue("iaszphgvfku"), getSessionId: jest.fn().mockReturnValue("iaszphgvfku"),
credentials: { userId: "@userId:matrix.org" }, credentials: { userId: "@userId:matrix.org" },
getKeyBackupVersion: jest.fn(),
secretStorage: { secretStorage: {
get: jest.fn(), get: jest.fn(),
@ -135,6 +134,7 @@ export function createTestClient(): MatrixClient {
restoreKeyBackupWithPassphrase: jest.fn(), restoreKeyBackupWithPassphrase: jest.fn(),
loadSessionBackupPrivateKeyFromSecretStorage: jest.fn(), loadSessionBackupPrivateKeyFromSecretStorage: jest.fn(),
storeSessionBackupPrivateKey: jest.fn(), storeSessionBackupPrivateKey: jest.fn(),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
}), }),
getPushActionsForEvent: jest.fn(), getPushActionsForEvent: jest.fn(),

View file

@ -96,12 +96,12 @@ describe("DeviceListener", () => {
}), }),
getSessionBackupPrivateKey: jest.fn(), getSessionBackupPrivateKey: jest.fn(),
isEncryptionEnabledInRoom: jest.fn(), isEncryptionEnabledInRoom: jest.fn(),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
} as unknown as Mocked<CryptoApi>; } as unknown as Mocked<CryptoApi>;
mockClient = getMockClientWithEventEmitter({ mockClient = getMockClientWithEventEmitter({
isGuest: jest.fn(), isGuest: jest.fn(),
getUserId: jest.fn().mockReturnValue(userId), getUserId: jest.fn().mockReturnValue(userId),
getSafeUserId: jest.fn().mockReturnValue(userId), getSafeUserId: jest.fn().mockReturnValue(userId),
getKeyBackupVersion: jest.fn().mockResolvedValue(undefined),
getRooms: jest.fn().mockReturnValue([]), getRooms: jest.fn().mockReturnValue([]),
isVersionSupported: jest.fn().mockResolvedValue(true), isVersionSupported: jest.fn().mockResolvedValue(true),
isInitialSyncComplete: jest.fn().mockReturnValue(true), isInitialSyncComplete: jest.fn().mockReturnValue(true),
@ -354,7 +354,7 @@ describe("DeviceListener", () => {
it("shows set up encryption toast when user has a key backup available", async () => { it("shows set up encryption toast when user has a key backup available", async () => {
// non falsy response // non falsy response
mockClient!.getKeyBackupVersion.mockResolvedValue({} as unknown as KeyBackupInfo); mockCrypto.getKeyBackupInfo.mockResolvedValue({} as unknown as KeyBackupInfo);
await createAndStart(); await createAndStart();
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith( expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
@ -673,7 +673,7 @@ describe("DeviceListener", () => {
describe("When Room Key Backup is not enabled", () => { describe("When Room Key Backup is not enabled", () => {
beforeEach(() => { beforeEach(() => {
// no backup // no backup
mockClient.getKeyBackupVersion.mockResolvedValue(null); mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
}); });
it("Should report recovery state as Enabled", async () => { it("Should report recovery state as Enabled", async () => {
@ -722,7 +722,7 @@ describe("DeviceListener", () => {
}); });
// no backup // no backup
mockClient.getKeyBackupVersion.mockResolvedValue(null); mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
await createAndStart(); await createAndStart();
@ -872,7 +872,7 @@ describe("DeviceListener", () => {
describe("When Room Key Backup is enabled", () => { describe("When Room Key Backup is enabled", () => {
beforeEach(() => { beforeEach(() => {
// backup enabled - just need a mock object // backup enabled - just need a mock object
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo); mockCrypto.getKeyBackupInfo.mockResolvedValue({} as KeyBackupInfo);
}); });
const testCases = [ const testCases = [

View file

@ -139,6 +139,7 @@ describe("<MatrixChat />", () => {
globalBlacklistUnverifiedDevices: false, globalBlacklistUnverifiedDevices: false,
// This needs to not finish immediately because we need to test the screen appears // This needs to not finish immediately because we need to test the screen appears
bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise), bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
}), }),
secretStorage: { secretStorage: {
isStored: jest.fn().mockReturnValue(null), isStored: jest.fn().mockReturnValue(null),
@ -148,7 +149,6 @@ describe("<MatrixChat />", () => {
whoami: jest.fn(), whoami: jest.fn(),
logout: jest.fn(), logout: jest.fn(),
getDeviceId: jest.fn(), getDeviceId: jest.fn(),
getKeyBackupVersion: jest.fn().mockResolvedValue(null),
}); });
let mockClient: Mocked<MatrixClient>; let mockClient: Mocked<MatrixClient>;
const serverConfig = { const serverConfig = {

View file

@ -22,7 +22,6 @@ describe("LogoutDialog", () => {
beforeEach(() => { beforeEach(() => {
mockClient = getMockClientWithEventEmitter({ mockClient = getMockClientWithEventEmitter({
...mockClientMethodsCrypto(), ...mockClientMethodsCrypto(),
getKeyBackupVersion: jest.fn(),
}); });
mockCrypto = mocked(mockClient.getCrypto()!); mockCrypto = mocked(mockClient.getCrypto()!);
@ -50,14 +49,14 @@ describe("LogoutDialog", () => {
}); });
it("Prompts user to connect backup if there is a backup on the server", async () => { it("Prompts user to connect backup if there is a backup on the server", async () => {
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo); mockCrypto.getKeyBackupInfo.mockResolvedValue({} as KeyBackupInfo);
const rendered = renderComponent(); const rendered = renderComponent();
await rendered.findByText("Connect this session to Key Backup"); await rendered.findByText("Connect this session to Key Backup");
expect(rendered.container).toMatchSnapshot(); expect(rendered.container).toMatchSnapshot();
}); });
it("Prompts user to set up backup if there is no backup on the server", async () => { it("Prompts user to set up backup if there is no backup on the server", async () => {
mockClient.getKeyBackupVersion.mockResolvedValue(null); mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
const rendered = renderComponent(); const rendered = renderComponent();
await rendered.findByText("Start using Key Backup"); await rendered.findByText("Start using Key Backup");
expect(rendered.container).toMatchSnapshot(); expect(rendered.container).toMatchSnapshot();
@ -69,7 +68,7 @@ describe("LogoutDialog", () => {
describe("when there is an error fetching backups", () => { describe("when there is an error fetching backups", () => {
filterConsole("Unable to fetch key backup status"); filterConsole("Unable to fetch key backup status");
it("prompts user to set up backup", async () => { it("prompts user to set up backup", async () => {
mockClient.getKeyBackupVersion.mockImplementation(async () => { mockCrypto.getKeyBackupInfo.mockImplementation(async () => {
throw new Error("beep"); throw new Error("beep");
}); });
const rendered = renderComponent(); const rendered = renderComponent();

View file

@ -77,7 +77,7 @@ describe("CreateSecretStorageDialog", () => {
filterConsole("Error fetching backup data from server"); filterConsole("Error fetching backup data from server");
it("shows an error", async () => { it("shows an error", async () => {
mockClient.getKeyBackupVersion.mockImplementation(async () => { jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
throw new Error("bleh bleh"); throw new Error("bleh bleh");
}); });
@ -92,7 +92,7 @@ describe("CreateSecretStorageDialog", () => {
expect(result.container).toMatchSnapshot(); expect(result.container).toMatchSnapshot();
// Now we can get the backup and we retry // Now we can get the backup and we retry
mockClient.getKeyBackupVersion.mockRestore(); jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockRestore();
await userEvent.click(screen.getByRole("button", { name: "Retry" })); await userEvent.click(screen.getByRole("button", { name: "Retry" }));
await screen.findByText("Your keys are now being backed up from this device."); await screen.findByText("Your keys are now being backed up from this device.");
}); });

View file

@ -28,7 +28,7 @@ describe("<RestoreKeyBackupDialog />", () => {
beforeEach(() => { beforeEach(() => {
matrixClient = stubClient(); matrixClient = stubClient();
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32)); jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({ version: "1" } as KeyBackupInfo); jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({ version: "1" } as KeyBackupInfo);
}); });
it("should render", async () => { it("should render", async () => {
@ -99,7 +99,7 @@ describe("<RestoreKeyBackupDialog />", () => {
test("should restore key backup when passphrase is filled", async () => { test("should restore key backup when passphrase is filled", async () => {
// Determine that the passphrase is required // Determine that the passphrase is required
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({ jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
version: "1", version: "1",
auth_data: { auth_data: {
private_key_salt: "salt", private_key_salt: "salt",

View file

@ -28,14 +28,13 @@ describe("<SecureBackupPanel />", () => {
const client = getMockClientWithEventEmitter({ const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId), ...mockClientMethodsUser(userId),
...mockClientMethodsCrypto(), ...mockClientMethodsCrypto(),
getKeyBackupVersion: jest.fn().mockReturnValue("1"),
getClientWellKnown: jest.fn(), getClientWellKnown: jest.fn(),
}); });
const getComponent = () => render(<SecureBackupPanel />); const getComponent = () => render(<SecureBackupPanel />);
beforeEach(() => { beforeEach(() => {
client.getKeyBackupVersion.mockResolvedValue({ jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
version: "1", version: "1",
algorithm: "test", algorithm: "test",
auth_data: { auth_data: {
@ -52,7 +51,6 @@ describe("<SecureBackupPanel />", () => {
}); });
mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false); mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false);
client.getKeyBackupVersion.mockClear();
mocked(accessSecretStorage).mockClear().mockResolvedValue(); mocked(accessSecretStorage).mockClear().mockResolvedValue();
}); });
@ -65,8 +63,8 @@ describe("<SecureBackupPanel />", () => {
}); });
it("handles error fetching backup", async () => { it("handles error fetching backup", async () => {
// getKeyBackupVersion can fail for various reasons // getKeyBackupInfo can fail for various reasons
client.getKeyBackupVersion.mockImplementation(async () => { jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
throw new Error("beep beep"); throw new Error("beep beep");
}); });
const renderResult = getComponent(); const renderResult = getComponent();
@ -75,9 +73,9 @@ describe("<SecureBackupPanel />", () => {
}); });
it("handles absence of backup", async () => { it("handles absence of backup", async () => {
client.getKeyBackupVersion.mockResolvedValue(null); jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue(null);
getComponent(); getComponent();
// flush getKeyBackupVersion promise // flush getKeyBackupInfo promise
await flushPromises(); await flushPromises();
expect(screen.getByText("Back up your keys before signing out to avoid losing them.")).toBeInTheDocument(); expect(screen.getByText("Back up your keys before signing out to avoid losing them.")).toBeInTheDocument();
}); });
@ -120,7 +118,7 @@ describe("<SecureBackupPanel />", () => {
}); });
it("deletes backup after confirmation", async () => { it("deletes backup after confirmation", async () => {
client.getKeyBackupVersion jest.spyOn(client.getCrypto()!, "getKeyBackupInfo")
.mockResolvedValueOnce({ .mockResolvedValueOnce({
version: "1", version: "1",
algorithm: "test", algorithm: "test",
@ -157,7 +155,7 @@ describe("<SecureBackupPanel />", () => {
// flush checkKeyBackup promise // flush checkKeyBackup promise
await flushPromises(); await flushPromises();
client.getKeyBackupVersion.mockClear(); jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockClear();
mocked(client.getCrypto()!).isKeyBackupTrusted.mockClear(); mocked(client.getCrypto()!).isKeyBackupTrusted.mockClear();
fireEvent.click(screen.getByText("Reset")); fireEvent.click(screen.getByText("Reset"));
@ -167,7 +165,7 @@ describe("<SecureBackupPanel />", () => {
await flushPromises(); await flushPromises();
// backup status refreshed // backup status refreshed
expect(client.getKeyBackupVersion).toHaveBeenCalled(); expect(client.getCrypto()!.getKeyBackupInfo).toHaveBeenCalled();
expect(client.getCrypto()!.isKeyBackupTrusted).toHaveBeenCalled(); expect(client.getCrypto()!.isKeyBackupTrusted).toHaveBeenCalled();
}); });
}); });

View file

@ -34,7 +34,6 @@ describe("<SecurityUserSettingsTab />", () => {
...mockClientMethodsCrypto(), ...mockClientMethodsCrypto(),
getRooms: jest.fn().mockReturnValue([]), getRooms: jest.fn().mockReturnValue([]),
getIgnoredUsers: jest.fn(), getIgnoredUsers: jest.fn(),
getKeyBackupVersion: jest.fn(),
}); });
const sdkContext = new SdkContextClass(); const sdkContext = new SdkContextClass();

View file

@ -37,6 +37,7 @@ describe("SetupEncryptionStore", () => {
getDeviceVerificationStatus: jest.fn(), getDeviceVerificationStatus: jest.fn(),
isDehydrationSupported: jest.fn().mockResolvedValue(false), isDehydrationSupported: jest.fn().mockResolvedValue(false),
startDehydration: jest.fn(), startDehydration: jest.fn(),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
} as unknown as Mocked<CryptoApi>; } as unknown as Mocked<CryptoApi>;
client.getCrypto.mockReturnValue(mockCrypto); client.getCrypto.mockReturnValue(mockCrypto);