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

Share historical room keys when inviting #5711

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
125 changes: 118 additions & 7 deletions src/components/views/dialogs/InviteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ import {UIFeature} from "../../../settings/UIFeature";
import CountlyAnalytics from "../../../CountlyAnalytics";
import {Room} from "matrix-js-sdk/src/models/room";
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import {EventTimeline} from "matrix-js-sdk/src/models/event-timeline";
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {getAddressType} from "../../../UserAddress";
import {sleep} from "../../../utils/promise";

// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */
Expand All @@ -53,6 +57,70 @@ export const KIND_CALL_TRANSFER = "call_transfer";
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked

/**
* Iterate backwards through the message history, returning messages that are
* visible to all members of the room (`m.room.history_visibility` is
* `world_readable` or `shared`), until the most recent message that is not.
*
* This function is intended to be used with MatrixClient.shareKeysForMessages.
*/
function iterateShareableHistoryForRoom(client, room) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this function make more sense in the JS SDK...? It doesn't really seem to have a UI component at least...

let timeline = room.getLiveTimeline();
const visibilityEvent = timeline.getState(EventTimeline.FORWARDS)
.getStateEvents("m.room.history_visibility", "");
let visibility = visibilityEvent && visibilityEvent.getContent() &&
visibilityEvent.getContent().history_visibility;
let events = timeline.getEvents();
let index = events.length;
let paginationToken;
const next = async () => {
if (index === 0) {
// get prev chunk
if (!paginationToken) {
const prevTimeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS);
if (prevTimeline) {
timeline = prevTimeline;
events = timeline.getEvents();
} else {
paginationToken = timeline.getPaginationToken(EventTimeline.BACKWARDS);
timeline = undefined;
}
}
if (!timeline && !paginationToken) {
return;
} else if (paginationToken) {
await sleep(1000);
const res = await client._createMessagesRequest(
room.roomId, paginationToken, 30, "b",
);
if (res.end === paginationToken || res.chunk.length === 0) {
// no new messages
paginationToken = undefined;
return;
} else {
paginationToken = res.end;
events = res.chunk.reverse().map(e => new MatrixEvent(e));
}
}
index = events.length;
}
index--;
const event = events[index];
if (event.isState()) {
if (event.getType() === "m.room.history_visibility") {
visibility = event.getPrevContent() &&
event.getPrevContent().history_visibility;
}
return next();
}
if (visibility !== "world_readable" && visibility !== "shared") {
return;
}
return event;
};
return next;
}

// This is the interface that is expected by various components in this file. It is a bit
// awkward because it also matches the RoomMember class from the js-sdk with some extra support
// for 3PIDs/email addresses.
Expand Down Expand Up @@ -676,14 +744,15 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
});
};

_inviteUsers = () => {
_inviteUsers = async () => {
const startTime = CountlyAnalytics.getTimestamp();
this.setState({busy: true});
this._convertFilter();
const targets = this._convertFilter();
const targetIds = targets.map(t => t.userId);

const room = MatrixClientPeg.get().getRoom(this.props.roomId);
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.roomId);
if (!room) {
console.error("Failed to find the room to invite users to");
this.setState({
Expand All @@ -693,20 +762,43 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return;
}

inviteMultipleToRoom(this.props.roomId, targetIds).then(result => {
try {
const res = await inviteMultipleToRoom(this.props.roomId, targetIds)
CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
if (!this._shouldAbortAfterInviteError(result)) { // handles setting error message too
if (!this._shouldAbortAfterInviteError(res)) { // handles setting error message too
this.props.onFinished();
}
}).catch(err => {

if (cli.isRoomEncrypted(this.props.roomId) &&
SettingsStore.getValue("feature_room_history_key_sharing")) {
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(res.states)) {
if (state === "invited" && getAddressType(addr) === "mx-user-id") {
invitedUsers.push(addr);
}
}
console.log("Sharing history with", invitedUsers);
cli.shareKeysForMessages(
this.props.roomId, invitedUsers,
iterateShareableHistoryForRoom(cli, room),
);
}
}
} catch (err) {
console.error(err);
this.setState({
busy: false,
errorText: _t(
"We couldn't invite those users. Please check the users you want to invite and try again.",
),
});
});
}
};

_transferCall = async () => {
Expand Down Expand Up @@ -1187,10 +1279,12 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
let helpText;
let buttonText;
let goButtonFn;
let keySharingWarning = <span />;

const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer);

const userId = MatrixClientPeg.get().getUserId();
const cli = MatrixClientPeg.get();
const userId = cli.getUserId();
if (this.props.kind === KIND_DM) {
title = _t("Direct Messages");

Expand Down Expand Up @@ -1281,6 +1375,22 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps

buttonText = _t("Invite");
goButtonFn = this._inviteUsers;

if (SettingsStore.getValue("feature_room_history_key_sharing") &&
cli.isRoomEncrypted(this.props.roomId)) {
const room = cli.getRoom(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") {
keySharingWarning =
<div>
{_t("Note: Decryption keys for old messages will be shared with invited users.")}
</div>;
}
}
} else if (this.props.kind === KIND_CALL_TRANSFER) {
title = _t("Transfer");
buttonText = _t("Transfer");
Expand Down Expand Up @@ -1314,6 +1424,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
{spinner}
</div>
</div>
{keySharingWarning}
{this._renderIdentityServerWarning()}
<div className='error'>{this.state.errorText}</div>
<div className='mx_InviteDialog_userSections'>
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 @@ -791,6 +791,7 @@
"Show message previews for reactions in DMs": "Show message previews for reactions in DMs",
"Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms",
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
"Share decryption keys for room history when inviting users": "Share decryption keys for room history when inviting users",
"Enable advanced debugging for the room list": "Enable advanced debugging for the room list",
"Show info about bridges in room settings": "Show info about bridges in room settings",
"Font size": "Font size",
Expand Down Expand Up @@ -2153,6 +2154,7 @@
"Go": "Go",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.",
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
"Note: Decryption keys for old messages will be shared with invited users.": "Note: Decryption keys for old messages will be shared with invited users.",
"Transfer": "Transfer",
"a new master key signature": "a new master key signature",
"a new cross-signing key signature": "a new cross-signing key signature",
Expand Down
6 changes: 6 additions & 0 deletions src/settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
"feature_room_history_key_sharing": {
isFeature: true,
displayName: _td("Share decryption keys for room history when inviting users"),
supportedLevels: LEVELS_FEATURE,
default: false,
},
"advancedRoomListLogging": {
// TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231
displayName: _td("Enable advanced debugging for the room list"),
Expand Down