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:
parent
2c608bca3c
commit
e9bb5bee9d
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.)
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user