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

Commit 07542b0

Browse files
committed
Fix inviting users with undisclosed profiles (#17269)
Signed-off-by: Michael Weimann <[email protected]> Signed-off-by: Michael Weimann <[email protected]>
1 parent 23cb0ae commit 07542b0

File tree

2 files changed

+160
-4
lines changed

2 files changed

+160
-4
lines changed

src/utils/MultiInviter.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,19 @@ export default class MultiInviter {
168168
}
169169

170170
if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
171-
const profile = await this.matrixClient.getProfileInfo(addr);
172-
if (!profile) {
173-
// noinspection ExceptionCaughtLocallyJS
174-
throw new Error("User has no profile");
171+
try {
172+
await this.matrixClient.getProfileInfo(addr);
173+
} catch (err) {
174+
// The error handling during the invitation process covers any API.
175+
// Some errors must to me mapped from profile API errors to more specific ones to avoid collisions.
176+
switch (err.errcode) {
177+
case 'M_FORBIDDEN':
178+
throw new MatrixError({ errcode: 'M_PROFILE_UNDISCLOSED' });
179+
case 'M_NOT_FOUND':
180+
throw new MatrixError({ errcode: 'M_USER_NOT_FOUND' });
181+
default:
182+
throw err;
183+
}
175184
}
176185
}
177186

test/utils/MultiInviter-test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
Copyright 2022 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 } from 'jest-mock';
18+
import { MatrixClient } from 'matrix-js-sdk/src/matrix';
19+
20+
import { MatrixClientPeg } from '../../src/MatrixClientPeg';
21+
import Modal, { ModalManager } from '../../src/Modal';
22+
import SettingsStore from '../../src/settings/SettingsStore';
23+
import MultiInviter, { CompletionStates } from '../../src/utils/MultiInviter';
24+
import * as TestUtilsMatrix from '../test-utils';
25+
26+
const ROOMID = '!room:server';
27+
28+
const MXID1 = '@user1:server';
29+
const MXID2 = '@user2:server';
30+
const MXID3 = '@user3:server';
31+
32+
const MXID_PROFILE_STATES = {
33+
[MXID1]: Promise.resolve({}),
34+
[MXID2]: Promise.reject({ errcode: 'M_FORBIDDEN' }),
35+
[MXID3]: Promise.reject({ errcode: 'M_NOT_FOUND' }),
36+
};
37+
38+
jest.mock('../../src/Modal', () => ({
39+
createTrackedDialog: jest.fn(),
40+
}));
41+
42+
jest.mock('../../src/settings/SettingsStore', () => ({
43+
getValue: jest.fn(),
44+
}));
45+
46+
const mockPromptBeforeInviteUnknownUsers = (value: boolean) => {
47+
mocked(SettingsStore.getValue).mockImplementation(
48+
(settingName: string, roomId: string = null, _excludeDefault = false): any => {
49+
if (settingName === 'promptBeforeInviteUnknownUsers' && roomId === ROOMID) {
50+
return value;
51+
}
52+
},
53+
);
54+
};
55+
56+
const mockCreateTrackedDialog = (callbackName: 'onInviteAnyways'|'onGiveUp') => {
57+
mocked(Modal.createTrackedDialog).mockImplementation(
58+
(
59+
_analyticsAction: string,
60+
_analyticsInfo: string,
61+
...rest: Parameters<ModalManager['createDialog']>
62+
): any => {
63+
rest[1][callbackName]();
64+
},
65+
);
66+
};
67+
68+
const expectAllInvitedResult = (result: CompletionStates) => {
69+
expect(result).toEqual({
70+
[MXID1]: 'invited',
71+
[MXID2]: 'invited',
72+
[MXID3]: 'invited',
73+
});
74+
};
75+
76+
describe('MultiInviter', () => {
77+
let client: jest.Mocked<MatrixClient>;
78+
let inviter: MultiInviter;
79+
80+
beforeEach(() => {
81+
jest.resetAllMocks();
82+
83+
TestUtilsMatrix.stubClient();
84+
client = MatrixClientPeg.get() as jest.Mocked<MatrixClient>;
85+
86+
client.invite = jest.fn();
87+
client.invite.mockResolvedValue({});
88+
89+
client.getProfileInfo = jest.fn();
90+
client.getProfileInfo.mockImplementation((userId: string) => {
91+
return MXID_PROFILE_STATES[userId] || Promise.reject();
92+
});
93+
94+
inviter = new MultiInviter(ROOMID);
95+
});
96+
97+
describe('invite', () => {
98+
describe('with promptBeforeInviteUnknownUsers = false', () => {
99+
beforeEach(() => mockPromptBeforeInviteUnknownUsers(false));
100+
101+
it('should invite all users', async () => {
102+
const result = await inviter.invite([MXID1, MXID2, MXID3]);
103+
104+
expect(client.invite).toHaveBeenCalledTimes(3);
105+
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
106+
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined, undefined);
107+
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined, undefined);
108+
109+
expectAllInvitedResult(result);
110+
});
111+
});
112+
113+
describe('with promptBeforeInviteUnknownUsers = true and', () => {
114+
beforeEach(() => mockPromptBeforeInviteUnknownUsers(true));
115+
116+
describe('confirming the unknown user dialog', () => {
117+
beforeEach(() => mockCreateTrackedDialog('onInviteAnyways'));
118+
119+
it('should invite all users', async () => {
120+
const result = await inviter.invite([MXID1, MXID2, MXID3]);
121+
122+
expect(client.invite).toHaveBeenCalledTimes(3);
123+
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
124+
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined, undefined);
125+
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined, undefined);
126+
127+
expectAllInvitedResult(result);
128+
});
129+
});
130+
131+
describe('declining the unknown user dialog', () => {
132+
beforeEach(() => mockCreateTrackedDialog('onGiveUp'));
133+
134+
it('should only invite existing users', async () => {
135+
const result = await inviter.invite([MXID1, MXID2, MXID3]);
136+
137+
expect(client.invite).toHaveBeenCalledTimes(1);
138+
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
139+
140+
// The resolved state is 'invited' for all users.
141+
// With the above client expectations, the test ensures that only the first user is invited.
142+
expectAllInvitedResult(result);
143+
});
144+
});
145+
});
146+
});
147+
});

0 commit comments

Comments
 (0)