1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-28 05:03:59 +03:00

Add keysharing on invites to File Tree Spaces

This commit is contained in:
Travis Ralston
2021-06-17 17:36:49 -06:00
parent 303119e113
commit 39892c98f9
3 changed files with 74 additions and 9 deletions

View File

@@ -105,7 +105,7 @@ describe("MSC3089TreeSpace", () => {
return Promise.resolve(); return Promise.resolve();
}); });
client.invite = fn; client.invite = fn;
await tree.invite(target, false); await tree.invite(target, false, false);
expect(fn).toHaveBeenCalledTimes(1); expect(fn).toHaveBeenCalledTimes(1);
}); });
@@ -118,7 +118,7 @@ describe("MSC3089TreeSpace", () => {
return Promise.resolve(); return Promise.resolve();
}); });
client.invite = fn; client.invite = fn;
await tree.invite(target, false); await tree.invite(target, false, false);
expect(fn).toHaveBeenCalledTimes(2); expect(fn).toHaveBeenCalledTimes(2);
}); });
@@ -131,7 +131,7 @@ describe("MSC3089TreeSpace", () => {
}); });
client.invite = fn; client.invite = fn;
try { try {
await tree.invite(target, false); await tree.invite(target, false, false);
// noinspection ExceptionCaughtLocallyJS // noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail"); throw new Error("Failed to fail");
@@ -159,10 +159,61 @@ describe("MSC3089TreeSpace", () => {
{ invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace, { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
]; ];
await tree.invite(target, true); await tree.invite(target, true, false);
expect(fn).toHaveBeenCalledTimes(4); expect(fn).toHaveBeenCalledTimes(4);
}); });
it('should share keys with invitees', async () => {
const target = targetUser;
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
expect(inviteRoomId).toEqual(roomId);
expect(userIds).toMatchObject([target]);
return Promise.resolve();
});
client.invite = () => Promise.resolve(); // we're not testing this here - see other tests
client.sendSharedHistoryKeys = sendKeysFn;
// Mock the history check as best as possible
const historyVis = "shared";
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
// We're not expecting a super rigid test: the function that calls this internally isn't
// really being tested here.
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
expect(stateKey).toEqual("");
return {getContent: () => ({history_visibility: historyVis})}; // eslint-disable-line camelcase
});
room.currentState.getStateEvents = historyFn;
// Note: inverse test is implicit from other tests, which disable the call stack of this
// test in order to pass.
await tree.invite(target, false, true);
expect(sendKeysFn).toHaveBeenCalledTimes(1);
expect(historyFn).toHaveBeenCalledTimes(1);
});
it('should not share keys with invitees if inappropriate history visibility', async () => {
const target = targetUser;
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
expect(inviteRoomId).toEqual(roomId);
expect(userIds).toMatchObject([target]);
return Promise.resolve();
});
client.invite = () => Promise.resolve(); // we're not testing this here - see other tests
client.sendSharedHistoryKeys = sendKeysFn;
const historyVis = "joined"; // NOTE: Changed.
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
expect(stateKey).toEqual("");
return {getContent: () => ({history_visibility: historyVis})}; // eslint-disable-line camelcase
});
room.currentState.getStateEvents = historyFn;
await tree.invite(target, false, true);
expect(sendKeysFn).toHaveBeenCalledTimes(0);
expect(historyFn).toHaveBeenCalledTimes(1);
});
async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) { async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) {
makePowerLevels(pls); makePowerLevels(pls);
const fn = jest.fn() const fn = jest.fn()

View File

@@ -37,7 +37,7 @@ import {
import { WITHHELD_MESSAGES } from '../OlmDevice'; import { WITHHELD_MESSAGES } from '../OlmDevice';
// determine whether the key can be shared with invitees // determine whether the key can be shared with invitees
function isRoomSharedHistory(room) { export function isRoomSharedHistory(room) {
const visibilityEvent = room.currentState && const visibilityEvent = room.currentState &&
room.currentState.getStateEvents("m.room.history_visibility", ""); room.currentState.getStateEvents("m.room.history_visibility", "");
// NOTE: if the room visibility is unset, it would normally default to // NOTE: if the room visibility is unset, it would normally default to

View File

@@ -29,6 +29,7 @@ import {
} from "../utils"; } from "../utils";
import { MSC3089Branch } from "./MSC3089Branch"; import { MSC3089Branch } from "./MSC3089Branch";
import promiseRetry from "p-retry"; import promiseRetry from "p-retry";
import { isRoomSharedHistory } from "../crypto/algorithms/megolm";
/** /**
* The recommended defaults for a tree space's power levels. Note that this * The recommended defaults for a tree space's power levels. Note that this
@@ -120,15 +121,28 @@ export class MSC3089TreeSpace {
* @param {string} userId The user ID to invite. * @param {string} userId The user ID to invite.
* @param {boolean} andSubspaces True (default) to invite the user to all * @param {boolean} andSubspaces True (default) to invite the user to all
* directories/subspaces too, recursively. * directories/subspaces too, recursively.
* @param {boolean} shareHistoryKeys True (default) to share encryption keys
* with the invited user. This will allow them to decrypt the events (files)
* in the tree. Keys will not be shared if the room is lacking appropriate
* history visibility (by default, history visibility is "shared" in trees,
* which is an appropriate visibility for these purposes).
* @returns {Promise<void>} Resolves when complete. * @returns {Promise<void>} Resolves when complete.
*/ */
public invite(userId: string, andSubspaces = true): Promise<void> { public invite(userId: string, andSubspaces = true, shareHistoryKeys = true): Promise<void> {
// TODO: [@@TR] Share keys
const promises: Promise<void>[] = [this.retryInvite(userId)]; const promises: Promise<void>[] = [this.retryInvite(userId)];
if (andSubspaces) { if (andSubspaces) {
promises.push(...this.getDirectories().map(d => d.invite(userId, andSubspaces))); promises.push(...this.getDirectories().map(d => d.invite(userId, andSubspaces, shareHistoryKeys)));
} }
return Promise.all(promises).then(); // .then() to coerce types return Promise.all(promises).then(() => {
// Note: key sharing is default on because for file trees it is relatively important that the invite
// target can actually decrypt the files. The implied use case is that by inviting a user to the tree
// it means the sender would like the receiver to view/download the files contained within, much like
// sharing a folder in other circles.
if (shareHistoryKeys && isRoomSharedHistory(this.room)) {
// noinspection JSIgnoredPromiseFromCall - we aren't concerned as much if this fails.
this.client.sendSharedHistoryKeys(this.roomId, [userId]);
}
});
} }
private retryInvite(userId: string): Promise<void> { private retryInvite(userId: string): Promise<void> {