Share e2ee keys when using /invite SlashCommand (#7655)

pull/21833/head
Michael Telatynski 2022-01-28 10:02:37 +00:00 committed by GitHub
parent 15276ea3b4
commit cbc671b19f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 53 additions and 35 deletions

View File

@ -43,16 +43,19 @@ export interface IInviteResult {
* *
* @param {string} roomId The ID of the room to invite to * @param {string} roomId The ID of the room to invite to
* @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids. * @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids.
* @param {boolean} sendSharedHistoryKeys whether to share e2ee keys with the invitees if applicable.
* @param {function} progressCallback optional callback, fired after each invite. * @param {function} progressCallback optional callback, fired after each invite.
* @returns {Promise} Promise * @returns {Promise} Promise
*/ */
export function inviteMultipleToRoom( export function inviteMultipleToRoom(
roomId: string, roomId: string,
addresses: string[], addresses: string[],
sendSharedHistoryKeys = false,
progressCallback?: () => void, progressCallback?: () => void,
): Promise<IInviteResult> { ): Promise<IInviteResult> {
const inviter = new MultiInviter(roomId, progressCallback); const inviter = new MultiInviter(roomId, progressCallback);
return inviter.invite(addresses).then(states => Promise.resolve({ states, inviter })); return inviter.invite(addresses, undefined, sendSharedHistoryKeys)
.then(states => Promise.resolve({ states, inviter }));
} }
export function showStartChatInviteDialog(initialText = ""): void { export function showStartChatInviteDialog(initialText = ""): void {
@ -110,8 +113,13 @@ export function isValid3pidInvite(event: MatrixEvent): boolean {
return true; return true;
} }
export function inviteUsersToRoom(roomId: string, userIds: string[], progressCallback?: () => void): Promise<void> { export function inviteUsersToRoom(
return inviteMultipleToRoom(roomId, userIds, progressCallback).then((result) => { roomId: string,
userIds: string[],
sendSharedHistoryKeys = false,
progressCallback?: () => void,
): Promise<void> {
return inviteMultipleToRoom(roomId, userIds, sendSharedHistoryKeys, progressCallback).then((result) => {
const room = MatrixClientPeg.get().getRoom(roomId); const room = MatrixClientPeg.get().getRoom(roomId);
showAnyInviteErrors(result.states, room, result.inviter); showAnyInviteErrors(result.states, room, result.inviter);
}).catch((err) => { }).catch((err) => {

View File

@ -35,7 +35,7 @@ import { linkifyAndSanitizeHtml } from './HtmlUtils';
import QuestionDialog from "./components/views/dialogs/QuestionDialog"; import QuestionDialog from "./components/views/dialogs/QuestionDialog";
import WidgetUtils from "./utils/WidgetUtils"; import WidgetUtils from "./utils/WidgetUtils";
import { textToHtmlRainbow } from "./utils/colour"; import { textToHtmlRainbow } from "./utils/colour";
import { getAddressType } from './UserAddress'; import { AddressType, getAddressType } from './UserAddress';
import { abbreviateUrl } from './utils/UrlUtils'; import { abbreviateUrl } from './utils/UrlUtils';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils'; import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
import { isPermalinkHost, parsePermalink } from "./utils/permalinks/Permalinks"; import { isPermalinkHost, parsePermalink } from "./utils/permalinks/Permalinks";
@ -500,7 +500,7 @@ export const Commands = [
// meaningful. // meaningful.
let prom = Promise.resolve(); let prom = Promise.resolve();
if ( if (
getAddressType(address) === 'email' && getAddressType(address) === AddressType.Email &&
!MatrixClientPeg.get().getIdentityServerUrl() !MatrixClientPeg.get().getIdentityServerUrl()
) { ) {
const defaultIdentityServerUrl = getDefaultIdentityServerUrl(); const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
@ -541,7 +541,7 @@ export const Commands = [
} }
const inviter = new MultiInviter(roomId); const inviter = new MultiInviter(roomId);
return success(prom.then(() => { return success(prom.then(() => {
return inviter.invite([address], reason); return inviter.invite([address], reason, true);
}).then(() => { }).then(() => {
if (inviter.getCompletionState(address) !== "invited") { if (inviter.getCompletionState(address) !== "invited") {
throw new Error(inviter.getErrorText(address)); throw new Error(inviter.getErrorText(address));

View File

@ -208,7 +208,7 @@ const CreateSpaceFromCommunityDialog: React.FC<IProps> = ({ matrixClient: cli, g
setProgress(Progress.InvitingUsers); setProgress(Progress.InvitingUsers);
const userIds = [...members, ...invitedMembers].map(m => m.userId).filter(m => m !== cli.getUserId()); const userIds = [...members, ...invitedMembers].map(m => m.userId).filter(m => m !== cli.getUserId());
await inviteUsersToRoom(roomId, userIds, () => setProgress(p => p + 1)); await inviteUsersToRoom(roomId, userIds, false, () => setProgress(p => p + 1));
// eagerly remove it from the community panel // eagerly remove it from the community panel
dis.dispatch(TagOrderActions.removeTag(cli, groupId)); dis.dispatch(TagOrderActions.removeTag(cli, groupId));

View File

@ -761,31 +761,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} }
try { try {
const result = await inviteMultipleToRoom(this.props.roomId, targetIds); const result = await inviteMultipleToRoom(this.props.roomId, targetIds, true);
CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length); CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
if (!this.shouldAbortAfterInviteError(result, room)) { // handles setting error message too if (!this.shouldAbortAfterInviteError(result, room)) { // handles setting error message too
this.props.onFinished(); this.props.onFinished();
} }
if (cli.isRoomEncrypted(this.props.roomId)) {
const visibilityEvent = room.currentState.getStateEvents(
"m.room.history_visibility", "",
);
const visibility = visibilityEvent && visibilityEvent.getContent() &&
visibilityEvent.getContent().history_visibility;
if (visibility == "world_readable" || visibility == "shared") {
const invitedUsers = [];
for (const [addr, state] of Object.entries(result.states)) {
if (state === "invited" && getAddressType(addr) === "mx-user-id") {
invitedUsers.push(addr);
}
}
logger.log("Sharing history with", invitedUsers);
cli.sendSharedHistoryKeys(
this.props.roomId, invitedUsers,
);
}
}
} catch (err) { } catch (err) {
logger.error(err); logger.error(err);
this.setState({ this.setState({

View File

@ -17,6 +17,9 @@ limitations under the License.
import { MatrixError } from "matrix-js-sdk/src/http-api"; import { MatrixError } from "matrix-js-sdk/src/http-api";
import { defer, IDeferred } from "matrix-js-sdk/src/utils"; import { defer, IDeferred } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
import { MatrixClientPeg } from '../MatrixClientPeg'; import { MatrixClientPeg } from '../MatrixClientPeg';
import { AddressType, getAddressType } from '../UserAddress'; import { AddressType, getAddressType } from '../UserAddress';
@ -49,6 +52,7 @@ const USER_ALREADY_INVITED = "IO.ELEMENT.ALREADY_INVITED";
export default class MultiInviter { export default class MultiInviter {
private readonly roomId?: string; private readonly roomId?: string;
private readonly groupId?: string; private readonly groupId?: string;
private readonly matrixClient: MatrixClient;
private canceled = false; private canceled = false;
private addresses: string[] = []; private addresses: string[] = [];
@ -71,6 +75,8 @@ export default class MultiInviter {
this.roomId = targetId; this.roomId = targetId;
this.groupId = null; this.groupId = null;
} }
this.matrixClient = MatrixClientPeg.get();
} }
public get fatal() { public get fatal() {
@ -83,9 +89,10 @@ export default class MultiInviter {
* *
* @param {array} addresses Array of addresses to invite * @param {array} addresses Array of addresses to invite
* @param {string} reason Reason for inviting (optional) * @param {string} reason Reason for inviting (optional)
* @param {boolean} sendSharedHistoryKeys whether to share e2ee keys with the invitees if applicable.
* @returns {Promise} Resolved when all invitations in the queue are complete * @returns {Promise} Resolved when all invitations in the queue are complete
*/ */
public invite(addresses, reason?: string): Promise<CompletionStates> { public invite(addresses, reason?: string, sendSharedHistoryKeys = false): Promise<CompletionStates> {
if (this.addresses.length > 0) { if (this.addresses.length > 0) {
throw new Error("Already inviting/invited"); throw new Error("Already inviting/invited");
} }
@ -104,7 +111,30 @@ export default class MultiInviter {
this.deferred = defer<CompletionStates>(); this.deferred = defer<CompletionStates>();
this.inviteMore(0); this.inviteMore(0);
return this.deferred.promise; if (!sendSharedHistoryKeys || !this.roomId || !this.matrixClient.isRoomEncrypted(this.roomId)) {
return this.deferred.promise;
}
const room = this.matrixClient.getRoom(this.roomId);
const visibilityEvent = room?.currentState.getStateEvents(EventType.RoomHistoryVisibility, "");
const visibility = visibilityEvent?.getContent().history_visibility;
if (visibility !== HistoryVisibility.WorldReadable && visibility !== HistoryVisibility.Shared) {
return this.deferred.promise;
}
return this.deferred.promise.then(async states => {
const invitedUsers = [];
for (const [addr, state] of Object.entries(states)) {
if (state === InviteState.Invited && getAddressType(addr) === AddressType.MatrixUserId) {
invitedUsers.push(addr);
}
}
logger.log("Sharing history with", invitedUsers);
await this.matrixClient.sendSharedHistoryKeys(this.roomId, invitedUsers);
return states;
});
} }
/** /**
@ -129,9 +159,9 @@ export default class MultiInviter {
const addrType = getAddressType(addr); const addrType = getAddressType(addr);
if (addrType === AddressType.Email) { if (addrType === AddressType.Email) {
return MatrixClientPeg.get().inviteByEmail(roomId, addr); return this.matrixClient.inviteByEmail(roomId, addr);
} else if (addrType === AddressType.MatrixUserId) { } else if (addrType === AddressType.MatrixUserId) {
const room = MatrixClientPeg.get().getRoom(roomId); const room = this.matrixClient.getRoom(roomId);
if (!room) throw new Error("Room not found"); if (!room) throw new Error("Room not found");
const member = room.getMember(addr); const member = room.getMember(addr);
@ -148,14 +178,14 @@ export default class MultiInviter {
} }
if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) { if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
const profile = await MatrixClientPeg.get().getProfileInfo(addr); const profile = await this.matrixClient.getProfileInfo(addr);
if (!profile) { if (!profile) {
// noinspection ExceptionCaughtLocallyJS // noinspection ExceptionCaughtLocallyJS
throw new Error("User has no profile"); throw new Error("User has no profile");
} }
} }
return MatrixClientPeg.get().invite(roomId, addr, undefined, this.reason); return this.matrixClient.invite(roomId, addr, undefined, this.reason);
} else { } else {
throw new Error('Unsupported address'); throw new Error('Unsupported address');
} }

View File

@ -117,7 +117,7 @@ export async function upgradeRoom(
if (toInvite.length > 0) { if (toInvite.length > 0) {
// Errors are handled internally to this function // Errors are handled internally to this function
await inviteUsersToRoom(newRoomId, toInvite, () => { await inviteUsersToRoom(newRoomId, toInvite, false, () => {
progress.inviteUsersProgress++; progress.inviteUsersProgress++;
progressCallback?.(progress); progressCallback?.(progress);
}); });