From 9b12c228235e5f13dfe9664f00f827e094b49adf Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 12 Oct 2018 10:38:10 -0400 Subject: [PATCH] de-lint plus some minor fixes --- .eslintrc.js | 1 + spec/unit/crypto/backup.spec.js | 148 +++++++++++------- src/crypto/algorithms/megolm.js | 4 - src/crypto/index.js | 45 ++++-- .../store/indexeddb-crypto-store-backend.js | 19 +-- src/crypto/store/indexeddb-crypto-store.js | 9 +- 6 files changed, 138 insertions(+), 88 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fec2d7b5a..ae1826de5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { }, extends: ["eslint:recommended", "google"], rules: { + "indent": ["error", 4], // rules we've always adhered to or now do "max-len": ["error", { code: 90, diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index b1948c8a2..7217f3226 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -37,6 +37,8 @@ if (global.Olm) { Crypto = require('../../../lib/crypto'); } +const Olm = global.Olm; + const MatrixClient = sdk.MatrixClient; const MatrixEvent = sdk.MatrixEvent; const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; @@ -93,16 +95,21 @@ describe("MegolmBackup", function() { let sessionStore; let cryptoStore; let megolmDecryption; - beforeEach(function() { + beforeEach(async function() { + await Olm.init(); testUtils.beforeEach(this); // eslint-disable-line no-invalid-this mockCrypto = testUtils.mock(Crypto, 'Crypto'); - mockCrypto.backupKey = new global.Olm.PkEncryption(); + mockCrypto.backupKey = new Olm.PkEncryption(); mockCrypto.backupKey.set_recipient_key( "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", ); mockCrypto.backupInfo = { + algorithm: "m.megolm_backup.v1", version: 1, + auth_data: { + public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", + }, }; mockStorage = new MockStorageApi(); @@ -136,7 +143,7 @@ describe("MegolmBackup", function() { }); it('automatically calls the key back up', function() { - const groupSession = new global.Olm.OutboundGroupSession(); + const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); // construct a fake decrypted key event via the use of a mocked @@ -161,8 +168,9 @@ describe("MegolmBackup", function() { mockCrypto.decryptEvent = function() { return Promise.resolve(decryptedData); }; + mockCrypto.cancelRoomKeyRequest = function() {}; - mockBaseApis.sendKeyBackup = expect.createSpy(); + mockCrypto.backupGroupSession = expect.createSpy(); return event.attemptDecryption(mockCrypto).then(() => { return megolmDecryption.onRoomKeyEvent(event); @@ -171,23 +179,23 @@ describe("MegolmBackup", function() { }); }); - it('sends backups to the server', function () { - this.timeout(12000); - const groupSession = new global.Olm.OutboundGroupSession(); + it('sends backups to the server', function() { + this.timeout(12000); // eslint-disable-line no-invalid-this + const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); - const ibGroupSession = new global.Olm.InboundGroupSession(); + const ibGroupSession = new Olm.InboundGroupSession(); ibGroupSession.create(groupSession.session_key()); const scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); const store = [ "getRoom", "getRooms", "getUser", "getSyncToken", "scrollback", "save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter", "getSyncAccumulator", "startup", "deleteAllData", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); @@ -216,32 +224,37 @@ describe("MegolmBackup", function() { return client.initCrypto() .then(() => { - return cryptoStore.doTxn("readwrite", [cryptoStore.STORE_SESSION], (txn) => { - cryptoStore.addEndToEndInboundGroupSession( - "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", - groupSession.session_id(), - { - forwardingCurve25519KeyChain: undefined, - keysClaimed: { - ed25519: "SENDER_ED25519", + return cryptoStore.doTxn( + "readwrite", + [cryptoStore.STORE_SESSION], + (txn) => { + cryptoStore.addEndToEndInboundGroupSession( + "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", + groupSession.session_id(), + { + forwardingCurve25519KeyChain: undefined, + keysClaimed: { + ed25519: "SENDER_ED25519", + }, + room_id: ROOM_ID, + session: ibGroupSession.pickle(olmDevice._pickleKey), }, - room_id: ROOM_ID, - session: ibGroupSession.pickle(olmDevice._pickleKey), - }, - txn); - }); + txn); + }); }) .then(() => { client.enableKeyBackup({ - algorithm: "foobar", + algorithm: "m.megolm_backup.v1", version: 1, auth_data: { - public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK" + public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", }, }); let numCalls = 0; return new Promise((resolve, reject) => { - client._http.authedRequest = function(callback, method, path, queryParams, data, opts) { + client._http.authedRequest = function( + callback, method, path, queryParams, data, opts, + ) { expect(++numCalls <= 1); if (numCalls >= 2) { // exit out of retry loop if there's something wrong @@ -252,11 +265,19 @@ describe("MegolmBackup", function() { expect(path).toBe("/room_keys/keys"); expect(queryParams.version).toBe(1); expect(data.rooms[ROOM_ID].sessions).toExist(); - expect(data.rooms[ROOM_ID].sessions).toIncludeKey(groupSession.session_id()); + expect(data.rooms[ROOM_ID].sessions).toIncludeKey( + groupSession.session_id(), + ); resolve(); return Promise.resolve({}); }; - client._crypto.backupGroupSession("roomId", "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", [], groupSession.session_id(), groupSession.session_key()); + client._crypto.backupGroupSession( + "roomId", + "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", + [], + groupSession.session_id(), + groupSession.session_key(), + ); }) .then(() => { expect(numCalls).toBe(1); @@ -264,23 +285,23 @@ describe("MegolmBackup", function() { }); }); - it('retries when a backup fails', function () { - this.timeout(12000); - const groupSession = new global.Olm.OutboundGroupSession(); + it('retries when a backup fails', function() { + this.timeout(12000); // eslint-disable-line no-invalid-this + const groupSession = new Olm.OutboundGroupSession(); groupSession.create(); - const ibGroupSession = new global.Olm.InboundGroupSession(); + const ibGroupSession = new Olm.InboundGroupSession(); ibGroupSession.create(groupSession.session_key()); const scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); const store = [ "getRoom", "getRooms", "getUser", "getSyncToken", "scrollback", "save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter", "getSyncAccumulator", "startup", "deleteAllData", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); @@ -309,32 +330,37 @@ describe("MegolmBackup", function() { return client.initCrypto() .then(() => { - return cryptoStore.doTxn("readwrite", [cryptoStore.STORE_SESSION], (txn) => { - cryptoStore.addEndToEndInboundGroupSession( - "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", - groupSession.session_id(), - { - forwardingCurve25519KeyChain: undefined, - keysClaimed: { - ed25519: "SENDER_ED25519", + return cryptoStore.doTxn( + "readwrite", + [cryptoStore.STORE_SESSION], + (txn) => { + cryptoStore.addEndToEndInboundGroupSession( + "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", + groupSession.session_id(), + { + forwardingCurve25519KeyChain: undefined, + keysClaimed: { + ed25519: "SENDER_ED25519", + }, + room_id: ROOM_ID, + session: ibGroupSession.pickle(olmDevice._pickleKey), }, - room_id: ROOM_ID, - session: ibGroupSession.pickle(olmDevice._pickleKey), - }, - txn); - }); + txn); + }); }) .then(() => { client.enableKeyBackup({ algorithm: "foobar", version: 1, auth_data: { - public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK" + public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo", }, }); let numCalls = 0; return new Promise((resolve, reject) => { - client._http.authedRequest = function(callback, method, path, queryParams, data, opts) { + client._http.authedRequest = function( + callback, method, path, queryParams, data, opts, + ) { expect(++numCalls <= 2); if (numCalls >= 3) { // exit out of retry loop if there's something wrong @@ -345,15 +371,25 @@ describe("MegolmBackup", function() { expect(path).toBe("/room_keys/keys"); expect(queryParams.version).toBe(1); expect(data.rooms[ROOM_ID].sessions).toExist(); - expect(data.rooms[ROOM_ID].sessions).toIncludeKey(groupSession.session_id()); + expect(data.rooms[ROOM_ID].sessions).toIncludeKey( + groupSession.session_id(), + ); if (numCalls > 1) { resolve(); return Promise.resolve({}); } else { - return Promise.reject(new Error("this is an expected failure")); + return Promise.reject( + new Error("this is an expected failure"), + ); } }; - client._crypto.backupGroupSession("roomId", "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", [], groupSession.session_id(), groupSession.session_key()); + client._crypto.backupGroupSession( + "roomId", + "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", + [], + groupSession.session_id(), + groupSession.session_key(), + ); }) .then(() => { expect(numCalls).toBe(2); @@ -369,13 +405,13 @@ describe("MegolmBackup", function() { const scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); const store = [ "getRoom", "getRooms", "getUser", "getSyncToken", "scrollback", "save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter", "getSyncAccumulator", "startup", "deleteAllData", - ].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {}); + ].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {}); store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); @@ -411,7 +447,7 @@ describe("MegolmBackup", function() { }; return client.restoreKeyBackups( "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" - + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", + + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", ROOM_ID, SESSION_ID, ).then(() => { @@ -435,7 +471,7 @@ describe("MegolmBackup", function() { }; return client.restoreKeyBackups( "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" - + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", + + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", ).then(() => { return megolmDecryption.decryptEvent(ENCRYPTED_EVENT); }).then((res) => { diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index 1e1de101b..af311e16b 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -849,10 +849,6 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { this._retryDecryption(senderKey, sessionId); }).then(() => { if (this._crypto.backupInfo) { - // XXX: No retries on this at all: if this request dies for whatever - // reason, this key will never be uploaded. - // More XXX: If this fails it'll cause the message send to fail, - // and this will happen if the backup is deleted from another client. return this._crypto.backupGroupSession( content.room_id, senderKey, forwardingKeyChain, content.session_id, content.session_key, keysClaimed, diff --git a/src/crypto/index.js b/src/crypto/index.js index 0ca0d8506..d45caabe3 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -991,33 +991,41 @@ Crypto.prototype._maybeSendKeyBackup = async function() { } const data = {}; for (const session of sessions) { - const room_id = session.sessionData.room_id; - if (data[room_id] === undefined) - data[room_id] = {sessions: {}}; + const roomId = session.sessionData.room_id; + if (data[roomId] === undefined) { + data[roomId] = {sessions: {}}; + } - const sessionData = await this._olmDevice.exportInboundGroupSession(session.senderKey, session.sessionId, session.sessionData); + const sessionData = await this._olmDevice.exportInboundGroupSession( + session.senderKey, session.sessionId, session.sessionData, + ); sessionData.algorithm = olmlib.MEGOLM_ALGORITHM; delete sessionData.session_id; delete sessionData.room_id; const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData)); - data[room_id]['sessions'][session.sessionId] = { + data[roomId]['sessions'][session.sessionId] = { first_message_index: 1, // FIXME - forwarded_count: (sessionData.forwardingCurve25519KeyChain || []).length, + forwarded_count: + (sessionData.forwardingCurve25519KeyChain || []).length, is_verified: false, // FIXME: how do we determine this? session_data: encrypted, }; } try { - await this._baseApis.sendKeyBackup(undefined, undefined, this.backupInfo.version, {rooms: data}); + await this._baseApis.sendKeyBackup( + undefined, undefined, this.backupInfo.version, + {rooms: data}, + ); numFailures = 0; await this._cryptoStore.unmarkSessionsNeedingBackup(sessions); - } - catch (err) { + } catch (err) { numFailures++; console.log("send failed", err); - if (err.httpStatus === 400 || err.httpStatus === 403 || err.httpStatus === 401) { + if (err.httpStatus === 400 + || err.httpStatus === 403 + || err.httpStatus === 401) { // retrying probably won't help much, so we should give up // FIXME: disable backups? return; @@ -1026,17 +1034,18 @@ Crypto.prototype._maybeSendKeyBackup = async function() { if (numFailures) { // exponential backoff if we have failures await new Promise((resolve, reject) => { - setTimeout(resolve, 1000 * Math.pow(2, Math.min(numFailures - 1, 4))); + setTimeout( + resolve, + 1000 * Math.pow(2, Math.min(numFailures - 1, 4)), + ); }); } } - } - finally - { + } finally { this._sendingBackups = false; } } -} +}; Crypto.prototype.backupGroupSession = async function( roomId, senderKey, forwardingCurve25519KeyChain, @@ -1057,13 +1066,15 @@ Crypto.prototype.backupGroupSession = async function( Crypto.prototype.backupAllGroupSessions = async function(version) { await this._cryptoStore.doTxn( - 'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], (txn) => { + 'readwrite', + [IndexedDBCryptoStore.STORE_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], + (txn) => { this._cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => { if (session !== null) { this._cryptoStore.markSessionsNeedingBackup([session], txn); } }); - } + }, ); this._maybeSendKeyBackup(); diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.js index 9935dbd38..d0bb9f1b7 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.js @@ -466,31 +466,33 @@ export class Backend { return new Promise((resolve, reject) => { const sessions = []; - const txn = this._db.transaction(["sessions_needing_backup", "inbound_group_sessions"], "readonly"); + const txn = this._db.transaction( + ["sessions_needing_backup", "inbound_group_sessions"], + "readonly", + ); txn.onerror = reject; txn.oncomplete = function() { resolve(sessions); - } + }; const objectStore = txn.objectStore("sessions_needing_backup"); const sessionStore = txn.objectStore("inbound_group_sessions"); const getReq = objectStore.openCursor(); getReq.onsuccess = function() { const cursor = getReq.result; if (cursor) { - const sessionGetReq = sessionStore.get(cursor.key) + const sessionGetReq = sessionStore.get(cursor.key); sessionGetReq.onsuccess = function() { sessions.push({ senderKey: sessionGetReq.result.senderCurve25519Key, sessionId: sessionGetReq.result.sessionId, - sessionData: sessionGetReq.result.session + sessionData: sessionGetReq.result.session, }); - } - //sessions.push(cursor.value); + }; if (!limit || sessions.length < limit) { cursor.continue(); } } - } + }; }); } @@ -516,13 +518,12 @@ export class Backend { return new Promise((resolve, reject) => { const req = objectStore.put({ senderCurve25519Key: session.senderKey, - sessionId: session.sessionId + sessionId: session.sessionId, }); req.onsuccess = resolve; req.onerror = reject; }); })); - } doTxn(mode, stores, func) { diff --git a/src/crypto/store/indexeddb-crypto-store.js b/src/crypto/store/indexeddb-crypto-store.js index 0eca4c373..59d68f8fc 100644 --- a/src/crypto/store/indexeddb-crypto-store.js +++ b/src/crypto/store/indexeddb-crypto-store.js @@ -407,10 +407,13 @@ export default class IndexedDBCryptoStore { this._backendPromise.value().getEndToEndRooms(txn, func); } + // session backups + /** * Get the inbound group sessions that need to be backed up. * @param {integer} limit The maximum number of sessions to retrieve. 0 * for no limit. + * @returns {Promise} resolves to an array of inbound group sessions */ getSessionsNeedingBackup(limit) { return this._connect().then((backend) => { @@ -420,7 +423,8 @@ export default class IndexedDBCryptoStore { /** * Unmark sessions as needing to be backed up. - * @param {[object]} The sessions that need to be backed up. + * @param {[object]} sessions The sessions that need to be backed up. + * @returns {Promise} resolves when the sessions are unmarked */ unmarkSessionsNeedingBackup(sessions) { return this._connect().then((backend) => { @@ -430,8 +434,9 @@ export default class IndexedDBCryptoStore { /** * Mark sessions as needing to be backed up. - * @param {[object]} The sessions that need to be backed up. + * @param {[object]} sessions The sessions that need to be backed up. * @param {*} txn An active transaction. See doTxn(). (optional) + * @returns {Promise} resolves when the sessions are marked */ markSessionsNeedingBackup(sessions, txn) { return this._connect().then((backend) => {