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

Fix unfederated invite dialog #9618

Merged
merged 8 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
34 changes: 26 additions & 8 deletions src/components/views/dialogs/InviteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

import React, { createRef, ReactNode, SyntheticEvent } from "react";
import classNames from "classnames";
import { RoomMember, Room, MatrixError } from "matrix-js-sdk/src/matrix";
import { RoomMember, Room, MatrixError, EventType } from "matrix-js-sdk/src/matrix";
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import { logger } from "matrix-js-sdk/src/logger";
import { uniqBy } from "lodash";
Expand Down Expand Up @@ -368,26 +368,32 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial

this.profilesStore = SdkContextClass.instance.userProfilesStore;

const alreadyInvited = new Set([MatrixClientPeg.safeGet().getUserId()!]);
const excludedIds = new Set([MatrixClientPeg.safeGet().getUserId()!]);
const welcomeUserId = SdkConfig.get("welcome_user_id");
if (welcomeUserId) alreadyInvited.add(welcomeUserId);
if (welcomeUserId) excludedIds.add(welcomeUserId);

if (isRoomInvite(props)) {
const room = MatrixClientPeg.safeGet().getRoom(props.roomId);
const isFederated = room?.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()["m.federate"];
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
room.getMembersWithMembership("invite").forEach((m) => alreadyInvited.add(m.userId));
room.getMembersWithMembership("join").forEach((m) => alreadyInvited.add(m.userId));
room.getMembersWithMembership("invite").forEach((m) => excludedIds.add(m.userId));
room.getMembersWithMembership("join").forEach((m) => excludedIds.add(m.userId));
// add banned users, so we don't try to invite them
room.getMembersWithMembership("ban").forEach((m) => alreadyInvited.add(m.userId));
room.getMembersWithMembership("ban").forEach((m) => excludedIds.add(m.userId));
if (isFederated === false) {
// exclude users from external servers
const homeserver = props.roomId.split(":")[1];
this.excludeExternals(homeserver, excludedIds);
}
}

this.state = {
targets: [], // array of Member objects (see interface above)
filterText: this.props.initialText || "",
// Mutates alreadyInvited set so that buildSuggestions doesn't duplicate any users
recents: InviteDialog.buildRecents(alreadyInvited),
recents: InviteDialog.buildRecents(excludedIds),
numRecentsShown: INITIAL_ROOMS_SHOWN,
suggestions: this.buildSuggestions(alreadyInvited),
suggestions: this.buildSuggestions(excludedIds),
numSuggestionsShown: INITIAL_ROOMS_SHOWN,
serverResultsMixin: [],
threepidResultsMixin: [],
Expand Down Expand Up @@ -418,6 +424,18 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.setState({ consultFirst: ev.target.checked });
};

private excludeExternals(homeserver: string, excludedTargetIds: Set<string>): void {
const client = MatrixClientPeg.safeGet();
// users with room membership
const members = Object.values(buildMemberScores(client)).map(({ member }) => member.userId);
// users with dm membership
const roomMembers = Object.keys(DMRoomMap.shared().getUniqueRoomsWithIndividuals());
roomMembers.forEach((id) => members.push(id));
// filter duplicates and user IDs from external servers
const externals = new Set(members.filter((id) => !id.includes(homeserver)));
externals.forEach((id) => excludedTargetIds.add(id));
}

public static buildRecents(excludedTargetIds: Set<string>): Result[] {
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room

Expand Down
2 changes: 2 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,8 @@
"error_permissions_space": "You do not have permission to invite people to this space.",
"error_profile_undisclosed": "User may or may not exist",
"error_transfer_multiple_target": "A call can only be transferred to a single user.",
"error_unfederated_room": "This room is unfederated. You cannot invite people from external servers.",
"error_unfederated_space": "This space is unfederated. You cannot invite people from external servers.",
"error_unknown": "Unknown server error",
"error_user_not_found": "User does not exist",
"error_version_unsupported_room": "The user's homeserver does not support the version of the room.",
Expand Down
16 changes: 13 additions & 3 deletions src/utils/MultiInviter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,16 +246,26 @@ export default class MultiInviter {

logger.error(err);

const isSpace = this.roomId && this.matrixClient.getRoom(this.roomId)?.isSpaceRoom();
const room = this.roomId ? this.matrixClient.getRoom(this.roomId) : null;
const isSpace = room?.isSpaceRoom();
const isFederated = room?.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()[
"m.federate"
];

let errorText: string | undefined;
let fatal = false;
switch (err.errcode) {
case "M_FORBIDDEN":
if (isSpace) {
errorText = _t("invite|error_permissions_space");
errorText =
isFederated === false
? _t("invite|error_unfederated_space")
: _t("invite|error_permissions_space");
} else {
errorText = _t("invite|error_permissions_room");
errorText =
isFederated === false
? _t("invite|error_unfederated_room")
: _t("invite|error_permissions_room");
}
fatal = true;
break;
Expand Down