Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

SecureBackupPanel: stop using deprecated APIs, and other fixes #11644

Merged
merged 5 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 56 additions & 21 deletions src/components/views/settings/SecureBackupPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ limitations under the License.
*/

import React, { ReactNode } from "react";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { logger } from "matrix-js-sdk/src/logger";
import { BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";

import type CreateKeyBackupDialog from "../../../async-components/views/dialogs/security/CreateKeyBackupDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
Expand All @@ -41,9 +40,34 @@ interface IState {
backupKeyWellFormed: boolean | null;
secretStorageKeyInAccount: boolean | null;
secretStorageReady: boolean | null;
backupInfo: IKeyBackupInfo | null;
backupSigStatus: TrustInfo | null;
sessionsRemaining: number;

/** Information on the current key backup version, as returned by the server.
*
* `null` could mean any of:
* * we haven't yet requested the data from the server.
* * we were unable to reach the server.
* * the server returned key backup version data we didn't understand or was malformed.
* * there is actually no backup on the server.
*/
backupInfo: KeyBackupInfo | null;

/**
* Information on whether the backup in `backupInfo` is correctly signed, and whether we have the right key to
* decrypt it.
*
* `undefined` if `backupInfo` is null, or if crypto is not enabled in the client.
*/
backupTrustInfo: BackupTrustInfo | undefined;

/**
* If key backup is currently enabled, the backup version we are backing up to.
*/
activeBackupVersion: string | null;

/**
* Number of sessions remaining to be backed up. `null` if we have no information on this.
*/
sessionsRemaining: number | null;
}

export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
Expand All @@ -61,8 +85,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
secretStorageKeyInAccount: null,
secretStorageReady: null,
backupInfo: null,
backupSigStatus: null,
sessionsRemaining: 0,
backupTrustInfo: undefined,
activeBackupVersion: null,
sessionsRemaining: null,
};
}

Expand Down Expand Up @@ -101,14 +126,19 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
this.setState({ loading: true });
this.getUpdatedDiagnostics();
try {
const backupInfo = await MatrixClientPeg.safeGet().getKeyBackupVersion();
const backupSigStatus = backupInfo ? await MatrixClientPeg.safeGet().isKeyBackupTrusted(backupInfo) : null;
const cli = MatrixClientPeg.safeGet();
const backupInfo = await cli.getKeyBackupVersion();
const backupTrustInfo = backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;

const activeBackupVersion = (await cli.getCrypto()?.getActiveSessionBackupVersion()) ?? null;

if (this.unmounted) return;
this.setState({
loading: false,
error: false,
backupInfo,
backupSigStatus,
backupTrustInfo,
activeBackupVersion,
});
} catch (e) {
logger.log("Unable to fetch key backup status", e);
Expand All @@ -117,7 +147,8 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
loading: false,
error: true,
backupInfo: null,
backupSigStatus: null,
backupTrustInfo: undefined,
activeBackupVersion: null,
});
}
}
Expand Down Expand Up @@ -173,8 +204,10 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
onFinished: (proceed) => {
if (!proceed) return;
this.setState({ loading: true });
const versionToDelete = this.state.backupInfo!.version!;
MatrixClientPeg.safeGet()
.deleteKeyBackupVersion(this.state.backupInfo!.version!)
.getCrypto()
?.deleteKeyBackupVersion(versionToDelete)
.then(() => {
this.loadBackupStatus();
});
Expand Down Expand Up @@ -209,7 +242,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
secretStorageKeyInAccount,
secretStorageReady,
backupInfo,
backupSigStatus,
backupTrustInfo,
sessionsRemaining,
} = this.state;

Expand All @@ -228,7 +261,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
} else if (backupInfo) {
let restoreButtonCaption = _t("Restore from Backup");

if (MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
if (this.state.activeBackupVersion !== null) {
andybalaam marked this conversation as resolved.
Show resolved Hide resolved
statusDescription = (
<SettingsSubsectionText>✅ {_t("This session is backing up your keys.")}</SettingsSubsectionText>
);
Expand All @@ -253,7 +286,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
}

let uploadStatus: ReactNode;
if (!MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
if (sessionsRemaining === null) {
// No upload status to show when backup disabled.
uploadStatus = "";
} else if (sessionsRemaining > 0) {
Expand All @@ -271,19 +304,21 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
}

let trustedLocally: string | undefined;
if (backupSigStatus?.trusted_locally) {
trustedLocally = _t("This backup is trusted because it has been restored on this session");
if (backupTrustInfo?.matchesDecryptionKey) {
trustedLocally = _t("This backup can be restored on this session");
}

extraDetailsTableRows = (
<>
<tr>
<th scope="row">{_t("Backup version:")}</th>
<td>{backupInfo.version}</td>
<th scope="row">{_t("Latest backup version on server:")}</th>
<td>
{backupInfo.version} ({_t("Algorithm:")} <code>{backupInfo.algorithm}</code>)
</td>
</tr>
<tr>
<th scope="row">{_t("Algorithm:")}</th>
<td>{backupInfo.algorithm}</td>
<th scope="row">{_t("Active backup version:")}</th>
<td>{this.state.activeBackupVersion === null ? _t("None") : this.state.activeBackupVersion}</td>
</tr>
</>
);
Expand Down
5 changes: 3 additions & 2 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2043,9 +2043,10 @@
"Connect this session to Key Backup": "Connect this session to Key Backup",
"Backing up %(sessionsRemaining)s keys…": "Backing up %(sessionsRemaining)s keys…",
"All keys backed up": "All keys backed up",
"This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session",
"Backup version:": "Backup version:",
"This backup can be restored on this session": "This backup can be restored on this session",
"Latest backup version on server:": "Latest backup version on server:",
"Algorithm:": "Algorithm:",
"Active backup version:": "Active backup version:",
"Your keys are <b>not being backed up from this session</b>.": "Your keys are <b>not being backed up from this session</b>.",
"Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.",
"Set up": "Set up",
Expand Down
25 changes: 12 additions & 13 deletions test/components/views/settings/SecureBackupPanel-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,8 @@ describe("<SecureBackupPanel />", () => {
const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsCrypto(),
getKeyBackupEnabled: jest.fn(),
getKeyBackupVersion: jest.fn().mockReturnValue("1"),
isKeyBackupTrusted: jest.fn().mockResolvedValue(true),
getClientWellKnown: jest.fn(),
deleteKeyBackupVersion: jest.fn(),
});

const getComponent = () => render(<SecureBackupPanel />);
Expand All @@ -53,15 +50,17 @@ describe("<SecureBackupPanel />", () => {
public_key: "1234",
},
});
client.isKeyBackupTrusted.mockResolvedValue({
usable: false,
sigs: [],
Object.assign(client.getCrypto()!, {
isKeyBackupTrusted: jest.fn().mockResolvedValue({
trusted: false,
matchesDecryptionKey: false,
}),
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
deleteKeyBackupVersion: jest.fn().mockResolvedValue(undefined),
});

mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false);
client.deleteKeyBackupVersion.mockClear().mockResolvedValue();
client.getKeyBackupVersion.mockClear();
client.isKeyBackupTrusted.mockClear();

mocked(accessSecretStorage).mockClear().mockResolvedValue();
});
Expand Down Expand Up @@ -100,7 +99,7 @@ describe("<SecureBackupPanel />", () => {
});

it("displays when session is connected to key backup", async () => {
client.getKeyBackupEnabled.mockReturnValue(true);
mocked(client.getCrypto()!).getActiveSessionBackupVersion.mockResolvedValue("1");
getComponent();
// flush checkKeyBackup promise
await flushPromises();
Expand All @@ -125,7 +124,7 @@ describe("<SecureBackupPanel />", () => {

fireEvent.click(within(dialog).getByText("Cancel"));

expect(client.deleteKeyBackupVersion).not.toHaveBeenCalled();
expect(client.getCrypto()!.deleteKeyBackupVersion).not.toHaveBeenCalled();
});

it("deletes backup after confirmation", async () => {
Expand Down Expand Up @@ -154,7 +153,7 @@ describe("<SecureBackupPanel />", () => {

fireEvent.click(within(dialog).getByTestId("dialog-primary-button"));

expect(client.deleteKeyBackupVersion).toHaveBeenCalledWith("1");
expect(client.getCrypto()!.deleteKeyBackupVersion).toHaveBeenCalledWith("1");

// delete request
await flushPromises();
Expand All @@ -169,7 +168,7 @@ describe("<SecureBackupPanel />", () => {
await flushPromises();

client.getKeyBackupVersion.mockClear();
client.isKeyBackupTrusted.mockClear();
mocked(client.getCrypto()!).isKeyBackupTrusted.mockClear();

fireEvent.click(screen.getByText("Reset"));

Expand All @@ -179,6 +178,6 @@ describe("<SecureBackupPanel />", () => {

// backup status refreshed
expect(client.getKeyBackupVersion).toHaveBeenCalled();
expect(client.isKeyBackupTrusted).toHaveBeenCalled();
expect(client.getCrypto()!.isKeyBackupTrusted).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -140,20 +140,27 @@ exports[`<SecureBackupPanel /> suggests connecting session to key backup when ba
<th
scope="row"
>
Backup version:
Latest backup version on server:
</th>
<td>
1
(
Algorithm:

<code>
test
</code>
)
</td>
</tr>
<tr>
<th
scope="row"
>
Algorithm:
Active backup version:
</th>
<td>
test
None
</td>
</tr>
</table>
Expand Down
Loading