From 888ac8f4c2c1b5c0cd2b00116a45e4cc8078de2e Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Thu, 14 Dec 2017 17:58:10 +0000 Subject: [PATCH] Electron: Started integrating encryption --- CliClient/app/app.js | 2 +- ElectronClient/app/app.js | 10 ++- ElectronClient/app/gui/SideBar.jsx | 4 +- ReactNativeClient/lib/folders-screen-utils.js | 2 +- ReactNativeClient/lib/joplin-database.js | 11 ++-- ReactNativeClient/lib/models/base-item.js | 6 +- ReactNativeClient/lib/models/folder.js | 2 +- ReactNativeClient/lib/models/tag.js | 6 +- ReactNativeClient/lib/reducer.js | 64 ++++++++++++++----- .../lib/services/DecryptionWorker.js | 15 +++++ ReactNativeClient/root.js | 2 +- 11 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 ReactNativeClient/lib/services/DecryptionWorker.js diff --git a/CliClient/app/app.js b/CliClient/app/app.js index 5683e3ef2e..e46b82b43d 100644 --- a/CliClient/app/app.js +++ b/CliClient/app/app.js @@ -353,7 +353,7 @@ class Application extends BaseApplication { this.dispatch({ type: 'TAG_UPDATE_ALL', - tags: tags, + items: tags, }); this.store().dispatch({ diff --git a/ElectronClient/app/app.js b/ElectronClient/app/app.js index a951f2c0fa..3d9577014a 100644 --- a/ElectronClient/app/app.js +++ b/ElectronClient/app/app.js @@ -5,6 +5,7 @@ const { FoldersScreenUtils } = require('lib/folders-screen-utils.js'); const { Setting } = require('lib/models/setting.js'); const { shim } = require('lib/shim.js'); const { BaseModel } = require('lib/base-model.js'); +const MasterKey = require('lib/models/MasterKey'); const { _, setLocale } = require('lib/locale.js'); const os = require('os'); const fs = require('fs-extra'); @@ -354,7 +355,14 @@ class Application extends BaseApplication { this.dispatch({ type: 'TAG_UPDATE_ALL', - tags: tags, + items: tags, + }); + + const masterKeys = await MasterKey.all(); + + this.dispatch({ + type: 'MASTERKEY_UPDATE_ALL', + items: masterKeys, }); this.store().dispatch({ diff --git a/ElectronClient/app/gui/SideBar.jsx b/ElectronClient/app/gui/SideBar.jsx index 8e9a019f5a..d72f079c64 100644 --- a/ElectronClient/app/gui/SideBar.jsx +++ b/ElectronClient/app/gui/SideBar.jsx @@ -180,6 +180,8 @@ class SideBarComponent extends React.Component { } } + const itemTitle = folder.encryption_applied ? 'Encrypted 🔑' : folder.title; + return { onDragOver(event, folder) } } @@ -189,7 +191,7 @@ class SideBarComponent extends React.Component { data-type={BaseModel.TYPE_FOLDER} onContextMenu={(event) => this.itemContextMenu(event)} key={folder.id} - style={style} onClick={() => {this.folderItem_click(folder)}}>{folder.title} + style={style} onClick={() => {this.folderItem_click(folder)}}>{itemTitle} } diff --git a/ReactNativeClient/lib/folders-screen-utils.js b/ReactNativeClient/lib/folders-screen-utils.js index 2a7f348ff8..93c5a1a571 100644 --- a/ReactNativeClient/lib/folders-screen-utils.js +++ b/ReactNativeClient/lib/folders-screen-utils.js @@ -7,7 +7,7 @@ class FoldersScreenUtils { this.dispatch({ type: 'FOLDER_UPDATE_ALL', - folders: initialFolders, + items: initialFolders, }); } diff --git a/ReactNativeClient/lib/joplin-database.js b/ReactNativeClient/lib/joplin-database.js index 62323dbc53..c61e161482 100644 --- a/ReactNativeClient/lib/joplin-database.js +++ b/ReactNativeClient/lib/joplin-database.js @@ -272,11 +272,12 @@ class JoplinDatabase extends Database { if (targetVersion == 9) { queries.push('CREATE TABLE master_keys (id TEXT PRIMARY KEY, created_time INT NOT NULL, updated_time INT NOT NULL, encryption_method INT NOT NULL, checksum TEXT NOT NULL, content TEXT NOT NULL);'); - queries.push('ALTER TABLE notes ADD COLUMN encryption_cipher_text TEXT NOT NULL DEFAULT ""'); - queries.push('ALTER TABLE folders ADD COLUMN encryption_cipher_text TEXT NOT NULL DEFAULT ""'); - queries.push('ALTER TABLE tags ADD COLUMN encryption_cipher_text TEXT NOT NULL DEFAULT ""'); - queries.push('ALTER TABLE note_tags ADD COLUMN encryption_cipher_text TEXT NOT NULL DEFAULT ""'); - queries.push('ALTER TABLE resources ADD COLUMN encryption_cipher_text TEXT NOT NULL DEFAULT ""'); + const tableNames = ['notes', 'folders', 'tags', 'note_tags', 'resources']; + for (let i = 0; i < tableNames.length; i++) { + const n = tableNames[i]; + queries.push('ALTER TABLE ' + n + ' ADD COLUMN encryption_cipher_text TEXT NOT NULL DEFAULT ""'); + queries.push('ALTER TABLE ' + n + ' ADD COLUMN encryption_applied INT NOT NULL DEFAULT 0'); + } } queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] }); diff --git a/ReactNativeClient/lib/models/base-item.js b/ReactNativeClient/lib/models/base-item.js index 2664cee669..551a1d6452 100644 --- a/ReactNativeClient/lib/models/base-item.js +++ b/ReactNativeClient/lib/models/base-item.js @@ -263,10 +263,6 @@ class BaseItem extends BaseModel { // List of keys that won't be encrypted - mostly foreign keys required to link items // with each others and timestamp required for synchronisation. const keepKeys = ['id', 'note_id', 'tag_id', 'parent_id', 'updated_time', 'type_']; - - // const keepKeys = ['id', 'title', 'note_id', 'tag_id', 'parent_id', 'body', 'updated_time', 'type_']; - // if ('title' in reducedItem) reducedItem.title = ''; - // if ('body' in reducedItem) reducedItem.body = ''; for (let n in reducedItem) { if (!reducedItem.hasOwnProperty(n)) continue; @@ -278,6 +274,7 @@ class BaseItem extends BaseModel { } } + reducedItem.encryption_applied = 1; reducedItem.encryption_cipher_text = cipherText; return ItemClass.serialize(reducedItem) @@ -293,6 +290,7 @@ class BaseItem extends BaseModel { const plainItem = await ItemClass.unserialize(plainText); plainItem.updated_time = item.updated_time; plainItem.encryption_cipher_text = ''; + plainItem.encryption_applied = 0; return ItemClass.save(plainItem, { autoTimestamp: false }); } diff --git a/ReactNativeClient/lib/models/folder.js b/ReactNativeClient/lib/models/folder.js index a7d68d915d..55f3321328 100644 --- a/ReactNativeClient/lib/models/folder.js +++ b/ReactNativeClient/lib/models/folder.js @@ -157,7 +157,7 @@ class Folder extends BaseItem { return super.save(o, options).then((folder) => { this.dispatch({ type: 'FOLDER_UPDATE_ONE', - folder: folder, + item: folder, }); return folder; }); diff --git a/ReactNativeClient/lib/models/tag.js b/ReactNativeClient/lib/models/tag.js index 8ff5f1d6b9..3a2cc7106f 100644 --- a/ReactNativeClient/lib/models/tag.js +++ b/ReactNativeClient/lib/models/tag.js @@ -70,7 +70,7 @@ class Tag extends BaseItem { this.dispatch({ type: 'TAG_UPDATE_ONE', - tag: await Tag.load(tagId), + item: await Tag.load(tagId), }); return output; @@ -84,7 +84,7 @@ class Tag extends BaseItem { this.dispatch({ type: 'TAG_UPDATE_ONE', - tag: await Tag.load(tagId), + item: await Tag.load(tagId), }); } @@ -132,7 +132,7 @@ class Tag extends BaseItem { return super.save(o, options).then((tag) => { this.dispatch({ type: 'TAG_UPDATE_ONE', - tag: tag, + item: tag, }); return tag; }); diff --git a/ReactNativeClient/lib/reducer.js b/ReactNativeClient/lib/reducer.js index bb6bcfe9c7..c7de674e06 100644 --- a/ReactNativeClient/lib/reducer.js +++ b/ReactNativeClient/lib/reducer.js @@ -8,6 +8,7 @@ const defaultState = { notesParentType: null, folders: [], tags: [], + masterKeys: [], searches: [], selectedNoteIds: [], selectedFolderId: null, @@ -29,6 +30,20 @@ const defaultState = { hasDisabledSyncItems: false, }; +function arrayHasEncryptedItems(array) { + for (let i = 0; i < array.length; i++) { + if (!!array[i].encryption_applied) return true; + } + return false +} + +function stateHasEncryptedItems(state) { + if (arrayHasEncryptedItems(state.notes)) return true; + if (arrayHasEncryptedItems(state.folders)) return true; + if (arrayHasEncryptedItems(state.tags)) return true; + return false; +} + // When deleting a note, tag or folder function handleItemDelete(state, action) { let newState = Object.assign({}, state); @@ -72,9 +87,16 @@ function handleItemDelete(state, action) { return newState; } -function updateOneTagOrFolder(state, action) { - let newItems = action.type === 'TAG_UPDATE_ONE' ? state.tags.splice(0) : state.folders.splice(0); - let item = action.type === 'TAG_UPDATE_ONE' ? action.tag : action.folder; +function updateOneItem(state, action) { + // let newItems = action.type === 'TAG_UPDATE_ONE' ? state.tags.splice(0) : state.folders.splice(0); + // let item = action.type === 'TAG_UPDATE_ONE' ? action.tag : action.folder; + let itemsKey = null; + if (action.type === 'TAG_UPDATE_ONE') itemsKey = 'tags'; + if (action.type === 'FOLDER_UPDATE_ONE') itemsKey = 'folders'; + if (action.type === 'MASTERKEY_UPDATE_ONE') itemsKey = 'masterKeys'; + + let newItems = state[itemsKey].splice(0); + let item = action.item; var found = false; for (let i = 0; i < newItems.length; i++) { @@ -90,11 +112,13 @@ function updateOneTagOrFolder(state, action) { let newState = Object.assign({}, state); - if (action.type === 'TAG_UPDATE_ONE') { - newState.tags = newItems; - } else { - newState.folders = newItems; - } + newState[itemsKey] = newItems; + + // if (action.type === 'TAG_UPDATE_ONE') { + // newState.tags = newItems; + // } else { + // newState.folders = newItems; + // } return newState; } @@ -307,14 +331,14 @@ const reducer = (state = defaultState, action) => { case 'FOLDER_UPDATE_ALL': newState = Object.assign({}, state); - newState.folders = action.folders; + newState.folders = action.items; break; case 'TAG_UPDATE_ALL': newState = Object.assign({}, state); - newState.tags = action.tags; - break; + newState.tags = action.items; + break; case 'TAG_SELECT': @@ -328,13 +352,10 @@ const reducer = (state = defaultState, action) => { break; case 'TAG_UPDATE_ONE': - - newState = updateOneTagOrFolder(state, action); - break; - case 'FOLDER_UPDATE_ONE': + case 'MASTERKEY_UPDATE_ONE': - newState = updateOneTagOrFolder(state, action); + newState = updateOneItem(state, action); break; case 'FOLDER_DELETE': @@ -342,6 +363,12 @@ const reducer = (state = defaultState, action) => { newState = handleItemDelete(state, action); break; + case 'MASTERKEY_UPDATE_ALL': + + newState = Object.assign({}, state); + newState.masterKeys = action.items; + break; + case 'SYNC_STARTED': newState = Object.assign({}, state); @@ -408,6 +435,11 @@ const reducer = (state = defaultState, action) => { throw error; } + if (action.type.indexOf('NOTE_UPDATE') === 0 || action.type.indexOf('FOLDER_UPDATE') === 0 || action.type.indexOf('TAG_UPDATE') === 0) { + newState = Object.assign({}, newState); + newState.hasEncryptedItems = stateHasEncryptedItems(newState); + } + return newState; } diff --git a/ReactNativeClient/lib/services/DecryptionWorker.js b/ReactNativeClient/lib/services/DecryptionWorker.js new file mode 100644 index 0000000000..7d54eba2d9 --- /dev/null +++ b/ReactNativeClient/lib/services/DecryptionWorker.js @@ -0,0 +1,15 @@ +class DecryptionWorker { + + constructor() { + this.state_ = 'idle'; + } + + start() { + if (this.state_ !== 'idle') return; + + this.state_ = 'started'; + } + +} + +module.exports = DecryptionWorker; \ No newline at end of file diff --git a/ReactNativeClient/root.js b/ReactNativeClient/root.js index c3c4faf02b..916286f392 100644 --- a/ReactNativeClient/root.js +++ b/ReactNativeClient/root.js @@ -349,7 +349,7 @@ async function initialize(dispatch) { dispatch({ type: 'TAG_UPDATE_ALL', - tags: tags, + items: tags, }); let folderId = Setting.value('activeFolderId');