From cde935629d6598f5cc9763cfd1f5dfff87c06975 Mon Sep 17 00:00:00 2001 From: CommanderRoot Date: Thu, 14 Apr 2022 23:23:27 +0200 Subject: [PATCH 1/4] Replace deprecated String.prototype.substr() (#2298) .substr() is deprecated so we replace it with .slice() which works similarily but isn't deprecated Signed-off-by: Tobias Speicher --- examples/node/app.js | 4 ++-- src/content-repo.ts | 4 ++-- src/crypto/store/localStorage-crypto-store.ts | 10 +++++----- src/crypto/store/memory-crypto-store.ts | 8 ++++---- src/filter-component.ts | 2 +- src/store/session/webstorage.js | 10 +++++----- src/utils.ts | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/node/app.js b/examples/node/app.js index 90396b56bbd..ae7eb707690 100644 --- a/examples/node/app.js +++ b/examples/node/app.js @@ -341,7 +341,7 @@ function printLine(event) { var maxNameWidth = 15; if (name.length > maxNameWidth) { - name = name.substr(0, maxNameWidth-1) + "\u2026"; + name = name.slice(0, maxNameWidth-1) + "\u2026"; } if (event.getType() === "m.room.message") { @@ -398,7 +398,7 @@ function print(str, formatter) { function fixWidth(str, len) { if (str.length > len) { - return str.substr(0, len-2) + "\u2026"; + return str.substring(0, len-2) + "\u2026"; } else if (str.length < len) { return str + new Array(len - str.length).join(" "); diff --git a/src/content-repo.ts b/src/content-repo.ts index febd0d1c960..333126a008d 100644 --- a/src/content-repo.ts +++ b/src/content-repo.ts @@ -73,8 +73,8 @@ export function getHttpUriForMxc( const fragmentOffset = serverAndMediaId.indexOf("#"); let fragment = ""; if (fragmentOffset >= 0) { - fragment = serverAndMediaId.substr(fragmentOffset); - serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset); + fragment = serverAndMediaId.slice(fragmentOffset); + serverAndMediaId = serverAndMediaId.slice(0, fragmentOffset); } const urlParams = (Object.keys(params).length === 0 ? "" : ("?" + utils.encodeParams(params))); diff --git a/src/crypto/store/localStorage-crypto-store.ts b/src/crypto/store/localStorage-crypto-store.ts index 599e74cb9b2..e9e0f99ca64 100644 --- a/src/crypto/store/localStorage-crypto-store.ts +++ b/src/crypto/store/localStorage-crypto-store.ts @@ -228,8 +228,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { // (hence 43 characters long). func({ - senderKey: key.substr(KEY_INBOUND_SESSION_PREFIX.length, 43), - sessionId: key.substr(KEY_INBOUND_SESSION_PREFIX.length + 44), + senderKey: key.slice(KEY_INBOUND_SESSION_PREFIX.length, KEY_INBOUND_SESSION_PREFIX.length + 43), + sessionId: key.slice(KEY_INBOUND_SESSION_PREFIX.length + 44), sessionData: getJsonItem(this.store, key), }); } @@ -299,7 +299,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { for (let i = 0; i < this.store.length; ++i) { const key = this.store.key(i); if (key.startsWith(prefix)) { - const roomId = key.substr(prefix.length); + const roomId = key.slice(prefix.length); result[roomId] = getJsonItem(this.store, key); } } @@ -313,8 +313,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { for (const session in sessionsNeedingBackup) { if (Object.prototype.hasOwnProperty.call(sessionsNeedingBackup, session)) { // see getAllEndToEndInboundGroupSessions for the magic number explanations - const senderKey = session.substr(0, 43); - const sessionId = session.substr(44); + const senderKey = session.slice(0, 43); + const sessionId = session.slice(44); this.getEndToEndInboundGroupSession( senderKey, sessionId, null, (sessionData) => { diff --git a/src/crypto/store/memory-crypto-store.ts b/src/crypto/store/memory-crypto-store.ts index 31dedd9ec9f..2e441908e3c 100644 --- a/src/crypto/store/memory-crypto-store.ts +++ b/src/crypto/store/memory-crypto-store.ts @@ -418,8 +418,8 @@ export class MemoryCryptoStore implements CryptoStore { // (hence 43 characters long). func({ - senderKey: key.substr(0, 43), - sessionId: key.substr(44), + senderKey: key.slice(0, 43), + sessionId: key.slice(44), sessionData: this.inboundGroupSessions[key], }); } @@ -482,8 +482,8 @@ export class MemoryCryptoStore implements CryptoStore { for (const session in this.sessionsNeedingBackup) { if (this.inboundGroupSessions[session]) { sessions.push({ - senderKey: session.substr(0, 43), - sessionId: session.substr(44), + senderKey: session.slice(0, 43), + sessionId: session.slice(44), sessionData: this.inboundGroupSessions[session], }); if (limit && session.length >= limit) { diff --git a/src/filter-component.ts b/src/filter-component.ts index 18a6b53b5b6..8cfbea667f3 100644 --- a/src/filter-component.ts +++ b/src/filter-component.ts @@ -36,7 +36,7 @@ import { function matchesWildcard(actualValue: string, filterValue: string): boolean { if (filterValue.endsWith("*")) { const typePrefix = filterValue.slice(0, -1); - return actualValue.substr(0, typePrefix.length) === typePrefix; + return actualValue.slice(0, typePrefix.length) === typePrefix; } else { return actualValue === filterValue; } diff --git a/src/store/session/webstorage.js b/src/store/session/webstorage.js index 91d5f31e5dd..f11bbe20798 100644 --- a/src/store/session/webstorage.js +++ b/src/store/session/webstorage.js @@ -78,7 +78,7 @@ WebStorageSessionStore.prototype = { const devices = {}; for (let i = 0; i < this.store.length; ++i) { const key = this.store.key(i); - const userId = key.substr(prefix.length); + const userId = key.slice(prefix.length); if (key.startsWith(prefix)) devices[userId] = getJsonItem(this.store, key); } return devices; @@ -125,7 +125,7 @@ WebStorageSessionStore.prototype = { const deviceKeys = getKeysWithPrefix(this.store, keyEndToEndSessions('')); const results = {}; for (const k of deviceKeys) { - const unprefixedKey = k.substr(keyEndToEndSessions('').length); + const unprefixedKey = k.slice(keyEndToEndSessions('').length); results[unprefixedKey] = getJsonItem(this.store, k); } return results; @@ -158,8 +158,8 @@ WebStorageSessionStore.prototype = { // (hence 43 characters long). result.push({ - senderKey: key.substr(prefix.length, 43), - sessionId: key.substr(prefix.length + 44), + senderKey: key.slice(prefix.length, prefix.length + 43), + sessionId: key.slice(prefix.length + 44), }); } return result; @@ -182,7 +182,7 @@ WebStorageSessionStore.prototype = { const roomKeys = getKeysWithPrefix(this.store, keyEndToEndRoom('')); const results = {}; for (const k of roomKeys) { - const unprefixedKey = k.substr(keyEndToEndRoom('').length); + const unprefixedKey = k.slice(keyEndToEndRoom('').length); results[unprefixedKey] = getJsonItem(this.store, k); } return results; diff --git a/src/utils.ts b/src/utils.ts index dd1e4cfd53e..80929c36e5a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -427,7 +427,7 @@ export function globToRegexp(glob: string, extended?: any): string { export function ensureNoTrailingSlash(url: string): string { if (url && url.endsWith("/")) { - return url.substr(0, url.length - 1); + return url.slice(0, -1); } else { return url; } From 9f4598638d56b77182d10764292ec50b73082923 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 15 Apr 2022 10:27:12 +0100 Subject: [PATCH 2/4] Add MatrixClient.doesServerSupportLogoutDevices() for MSC2457 (#2297) --- src/client.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/client.ts b/src/client.ts index a601c96de24..f98861da227 100644 --- a/src/client.ts +++ b/src/client.ts @@ -6593,6 +6593,14 @@ export class MatrixClient extends TypedEventEmitter} true if server supports the `logout_devices` parameter + */ + public doesServerSupportLogoutDevices(): Promise { + return this.isVersionSupported("r0.6.1"); + } + /** * Get if lazy loading members is being used. * @return {boolean} Whether or not members are lazy loaded by this client From db58a66e19608683682a780c777c606af68bc205 Mon Sep 17 00:00:00 2001 From: Faye Duxovni Date: Tue, 19 Apr 2022 10:02:17 -0400 Subject: [PATCH 3/4] Add method for checking whether our other devices are cross-signed, even when this device isn't (#2288) --- spec/unit/crypto/cross-signing.spec.js | 134 +++++++++++++++++++++++++ src/client.ts | 15 +++ src/crypto/index.ts | 19 ++++ 3 files changed, 168 insertions(+) diff --git a/spec/unit/crypto/cross-signing.spec.js b/spec/unit/crypto/cross-signing.spec.js index f8639781b80..780aea80002 100644 --- a/spec/unit/crypto/cross-signing.spec.js +++ b/spec/unit/crypto/cross-signing.spec.js @@ -883,4 +883,138 @@ describe("Cross Signing", function() { expect(bobTrust3.isCrossSigningVerified()).toBeTruthy(); expect(bobTrust3.isTofu()).toBeTruthy(); }); + + it( + "should observe that our own device is cross-signed, even if this device doesn't trust the key", + async function() { + const { client: alice } = await makeTestClient( + { userId: "@alice:example.com", deviceId: "Osborne2" }, + ); + alice.uploadDeviceSigningKeys = async () => {}; + alice.uploadKeySignatures = async () => {}; + + // Generate Alice's SSK etc + const aliceMasterSigning = new global.Olm.PkSigning(); + const aliceMasterPrivkey = aliceMasterSigning.generate_seed(); + const aliceMasterPubkey = aliceMasterSigning.init_with_seed(aliceMasterPrivkey); + const aliceSigning = new global.Olm.PkSigning(); + const alicePrivkey = aliceSigning.generate_seed(); + const alicePubkey = aliceSigning.init_with_seed(alicePrivkey); + const aliceSSK = { + user_id: "@alice:example.com", + usage: ["self_signing"], + keys: { + ["ed25519:" + alicePubkey]: alicePubkey, + }, + }; + const sskSig = aliceMasterSigning.sign(anotherjson.stringify(aliceSSK)); + aliceSSK.signatures = { + "@alice:example.com": { + ["ed25519:" + aliceMasterPubkey]: sskSig, + }, + }; + + // Alice's device downloads the keys, but doesn't trust them yet + alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", { + keys: { + master: { + user_id: "@alice:example.com", + usage: ["master"], + keys: { + ["ed25519:" + aliceMasterPubkey]: aliceMasterPubkey, + }, + }, + self_signing: aliceSSK, + }, + firstUse: 1, + unsigned: {}, + }); + + // Alice has a second device that's cross-signed + const aliceCrossSignedDevice = { + user_id: "@alice:example.com", + device_id: "Dynabook", + algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"], + keys: { + "curve25519:Dynabook": "somePubkey", + "ed25519:Dynabook": "someOtherPubkey", + }, + }; + const sig = aliceSigning.sign(anotherjson.stringify(aliceCrossSignedDevice)); + aliceCrossSignedDevice.signatures = { + "@alice:example.com": { + ["ed25519:" + alicePubkey]: sig, + }, + }; + alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", { + Dynabook: aliceCrossSignedDevice, + }); + + // We don't trust the cross-signing keys yet... + expect(alice.checkDeviceTrust(aliceCrossSignedDevice.device_id).isCrossSigningVerified()).toBeFalsy(); + // ... but we do acknowledge that the device is signed by them + expect(alice.checkIfOwnDeviceCrossSigned(aliceCrossSignedDevice.device_id)).toBeTruthy(); + }, + ); + + it("should observe that our own device isn't cross-signed", async function() { + const { client: alice } = await makeTestClient( + { userId: "@alice:example.com", deviceId: "Osborne2" }, + ); + alice.uploadDeviceSigningKeys = async () => {}; + alice.uploadKeySignatures = async () => {}; + + // Generate Alice's SSK etc + const aliceMasterSigning = new global.Olm.PkSigning(); + const aliceMasterPrivkey = aliceMasterSigning.generate_seed(); + const aliceMasterPubkey = aliceMasterSigning.init_with_seed(aliceMasterPrivkey); + const aliceSigning = new global.Olm.PkSigning(); + const alicePrivkey = aliceSigning.generate_seed(); + const alicePubkey = aliceSigning.init_with_seed(alicePrivkey); + const aliceSSK = { + user_id: "@alice:example.com", + usage: ["self_signing"], + keys: { + ["ed25519:" + alicePubkey]: alicePubkey, + }, + }; + const sskSig = aliceMasterSigning.sign(anotherjson.stringify(aliceSSK)); + aliceSSK.signatures = { + "@alice:example.com": { + ["ed25519:" + aliceMasterPubkey]: sskSig, + }, + }; + + // Alice's device downloads the keys + alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", { + keys: { + master: { + user_id: "@alice:example.com", + usage: ["master"], + keys: { + ["ed25519:" + aliceMasterPubkey]: aliceMasterPubkey, + }, + }, + self_signing: aliceSSK, + }, + firstUse: 1, + unsigned: {}, + }); + + // Alice has a second device that's also not cross-signed + const aliceNotCrossSignedDevice = { + user_id: "@alice:example.com", + device_id: "Dynabook", + algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"], + keys: { + "curve25519:Dynabook": "somePubkey", + "ed25519:Dynabook": "someOtherPubkey", + }, + }; + alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", { + Dynabook: aliceNotCrossSignedDevice, + }); + + expect(alice.checkIfOwnDeviceCrossSigned(aliceNotCrossSignedDevice.device_id)).toBeFalsy(); + }); }); diff --git a/src/client.ts b/src/client.ts index f98861da227..7839fd887d2 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2072,6 +2072,21 @@ export class MatrixClient extends TypedEventEmitter Date: Tue, 19 Apr 2022 17:15:06 +0100 Subject: [PATCH 4/4] Update threads handling for replies-to-thread-responses as per MSC update (#2305) * Update threads handling for replies-to-thread-responses as per MSC update * Update tests to match new behaviour --- spec/integ/matrix-client-methods.spec.js | 4 +-- spec/unit/room.spec.ts | 34 +++++++++++------------- src/models/room.ts | 10 ------- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/spec/integ/matrix-client-methods.spec.js b/spec/integ/matrix-client-methods.spec.js index b56744cbabc..86e5fd18551 100644 --- a/spec/integ/matrix-client-methods.spec.js +++ b/spec/integ/matrix-client-methods.spec.js @@ -797,7 +797,7 @@ describe("MatrixClient", function() { ]); }); - it("sends reply to thread responses to thread timeline only", () => { + it("sends reply to thread responses to main timeline only", () => { client.clientOpts = { experimentalThreadSupport: true }; const threadRootEvent = buildEventPollStartThreadRoot(); @@ -814,12 +814,12 @@ describe("MatrixClient", function() { expect(timeline).toEqual([ threadRootEvent, + replyToThreadResponse, ]); expect(threaded).toEqual([ threadRootEvent, eventMessageInThread, - replyToThreadResponse, ]); }); }); diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index 85f4c21d572..bc1d49a05d5 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -2154,36 +2154,32 @@ describe("Room", function() { expect(room.eventShouldLiveIn(threadReaction2Redaction, events, roots).threadId).toBe(threadRoot.getId()); }); - it("reply to thread response and its relations&redactions should be only in thread timeline", () => { + it("reply to thread response and its relations&redactions should be only in main timeline", () => { const threadRoot = mkMessage(); const threadResponse1 = mkThreadResponse(threadRoot); const reply1 = mkReply(threadResponse1); - const threadReaction1 = mkReaction(reply1); - const threadReaction2 = mkReaction(reply1); - const threadReaction2Redaction = mkRedaction(reply1); + const reaction1 = mkReaction(reply1); + const reaction2 = mkReaction(reply1); + const reaction2Redaction = mkRedaction(reply1); const roots = new Set([threadRoot.getId()]); const events = [ threadRoot, threadResponse1, reply1, - threadReaction1, - threadReaction2, - threadReaction2Redaction, + reaction1, + reaction2, + reaction2Redaction, ]; - expect(room.eventShouldLiveIn(reply1, events, roots).shouldLiveInRoom).toBeFalsy(); - expect(room.eventShouldLiveIn(reply1, events, roots).shouldLiveInThread).toBeTruthy(); - expect(room.eventShouldLiveIn(reply1, events, roots).threadId).toBe(threadRoot.getId()); - expect(room.eventShouldLiveIn(threadReaction1, events, roots).shouldLiveInRoom).toBeFalsy(); - expect(room.eventShouldLiveIn(threadReaction1, events, roots).shouldLiveInThread).toBeTruthy(); - expect(room.eventShouldLiveIn(threadReaction1, events, roots).threadId).toBe(threadRoot.getId()); - expect(room.eventShouldLiveIn(threadReaction2, events, roots).shouldLiveInRoom).toBeFalsy(); - expect(room.eventShouldLiveIn(threadReaction2, events, roots).shouldLiveInThread).toBeTruthy(); - expect(room.eventShouldLiveIn(threadReaction2, events, roots).threadId).toBe(threadRoot.getId()); - expect(room.eventShouldLiveIn(threadReaction2Redaction, events, roots).shouldLiveInRoom).toBeFalsy(); - expect(room.eventShouldLiveIn(threadReaction2Redaction, events, roots).shouldLiveInThread).toBeTruthy(); - expect(room.eventShouldLiveIn(threadReaction2Redaction, events, roots).threadId).toBe(threadRoot.getId()); + expect(room.eventShouldLiveIn(reply1, events, roots).shouldLiveInRoom).toBeTruthy(); + expect(room.eventShouldLiveIn(reply1, events, roots).shouldLiveInThread).toBeFalsy(); + expect(room.eventShouldLiveIn(reaction1, events, roots).shouldLiveInRoom).toBeTruthy(); + expect(room.eventShouldLiveIn(reaction1, events, roots).shouldLiveInThread).toBeFalsy(); + expect(room.eventShouldLiveIn(reaction2, events, roots).shouldLiveInRoom).toBeTruthy(); + expect(room.eventShouldLiveIn(reaction2, events, roots).shouldLiveInThread).toBeFalsy(); + expect(room.eventShouldLiveIn(reaction2Redaction, events, roots).shouldLiveInRoom).toBeTruthy(); + expect(room.eventShouldLiveIn(reaction2Redaction, events, roots).shouldLiveInThread).toBeFalsy(); }); it("reply to reply to thread root should only be in the main timeline", () => { diff --git a/src/models/room.ts b/src/models/room.ts index a02b4f9511a..1a3480b3b26 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -1612,16 +1612,6 @@ export class Room extends TypedEventEmitter }; } - // A reply directly to a thread response is shown as part of the thread only, this is to provide a better - // experience when communicating with users using clients without full threads support - if (parentEvent?.isThreadRelation) { - return { - shouldLiveInRoom: false, - shouldLiveInThread: true, - threadId: parentEvent.threadRootId, - }; - } - // We've exhausted all scenarios, can safely assume that this event should live in the room timeline only return { shouldLiveInRoom: true,