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

Commit 46eb34a

Browse files
authored
Kill off references to deprecated getStoredDevice and getStoredDevicesForUser (#11152)
* Use new `CryptoEvent.VerificationRequestReceived` event matrix-org/matrix-js-sdk#3514 deprecates `CryptoEvent.VerificationRequest` in favour of `CryptoEvent.VerificationRequestReceived`. Use the new event. * Factor out `getDeviceCryptoInfo` function I seem to be writing this logic several times, so let's factor it out. * Factor out `getUserDeviceIds` function Another utility function * VerificationRequestToast: `getStoredDevice` -> `getDeviceCryptoInfo` * SlashCommands: `getStoredDevice` -> `getDeviceCryptoInfo` * MemberTile: `getStoredDevicesForUser` -> `getUserDeviceIds` * Remove redundant mock of `getStoredDevicesForUser`
1 parent 0a3a111 commit 46eb34a

File tree

11 files changed

+189
-34
lines changed

11 files changed

+189
-34
lines changed

src/DeviceListener.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { recordClientInformation, removeClientInformation } from "./utils/device
4646
import SettingsStore, { CallbackFn } from "./settings/SettingsStore";
4747
import { UIFeature } from "./settings/UIFeature";
4848
import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder";
49+
import { getUserDeviceIds } from "./utils/crypto/deviceInfo";
4950

5051
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
5152

@@ -161,12 +162,8 @@ export default class DeviceListener {
161162
*/
162163
private async getDeviceIds(): Promise<Set<string>> {
163164
const cli = this.client;
164-
const crypto = cli?.getCrypto();
165-
if (crypto === undefined) return new Set();
166-
167-
const userId = cli!.getSafeUserId();
168-
const devices = await crypto.getUserDeviceInfo([userId]);
169-
return new Set(devices.get(userId)?.keys() ?? []);
165+
if (!cli) return new Set();
166+
return await getUserDeviceIds(cli, cli.getSafeUserId());
170167
}
171168

172169
private onWillUpdateDevices = async (users: string[], initialFetch?: boolean): Promise<void> => {

src/SlashCommands.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { leaveRoomBehaviour } from "./utils/leave-behaviour";
7070
import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
7171
import { SdkContextClass } from "./contexts/SDKContext";
7272
import { MatrixClientPeg } from "./MatrixClientPeg";
73+
import { getDeviceCryptoInfo } from "./utils/crypto/deviceInfo";
7374

7475
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
7576
interface HTMLInputEvent extends Event {
@@ -1031,7 +1032,7 @@ export const Commands = [
10311032

10321033
return success(
10331034
(async (): Promise<void> => {
1034-
const device = cli.getStoredDevice(userId, deviceId);
1035+
const device = await getDeviceCryptoInfo(cli, userId, deviceId);
10351036
if (!device) {
10361037
throw new UserFriendlyError(
10371038
"Unknown (user, session) pair: (%(userId)s, %(deviceId)s)",

src/components/views/right_panel/VerificationPanel.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import E2EIcon, { E2EState } from "../rooms/E2EIcon";
3636
import Spinner from "../elements/Spinner";
3737
import AccessibleButton from "../elements/AccessibleButton";
3838
import VerificationShowSas from "../verification/VerificationShowSas";
39+
import { getDeviceCryptoInfo } from "../../../utils/crypto/deviceInfo";
3940

4041
interface IProps {
4142
layout: string;
@@ -224,12 +225,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
224225
return;
225226
}
226227
this.haveCheckedDevice = true;
227-
228-
const deviceMap = await client.getCrypto()?.getUserDeviceInfo([userId]);
229-
if (!deviceMap) return;
230-
const userDevices = deviceMap.get(userId);
231-
if (!userDevices) return;
232-
this.setState({ otherDeviceDetails: userDevices.get(deviceId) });
228+
this.setState({ otherDeviceDetails: await getDeviceCryptoInfo(client, userId, deviceId) });
233229
}
234230

235231
private renderQRReciprocatePhase(): JSX.Element {

src/components/views/rooms/MemberTile.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import DisambiguatedProfile from "../messages/DisambiguatedProfile";
3434
import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
3535
import { E2EState } from "./E2EIcon";
3636
import { asyncSome } from "../../../utils/arrays";
37+
import { getUserDeviceIds } from "../../../utils/crypto/deviceInfo";
3738

3839
interface IProps {
3940
member: RoomMember;
@@ -127,9 +128,8 @@ export default class MemberTile extends React.Component<IProps, IState> {
127128
return;
128129
}
129130

130-
const devices = cli.getStoredDevicesForUser(userId);
131-
const anyDeviceUnverified = await asyncSome(devices, async (device) => {
132-
const { deviceId } = device;
131+
const deviceIDs = await getUserDeviceIds(cli, userId);
132+
const anyDeviceUnverified = await asyncSome(deviceIDs, async (deviceId) => {
133133
// For your own devices, we use the stricter check of cross-signing
134134
// verification to encourage everyone to trust their own devices via
135135
// cross-signing so that other users can then safely trust you.

src/components/views/toasts/VerificationRequestToast.tsx

+9-10
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import {
2020
VerificationRequest,
2121
VerificationRequestEvent,
2222
} from "matrix-js-sdk/src/crypto-api";
23-
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
2423
import { logger } from "matrix-js-sdk/src/logger";
24+
import { Device } from "matrix-js-sdk/src/matrix";
2525

2626
import { _t } from "../../../languageHandler";
2727
import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -35,6 +35,7 @@ import { Action } from "../../../dispatcher/actions";
3535
import VerificationRequestDialog from "../dialogs/VerificationRequestDialog";
3636
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
3737
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
38+
import { getDeviceCryptoInfo } from "../../../utils/crypto/deviceInfo";
3839

3940
interface IProps {
4041
toastKey: string;
@@ -44,7 +45,7 @@ interface IProps {
4445
interface IState {
4546
/** number of seconds left in the timeout counter. Zero if there is no timeout. */
4647
counter: number;
47-
device?: DeviceInfo;
48+
device?: Device;
4849
ip?: string;
4950
}
5051

@@ -74,15 +75,13 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
7475
// a toast hanging around after logging in if you did a verification as part of login).
7576
this.checkRequestIsPending();
7677

77-
if (request.isSelfVerification) {
78+
const otherDeviceId = request.otherDeviceId;
79+
if (request.isSelfVerification && !!otherDeviceId) {
7880
const cli = MatrixClientPeg.safeGet();
79-
const device = request.otherDeviceId ? await cli.getDevice(request.otherDeviceId) : null;
80-
const ip = device?.last_seen_ip;
81+
const device = await cli.getDevice(otherDeviceId);
8182
this.setState({
82-
device:
83-
(request.otherDeviceId && cli.getStoredDevice(cli.getSafeUserId(), request.otherDeviceId)) ||
84-
undefined,
85-
ip,
83+
ip: device.last_seen_ip,
84+
device: await getDeviceCryptoInfo(cli, cli.getSafeUserId(), otherDeviceId),
8685
});
8786
}
8887
}
@@ -158,7 +157,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
158157
let detail;
159158
if (request.isSelfVerification) {
160159
if (this.state.device) {
161-
description = this.state.device.getDisplayName();
160+
description = this.state.device.displayName;
162161
detail = _t("%(deviceId)s from %(ip)s", {
163162
deviceId: this.state.device.deviceId,
164163
ip: this.state.ip,

src/utils/crypto/deviceInfo.ts

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { Device, MatrixClient } from "matrix-js-sdk/src/matrix";
18+
19+
/**
20+
* Get crypto information on a specific device.
21+
*
22+
* Only devices with Crypto support are returned. If the MatrixClient doesn't support cryptography, `undefined` is
23+
* returned.
24+
*
25+
* @param client - Matrix Client.
26+
* @param userId - ID of the user owning the device.
27+
* @param deviceId - ID of the device.
28+
* @param downloadUncached - If true, download the device list for users whose device list we are not
29+
* currently tracking. Defaults to false.
30+
*
31+
* @returns Information on the device if it is known.
32+
*/
33+
export async function getDeviceCryptoInfo(
34+
client: MatrixClient,
35+
userId: string,
36+
deviceId: string,
37+
downloadUncached?: boolean,
38+
): Promise<Device | undefined> {
39+
const crypto = client.getCrypto();
40+
if (!crypto) {
41+
// no crypto support, no device.
42+
return undefined;
43+
}
44+
45+
const deviceMap = await crypto.getUserDeviceInfo([userId], downloadUncached);
46+
return deviceMap.get(userId)?.get(deviceId);
47+
}
48+
49+
/**
50+
* Get the IDs of the given user's devices.
51+
*
52+
* Only devices with Crypto support are returned. If the MatrixClient doesn't support cryptography, an empty Set is
53+
* returned.
54+
*
55+
* @param client - Matrix Client.
56+
* @param userId - ID of the user to query.
57+
*/
58+
59+
export async function getUserDeviceIds(client: MatrixClient, userId: string): Promise<Set<string>> {
60+
const crypto = client.getCrypto();
61+
if (!crypto) {
62+
return new Set();
63+
}
64+
65+
const deviceMap = await crypto.getUserDeviceInfo([userId]);
66+
return new Set(deviceMap.get(userId)?.keys() ?? []);
67+
}

test/components/views/right_panel/UserInfo-test.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ beforeEach(() => {
160160
credentials: {},
161161
setPowerLevel: jest.fn(),
162162
downloadKeys: jest.fn(),
163-
getStoredDevicesForUser: jest.fn(),
164163
getCrypto: jest.fn().mockReturnValue(mockCrypto),
165164
getStoredCrossSigningForUser: jest.fn(),
166165
} as unknown as MatrixClient);

test/components/views/toasts/VerificationRequestToast-test.tsx

+18-7
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,23 @@ limitations under the License.
1515
*/
1616

1717
import React, { ComponentProps } from "react";
18-
import { Mocked } from "jest-mock";
18+
import { mocked, Mocked } from "jest-mock";
1919
import { act, render, RenderResult } from "@testing-library/react";
2020
import {
2121
VerificationRequest,
2222
VerificationRequestEvent,
2323
} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
24-
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
2524
import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/client";
2625
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
26+
import { Device } from "matrix-js-sdk/src/matrix";
2727

2828
import VerificationRequestToast from "../../../../src/components/views/toasts/VerificationRequestToast";
29-
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
29+
import {
30+
flushPromises,
31+
getMockClientWithEventEmitter,
32+
mockClientMethodsCrypto,
33+
mockClientMethodsUser,
34+
} from "../../../test-utils";
3035
import ToastStore from "../../../../src/stores/ToastStore";
3136

3237
function renderComponent(
@@ -46,7 +51,7 @@ describe("VerificationRequestToast", () => {
4651
beforeEach(() => {
4752
client = getMockClientWithEventEmitter({
4853
...mockClientMethodsUser(),
49-
getStoredDevice: jest.fn(),
54+
...mockClientMethodsCrypto(),
5055
getDevice: jest.fn(),
5156
});
5257
});
@@ -56,9 +61,15 @@ describe("VerificationRequestToast", () => {
5661
const otherIDevice: IMyDevice = { device_id: otherDeviceId, last_seen_ip: "1.1.1.1" };
5762
client.getDevice.mockResolvedValue(otherIDevice);
5863

59-
const otherDeviceInfo = new DeviceInfo(otherDeviceId);
60-
otherDeviceInfo.unsigned = { device_display_name: "my other device" };
61-
client.getStoredDevice.mockReturnValue(otherDeviceInfo);
64+
const otherDeviceInfo = new Device({
65+
algorithms: [],
66+
keys: new Map(),
67+
userId: "",
68+
deviceId: otherDeviceId,
69+
displayName: "my other device",
70+
});
71+
const deviceMap = new Map([[client.getSafeUserId(), new Map([[otherDeviceId, otherDeviceInfo]])]]);
72+
mocked(client.getCrypto()!.getUserDeviceInfo).mockResolvedValue(deviceMap);
6273

6374
const request = makeMockVerificationRequest({
6475
isSelfVerification: true,

test/test-utils/client.ts

+3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export class MockClientWithEventEmitter extends EventEmitter {
6464
getUserId: jest.fn().mockReturnValue(aliceId),
6565
});
6666
* ```
67+
*
68+
* See also `stubClient()` which does something similar but uses a more complete mock client.
6769
*/
6870
export const getMockClientWithEventEmitter = (
6971
mockProperties: Partial<Record<keyof MatrixClient, unknown>>,
@@ -158,6 +160,7 @@ export const mockClientMethodsCrypto = (): Partial<
158160
getSessionBackupPrivateKey: jest.fn(),
159161
},
160162
getCrypto: jest.fn().mockReturnValue({
163+
getUserDeviceInfo: jest.fn(),
161164
getCrossSigningStatus: jest.fn().mockResolvedValue({
162165
publicKeysOnDevice: true,
163166
privateKeysInSecretStorage: false,

test/test-utils/test-utils.ts

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ import MatrixClientBackedSettingsHandler from "../../src/settings/handlers/Matri
6060
* TODO: once the components are updated to get their MatrixClients from
6161
* the react context, we can get rid of this and just inject a test client
6262
* via the context instead.
63+
*
64+
* See also `getMockClientWithEventEmitter` which does something similar but different.
6365
*/
6466
export function stubClient(): MatrixClient {
6567
const client = createTestClient();

test/utils/crypto/deviceInfo-test.ts

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { Mocked, mocked } from "jest-mock";
18+
import { Device, MatrixClient } from "matrix-js-sdk/src/matrix";
19+
20+
import { getDeviceCryptoInfo, getUserDeviceIds } from "../../../src/utils/crypto/deviceInfo";
21+
import { getMockClientWithEventEmitter, mockClientMethodsCrypto } from "../../test-utils";
22+
23+
describe("getDeviceCryptoInfo()", () => {
24+
let mockClient: Mocked<MatrixClient>;
25+
26+
beforeEach(() => {
27+
mockClient = getMockClientWithEventEmitter({ ...mockClientMethodsCrypto() });
28+
});
29+
30+
it("should return undefined on clients with no crypto", async () => {
31+
jest.spyOn(mockClient, "getCrypto").mockReturnValue(undefined);
32+
await expect(getDeviceCryptoInfo(mockClient, "@user:id", "device_id")).resolves.toBeUndefined();
33+
});
34+
35+
it("should return undefined for unknown users", async () => {
36+
mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(new Map());
37+
await expect(getDeviceCryptoInfo(mockClient, "@user:id", "device_id")).resolves.toBeUndefined();
38+
});
39+
40+
it("should return undefined for unknown devices", async () => {
41+
mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(new Map([["@user:id", new Map()]]));
42+
await expect(getDeviceCryptoInfo(mockClient, "@user:id", "device_id")).resolves.toBeUndefined();
43+
});
44+
45+
it("should return the right result for known devices", async () => {
46+
const mockDevice = { deviceId: "device_id" } as Device;
47+
mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(
48+
new Map([["@user:id", new Map([["device_id", mockDevice]])]]),
49+
);
50+
await expect(getDeviceCryptoInfo(mockClient, "@user:id", "device_id")).resolves.toBe(mockDevice);
51+
expect(mockClient.getCrypto()!.getUserDeviceInfo).toHaveBeenCalledWith(["@user:id"], undefined);
52+
});
53+
});
54+
55+
describe("getUserDeviceIds", () => {
56+
let mockClient: Mocked<MatrixClient>;
57+
58+
beforeEach(() => {
59+
mockClient = getMockClientWithEventEmitter({ ...mockClientMethodsCrypto() });
60+
});
61+
62+
it("should return empty set on clients with no crypto", async () => {
63+
jest.spyOn(mockClient, "getCrypto").mockReturnValue(undefined);
64+
await expect(getUserDeviceIds(mockClient, "@user:id")).resolves.toEqual(new Set());
65+
});
66+
67+
it("should return empty set for unknown users", async () => {
68+
mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(new Map());
69+
await expect(getUserDeviceIds(mockClient, "@user:id")).resolves.toEqual(new Set());
70+
});
71+
72+
it("should return the right result for known users", async () => {
73+
const mockDevice = { deviceId: "device_id" } as Device;
74+
mocked(mockClient.getCrypto()!.getUserDeviceInfo).mockResolvedValue(
75+
new Map([["@user:id", new Map([["device_id", mockDevice]])]]),
76+
);
77+
await expect(getUserDeviceIds(mockClient, "@user:id")).resolves.toEqual(new Set(["device_id"]));
78+
expect(mockClient.getCrypto()!.getUserDeviceInfo).toHaveBeenCalledWith(["@user:id"]);
79+
});
80+
});

0 commit comments

Comments
 (0)