1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-27 08:21:03 +02:00

All: Better handling of encrypted data on UI side and prevent modification of encrypted notes

This commit is contained in:
Laurent Cozic 2017-12-14 20:21:36 +00:00
parent 2c608bca3c
commit e9bb5bee9d
9 changed files with 108 additions and 40 deletions

View File

@ -132,7 +132,7 @@ class MainScreenComponent extends React.Component {
}
},
});
} else if (command.name === 'renameNotebook') {
} else if (command.name === 'renameFolder') {
const folder = await Folder.load(command.id);
if (!folder) return;
@ -143,7 +143,8 @@ class MainScreenComponent extends React.Component {
onClose: async (answer) => {
if (answer !== null) {
try {
await Folder.save({ id: folder.id, title: answer }, { userSideValidation: true });
folder.title = answer;
await Folder.save(folder, { fields: ['title'], userSideValidation: true });
} catch (error) {
bridge().showErrorMessageBox(error.message);
}

View File

@ -22,9 +22,9 @@ class MasterKeysScreenComponent extends React.Component {
this.setState({
masterKeys: this.props.masterKeys,
passwords: this.props.passwords ? this.props.passwords : {},
}, () => {
this.checkPasswords();
});
this.checkPasswords();
}
async checkPasswords() {

View File

@ -3,6 +3,7 @@ const React = require('react');
const { connect } = require('react-redux');
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('../theme.js');
const BaseModel = require('lib/BaseModel');
const { _ } = require('lib/locale.js');
const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
@ -56,23 +57,32 @@ class NoteListComponent extends React.Component {
const noteIds = this.props.selectedNoteIds;
if (!noteIds.length) return;
const notes = noteIds.map((id) => BaseModel.byId(this.props.notes, id));
let hasEncrypted = false;
for (let i = 0; i < notes.length; i++) {
if (!!notes[i].encryption_applied) hasEncrypted = true;
}
const menu = new Menu()
menu.append(new MenuItem({label: _('Add or remove tags'), enabled: noteIds.length === 1, click: async () => {
this.props.dispatch({
type: 'WINDOW_COMMAND',
name: 'setTags',
noteId: noteIds[0],
});
}}));
if (!hasEncrypted) {
menu.append(new MenuItem({label: _('Add or remove tags'), enabled: noteIds.length === 1, click: async () => {
this.props.dispatch({
type: 'WINDOW_COMMAND',
name: 'setTags',
noteId: noteIds[0],
});
}}));
menu.append(new MenuItem({label: _('Switch between note and to-do type'), click: async () => {
for (let i = 0; i < noteIds.length; i++) {
const note = await Note.load(noteIds[i]);
await Note.save(Note.toggleIsTodo(note));
eventManager.emit('noteTypeToggle', { noteId: note.id });
}
}}));
menu.append(new MenuItem({label: _('Switch between note and to-do type'), click: async () => {
for (let i = 0; i < noteIds.length; i++) {
const note = await Note.load(noteIds[i]);
await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
eventManager.emit('noteTypeToggle', { noteId: note.id });
}
}}));
}
menu.append(new MenuItem({label: _('Delete'), click: async () => {
const ok = bridge().showConfirmMessageBox(noteIds.length > 1 ? _('Delete notes?') : _('Delete note?'));
@ -120,7 +130,7 @@ class NoteListComponent extends React.Component {
id: item.id,
todo_completed: checked ? time.unixMs() : 0,
}
await Note.save(newNote);
await Note.save(newNote, { userSideValidation: true });
eventManager.emit('todoToggle', { noteId: item.id });
}
@ -154,7 +164,7 @@ class NoteListComponent extends React.Component {
onClick={(event) => { onTitleClick(event, item) }}
onDragStart={(event) => onDragStart(event) }
>
{item.title}
{Note.displayTitle(item)}
</a>
</div>
}

View File

@ -418,7 +418,7 @@ class NoteTextComponent extends React.Component {
const innerWidth = rootStyle.width - rootStyle.paddingLeft - rootStyle.paddingRight - borderWidth;
if (!note) {
if (!note || !!note.encryption_applied) {
const emptyDivStyle = Object.assign({
backgroundColor: 'black',
opacity: 0.1,

View File

@ -107,6 +107,11 @@ class SideBarComponent extends React.Component {
const menu = new Menu();
let item = null;
if (itemType === BaseModel.TYPE_FOLDER) {
item = BaseModel.byId(this.props.folders, itemId);
}
menu.append(new MenuItem({label: _('Delete'), click: async () => {
const ok = bridge().showConfirmMessageBox(deleteMessage);
if (!ok) return;
@ -123,11 +128,11 @@ class SideBarComponent extends React.Component {
}
}}))
if (itemType === BaseModel.TYPE_FOLDER) {
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
menu.append(new MenuItem({label: _('Rename'), click: async () => {
this.props.dispatch({
type: 'WINDOW_COMMAND',
name: 'renameNotebook',
name: 'renameFolder',
id: itemId,
});
}}))
@ -180,7 +185,7 @@ class SideBarComponent extends React.Component {
}
}
const itemTitle = folder.encryption_applied ? 'Encrypted 🔑' : folder.title;
const itemTitle = Folder.displayTitle(folder);
return <a
className="list-item"
@ -198,7 +203,7 @@ class SideBarComponent extends React.Component {
tagItem(tag, selected) {
let style = Object.assign({}, this.style().listItem);
if (selected) style = Object.assign(style, this.style().listItemSelected);
return <a className="list-item" href="#" data-id={tag.id} data-type={BaseModel.TYPE_TAG} onContextMenu={(event) => this.itemContextMenu(event)} key={tag.id} style={style} onClick={() => {this.tagItem_click(tag)}}>{tag.title}</a>
return <a className="list-item" href="#" data-id={tag.id} data-type={BaseModel.TYPE_TAG} onContextMenu={(event) => this.itemContextMenu(event)} key={tag.id} style={style} onClick={() => {this.tagItem_click(tag)}}>{Tag.displayTitle(tag)}</a>
}
searchItem(search, selected) {

View File

@ -200,18 +200,37 @@ class BaseModel {
static diffObjects(oldModel, newModel) {
let output = {};
let type = null;
const fields = this.diffObjectsFields(oldModel, newModel);
for (let i = 0; i < fields.length; i++) {
output[fields[i]] = newModel[fields[i]];
}
if ('type_' in newModel) output.type_ = newModel.type_;
return output;
// let output = {};
// let type = null;
// for (let n in newModel) {
// if (!newModel.hasOwnProperty(n)) continue;
// if (n == 'type_') {
// type = newModel[n];
// continue;
// }
// if (!(n in oldModel) || newModel[n] !== oldModel[n]) {
// output[n] = newModel[n];
// }
// }
// if (type !== null) output.type_ = type;
// return output;
}
static diffObjectsFields(oldModel, newModel) {
let output = [];
for (let n in newModel) {
if (!newModel.hasOwnProperty(n)) continue;
if (n == 'type_') {
type = newModel[n];
continue;
}
if (n == 'type_') continue;
if (!(n in oldModel) || newModel[n] !== oldModel[n]) {
output[n] = newModel[n];
output.push(n);
}
}
if (type !== null) output.type_ = type;
return output;
}
@ -269,6 +288,16 @@ class BaseModel {
let where = { id: o.id };
let temp = Object.assign({}, o);
delete temp.id;
if (options.fields) {
let filtered = {};
for (let i = 0; i < options.fields.length; i++) {
const f = options.fields[i];
filtered[f] = o[f];
}
temp = filtered;
}
query = Database.updateQuery(this.tableName(), temp, where);
}

View File

@ -37,16 +37,23 @@ shared.saveNoteButton_press = async function(comp) {
}
// Save only the properties that have changed
let diff = null;
// let diff = null;
// if (!isNew) {
// diff = BaseModel.diffObjects(comp.state.lastSavedNote, note);
// diff.type_ = note.type_;
// diff.id = note.id;
// } else {
// diff = Object.assign({}, note);
// }
// const savedNote = await Note.save(diff);
let options = {};
if (!isNew) {
diff = BaseModel.diffObjects(comp.state.lastSavedNote, note);
diff.type_ = note.type_;
diff.id = note.id;
} else {
diff = Object.assign({}, note);
options.fields = BaseModel.diffObjectsFields(comp.state.lastSavedNote, note);
}
const savedNote = await Note.save(diff);
const savedNote = ('fields' in options) && !options.fields.length ? Object.assign({}, note) : await Note.save(note, { userSideValidation: true });
const stateNote = comp.state.note;
// Re-assign any property that might have changed during saving (updated_time, etc.)

View File

@ -3,6 +3,7 @@ const { Database } = require('lib/database.js');
const Setting = require('lib/models/Setting.js');
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const { _ } = require('lib/locale.js');
const moment = require('moment');
class BaseItem extends BaseModel {
@ -540,6 +541,21 @@ class BaseItem extends BaseModel {
await this.db().transactionExecBatch(queries);
}
static displayTitle(item) {
if (!item) return '';
return !!item.encryption_applied ? '🔑 ' + _('Encrypted') : item.title + '';
}
static async save(o, options = null) {
if (!options) options = {};
if (options.userSideValidation === true) {
if (!!o.encryption_applied) throw new Error(_('Encrypted items cannot be modified'));
}
return super.save(o, options);
}
}
BaseItem.encryptionService_ = null;

View File

@ -153,7 +153,7 @@ class Note extends BaseItem {
}
static previewFields() {
return ['id', 'title', 'body', 'is_todo', 'todo_completed', 'parent_id', 'updated_time', 'user_updated_time'];
return ['id', 'title', 'body', 'is_todo', 'todo_completed', 'parent_id', 'updated_time', 'user_updated_time', 'encryption_applied'];
}
static previewFieldsSql() {