From 60a3a5e1a3176b6854c75392cfec6ee29a88e6e8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 Jul 2023 13:24:48 +0100 Subject: [PATCH 1/4] Offer to unban user during invite if inviter has sufficient permissions --- src/i18n/strings/en_EN.json | 3 ++- src/utils/MultiInviter.ts | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4b3d180041f..7c7763216ed 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -736,6 +736,8 @@ "Not a valid %(brand)s keyfile": "Not a valid %(brand)s keyfile", "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", "Unrecognised address": "Unrecognised address", + "Unban": "Unban", + "User cannot be invited until they are unbanned": "User cannot be invited until they are unbanned", "You do not have permission to invite people to this space.": "You do not have permission to invite people to this space.", "You do not have permission to invite people to this room.": "You do not have permission to invite people to this room.", "User is already invited to the space": "User is already invited to the space", @@ -1705,7 +1707,6 @@ "Upload custom sound": "Upload custom sound", "Browse": "Browse", "Failed to unban": "Failed to unban", - "Unban": "Unban", "Banned by %(displayName)s": "Banned by %(displayName)s", "Reason": "Reason", "Error changing power level requirement": "Error changing power level requirement", diff --git a/src/utils/MultiInviter.ts b/src/utils/MultiInviter.ts index 31ca6f87b54..c7a7ea4dcda 100644 --- a/src/utils/MultiInviter.ts +++ b/src/utils/MultiInviter.ts @@ -26,6 +26,7 @@ import { _t } from "../languageHandler"; import Modal from "../Modal"; import SettingsStore from "../settings/SettingsStore"; import AskInviteAnywayDialog from "../components/views/dialogs/AskInviteAnywayDialog"; +import ConfirmUserActionDialog from "../components/views/dialogs/ConfirmUserActionDialog"; export enum InviteState { Invited = "invited", @@ -48,6 +49,7 @@ export type CompletionStates = Record; const USER_ALREADY_JOINED = "IO.ELEMENT.ALREADY_JOINED"; const USER_ALREADY_INVITED = "IO.ELEMENT.ALREADY_INVITED"; +const USER_BANNED = "IO.ELEMENT.BANNED"; /** * Invites multiple addresses to a room, handling rate limiting from the server @@ -170,6 +172,27 @@ export default class MultiInviter { errcode: USER_ALREADY_INVITED, error: "Member already invited", }); + } else if (member?.membership === "ban") { + let proceed = false; + const ourMember = room.getMember(this.matrixClient.getSafeUserId()); + if (!!ourMember && room.currentState.hasSufficientPowerLevelFor("ban", ourMember.powerLevel)) { + const { finished } = Modal.createDialog(ConfirmUserActionDialog, { + member, + action: _t("Unban"), + title: _t("User cannot be invited until they are unbanned"), + }); + [proceed = false] = await finished; + if (proceed) { + await this.matrixClient.unban(roomId, member.userId); + } + } + + if (!proceed) { + throw new MatrixError({ + errcode: USER_BANNED, + error: "Member is banned", + }); + } } if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) { @@ -268,6 +291,7 @@ export default class MultiInviter { } break; case "M_BAD_STATE": + case USER_BANNED: errorText = _t("The user must be unbanned before they can be invited."); break; case "M_UNSUPPORTED_ROOM_VERSION": From 02535f5f7f47c0759f0ba92387990e69d72230a6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 14 Jul 2023 10:56:46 +0100 Subject: [PATCH 2/4] Improve unban check in MultiInviter --- src/utils/MultiInviter.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils/MultiInviter.ts b/src/utils/MultiInviter.ts index c7a7ea4dcda..86cd17d6a34 100644 --- a/src/utils/MultiInviter.ts +++ b/src/utils/MultiInviter.ts @@ -175,7 +175,12 @@ export default class MultiInviter { } else if (member?.membership === "ban") { let proceed = false; const ourMember = room.getMember(this.matrixClient.getSafeUserId()); - if (!!ourMember && room.currentState.hasSufficientPowerLevelFor("ban", ourMember.powerLevel)) { + if ( + !!ourMember && + member.powerLevel < ourMember.powerLevel && + room.currentState.hasSufficientPowerLevelFor("ban", ourMember.powerLevel) && + room.currentState.hasSufficientPowerLevelFor("kick", ourMember.powerLevel) + ) { const { finished } = Modal.createDialog(ConfirmUserActionDialog, { member, action: _t("Unban"), From e3a83bbe233bf27ba9f0fb4be2900009fa04f3f9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 14 Jul 2023 11:08:48 +0100 Subject: [PATCH 3/4] Improve coverage --- test/utils/MultiInviter-test.ts | 35 ++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/utils/MultiInviter-test.ts b/test/utils/MultiInviter-test.ts index 4e63407119b..d92710bd2a0 100644 --- a/test/utils/MultiInviter-test.ts +++ b/test/utils/MultiInviter-test.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { mocked } from "jest-mock"; -import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, MatrixError, Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import Modal, { ComponentType, ComponentProps } from "../../src/Modal"; @@ -23,6 +23,7 @@ import SettingsStore from "../../src/settings/SettingsStore"; import MultiInviter, { CompletionStates } from "../../src/utils/MultiInviter"; import * as TestUtilsMatrix from "../test-utils"; import AskInviteAnywayDialog from "../../src/components/views/dialogs/AskInviteAnywayDialog"; +import ConfirmUserActionDialog from "../../src/components/views/dialogs/ConfirmUserActionDialog"; const ROOMID = "!room:server"; @@ -89,6 +90,7 @@ describe("MultiInviter", () => { client.getProfileInfo.mockImplementation((userId: string) => { return MXID_PROFILE_STATES[userId] || Promise.reject(); }); + client.unban = jest.fn(); inviter = new MultiInviter(client, ROOMID); }); @@ -154,5 +156,36 @@ describe("MultiInviter", () => { `"Cannot invite user by email without an identity server. You can connect to one under "Settings"."`, ); }); + + it("should ask if user wants to unban user if they have permission", async () => { + mocked(Modal.createDialog).mockImplementation( + (Element: ComponentType, props?: ComponentProps): any => { + // We stub out the modal with an immediate affirmative (proceed) return + return { finished: Promise.resolve([true]) }; + }, + ); + + const room = new Room(ROOMID, client, client.getSafeUserId()); + mocked(client.getRoom).mockReturnValue(room); + const ourMember = new RoomMember(ROOMID, client.getSafeUserId()); + ourMember.membership = "join"; + ourMember.powerLevel = 100; + const member = new RoomMember(ROOMID, MXID1); + member.membership = "ban"; + member.powerLevel = 0; + room.getMember = (userId: string) => { + if (userId === client.getSafeUserId()) return ourMember; + if (userId === MXID1) return member; + return null; + }; + + await inviter.invite([MXID1]); + expect(Modal.createDialog).toHaveBeenCalledWith(ConfirmUserActionDialog, { + member, + title: "User cannot be invited until they are unbanned", + action: "Unban", + }); + expect(client.unban).toHaveBeenCalledWith(ROOMID, MXID1); + }); }); }); From ef18314c5ebed8d1e1673d151a279cd84ddaedea Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 14 Jul 2023 13:59:27 +0100 Subject: [PATCH 4/4] Update src/utils/MultiInviter.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- src/utils/MultiInviter.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/MultiInviter.ts b/src/utils/MultiInviter.ts index 86cd17d6a34..0066da81f31 100644 --- a/src/utils/MultiInviter.ts +++ b/src/utils/MultiInviter.ts @@ -174,6 +174,8 @@ export default class MultiInviter { }); } else if (member?.membership === "ban") { let proceed = false; + // Check if we can unban the invitee. + // See https://spec.matrix.org/v1.7/rooms/v10/#authorization-rules, particularly 4.5.3 and 4.5.4. const ourMember = room.getMember(this.matrixClient.getSafeUserId()); if ( !!ourMember &&