From 40cd4629dbd1fc17d655afce7738e9bf8a03a50f Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 18 May 2021 17:21:06 +0100 Subject: [PATCH] Decrypt relations before applying them to target event (#1696) --- src/client.js | 22 +++++++++++++++++++++- src/crypto/index.js | 5 ++++- src/models/event-timeline-set.js | 2 +- src/models/relations.js | 32 ++++++++++++++++++++++---------- src/webrtc/callEventHandler.ts | 10 +++++++--- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/client.js b/src/client.js index 12bfc6a33..d71aa6cb5 100644 --- a/src/client.js +++ b/src/client.js @@ -5566,7 +5566,7 @@ function _PojoToMatrixEventMapper(client, options = {}) { ]); } if (decrypt) { - event.attemptDecryption(client._crypto); + client.decryptEventIfNeeded(event); } } if (!preventReEmit) { @@ -5608,6 +5608,26 @@ MatrixClient.prototype.generateClientSecret = function() { return randomString(32); }; +/** + * Attempts to decrypt an event + * @param {MatrixEvent} event The event to decrypt + * @returns {Promise} A decryption promise + * @param {object} options + * @param {bool} options.isRetry True if this is a retry (enables more logging) + * @param {bool} options.emit Emits "event.decrypted" if set to true + */ +MatrixClient.prototype.decryptEventIfNeeded = function(event, options) { + if (event.shouldAttemptDecryption()) { + event.attemptDecryption(this._crypto, options); + } + + if (event.isBeingDecrypted()) { + return event._decryptionPromise; + } else { + return Promise.resolve(); + } +}; + // MatrixClient Event JSDocs /** diff --git a/src/crypto/index.js b/src/crypto/index.js index cdcce3cd5..e418195ae 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -3322,7 +3322,10 @@ Crypto.prototype._onToDeviceEvent = function(event) { this._onKeyVerificationMessage(event); } else if (event.getContent().msgtype === "m.bad.encrypted") { this._onToDeviceBadEncrypted(event); - } else if (event.isBeingDecrypted()) { + } else if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) { + if (!event.isBeingDecrypted()) { + event.attemptDecryption(this); + } // once the event has been decrypted, try again event.once('Event.decrypted', (ev) => { this._onToDeviceEvent(ev); diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 84853d07d..784dea359 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -769,7 +769,7 @@ EventTimelineSet.prototype.aggregateRelations = function(event) { } // If the event is currently encrypted, wait until it has been decrypted. - if (event.isBeingDecrypted()) { + if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) { event.once("Event.decrypted", () => { this.aggregateRelations(event); }); diff --git a/src/models/relations.js b/src/models/relations.js index 273a823be..304c7927b 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -46,6 +46,7 @@ export class Relations extends EventEmitter { this._annotationsBySender = {}; this._sortedAnnotationsByKey = []; this._targetEvent = null; + this._room = room; } /** @@ -54,7 +55,7 @@ export class Relations extends EventEmitter { * @param {MatrixEvent} event * The new relation event to be added. */ - addEvent(event) { + async addEvent(event) { if (this._relations.has(event)) { return; } @@ -84,7 +85,8 @@ export class Relations extends EventEmitter { if (this.relationType === "m.annotation") { this._addAnnotationToAggregation(event); } else if (this.relationType === "m.replace" && this._targetEvent) { - this._targetEvent.makeReplaced(this.getLastReplacement()); + const lastReplacement = await this.getLastReplacement(); + this._targetEvent.makeReplaced(lastReplacement); } event.on("Event.beforeRedaction", this._onBeforeRedaction); @@ -98,7 +100,7 @@ export class Relations extends EventEmitter { * @param {MatrixEvent} event * The relation event to remove. */ - _removeEvent(event) { + async _removeEvent(event) { if (!this._relations.has(event)) { return; } @@ -122,7 +124,8 @@ export class Relations extends EventEmitter { if (this.relationType === "m.annotation") { this._removeAnnotationFromAggregation(event); } else if (this.relationType === "m.replace" && this._targetEvent) { - this._targetEvent.makeReplaced(this.getLastReplacement()); + const lastReplacement = await this.getLastReplacement(); + this._targetEvent.makeReplaced(lastReplacement); } this.emit("Relations.remove", event); @@ -227,7 +230,7 @@ export class Relations extends EventEmitter { * @param {MatrixEvent} redactedEvent * The original relation event that is about to be redacted. */ - _onBeforeRedaction = (redactedEvent) => { + _onBeforeRedaction = async (redactedEvent) => { if (!this._relations.has(redactedEvent)) { return; } @@ -238,7 +241,8 @@ export class Relations extends EventEmitter { // Remove the redacted annotation from aggregation by key this._removeAnnotationFromAggregation(redactedEvent); } else if (this.relationType === "m.replace" && this._targetEvent) { - this._targetEvent.makeReplaced(this.getLastReplacement()); + const lastReplacement = await this.getLastReplacement(); + this._targetEvent.makeReplaced(lastReplacement); } redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction); @@ -291,7 +295,7 @@ export class Relations extends EventEmitter { * * @return {MatrixEvent?} */ - getLastReplacement() { + async getLastReplacement() { if (this.relationType !== "m.replace") { // Aggregating on last only makes sense for this relation type return null; @@ -309,7 +313,7 @@ export class Relations extends EventEmitter { this._targetEvent.getServerAggregatedRelation("m.replace"); const minTs = replaceRelation && replaceRelation.origin_server_ts; - return this.getRelations().reduce((last, event) => { + const lastReplacement = this.getRelations().reduce((last, event) => { if (event.getSender() !== this._targetEvent.getSender()) { return last; } @@ -321,18 +325,26 @@ export class Relations extends EventEmitter { } return event; }, null); + + if (lastReplacement?.shouldAttemptDecryption()) { + await lastReplacement.attemptDecryption(this._room._client._crypto); + } else if (lastReplacement?.isBeingDecrypted()) { + await lastReplacement._decryptionPromise; + } + + return lastReplacement; } /* * @param {MatrixEvent} targetEvent the event the relations are related to. */ - setTargetEvent(event) { + async setTargetEvent(event) { if (this._targetEvent) { return; } this._targetEvent = event; if (this.relationType === "m.replace") { - const replacement = this.getLastReplacement(); + const replacement = await this.getLastReplacement(); // this is the initial update, so only call it if we already have something // to not emit Event.replaced needlessly if (replacement) { diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 9c4c38b26..44815ee63 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -55,7 +55,9 @@ export class CallEventHandler { private evaluateEventBuffer = () => { if (this.client.getSyncState() === "SYNCING") { // don't process any events until they are all decrypted - if (this.callEventBuffer.some((e) => e.isBeingDecrypted())) return; + if (this.callEventBuffer.some((e) => { + return e.isBeingDecrypted() || e.shouldAttemptDecryption() + })) return; const ignoreCallIds = new Set(); // inspect the buffer and mark all calls which have been answered @@ -87,17 +89,19 @@ export class CallEventHandler { private onEvent = (event: MatrixEvent) => { // any call events or ones that might be once they're decrypted + const isBeingDecrypted = event.isBeingDecrypted(); + const shouldAttemptDecryption = event.shouldAttemptDecryption(); if ( event.getType().indexOf("m.call.") === 0 || event.getType().indexOf("org.matrix.call.") === 0 - || event.isBeingDecrypted() + || isBeingDecrypted || shouldAttemptDecryption ) { // queue up for processing once all events from this sync have been // processed (see above). this.callEventBuffer.push(event); } - if (event.isBeingDecrypted() || event.isDecryptionFailure()) { + if (event.isDecryptionFailure() || isBeingDecrypted || shouldAttemptDecryption) { // add an event listener for once the event is decrypted. event.once("Event.decrypted", () => { if (event.getType().indexOf("m.call.") === -1) return;