You've already forked matrix-js-sdk
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:
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
Reference in New Issue
Block a user