mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
Desktop: Decrypt notes that are meant to be shared
This commit is contained in:
parent
f60d6e0748
commit
58200ecdb1
@ -1540,5 +1540,43 @@ describe('Synchronizer', function() {
|
||||
expect((await synchronizer().lockFiles_()).length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should not encrypt notes that are shared', asyncTest(async () => {
|
||||
Setting.setValue('encryption.enabled', true);
|
||||
await loadEncryptionMasterKey();
|
||||
|
||||
let folder1 = await Folder.save({ title: 'folder1' });
|
||||
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
|
||||
let note2 = await Note.save({ title: 'deux', parent_id: folder1.id });
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
const origNote2 = Object.assign({}, note2);
|
||||
await BaseItem.updateShareStatus(note2, true);
|
||||
note2 = await Note.load(note2.id);
|
||||
|
||||
// Sharing a note should not modify the timestamps
|
||||
expect(note2.user_updated_time).toBe(origNote2.user_updated_time);
|
||||
expect(note2.user_created_time).toBe(origNote2.user_created_time);
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
|
||||
// The shared note should be decrypted
|
||||
let note2_2 = await Note.load(note2.id);
|
||||
expect(note2_2.title).toBe('deux');
|
||||
expect(note2_2.is_shared).toBe(1);
|
||||
|
||||
// The non-shared note should be encrypted
|
||||
let note1_2 = await Note.load(note1.id);
|
||||
expect(note1_2.title).toBe('');
|
||||
}));
|
||||
|
||||
});
|
||||
|
@ -1,5 +1,3 @@
|
||||
// const React = require('react');
|
||||
// const { useState, useEffect } = React;
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import JoplinServerApi from '../lib/JoplinServerApi';
|
||||
@ -9,6 +7,7 @@ const { themeStyle, buildStyle } = require('../theme.js');
|
||||
const DialogButtonRow = require('./DialogButtonRow.min');
|
||||
const Note = require('lib/models/Note');
|
||||
const Setting = require('lib/models/Setting');
|
||||
const BaseItem = require('lib/models/BaseItem');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { clipboard } = require('electron');
|
||||
|
||||
@ -39,6 +38,7 @@ function styles_(props:ShareNoteDialogProps) {
|
||||
marginBottom: 5,
|
||||
},
|
||||
noteTitle: {
|
||||
...theme.textStyle,
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
color: theme.color,
|
||||
@ -96,24 +96,30 @@ export default function ShareNoteDialog(props:ShareNoteDialogProps) {
|
||||
clipboard.writeText(links.join('\n'));
|
||||
};
|
||||
|
||||
const synchronize = async () => {
|
||||
const synchronizer = await reg.syncTarget().synchronizer();
|
||||
await synchronizer.waitForSyncToFinish();
|
||||
await reg.scheduleSync(0);
|
||||
};
|
||||
|
||||
const shareLinkButton_click = async () => {
|
||||
let hasSynced = false;
|
||||
let tryToSync = false;
|
||||
while (true) {
|
||||
try {
|
||||
setSharesState('creating');
|
||||
|
||||
if (tryToSync) {
|
||||
const synchronizer = await reg.syncTarget().synchronizer();
|
||||
await synchronizer.waitForSyncToFinish();
|
||||
await reg.scheduleSync(0);
|
||||
setSharesState('synchronizing');
|
||||
await synchronize();
|
||||
tryToSync = false;
|
||||
hasSynced = true;
|
||||
}
|
||||
|
||||
setSharesState('creating');
|
||||
|
||||
const api = await appApi();
|
||||
const syncTargetId = api.syncTargetId(Setting.toPlainObject());
|
||||
const newShares = Object.assign({}, shares);
|
||||
let sharedStatusChanged = false;
|
||||
|
||||
for (const note of notes) {
|
||||
const result = await api.exec('POST', 'shares', {
|
||||
@ -121,10 +127,19 @@ export default function ShareNoteDialog(props:ShareNoteDialogProps) {
|
||||
noteId: note.id,
|
||||
});
|
||||
newShares[note.id] = result;
|
||||
|
||||
const changed = await BaseItem.updateShareStatus(note, true);
|
||||
if (changed) sharedStatusChanged = true;
|
||||
}
|
||||
|
||||
setShares(newShares);
|
||||
|
||||
if (sharedStatusChanged) {
|
||||
setSharesState('synchronizing');
|
||||
await synchronize();
|
||||
setSharesState('creating');
|
||||
}
|
||||
|
||||
copyLinksToClipboard(newShares);
|
||||
|
||||
setSharesState('created');
|
||||
@ -178,11 +193,14 @@ export default function ShareNoteDialog(props:ShareNoteDialogProps) {
|
||||
};
|
||||
|
||||
const statusMessage = (sharesState:string):string => {
|
||||
if (sharesState === 'synchronizing') return _('Synchronising...');
|
||||
if (sharesState === 'creating') return _n('Generating link...', 'Generating links...', noteCount);
|
||||
if (sharesState === 'created') return _n('Link has been copied to clipboard!', 'Links have been copied to clipboard!', noteCount);
|
||||
return '';
|
||||
};
|
||||
|
||||
const encryptionWarningMessage = !Setting.value('encryption.enabled') ? null : <div style={theme.textStyle}>{_('Note: When a note is shared, it will no longer be encrypted on the server.')}</div>;
|
||||
|
||||
const rootStyle = Object.assign({}, theme.dialogBox);
|
||||
rootStyle.width = '50%';
|
||||
|
||||
@ -191,7 +209,8 @@ export default function ShareNoteDialog(props:ShareNoteDialogProps) {
|
||||
<div style={rootStyle}>
|
||||
<div style={theme.dialogTitle}>{_('Share Notes')}</div>
|
||||
{renderNoteList(notes)}
|
||||
<button disabled={sharesState === 'creating'} style={styles.copyShareLinkButton} onClick={shareLinkButton_click}>{_n('Copy Shareable Link', 'Copy Shareable Links', noteCount)}</button>
|
||||
<button disabled={['creating', 'synchronizing'].indexOf(sharesState) >= 0} style={styles.copyShareLinkButton} onClick={shareLinkButton_click}>{_n('Copy Shareable Link', 'Copy Shareable Links', noteCount)}</button>
|
||||
{encryptionWarningMessage}
|
||||
<div style={theme.textStyle}>{statusMessage(sharesState)}</div>
|
||||
<DialogButtonRow theme={props.theme} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
||||
</div>
|
||||
|
@ -7,9 +7,9 @@ xcopy /C /I /H /R /Y /S /Q %script_dir%\..\ReactNativeClient\lib %script_dir%\ap
|
||||
REM if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
rem Note that TypeScript must be installed globally for this to work
|
||||
cd %script_dir%\..
|
||||
call tsc
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
REM cd %script_dir%\..
|
||||
REM call tsc
|
||||
REM if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
cd %script_dir%\app
|
||||
call npm run compile
|
||||
|
@ -302,7 +302,7 @@ class JoplinDatabase extends Database {
|
||||
// must be set in the synchronizer too.
|
||||
|
||||
// Note: v16 and v17 don't do anything. They were used to debug an issue.
|
||||
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25];
|
||||
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26];
|
||||
|
||||
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
|
||||
|
||||
@ -649,6 +649,14 @@ class JoplinDatabase extends Database {
|
||||
GROUP BY tags.id`);
|
||||
}
|
||||
|
||||
if (targetVersion == 26) {
|
||||
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 is_shared INT NOT NULL DEFAULT 0`);
|
||||
}
|
||||
}
|
||||
|
||||
queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] });
|
||||
|
||||
try {
|
||||
|
@ -329,7 +329,7 @@ class BaseItem extends BaseModel {
|
||||
|
||||
const serialized = await ItemClass.serialize(item, shownKeys);
|
||||
|
||||
if (!Setting.value('encryption.enabled') || !ItemClass.encryptionSupported()) {
|
||||
if (!Setting.value('encryption.enabled') || !ItemClass.encryptionSupported() || item.is_shared) {
|
||||
// Normally not possible since itemsThatNeedSync should only return decrypted items
|
||||
if (item.encryption_applied) throw new JoplinError('Item is encrypted but encryption is currently disabled', 'cannotSyncEncrypted');
|
||||
return serialized;
|
||||
@ -716,6 +716,25 @@ class BaseItem extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
static async updateShareStatus(item, isShared) {
|
||||
if (!item.id || !item.type_) throw new Error('Item must have an ID and a type');
|
||||
if (!!item.is_shared === !!isShared) return false;
|
||||
const ItemClass = this.getClassByItemType(item.type_);
|
||||
|
||||
// No auto-timestamp because sharing a note is not seen as an update
|
||||
await ItemClass.save({
|
||||
id: item.id,
|
||||
is_shared: isShared ? 1 : 0,
|
||||
updated_time: Date.now(),
|
||||
}, { autoTimestamp: false });
|
||||
|
||||
// The timestamps have not been changed but still need the note to be synced
|
||||
// so we force-sync it.
|
||||
// await this.forceSync(item.id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static async forceSync(itemId) {
|
||||
await this.db().exec('UPDATE sync_items SET force_sync = 1 WHERE item_id = ?', [itemId]);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user