diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index 5e6a8840b..218bf5c5e 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -274,8 +274,9 @@ Pad.prototype.text = function () { * @param {number} ndel - Number of characters to remove starting at `start`. Must be a non-negative * integer less than or equal to `this.text().length - start`. * @param {string} ins - New text to insert at `start` (after the `ndel` characters are deleted). + * @param {string} [authorId] - Author ID of the user making the change (if applicable). */ -Pad.prototype.spliceText = async function (start, ndel, ins) { +Pad.prototype.spliceText = async function (start, ndel, ins, authorId = '') { if (start < 0) throw new RangeError(`start index must be non-negative (is ${start})`); if (ndel < 0) throw new RangeError(`characters to delete must be non-negative (is ${ndel})`); const orig = this.text(); @@ -289,7 +290,7 @@ Pad.prototype.spliceText = async function (start, ndel, ins) { if (!willEndWithNewline) ins += '\n'; if (ndel === 0 && ins.length === 0) return; const changeset = Changeset.makeSplice(orig, start, ndel, ins); - await this.appendRevision(changeset); + await this.appendRevision(changeset, authorId); }; /** @@ -297,18 +298,20 @@ Pad.prototype.spliceText = async function (start, ndel, ins) { * * @param {string} newText - The pad's new text. If this string does not end with a newline, one * will be automatically appended. + * @param {string} [authorId] - The author ID of the user that initiated the change, if applicable. */ -Pad.prototype.setText = async function (newText) { - await this.spliceText(0, this.text().length, newText); +Pad.prototype.setText = async function (newText, authorId = '') { + await this.spliceText(0, this.text().length, newText, authorId); }; /** * Appends text to the pad. * * @param {string} newText - Text to insert just BEFORE the pad's existing terminating newline. + * @param {string} [authorId] - The author ID of the user that initiated the change, if applicable. */ -Pad.prototype.appendText = async function (newText) { - await this.spliceText(this.text().length - 1, 0, newText); +Pad.prototype.appendText = async function (newText, authorId = '') { + await this.spliceText(this.text().length - 1, 0, newText, authorId); }; /** @@ -368,7 +371,7 @@ Pad.prototype.getChatMessages = async function (start, end) { }); }; -Pad.prototype.init = async function (text) { +Pad.prototype.init = async function (text, authorId = '') { // replace text with default text if text isn't set if (text == null) { text = settings.defaultPadText; @@ -391,7 +394,7 @@ Pad.prototype.init = async function (text) { // this pad doesn't exist, so create it const firstChangeset = Changeset.makeSplice('\n', 0, 0, exports.cleanText(text)); - await this.appendRevision(firstChangeset, ''); + await this.appendRevision(firstChangeset, authorId); } }; @@ -476,7 +479,7 @@ Pad.prototype.copyAuthorInfoToDestinationPad = async function (destinationID) { (authorID) => authorManager.addPad(authorID, destinationID))); }; -Pad.prototype.copyPadWithoutHistory = async function (destinationID, force) { +Pad.prototype.copyPadWithoutHistory = async function (destinationID, force, authorId = '') { // flush the source pad this.saveToDatabase(); @@ -494,7 +497,7 @@ Pad.prototype.copyPadWithoutHistory = async function (destinationID, force) { } // initialize the pad with a new line to avoid getting the defaultText - const newPad = await padManager.getPad(destinationID, '\n'); + const newPad = await padManager.getPad(destinationID, '\n', authorId); newPad.pool = this.pool.clone(); const oldAText = this.atext; @@ -514,7 +517,7 @@ Pad.prototype.copyPadWithoutHistory = async function (destinationID, force) { // create a changeset that removes the previous text and add the newText with // all atributes present on the source pad const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText); - newPad.appendRevision(changeset); + newPad.appendRevision(changeset, authorId); await hooks.aCallAll('padCopy', {originalPad: this, destinationID}); diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index 1d55fda89..861e54710 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -91,9 +91,11 @@ const padList = new class { /** * Returns a Pad Object with the callback * @param id A String with the id of the pad - * @param {Function} callback + * @param {string} [text] - Optional initial pad text if creating a new pad. + * @param {string} [authorId] - Optional author ID of the user that initiated the pad creation (if + * applicable). */ -exports.getPad = async (id, text) => { +exports.getPad = async (id, text, authorId = '') => { // check if this is a valid padId if (!exports.isValidPadId(id)) { throw new CustomError(`${id} is not a valid padId`, 'apierror'); @@ -123,7 +125,7 @@ exports.getPad = async (id, text) => { pad = new Pad.Pad(id); // initialize the pad - await pad.init(text); + await pad.init(text, authorId); hooks.callAll('padLoad', {pad}); globalPads.set(id, pad); padList.addPad(id); diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 342dd5b7f..f6f26d3df 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -74,7 +74,7 @@ const tmpDirectory = os.tmpdir(); /** * do a requested import */ -const doImport = async (req, res, padId) => { +const doImport = async (req, res, padId, authorId) => { // pipe to a file // convert file to html via abiword or soffice // set html in the pad @@ -140,7 +140,7 @@ const doImport = async (req, res, padId) => { let directDatabaseAccess = false; if (fileIsEtherpad) { // Use '\n' to avoid the default pad text if the pad doesn't yet exist. - const pad = await padManager.getPad(padId, '\n'); + const pad = await padManager.getPad(padId, '\n', authorId); const headCount = pad.head; if (headCount >= 10) { logger.warn('Aborting direct database import attempt of a pad that already has content'); @@ -148,7 +148,7 @@ const doImport = async (req, res, padId) => { } const text = await fs.readFile(srcFile, 'utf8'); directDatabaseAccess = true; - await importEtherpad.setPadRaw(padId, text); + await importEtherpad.setPadRaw(padId, text, authorId); } // convert file to html if necessary @@ -205,12 +205,12 @@ const doImport = async (req, res, padId) => { if (!directDatabaseAccess) { if (importHandledByPlugin || useConverter || fileIsHTML) { try { - await importHtml.setPadHTML(pad, text); + await importHtml.setPadHTML(pad, text, authorId); } catch (err) { logger.warn(`Error importing, possibly caused by malformed HTML: ${err.stack || err}`); } } else { - await pad.setText(text); + await pad.setText(text, authorId); } } @@ -233,13 +233,13 @@ const doImport = async (req, res, padId) => { return false; }; -exports.doImport = async (req, res, padId) => { +exports.doImport = async (req, res, padId, authorId = '') => { let httpStatus = 200; let code = 0; let message = 'ok'; let directDatabaseAccess; try { - directDatabaseAccess = await doImport(req, res, padId); + directDatabaseAccess = await doImport(req, res, padId, authorId); } catch (err) { const known = err instanceof ImportError && err.status; if (!known) logger.error(`Internal error during import: ${err.stack || err}`); diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 2ab904d46..cdc203092 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -676,13 +676,13 @@ const handleUserChanges = async (socket, message) => { const correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); if (correctionChangeset) { - await pad.appendRevision(correctionChangeset); + await pad.appendRevision(correctionChangeset, thisSession.author); } // Make sure the pad always ends with an empty line. if (pad.text().lastIndexOf('\n') !== pad.text().length - 1) { const nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length - 1, 0, '\n'); - await pad.appendRevision(nlChangeset); + await pad.appendRevision(nlChangeset, thisSession.author); } // The client assumes that ACCEPT_COMMIT and NEW_CHANGES messages arrive in order. Make sure we diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.js index ab8d60376..96f0efab4 100644 --- a/src/node/hooks/express/importexport.js +++ b/src/node/hooks/express/importexport.js @@ -70,12 +70,12 @@ exports.expressCreateServer = (hookName, args, cb) => { args.app.post('/p/:pad/import', (req, res, next) => { (async () => { const {session: {user} = {}} = req; - const {accessStatus} = await securityManager.checkAccess( + const {accessStatus, authorID: authorId} = await securityManager.checkAccess( req.params.pad, req.cookies.sessionID, req.cookies.token, user); if (accessStatus !== 'grant' || !webaccess.userCanModify(req.params.pad, req)) { return res.status(403).send('Forbidden'); } - await importHandler.doImport(req, res, req.params.pad); + await importHandler.doImport(req, res, req.params.pad, authorId); })().catch((err) => next(err || new Error(err))); }); diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.js index 60a3b8492..de0280e23 100644 --- a/src/node/utils/ImportEtherpad.js +++ b/src/node/utils/ImportEtherpad.js @@ -27,7 +27,7 @@ const supportedElems = require('../../static/js/contentcollector').supportedElem const logger = log4js.getLogger('ImportEtherpad'); -exports.setPadRaw = async (padId, r) => { +exports.setPadRaw = async (padId, r, authorId = '') => { const records = JSON.parse(r); // get supported block Elements from plugins, we will use this later. @@ -110,7 +110,7 @@ exports.setPadRaw = async (padId, r) => { return v; }, }); - await pad.init(); + await pad.init(null, authorId); await pad.check(); await Promise.all([ diff --git a/src/node/utils/ImportHtml.js b/src/node/utils/ImportHtml.js index df3dcf5ef..d7b2172b0 100644 --- a/src/node/utils/ImportHtml.js +++ b/src/node/utils/ImportHtml.js @@ -23,7 +23,7 @@ const jsdom = require('jsdom'); const apiLogger = log4js.getLogger('ImportHtml'); let processor; -exports.setPadHTML = async (pad, html) => { +exports.setPadHTML = async (pad, html, authorId = '') => { if (processor == null) { const [{rehype}, {default: minifyWhitespace}] = await Promise.all([import('rehype'), import('rehype-minify-whitespace')]); @@ -88,6 +88,6 @@ exports.setPadHTML = async (pad, html) => { const theChangeset = builder.toString(); apiLogger.debug(`The changeset: ${theChangeset}`); - await pad.setText('\n'); - await pad.appendRevision(theChangeset); + await pad.setText('\n', authorId); + await pad.appendRevision(theChangeset, authorId); };