From 14f7a8b7b99c11657b92f9789399517b97c37557 Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 11 Feb 2019 23:45:58 +0100 Subject: [PATCH] safe import implementation --- src/public/javascripts/dialogs/import.js | 6 +++- src/routes/api/import.js | 12 +++++-- src/routes/routes.js | 2 +- src/services/attributes.js | 40 +++++++++++++++++------- src/services/import/tar.js | 10 ++++++ 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/public/javascripts/dialogs/import.js b/src/public/javascripts/dialogs/import.js index d94a1fc0b..95f19a940 100644 --- a/src/public/javascripts/dialogs/import.js +++ b/src/public/javascripts/dialogs/import.js @@ -12,6 +12,7 @@ const $fileUploadInput = $("#import-file-upload-input"); const $importProgressCountWrapper = $("#import-progress-count-wrapper"); const $importProgressCount = $("#import-progress-count"); const $importButton = $("#import-button"); +const $safeImport = $("#safe-import"); let importId; @@ -21,6 +22,7 @@ async function showDialog() { $importProgressCountWrapper.hide(); $importProgressCount.text('0'); $fileUploadInput.val('').change(); // to trigger Import button disabling listener below + $safeImport.attr("checked", "checked"); glob.activeDialog = $dialog; @@ -49,8 +51,10 @@ function importIntoNote(importNoteId) { // dialog (which shouldn't happen, but still ...) importId = utils.randomString(10); + const safeImport = $safeImport.is(":checked") ? 1 : 0; + $.ajax({ - url: baseApiUrl + 'notes/' + importNoteId + '/import/' + importId, + url: baseApiUrl + 'notes/' + importNoteId + '/import/' + importId + '/safe/' + safeImport, headers: server.getHeaders(), data: formData, dataType: 'json', diff --git a/src/routes/api/import.js b/src/routes/api/import.js index 23c7eaa14..59161e3c2 100644 --- a/src/routes/api/import.js +++ b/src/routes/api/import.js @@ -12,10 +12,13 @@ const noteCacheService = require('../../services/note_cache'); const log = require('../../services/log'); class ImportContext { - constructor(importId) { + constructor(importId, safeImport) { // importId is to distinguish between different import events - it is possible (though not recommended) // to have multiple imports going at the same time this.importId = importId; + + this.safeImport = safeImport; + // // count is mean to represent count of exported notes where practical, otherwise it's just some measure of progress this.progressCount = 0; this.lastSentCountTs = Date.now(); @@ -53,7 +56,10 @@ class ImportContext { } async function importToBranch(req) { - const {parentNoteId, importId} = req.params; + let {parentNoteId, importId, safeImport} = req.params; + + safeImport = safeImport !== '0'; + const file = req.file; if (!file) { @@ -74,7 +80,7 @@ async function importToBranch(req) { let note; // typically root of the import - client can show it after finishing the import - const importContext = new ImportContext(importId); + const importContext = new ImportContext(importId, safeImport); try { if (extension === '.tar') { diff --git a/src/routes/routes.js b/src/routes/routes.js index 4de545612..87b494038 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -129,7 +129,7 @@ function register(app) { apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); route(GET, '/api/notes/:branchId/export/:type/:format/:exportId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); - route(POST, '/api/notes/:parentNoteId/import/:importId', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler); + route(POST, '/api/notes/:parentNoteId/import/:importId/safe/:safeImport', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler); route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware], filesRoute.uploadFile, apiResultHandler); diff --git a/src/services/attributes.js b/src/services/attributes.js index c3f431747..38d1b6c34 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -5,13 +5,14 @@ const sql = require('./sql'); const utils = require('./utils'); const Attribute = require('../entities/attribute'); +const ATTRIBUTE_TYPES = [ 'label', 'label-definition', 'relation', 'relation-definition' ]; + const BUILTIN_ATTRIBUTES = [ // label names { type: 'label', name: 'disableVersioning' }, { type: 'label', name: 'calendarRoot' }, { type: 'label', name: 'archived' }, { type: 'label', name: 'excludeFromExport' }, - { type: 'label', name: 'run' }, { type: 'label', name: 'manualTransactionHandling' }, { type: 'label', name: 'disableInclusion' }, { type: 'label', name: 'appCss' }, @@ -19,19 +20,20 @@ const BUILTIN_ATTRIBUTES = [ { type: 'label', name: 'hideChildrenOverview' }, { type: 'label', name: 'hidePromotedAttributes' }, { type: 'label', name: 'readOnly' }, - { type: 'label', name: 'customRequestHandler' }, - { type: 'label', name: 'customResourceProvider' }, + { type: 'label', name: 'run', isDangerous: true }, + { type: 'label', name: 'customRequestHandler', isDangerous: true }, + { type: 'label', name: 'customResourceProvider', isDangerous: true }, // relation names - { type: 'relation', name: 'runOnNoteView' }, - { type: 'relation', name: 'runOnNoteCreation' }, - { type: 'relation', name: 'runOnNoteTitleChange' }, - { type: 'relation', name: 'runOnNoteChange' }, - { type: 'relation', name: 'runOnChildNoteCreation' }, - { type: 'relation', name: 'runOnAttributeCreation' }, - { type: 'relation', name: 'runOnAttributeChange' }, + { type: 'relation', name: 'runOnNoteView', isDangerous: true }, + { type: 'relation', name: 'runOnNoteCreation', isDangerous: true }, + { type: 'relation', name: 'runOnNoteTitleChange', isDangerous: true }, + { type: 'relation', name: 'runOnNoteChange', isDangerous: true }, + { type: 'relation', name: 'runOnChildNoteCreation', isDangerous: true }, + { type: 'relation', name: 'runOnAttributeCreation', isDangerous: true }, + { type: 'relation', name: 'runOnAttributeChange', isDangerous: true }, { type: 'relation', name: 'template' }, - { type: 'relation', name: 'renderNote' } + { type: 'relation', name: 'renderNote', isDangerous: true } ]; async function getNotesWithLabel(name, value) { @@ -94,11 +96,25 @@ async function getAttributeNames(type, nameLike) { return names; } +function isAttributeType(type) { + return ATTRIBUTE_TYPES.includes(type); +} + +function isAttributeDangerous(type, name) { + return BUILTIN_ATTRIBUTES.some(attr => + attr.type === attr.type && + attr.name.toLowerCase() === name.trim().toLowerCase() && + attr.isDangerous + ); +} + module.exports = { getNotesWithLabel, getNotesWithLabels, getNoteWithLabel, createLabel, createAttribute, - getAttributeNames + getAttributeNames, + isAttributeType, + isAttributeDangerous }; \ No newline at end of file diff --git a/src/services/import/tar.js b/src/services/import/tar.js index cca223e2d..888f3c2f4 100644 --- a/src/services/import/tar.js +++ b/src/services/import/tar.js @@ -6,6 +6,7 @@ const utils = require('../../services/utils'); const log = require('../../services/log'); const repository = require('../../services/repository'); const noteService = require('../../services/notes'); +const attributeService = require('../../services/attributes'); const Branch = require('../../entities/branch'); const tar = require('tar-stream'); const stream = require('stream'); @@ -153,10 +154,19 @@ async function importTar(importContext, fileBuffer, importRootNote) { for (const attr of noteMeta.attributes) { attr.noteId = note.noteId; + if (!attributeService.isAttributeType(attr.type)) { + log.error("Unrecognized attribute type " + attr.type); + continue; + } + if (attr.type === 'relation') { attr.value = getNewNoteId(attr.value); } + if (importContext.safeImport && attributeService.isAttributeDangerous(attr.type, attr.name)) { + attr.name = 'disabled-' + attr.name; + } + attributes.push(attr); }