1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2026-01-03 23:22:30 +03:00

Merge pull request #1163 from matrix-org/bwindels/verificationaccceptedbyotherdevice

For dm-verification, also consider events sent by other devices of same user as "our" events
This commit is contained in:
Bruno Windels
2020-01-23 13:27:15 +00:00
committed by GitHub
5 changed files with 193 additions and 9 deletions

View File

@@ -24,7 +24,7 @@ const Olm = global.Olm;
jest.useFakeTimers();
describe("verification request", function() {
describe("verification request integration tests with crypto layer", function() {
if (!global.Olm) {
logger.warn('Not running device verification unit tests: libolm not present');
return;

View File

@@ -0,0 +1,177 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {VerificationRequest, READY_TYPE, START_TYPE, DONE_TYPE} from
"../../../../src/crypto/verification/request/VerificationRequest";
import {InRoomChannel} from "../../../../src/crypto/verification/request/InRoomChannel";
import {MatrixEvent} from "../../../../src/models/event";
function makeMockClient(userId, deviceId) {
let counter = 1;
let events = [];
return {
getUserId() { return userId; },
getDeviceId() { return deviceId; },
sendEvent(roomId, type, content) {
counter = counter + 1;
const eventId = `$${userId}-${deviceId}-${counter}`;
events.push(new MatrixEvent({
sender: userId,
event_id: eventId,
room_id: roomId,
type,
content,
origin_server_ts: Date.now(),
}));
return Promise.resolve({event_id: eventId});
},
popEvents() {
const e = events;
events = [];
return e;
},
};
}
const MOCK_METHOD = "mock-verify";
class MockVerifier {
constructor(channel, client, userId, deviceId, startEvent) {
this._channel = channel;
this._startEvent = startEvent;
}
get events() {
return [DONE_TYPE];
}
async start() {
if (this._startEvent) {
await this._channel.send(DONE_TYPE, {});
} else {
await this._channel.send(START_TYPE, {method: MOCK_METHOD});
}
}
async handleEvent(event) {
if (event.getType() === DONE_TYPE && !this._startEvent) {
await this._channel.send(DONE_TYPE, {});
}
}
}
function makeRemoteEcho(event) {
return new MatrixEvent(Object.assign({}, event.event, {
unsigned: {
transaction_id: "abc",
},
}));
}
async function distributeEvent(ownRequest, theirRequest, event) {
await ownRequest.channel.handleEvent(
makeRemoteEcho(event), ownRequest, true);
await theirRequest.channel.handleEvent(event, theirRequest, true);
}
describe("verification request unit tests", function() {
it("transition from UNSENT to DONE through happy path", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()),
new Map([[MOCK_METHOD, MockVerifier]]), alice);
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
new Map([[MOCK_METHOD, MockVerifier]]), bob);
expect(aliceRequest.invalid).toBe(true);
expect(bobRequest.invalid).toBe(true);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
expect(requestEvent.getType()).toBe("m.room.message");
await distributeEvent(aliceRequest, bobRequest, requestEvent);
expect(aliceRequest.requested).toBe(true);
expect(bobRequest.requested).toBe(true);
await bobRequest.accept();
const [readyEvent] = bob.popEvents();
expect(readyEvent.getType()).toBe(READY_TYPE);
await distributeEvent(bobRequest, aliceRequest, readyEvent);
expect(bobRequest.ready).toBe(true);
expect(aliceRequest.ready).toBe(true);
const verifier = aliceRequest.beginKeyVerification(MOCK_METHOD);
await verifier.start();
const [startEvent] = alice.popEvents();
expect(startEvent.getType()).toBe(START_TYPE);
await distributeEvent(aliceRequest, bobRequest, startEvent);
expect(aliceRequest.started).toBe(true);
expect(aliceRequest.verifier).toBeInstanceOf(MockVerifier);
expect(bobRequest.started).toBe(true);
expect(bobRequest.verifier).toBeInstanceOf(MockVerifier);
await bobRequest.verifier.start();
const [bobDoneEvent] = bob.popEvents();
expect(bobDoneEvent.getType()).toBe(DONE_TYPE);
await distributeEvent(bobRequest, aliceRequest, bobDoneEvent);
const [aliceDoneEvent] = alice.popEvents();
expect(aliceDoneEvent.getType()).toBe(DONE_TYPE);
await distributeEvent(aliceRequest, bobRequest, aliceDoneEvent);
expect(aliceRequest.done).toBe(true);
expect(bobRequest.done).toBe(true);
});
it("methods only contains common methods", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()),
new Map([["c", function() {}], ["a", function() {}]]), alice);
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
new Map([["c", function() {}], ["b", function() {}]]), bob);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
await distributeEvent(aliceRequest, bobRequest, requestEvent);
await bobRequest.accept();
const [readyEvent] = bob.popEvents();
await distributeEvent(bobRequest, aliceRequest, readyEvent);
expect(aliceRequest.methods).toStrictEqual(["c"]);
expect(bobRequest.methods).toStrictEqual(["c"]);
});
it("other client accepting request puts it in observeOnly mode", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob1 = makeMockClient("@bob:matrix.tld", "device1");
const bob2 = makeMockClient("@bob:matrix.tld", "device2");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob1.getUserId()), new Map(), alice);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
const bob1Request = new VerificationRequest(
new InRoomChannel(bob1, "!room"), new Map(), bob1);
const bob2Request = new VerificationRequest(
new InRoomChannel(bob2, "!room"), new Map(), bob2);
await bob1Request.channel.handleEvent(requestEvent, bob1Request, true);
await bob2Request.channel.handleEvent(requestEvent, bob2Request, true);
await bob1Request.accept();
const [readyEvent] = bob1.popEvents();
expect(bob2Request.observeOnly).toBe(false);
await bob2Request.channel.handleEvent(readyEvent, bob2Request, true);
expect(bob2Request.observeOnly).toBe(true);
});
});

View File

@@ -210,8 +210,10 @@ export class InRoomChannel {
}
const isRemoteEcho = !!event.getUnsigned().transaction_id;
const isSentByUs = event.getSender() === this._client.getUserId();
return await request.handleEvent(type, event, isLiveEvent, isRemoteEcho);
return await request.handleEvent(
type, event, isLiveEvent, isRemoteEcho, isSentByUs);
}
/**

View File

@@ -153,7 +153,7 @@ export class ToDeviceChannel {
const wasStarted = request.phase === PHASE_STARTED ||
request.phase === PHASE_READY;
await request.handleEvent(event.getType(), event, isLiveEvent, false);
await request.handleEvent(event.getType(), event, isLiveEvent, false, false);
const isStarted = request.phase === PHASE_STARTED ||
request.phase === PHASE_READY;
@@ -245,6 +245,7 @@ export class ToDeviceChannel {
remoteEchoEvent,
/*isLiveEvent=*/true,
/*isRemoteEcho=*/true,
/*isSentByUs=*/true,
);
return result;
}

View File

@@ -458,13 +458,16 @@ export class VerificationRequest extends EventEmitter {
* @param {MatrixEvent} event the event to handle. Don't call getType() on it but use the `type` parameter instead.
* @param {bool} isLiveEvent whether this is an even received through sync or not
* @param {bool} isRemoteEcho whether this is the remote echo of an event sent by the same device
* @param {bool} isSentByUs whether this event is sent by a party that can accept and/or observe the request like one of our peers.
* For InRoomChannel this means any device for the syncing user. For ToDeviceChannel, just the syncing device.
* @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
*/
async handleEvent(type, event, isLiveEvent, isRemoteEcho) {
async handleEvent(type, event, isLiveEvent, isRemoteEcho, isSentByUs) {
// if reached phase cancelled or done, ignore anything else that comes
if (!this.pending) {
return;
}
const wasObserveOnly = this._observeOnly;
this._adjustObserveOnly(event, isLiveEvent);
@@ -474,7 +477,7 @@ export class VerificationRequest extends EventEmitter {
}
}
this._addEvent(type, event, isRemoteEcho);
this._addEvent(type, event, isSentByUs);
const transitions = this._calculatePhaseTransitions();
const existingIdx = transitions.findIndex(t => t.phase === this.phase);
@@ -486,7 +489,7 @@ export class VerificationRequest extends EventEmitter {
}
// only pass events from the other side to the verifier,
// no remote echos of our own events
if (this._verifier && !isRemoteEcho) {
if (this._verifier && !isRemoteEcho && !this.observeOnly) {
if (type === CANCEL_TYPE || (this._verifier.events
&& this._verifier.events.includes(type))) {
this._verifier.handleEvent(event);
@@ -500,6 +503,8 @@ export class VerificationRequest extends EventEmitter {
this._setupTimeout(phase);
// set phase as last thing as this emits the "change" event
this._setPhase(phase);
} else if (this._observeOnly !== wasObserveOnly) {
this.emit("change");
}
}
@@ -539,7 +544,6 @@ export class VerificationRequest extends EventEmitter {
}
}
/* FIXME: https://github.com/vector-im/riot-web/issues/11765 */
const isUnexpectedRequest = type === REQUEST_TYPE && this.phase !== PHASE_UNSENT;
const isUnexpectedReady = type === READY_TYPE && this.phase !== PHASE_REQUESTED;
if (isUnexpectedRequest || isUnexpectedReady) {
@@ -570,8 +574,8 @@ export class VerificationRequest extends EventEmitter {
}
}
_addEvent(type, event, isRemoteEcho) {
if (isRemoteEcho || this._wasSentByOwnDevice(event)) {
_addEvent(type, event, isSentByUs) {
if (isSentByUs) {
this._eventsByUs.set(type, event);
} else {
this._eventsByThem.set(type, event);