Move all remaining stores to SQLCipher

This commit is contained in:
Scott Nonnenberg 2018-10-17 18:01:21 -07:00
parent 7aa9031c7f
commit 1755e0adfd
25 changed files with 2464 additions and 2047 deletions

View file

@ -1,10 +1,19 @@
/* global window, setTimeout */
/* global window, setTimeout, IDBKeyRange */
const electron = require('electron');
const { forEach, isFunction, isObject, merge } = require('lodash');
const {
cloneDeep,
forEach,
get,
isFunction,
isObject,
map,
merge,
set,
} = require('lodash');
const { deferredToPromise } = require('./deferred_to_promise');
const { base64ToArrayBuffer, arrayBufferToBase64 } = require('./crypto');
const MessageType = require('./types/message');
const { ipcRenderer } = electron;
@ -13,11 +22,6 @@ const { ipcRenderer } = electron;
// any warnings that might be sent to the console in that case.
ipcRenderer.setMaxListeners(0);
// calls to search for when finding functions to convert:
// .fetch(
// .save(
// .destroy(
const DATABASE_UPDATE_TIMEOUT = 2 * 60 * 1000; // two minutes
const SQL_CHANNEL_KEY = 'sql-channel';
@ -38,6 +42,47 @@ module.exports = {
close,
removeDB,
createOrUpdateGroup,
getGroupById,
getAllGroupIds,
bulkAddGroups,
removeGroupById,
removeAllGroups,
createOrUpdateIdentityKey,
getIdentityKeyById,
bulkAddIdentityKeys,
removeIdentityKeyById,
removeAllIdentityKeys,
createOrUpdatePreKey,
getPreKeyById,
bulkAddPreKeys,
removePreKeyById,
removeAllPreKeys,
createOrUpdateSignedPreKey,
getSignedPreKeyById,
getAllSignedPreKeys,
bulkAddSignedPreKeys,
removeSignedPreKeyById,
removeAllSignedPreKeys,
createOrUpdateItem,
getItemById,
getAllItems,
bulkAddItems,
removeItemById,
removeAllItems,
createOrUpdateSession,
getSessionById,
getSessionsByNumber,
bulkAddSessions,
removeSessionById,
removeSessionsByNumber,
removeAllSessions,
getConversationCount,
saveConversation,
saveConversations,
@ -81,6 +126,8 @@ module.exports = {
removeAllUnprocessed,
removeAll,
removeAllConfiguration,
removeOtherData,
cleanupOrphanedAttachments,
@ -229,6 +276,36 @@ forEach(module.exports, fn => {
}
});
function keysToArrayBuffer(keys, data) {
const updated = cloneDeep(data);
for (let i = 0, max = keys.length; i < max; i += 1) {
const key = keys[i];
const value = get(data, key);
if (value) {
set(updated, key, base64ToArrayBuffer(value));
}
}
return updated;
}
function keysFromArrayBuffer(keys, data) {
const updated = cloneDeep(data);
for (let i = 0, max = keys.length; i < max; i += 1) {
const key = keys[i];
const value = get(data, key);
if (value) {
set(updated, key, arrayBufferToBase64(value));
}
}
return updated;
}
// Top-level calls
// Note: will need to restart the app after calling this, to set up afresh
async function close() {
await channels.close();
@ -239,6 +316,182 @@ async function removeDB() {
await channels.removeDB();
}
// Groups
async function createOrUpdateGroup(data) {
await channels.createOrUpdateGroup(data);
}
async function getGroupById(id) {
const group = await channels.getGroupById(id);
return group;
}
async function getAllGroupIds() {
const ids = await channels.getAllGroupIds();
return ids;
}
async function bulkAddGroups(array) {
await channels.bulkAddGroups(array);
}
async function removeGroupById(id) {
await channels.removeGroupById(id);
}
async function removeAllGroups() {
await channels.removeAllGroups();
}
// Identity Keys
const IDENTITY_KEY_KEYS = ['publicKey'];
async function createOrUpdateIdentityKey(data) {
const updated = keysFromArrayBuffer(IDENTITY_KEY_KEYS, data);
await channels.createOrUpdateIdentityKey(updated);
}
async function getIdentityKeyById(id) {
const data = await channels.getIdentityKeyById(id);
return keysToArrayBuffer(IDENTITY_KEY_KEYS, data);
}
async function bulkAddIdentityKeys(array) {
const updated = map(array, data =>
keysFromArrayBuffer(IDENTITY_KEY_KEYS, data)
);
await channels.bulkAddIdentityKeys(updated);
}
async function removeIdentityKeyById(id) {
await channels.removeIdentityKeyById(id);
}
async function removeAllIdentityKeys() {
await channels.removeAllIdentityKeys();
}
// Pre Keys
async function createOrUpdatePreKey(data) {
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
await channels.createOrUpdatePreKey(updated);
}
async function getPreKeyById(id) {
const data = await channels.getPreKeyById(id);
return keysToArrayBuffer(PRE_KEY_KEYS, data);
}
async function bulkAddPreKeys(array) {
const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data));
await channels.bulkAddPreKeys(updated);
}
async function removePreKeyById(id) {
await channels.removePreKeyById(id);
}
async function removeAllPreKeys() {
await channels.removeAllPreKeys();
}
// Signed Pre Keys
const PRE_KEY_KEYS = ['privateKey', 'publicKey'];
async function createOrUpdateSignedPreKey(data) {
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
await channels.createOrUpdateSignedPreKey(updated);
}
async function getSignedPreKeyById(id) {
const data = await channels.getSignedPreKeyById(id);
return keysToArrayBuffer(PRE_KEY_KEYS, data);
}
async function getAllSignedPreKeys() {
const keys = await channels.getAllSignedPreKeys();
return keys;
}
async function bulkAddSignedPreKeys(array) {
const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data));
await channels.bulkAddSignedPreKeys(updated);
}
async function removeSignedPreKeyById(id) {
await channels.removeSignedPreKeyById(id);
}
async function removeAllSignedPreKeys() {
await channels.removeAllSignedPreKeys();
}
// Items
const ITEM_KEYS = {
identityKey: ['value.pubKey', 'value.privKey'],
senderCertificate: [
'value.certificate',
'value.signature',
'value.serialized',
],
signaling_key: ['value'],
profileKey: ['value'],
};
async function createOrUpdateItem(data) {
const { id } = data;
if (!id) {
throw new Error(
'createOrUpdateItem: Provided data did not have a truthy id'
);
}
const keys = ITEM_KEYS[id];
const updated = Array.isArray(keys) ? keysFromArrayBuffer(keys, data) : data;
await channels.createOrUpdateItem(updated);
}
async function getItemById(id) {
const keys = ITEM_KEYS[id];
const data = await channels.getItemById(id);
return Array.isArray(keys) ? keysToArrayBuffer(keys, data) : data;
}
async function getAllItems() {
const items = await channels.getAllItems();
return map(items, item => {
const { id } = item;
const keys = ITEM_KEYS[id];
return Array.isArray(keys) ? keysToArrayBuffer(keys, item) : item;
});
}
async function bulkAddItems(array) {
const updated = map(array, data => {
const { id } = data;
const keys = ITEM_KEYS[id];
return Array.isArray(keys) ? keysFromArrayBuffer(keys, data) : data;
});
await channels.bulkAddItems(updated);
}
async function removeItemById(id) {
await channels.removeItemById(id);
}
async function removeAllItems() {
await channels.removeAllItems();
}
// Sessions
async function createOrUpdateSession(data) {
await channels.createOrUpdateSession(data);
}
async function getSessionById(id) {
const session = await channels.getSessionById(id);
return session;
}
async function getSessionsByNumber(number) {
const sessions = await channels.getSessionsByNumber(number);
return sessions;
}
async function bulkAddSessions(array) {
await channels.bulkAddSessions(array);
}
async function removeSessionById(id) {
await channels.removeSessionById(id);
}
async function removeSessionsByNumber(number) {
await channels.removeSessionsByNumber(number);
}
async function removeAllSessions(id) {
await channels.removeAllSessions(id);
}
// Conversation
async function getConversationCount() {
return channels.getConversationCount();
}
@ -319,6 +572,8 @@ async function searchConversations(query, { ConversationCollection }) {
return collection;
}
// Message
async function getMessageCount() {
return channels.getMessageCount();
}
@ -329,10 +584,41 @@ async function saveMessage(data, { forceSave, Message } = {}) {
return id;
}
async function saveLegacyMessage(data, { Message }) {
const message = new Message(data);
await deferredToPromise(message.save());
return message.id;
async function saveLegacyMessage(data) {
const db = await window.Whisper.Database.open();
try {
await new Promise((resolve, reject) => {
const transaction = db.transaction('messages', 'readwrite');
transaction.onerror = () => {
window.Whisper.Database.handleDOMException(
'saveLegacyMessage transaction error',
transaction.error,
reject
);
};
transaction.oncomplete = resolve;
const store = transaction.objectStore('messages');
if (!data.id) {
// eslint-disable-next-line no-param-reassign
data.id = window.getGuid();
}
const request = store.put(data, data.id);
request.onsuccess = resolve;
request.onerror = () => {
window.Whisper.Database.handleDOMException(
'saveLegacyMessage request error',
request.error,
reject
);
};
});
} finally {
db.close();
}
}
async function saveMessages(arrayOfMessages, { forceSave } = {}) {
@ -459,6 +745,8 @@ async function getNextExpiringMessage({ MessageCollection }) {
return new MessageCollection(messages);
}
// Unprocessed
async function getUnprocessedCount() {
return channels.getUnprocessedCount();
}
@ -495,10 +783,16 @@ async function removeAllUnprocessed() {
await channels.removeAllUnprocessed();
}
// Other
async function removeAll() {
await channels.removeAll();
}
async function removeAllConfiguration() {
await channels.removeAllConfiguration();
}
async function cleanupOrphanedAttachments() {
await callChannel(CLEANUP_ORPHANED_ATTACHMENTS_KEY);
}
@ -529,28 +823,61 @@ async function callChannel(name) {
});
}
// Functions below here return JSON
// Functions below here return plain JSON instead of Backbone Models
async function getLegacyMessagesNeedingUpgrade(
limit,
{ MessageCollection, maxVersion = MessageType.CURRENT_SCHEMA_VERSION }
{ maxVersion = MessageType.CURRENT_SCHEMA_VERSION }
) {
const messages = new MessageCollection();
const db = await window.Whisper.Database.open();
try {
await new Promise((resolve, reject) => {
const transaction = db.transaction('messages', 'readonly');
const messages = [];
await deferredToPromise(
messages.fetch({
limit,
index: {
name: 'schemaVersion',
upper: maxVersion,
excludeUpper: true,
order: 'desc',
},
})
);
transaction.onerror = () => {
window.Whisper.Database.handleDOMException(
'getLegacyMessagesNeedingUpgrade transaction error',
transaction.error,
reject
);
};
transaction.oncomplete = () => {
resolve(messages);
};
const models = messages.models || [];
return models.map(model => model.toJSON());
const store = transaction.objectStore('messages');
const index = store.index('schemaVersion');
const range = IDBKeyRange.upperBound(maxVersion, true);
const request = index.openCursor(range);
let count = 0;
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
count += 1;
messages.push(cursor.value);
if (count >= limit) {
return;
}
cursor.continue();
}
};
request.onerror = () => {
window.Whisper.Database.handleDOMException(
'getLegacyMessagesNeedingUpgrade request error',
request.error,
reject
);
};
});
} finally {
db.close();
}
}
async function getMessagesNeedingUpgrade(