diff --git a/Common/config/default.json b/Common/config/default.json index 15e472b4d..93dc6c1e0 100644 --- a/Common/config/default.json +++ b/Common/config/default.json @@ -168,6 +168,10 @@ "useforrequest": false, "errorcode": 403 }, + "request-filtering-agent" : { + "allowPrivateIPAddress": true, + "allowMetaIPAddress": true + }, "secret": { "browser": {"string": "secret", "file": "", "tenants": {}}, "inbox": {"string": "secret", "file": "", "tenants": {}}, @@ -254,6 +258,7 @@ "x2tPath": "null", "docbuilderPath": "null", "docbuilderAllFontsPath": "null", + "docbuilderCoreFontsPath": "", "args": "", "spawnOptions": {}, "errorfiles": "", diff --git a/Common/config/development-linux.json b/Common/config/development-linux.json index ef2eac3bc..26de849df 100644 --- a/Common/config/development-linux.json +++ b/Common/config/development-linux.json @@ -1,31 +1,31 @@ { "log": { - "filePath": "../../Common/config/log4js/development.json" + "filePath": "../Common/config/log4js/development.json" }, "storage": { "fs": { - "folderPath": "../../App_Data" + "folderPath": "../App_Data" } }, "services": { "CoAuthoring": { "server": { - "port": 8001, + "port": 8000, "static_content": { "/fonts": { - "path": "../../../fonts" + "path": "../../fonts" }, "/sdkjs": { - "path": "../../../sdkjs" + "path": "../../sdkjs" }, "/web-apps": { - "path": "../../../web-apps" + "path": "../../web-apps" }, "/sdkjs-plugins": { - "path": "../../../sdkjs-plugins" + "path": "../../sdkjs-plugins" }, "/dictionaries": { - "path": "../../../dictionaries" + "path": "../../dictionaries" }, "/info": { "path": "../branding/info" @@ -35,28 +35,33 @@ "utils": { "utils_common_fontdir": "/usr/share/fonts" }, + "request-filtering-agent" : { + "allowPrivateIPAddress": true, + "allowMetaIPAddress": true + }, "sockjs": { "sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js" } } }, "license": { - "license_file": "./../../license.lic", + "license_file": "./../license.lic", "warning_limit_percents": 70, "packageType": 0 }, "FileConverter": { "converter": { "fontDir": "/usr/share/fonts", - "presentationThemesDir": "../../../sdkjs/slide/themes", - "x2tPath": "../../FileConverter/bin/x2t", - "docbuilderPath": "../../FileConverter/bin/docbuilder", - "docbuilderAllFontsPath": "../../App_Data/docbuilder/AllFonts.js" + "presentationThemesDir": "../../sdkjs/slide/themes", + "x2tPath": "../FileConverter/bin/x2t", + "docbuilderPath": "../FileConverter/bin/docbuilder", + "docbuilderAllFontsPath": "../App_Data/docbuilder/AllFonts.js", + "docbuilderCoreFontsPath": "../../core-fonts" } }, "SpellChecker": { "server": { - "dictDir": "../../../dictionaries" + "dictDir": "../../dictionaries" } } } diff --git a/Common/config/development-mac.json b/Common/config/development-mac.json index 9f1826cb5..30adaa9c7 100644 --- a/Common/config/development-mac.json +++ b/Common/config/development-mac.json @@ -1,34 +1,34 @@ { "log": { - "filePath": "../../Common/config/log4js/development.json" + "filePath": "../Common/config/log4js/development.json" }, "storage": { "fs": { - "folderPath": "../../App_Data" + "folderPath": "../App_Data" } }, "services": { "CoAuthoring": { "server": { - "port": 8001, + "port": 8000, "static_content": { "/fonts": { - "path": "../../../fonts" + "path": "../../fonts" }, "/sdkjs": { - "path": "../../../sdkjs" + "path": "../../sdkjs" }, "/web-apps": { - "path": "../../../web-apps" + "path": "../../web-apps" }, "/sdkjs-plugins": { - "path": "../../../sdkjs-plugins" + "path": "../../sdkjs-plugins" }, "/dictionaries": { - "path": "../../../dictionaries" + "path": "../../dictionaries" }, "/info": { - "path": "../../branding/info" + "path": "../branding/info" } } }, @@ -41,29 +41,34 @@ "dbUser": "root", "dbPass": "onlyoffice" }, + "request-filtering-agent" : { + "allowPrivateIPAddress": true, + "allowMetaIPAddress": true + }, "sockjs": { - "sockjs_url": "/office/vendor/sockjs/sockjs.min.js" + "sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js" } } }, "license": { - "license_file": "./../../license.lic", + "license_file": "./../license.lic", "warning_limit_percents": 70, "packageType": 0 }, "FileConverter": { "converter": { "fontDir": "", - "presentationThemesDir": "../../../OfficeWeb/PowerPoint/themes", - "x2tPath": "../../FileConverter/bin/x2t", - "docbuilderPath": "../../FileConverter/Bin/docbuilder", - "docbuilderAllFontsPath": "../../App_Data/docbuilder/AllFonts.js", + "presentationThemesDir": "../../sdkjs/slide/themes", + "x2tPath": "../FileConverter/bin/x2t", + "docbuilderPath": "../FileConverter/Bin/docbuilder", + "docbuilderAllFontsPath": "../App_Data/docbuilder/AllFonts.js", + "docbuilderCoreFontsPath": "../../core-fonts", "errorfiles": "error" } }, "SpellChecker": { "server": { - "dictDir": "../../../dictionaries" + "dictDir": "../../dictionaries" } } } \ No newline at end of file diff --git a/Common/config/development-windows.json b/Common/config/development-windows.json index 8af1f3340..4c87b9527 100644 --- a/Common/config/development-windows.json +++ b/Common/config/development-windows.json @@ -1,34 +1,34 @@ { "log": { - "filePath": "../../Common/config/log4js/development.json" + "filePath": "../Common/config/log4js/development.json" }, "storage": { "fs": { - "folderPath": "../../App_Data" + "folderPath": "../App_Data" } }, "services": { "CoAuthoring": { "server": { - "port": 8001, + "port": 8000, "static_content": { "/fonts": { - "path": "../../../fonts" + "path": "../../fonts" }, "/sdkjs": { - "path": "../../../sdkjs" + "path": "../../sdkjs" }, "/web-apps": { - "path": "../../../web-apps" + "path": "../../web-apps" }, "/sdkjs-plugins": { - "path": "../../../sdkjs-plugins" + "path": "../../sdkjs-plugins" }, "/dictionaries": { - "path": "../../../dictionaries" + "path": "../../dictionaries" }, "/info": { - "path": "../../branding/info" + "path": "../branding/info" } } }, @@ -41,29 +41,34 @@ "dbUser": "root", "dbPass": "onlyoffice" }, + "request-filtering-agent" : { + "allowPrivateIPAddress": true, + "allowMetaIPAddress": true + }, "sockjs": { "sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js" } } }, "license": { - "license_file": "./../../license.lic", + "license_file": "./../license.lic", "warning_limit_percents": 70, "packageType": 0 }, "FileConverter": { "converter": { "fontDir": "", - "presentationThemesDir": "../../../sdkjs/slide/themes", - "x2tPath": "../../FileConverter/Bin/x2t.exe", - "docbuilderPath": "../../FileConverter/Bin/docbuilder.exe", - "docbuilderAllFontsPath": "../../App_Data/docbuilder/AllFonts.js", + "presentationThemesDir": "../../sdkjs/slide/themes", + "x2tPath": "../FileConverter/Bin/x2t.exe", + "docbuilderPath": "../FileConverter/Bin/docbuilder.exe", + "docbuilderAllFontsPath": "../App_Data/docbuilder/AllFonts.js", + "docbuilderCoreFontsPath": "../../core-fonts", "errorfiles": "error" } }, "SpellChecker": { "server": { - "dictDir": "../../../dictionaries" + "dictDir": "../../dictionaries" } } } \ No newline at end of file diff --git a/Common/config/production-linux.json b/Common/config/production-linux.json index 07585e2a0..0e2a99705 100644 --- a/Common/config/production-linux.json +++ b/Common/config/production-linux.json @@ -60,7 +60,8 @@ "presentationThemesDir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes", "x2tPath": "/var/www/onlyoffice/documentserver/server/FileConverter/bin/x2t", "docbuilderPath": "/var/www/onlyoffice/documentserver/server/FileConverter/bin/docbuilder", - "docbuilderAllFontsPath": "/var/lib/onlyoffice/documentserver/App_Data/docbuilder/AllFonts.js" + "docbuilderAllFontsPath": "/var/lib/onlyoffice/documentserver/App_Data/docbuilder/AllFonts.js", + "docbuilderCoreFontsPath": "/var/www/onlyoffice/documentserver/core-fonts" } }, "FileStorage": { diff --git a/Common/config/production-windows.json b/Common/config/production-windows.json index 8d1caecb4..22fd43e77 100644 --- a/Common/config/production-windows.json +++ b/Common/config/production-windows.json @@ -60,7 +60,8 @@ "presentationThemesDir": "../../sdkjs/slide/themes", "x2tPath": "../FileConverter/bin/x2t.exe", "docbuilderPath": "../FileConverter/bin/docbuilder.exe", - "docbuilderAllFontsPath": "../App_Data/docbuilder/AllFonts.js" + "docbuilderAllFontsPath": "../App_Data/docbuilder/AllFonts.js", + "docbuilderCoreFontsPath": "../../core-fonts" } }, "SpellChecker": { diff --git a/Common/npm-shrinkwrap.json b/Common/npm-shrinkwrap.json index c9ff3cb52..422937b4c 100644 --- a/Common/npm-shrinkwrap.json +++ b/Common/npm-shrinkwrap.json @@ -684,6 +684,21 @@ } } }, + "request-filtering-agent": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-filtering-agent/-/request-filtering-agent-1.0.5.tgz", + "integrity": "sha512-zkO5wg1VhoPqz5FIIXLY+iEveiX+p6A/i++SS4ZXd0UDms6VtHLLYLw9hQnIbMsKbYwHgzhNeIis7jMncoG9ag==", + "requires": { + "ipaddr.js": "^1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + } + } + }, "rfdc": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.2.tgz", diff --git a/Common/package.json b/Common/package.json index fcada3c3c..b3a44bef9 100644 --- a/Common/package.json +++ b/Common/package.json @@ -23,6 +23,7 @@ "node-statsd": "^0.1.1", "openpgp": "^4.10.8", "request": "^2.88.0", + "request-filtering-agent": "^1.0.5", "rhea": "^0.3.9", "uri-js": "^4.2.2" } diff --git a/Common/sources/commondefines.js b/Common/sources/commondefines.js index 3f704833c..ec6a87015 100644 --- a/Common/sources/commondefines.js +++ b/Common/sources/commondefines.js @@ -37,6 +37,8 @@ const constants = require('./constants'); function InputCommand(data, copyExplicit) { //must be set explicitly to prevent vulnerability(downloadAs(with url) creates request to integrator with authorization) this['withAuthorization'] = undefined;//bool + this['isbuilder'] = undefined;//bool + this['externalChangeInfo'] = undefined;//zero DB changes case: set password, undo all changes if (data) { this['c'] = data['c']; this['id'] = data['id']; @@ -88,17 +90,19 @@ function InputCommand(data, copyExplicit) { this['inline'] = data['inline']; this['password'] = data['password']; this['savepassword'] = data['savepassword']; + this['withoutPassword'] = data['withoutPassword']; this['outputurls'] = data['outputurls']; this['closeonerror'] = data['closeonerror']; this['serverVersion'] = data['serverVersion']; this['rediskey'] = data['rediskey']; this['nobase64'] = data['nobase64']; this['forgotten'] = data['forgotten']; - this['isbuilder'] = data['isbuilder']; this['status_info_in'] = data['status_info_in']; this['attempt'] = data['attempt']; if (copyExplicit) { this['withAuthorization'] = data['withAuthorization']; + this['isbuilder'] = data['isbuilder']; + this['externalChangeInfo'] = data['externalChangeInfo']; } } else { this['c'] = undefined;//string command @@ -143,13 +147,13 @@ function InputCommand(data, copyExplicit) { this['inline'] = undefined;//content disposition this['password'] = undefined; this['savepassword'] = undefined; + this['withoutPassword'] = undefined; this['outputurls'] = undefined; this['closeonerror'] = undefined; this['serverVersion'] = undefined; this['rediskey'] = undefined; this['nobase64'] = true; this['forgotten'] = undefined; - this['isbuilder'] = undefined; this['status_info_in'] = undefined; this['attempt'] = undefined; } @@ -377,6 +381,12 @@ InputCommand.prototype = { setSavePassword: function(data) { this['savepassword'] = data; }, + getWithoutPassword: function() { + return this['withoutPassword']; + }, + setWithoutPassword: function(data) { + this['withoutPassword'] = data; + }, setOutputUrls: function(data) { this['outputurls'] = data; }, @@ -430,6 +440,12 @@ InputCommand.prototype = { }, setWithAuthorization: function(data) { this['withAuthorization'] = data; + }, + getExternalChangeInfo: function() { + return this['externalChangeInfo']; + }, + setExternalChangeInfo: function(data) { + this['externalChangeInfo'] = data; } }; diff --git a/Common/sources/license.js b/Common/sources/license.js index 19f0b61e4..ea257bdcf 100644 --- a/Common/sources/license.js +++ b/Common/sources/license.js @@ -56,4 +56,5 @@ exports.readLicense = function*() { endDate: null }; }; + exports.packageType = constants.PACKAGE_TYPE_OS; diff --git a/Common/sources/utils.js b/Common/sources/utils.js index c2af6e015..f39533721 100644 --- a/Common/sources/utils.js +++ b/Common/sources/utils.js @@ -54,9 +54,11 @@ const jwt = require('jsonwebtoken'); const NodeCache = require( "node-cache" ); const ms = require('ms'); const constants = require('./constants'); +const commonDefines = require('./commondefines'); const logger = require('./logger'); const forwarded = require('forwarded'); const mime = require('mime'); +const { RequestFilteringHttpAgent, RequestFilteringHttpsAgent } = require("request-filtering-agent"); const openpgp = require('openpgp'); var configIpFilter = config.get('services.CoAuthoring.ipfilter'); @@ -79,6 +81,7 @@ const cfgTokenOutboxUrlExclusionRegex = config.get('services.CoAuthoring.token.o const cfgPasswordEncrypt = config.get('openpgpjs.encrypt'); const cfgPasswordDecrypt = config.get('openpgpjs.decrypt'); const cfgPasswordConfig = config.get('openpgpjs.config'); +const cfgRequesFilteringAgent = config.get('services.CoAuthoring.request-filtering-agent'); Object.assign(openpgp.config, cfgPasswordConfig); @@ -102,6 +105,10 @@ var g_oIpFilterRules = function() { }(); const pemfileCache = new NodeCache({stdTTL: ms(cfgExpPemStdTtl) / 1000, checkperiod: ms(cfgExpPemCheckPeriod) / 1000, errorOnMissing: false, useClones: true}); +function getRequestFilterAgent(url, options) { + return url.startsWith("https") ? new RequestFilteringHttpsAgent(options) : new RequestFilteringHttpAgent(options); +} + exports.CONVERTION_TIMEOUT = 1.5 * (cfgVisibilityTimeout + cfgQueueRetentionPeriod) * 1000; exports.addSeconds = function(date, sec) { @@ -121,7 +128,7 @@ exports.encodeXml = function(value) { case '\r': return ' '; case '\n': return ' '; case '\t': return ' '; - case '\xA0': return '&#A0;'; + case '\xA0': return ' '; } }); }; @@ -253,13 +260,43 @@ function raiseError(ro, code, msg) { ro.emit('error', error); } function downloadUrlPromise(uri, optTimeout, optLimit, opt_Authorization) { + //todo replace deprecated request module + let filterPrivate = opt_Authorization ? false : true; + const maxRedirects = (undefined !== cfgRequestDefaults.maxRedirects) ? cfgRequestDefaults.maxRedirects : 10; + const followRedirect = (undefined !== cfgRequestDefaults.followRedirect) ? cfgRequestDefaults.followRedirect : true; + var redirectsFollowed = 0; + let doRequest = function(curUrl) { + return downloadUrlPromiseWithoutRedirect(curUrl, optTimeout, optLimit, opt_Authorization, filterPrivate) + .catch(function(err) { + let response = err.response; + if (response && response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location')) { + let redirectTo = response.caseless.get('location'); + if (followRedirect && redirectsFollowed < maxRedirects) { + if (!/^https?:/.test(redirectTo) && err.request) { + redirectTo = url.resolve(err.request.uri.href, redirectTo) + } + + logger.debug('downloadUrlPromise redirectsFollowed:%d redirectTo: %s', redirectsFollowed, redirectTo); + redirectsFollowed++; + return doRequest(redirectTo); + } + } + throw err; + }); + }; + return doRequest(uri); +} +function downloadUrlPromiseWithoutRedirect(uri, optTimeout, optLimit, opt_Authorization, opt_filterPrivate) { return new Promise(function (resolve, reject) { //IRI to URI uri = URI.serialize(URI.parse(uri)); var urlParsed = url.parse(uri); //if you expect binary data, you should set encoding: null let connectionAndInactivity = optTimeout && optTimeout.connectionAndInactivity && ms(optTimeout.connectionAndInactivity); - var options = {uri: urlParsed, encoding: null, timeout: connectionAndInactivity}; + var options = {uri: urlParsed, encoding: null, timeout: connectionAndInactivity, followRedirect: false}; + if (opt_filterPrivate) { + options.agent = getRequestFilterAgent(uri, cfgRequesFilteringAgent); + } if (opt_Authorization) { options.headers = {}; options.headers[cfgTokenOutboxHeader] = cfgTokenOutboxPrefix + opt_Authorization; @@ -283,7 +320,11 @@ function downloadUrlPromise(uri, optTimeout, optLimit, opt_Authorization) { } resolve(body); } else { - reject(new Error('Error response: statusCode:' + response.statusCode + ' ;body:\r\n' + body)); + let error = new Error('Error response: statusCode:' + response.statusCode + ' ;body:\r\n' + body); + error.statusCode = response.statusCode; + error.request = ro; + error.response = response; + reject(error); } } }).on('response', function(response) { @@ -828,3 +869,44 @@ exports.decryptPassword = co.wrap(function* (password) { const { data: decrypted } = yield openpgp.decrypt(params); return decrypted; }); + +exports.convertLicenseInfoToFileParams = function(licenseInfo) { + // todo + // { + // user_quota = 0; + // portal_count = 0; + // process = 2; + // ssbranding = false; + // whiteLabel = false; + // } + let license = {}; + license.end_date = licenseInfo.endDate && licenseInfo.endDate.toJSON(); + license.timelimited = 0 !== (constants.LICENSE_MODE.Limited & licenseInfo.mode); + license.trial = 0 !== (constants.LICENSE_MODE.Trial & licenseInfo.mode); + license.developer = 0 !== (constants.LICENSE_MODE.Developer & licenseInfo.mode); + if(license.developer) { + license.mode = 'developer'; + } else if(license.trial) { + license.mode = 'trial'; + } else { + license.mode = ''; + } + license.light = licenseInfo.light; + license.branding = licenseInfo.branding; + license.customization = licenseInfo.customization; + license.plugins = licenseInfo.plugins; + license.connections = licenseInfo.connections; + license.users_count = licenseInfo.usersCount; + license.users_expire = licenseInfo.usersExpire / constants.LICENSE_EXPIRE_USERS_ONE_DAY; + return license; +}; +exports.convertLicenseInfoToServerParams = function(licenseInfo) { + let license = {}; + license.workersCount = licenseInfo.count; + license.resultType = licenseInfo.type; + license.packageType = licenseInfo.packageType; + license.buildDate = licenseInfo.buildDate && licenseInfo.buildDate.toJSON(); + license.buildVersion = commonDefines.buildVersion; + license.buildNumber = commonDefines.buildNumber; + return license; +}; diff --git a/DocService/sources/DocsCoServer.js b/DocService/sources/DocsCoServer.js index a50990094..03655108f 100644 --- a/DocService/sources/DocsCoServer.js +++ b/DocService/sources/DocsCoServer.js @@ -78,7 +78,6 @@ const url = require('url'); const os = require('os'); const cluster = require('cluster'); const crypto = require('crypto'); -const cron = require('cron'); const co = require('co'); const jwt = require('jsonwebtoken'); const jwa = require('jwa'); @@ -91,6 +90,7 @@ const constants = require('./../../Common/sources/constants'); const utils = require('./../../Common/sources/utils'); const commonDefines = require('./../../Common/sources/commondefines'); const statsDClient = require('./../../Common/sources/statsdclient'); +const license = require('./../../Common/sources/license'); const configCommon = require('config'); const config = configCommon.get('services.CoAuthoring'); const sqlBase = require('./baseConnector'); @@ -165,6 +165,7 @@ let pubsub; let queue; let licenseInfo = {type: constants.LICENSE_RESULT.Error, light: false, branding: false, customization: false, plugins: false}; let shutdownFlag = false; +let expDocumentsStep = gc.getCronStep(cfgExpDocumentsCron); const MIN_SAVE_EXPIRATION = 60000; const FORCE_SAVE_EXPIRATION = Math.min(Math.max(cfgForceSaveInterval, MIN_SAVE_EXPIRATION), @@ -450,7 +451,7 @@ function removePresence(conn) { } let changeConnectionInfo = co.wrap(function*(conn, cmd) { - if (conn.canChangeName && conn.user) { + if (!conn.denyChangeName && conn.user) { yield* publish({type: commonDefines.c_oPublishType.changeConnecitonInfo, docId: conn.docId, useridoriginal: conn.user.idOriginal, cmd: cmd}); return true; } @@ -476,7 +477,7 @@ function fillJwtByConnection(conn) { edit.ds_view = conn.user.view; edit.ds_isCloseCoAuthoring = conn.isCloseCoAuthoring; edit.ds_isEnterCorrectPassword = conn.isEnterCorrectPassword; - edit.ds_canChangeName = conn.canChangeName; + edit.ds_denyChangeName = conn.denyChangeName; var options = {algorithm: cfgTokenSessionAlgorithm, expiresIn: cfgTokenSessionExpires / 1000}; var secret = utils.getSecretByElem(cfgSecretSession); @@ -707,6 +708,19 @@ function* getChangesIndex(docId) { } return res; } + +const hasChanges = co.wrap(function*(docId) { + //todo check editorData.getForceSave in case of "undo all changes" + let puckerIndex = yield* getChangesIndex(docId); + if (0 === puckerIndex) { + let selectRes = yield taskResult.select(docId); + if (selectRes.length > 0 && selectRes[0].password) { + return sqlBase.DocumentPassword.prototype.hasPasswordChanges(docId, selectRes[0].password); + } + return false; + } + return true; +}); function* setForceSave(docId, forceSave, cmd, success) { let forceSaveType = forceSave.getType(); if (commonDefines.c_oAscForceSaveTypes.Form !== forceSaveType) { @@ -767,7 +781,7 @@ let startForceSave = co.wrap(function*(docId, type, opt_userdata, opt_userId, op priority = constants.QUEUE_PRIORITY_LOW; } //start new convert - let status = yield* converterService.convertFromChanges(docId, baseUrl, forceSave, opt_userdata, + let status = yield* converterService.convertFromChanges(docId, baseUrl, forceSave, startedForceSave.changeInfo, opt_userdata, opt_userConnectionId, opt_responseKey, priority, expiration, opt_queue); if (constants.NO_ERROR === status.err) { res.time = forceSave.getTime(); @@ -781,6 +795,19 @@ let startForceSave = co.wrap(function*(docId, type, opt_userdata, opt_userId, op logger.debug('startForceSave end:docId = %s', docId); return res; }); +function getExternalChangeInfo(user, date) { + return {user_id: user.id, user_id_original: user.idOriginal, user_name: user.username, change_date: date}; +} +let resetForceSaveAfterChanges = co.wrap(function*(docId, newChangesLastTime, puckerIndex, baseUrl, changeInfo) { + //last save + if (newChangesLastTime) { + yield editorData.setForceSave(docId, newChangesLastTime, puckerIndex, baseUrl, changeInfo); + if (cfgForceSaveEnable) { + let expireAt = newChangesLastTime + cfgForceSaveInterval; + yield editorData.addForceSaveTimerNX(docId, expireAt); + } + } +}); function* startRPC(conn, responseKey, data) { let docId = conn.docId; logger.debug('startRPC start responseKey:%s , %j:docId = %s', responseKey, data, docId); @@ -856,8 +883,8 @@ function* sendStatusDocument(docId, bChangeBase, opt_userAction, opt_userIndex, var status = c_oAscServerStatus.Editing; var participants = yield* getOriginalParticipantsId(docId); if (0 === participants.length) { - var puckerIndex = yield* getChangesIndex(docId); - if (!(puckerIndex > 0) || opt_forceClose) { + let bHasChanges = yield hasChanges(docId); + if (!bHasChanges || opt_forceClose) { status = c_oAscServerStatus.Closed; } } @@ -1004,6 +1031,7 @@ function* cleanDocumentOnExit(docId, deleteChanges) { yield editorData.cleanDocumentOnExit(docId); //remove changes if (deleteChanges) { + yield taskResult.restoreInitialPassword(docId); sqlBase.deleteChanges(docId, null); //delete forgotten after successful send on callbackUrl yield storage.deletePath(cfgForgottenFiles + '/' + docId); @@ -1180,11 +1208,13 @@ exports.hasEditors = hasEditors; exports.getEditorsCountPromise = co.wrap(getEditorsCount); exports.getCallback = getCallback; exports.getIsShutdown = getIsShutdown; -exports.getChangesIndexPromise = co.wrap(getChangesIndex); +exports.hasChanges = hasChanges; exports.cleanDocumentOnExitPromise = co.wrap(cleanDocumentOnExit); exports.cleanDocumentOnExitNoChangesPromise = co.wrap(cleanDocumentOnExitNoChanges); exports.setForceSave = setForceSave; exports.startForceSave = startForceSave; +exports.resetForceSaveAfterChanges = resetForceSaveAfterChanges; +exports.getExternalChangeInfo = getExternalChangeInfo; exports.checkJwt = checkJwt; exports.getRequestParams = getRequestParams; exports.checkJwtHeader = checkJwtHeader; @@ -1399,8 +1429,7 @@ exports.install = function(server, callbackFunction) { // Только если редактируем if (false === isView) { bHasEditors = yield* hasEditors(docId, hvals); - var puckerIndex = yield* getChangesIndex(docId); - bHasChanges = puckerIndex > 0; + bHasChanges = yield hasChanges(docId); let needSendStatus = true; if (conn.encrypted) { @@ -1865,7 +1894,7 @@ exports.install = function(server, callbackFunction) { data.isCloseCoAuthoring = edit.ds_isCloseCoAuthoring; } data.isEnterCorrectPassword = edit.ds_isEnterCorrectPassword; - data.canChangeName = edit.ds_canChangeName; + data.denyChangeName = edit.ds_denyChangeName; if (edit.user) { var dataUser = data.user; var user = edit.user; @@ -1884,12 +1913,12 @@ exports.install = function(server, callbackFunction) { if (null != user.lastname) { dataUser.lastname = user.lastname; } - if (null != user.name) { + if (user.name) { dataUser.username = user.name; } } - if (!(edit.user && edit.user.name)) { - data.canChangeName = true; + if (edit.user && edit.user.name) { + data.denyChangeName = true; } } @@ -1991,7 +2020,7 @@ exports.install = function(server, callbackFunction) { }; conn.isCloseCoAuthoring = data.isCloseCoAuthoring; conn.isEnterCorrectPassword = data.isEnterCorrectPassword; - conn.canChangeName = data.canChangeName; + conn.denyChangeName = data.denyChangeName; conn.editorType = data['editorType']; if (data.sessionTimeConnect) { conn.sessionTimeConnect = data.sessionTimeConnect; @@ -2539,13 +2568,8 @@ exports.install = function(server, callbackFunction) { // Автоматически снимаем lock сами и посылаем индекс для сохранения yield* unSaveLock(conn, changesIndex, newChangesLastTime); //last save - if (newChangesLastTime) { - yield editorData.setForceSave(docId, newChangesLastTime, puckerIndex, utils.getBaseUrlByConnection(conn)); - if (cfgForceSaveEnable) { - let expireAt = newChangesLastTime + cfgForceSaveInterval; - yield editorData.addForceSaveTimerNX(docId, expireAt); - } - } + let changeInfo = getExternalChangeInfo(conn.user, newChangesLastTime); + yield resetForceSaveAfterChanges(docId, newChangesLastTime, puckerIndex, utils.getBaseUrlByConnection(conn), changeInfo); } else { let changesToSend = arrNewDocumentChanges; if(changesToSend.length > cfgPubSubMaxChanges) { @@ -2952,7 +2976,7 @@ exports.install = function(server, callbackFunction) { participants = getParticipants(data.docId); for (i = 0; i < participants.length; ++i) { participant = participants[i]; - if (participant.canChangeName && participant.user.idOriginal === data.useridoriginal) { + if (!participant.denyChangeName && participant.user.idOriginal === data.useridoriginal) { hasChanges = true; logger.debug('changeConnectionInfo: docId = %s, userId = %s', data.docId, data.useridoriginal); participant.user.username = cmd.getUserName(); @@ -2988,15 +3012,13 @@ exports.install = function(server, callbackFunction) { yield editorData.setEditorConnections(countEdit, countView, now, PRECISION); } function expireDoc() { - var cronJob = this; return co(function* () { try { var countEditByShard = 0; var countViewByShard = 0; logger.debug('expireDoc connections.length = %d', connections.length); var nowMs = new Date().getTime(); - var nextMs = cronJob.nextDate(); - var maxMs = Math.max(nowMs + cfgExpSessionCloseCommand, nextMs); + var maxMs = nowMs + Math.max(cfgExpSessionCloseCommand, expDocumentsStep); for (var i = 0; i < connections.length; ++i) { var conn = connections[i]; if (cfgExpSessionAbsolute > 0) { @@ -3045,16 +3067,12 @@ exports.install = function(server, callbackFunction) { } } catch (err) { logger.error('expireDoc error:\r\n%s', err.stack); + } finally { + setTimeout(expireDoc, expDocumentsStep); } }); } - var innerPingJob = function(opt_isStart) { - if (!opt_isStart) { - logger.warn('expireDoc restart'); - } - new cron.CronJob(cfgExpDocumentsCron, expireDoc, innerPingJob, true); - }; - innerPingJob(true); + setTimeout(expireDoc, expDocumentsStep); pubsub = new pubsubService(); pubsub.on('message', pubsubOnMessage); @@ -3206,12 +3224,26 @@ exports.licenseInfo = function(req, res) { } }); }; +let commandLicense = co.wrap(function*() { + let res = { + license: utils.convertLicenseInfoToFileParams(licenseInfo), + server: utils.convertLicenseInfoToServerParams(licenseInfo), quota: {users: []} + }; + const nowUTC = getLicenseNowUtc(); + let scores = []; + let execRes = yield editorData.getPresenceUniqueUser(nowUTC, scores); + execRes.forEach(function(currentValue, index) { + res.quota.users.push({userid: currentValue, expire: new Date(scores[index] * 1000)}); + }); + return res; +}); // Команда с сервера (в частности teamlab) exports.commandFromServer = function (req, res) { return co(function* () { let result = commonDefines.c_oAscServerCommandErrors.NoError; let docId = 'commandFromServer'; let version = undefined; + let outputLicense = undefined; try { let authRes = getRequestParams(docId, req); let params = authRes.params; @@ -3222,7 +3254,7 @@ exports.commandFromServer = function (req, res) { } // Ключ id-документа docId = params.key; - if (commonDefines.c_oAscServerCommandErrors.NoError === result && null == docId && 'version' != params.c) { + if (commonDefines.c_oAscServerCommandErrors.NoError === result && null == docId && 'version' !== params.c && 'license' !== params.c) { result = commonDefines.c_oAscServerCommandErrors.DocumentIdError; } else if(commonDefines.c_oAscServerCommandErrors.NoError === result) { logger.debug('Start commandFromServer: docId = %s c = %s', docId, params.c); @@ -3270,6 +3302,9 @@ exports.commandFromServer = function (req, res) { case 'version': version = commonDefines.buildVersion + '.' + commonDefines.buildNumber; break; + case 'license': + outputLicense = yield commandLicense(); + break; default: result = commonDefines.c_oAscServerCommandErrors.UnknownCommand; break; @@ -3280,9 +3315,12 @@ exports.commandFromServer = function (req, res) { logger.error('Error commandFromServer: docId = %s\r\n%s', docId, err.stack); } finally { //undefined value are excluded in JSON.stringify - const output = JSON.stringify({'key': docId, 'error': result, 'version': version}); - logger.debug('End commandFromServer: docId = %s %s', docId, output); - const outputBuffer = Buffer.from(output, 'utf8'); + let output = {'key': docId, 'error': result, 'version': version}; + if (outputLicense) { + Object.assign(output, outputLicense); + } + logger.debug('End commandFromServer: docId = %s %j', docId, output); + const outputBuffer = Buffer.from(JSON.stringify(output), 'utf8'); res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Length', outputBuffer.length); res.send(outputBuffer); diff --git a/DocService/sources/baseConnector.js b/DocService/sources/baseConnector.js index 95c857fa6..eafc0b6e6 100644 --- a/DocService/sources/baseConnector.js +++ b/DocService/sources/baseConnector.js @@ -314,3 +314,55 @@ UserCallback.prototype.getCallbackByUserIndex = function(docId, callbacksStr, op return callbackUrl; }; exports.UserCallback = UserCallback; + +function DocumentPassword() { + this.password = undefined; + this.change = undefined; +} +DocumentPassword.prototype.fromString = function(passwordStr){ + var parsed = JSON.parse(passwordStr); + this.fromValues(parsed.password, parsed.change); +}; +DocumentPassword.prototype.fromValues = function(password, change){ + if(null !== password){ + this.password = password; + } + if(null !== change) { + this.change = change; + } +}; +DocumentPassword.prototype.delimiter = String.fromCharCode(5); +DocumentPassword.prototype.toSQLInsert = function(){ + return this.delimiter + JSON.stringify(this); +}; +DocumentPassword.prototype.isInitial = function(){ + return !this.change; +}; +DocumentPassword.prototype.getDocPassword = function(docId, docPasswordStr) { + let res = {initial: undefined, current: undefined, change: undefined}; + if (docPasswordStr) { + logger.debug("getDocPassword: docId = %s passwords = %s", docId, docPasswordStr); + let passwords = docPasswordStr.split(UserCallback.prototype.delimiter); + + for (let i = 1; i < passwords.length; ++i) { + let password = new DocumentPassword(); + password.fromString(passwords[i]); + if (password.isInitial()) { + res.initial = password.password; + } else { + res.change = password.change; + } + res.current = password.password; + } + } + return res; +}; +DocumentPassword.prototype.getCurPassword = function(docId, docPasswordStr) { + let docPassword = this.getDocPassword(docId, docPasswordStr); + return docPassword.current; +}; +DocumentPassword.prototype.hasPasswordChanges = function(docId, docPasswordStr) { + let docPassword = this.getDocPassword(docId, docPasswordStr); + return docPassword.initial !== docPassword.current; +}; +exports.DocumentPassword = DocumentPassword; diff --git a/DocService/sources/canvasservice.js b/DocService/sources/canvasservice.js index d665c80ca..887a8e4b5 100644 --- a/DocService/sources/canvasservice.js +++ b/DocService/sources/canvasservice.js @@ -136,7 +136,7 @@ function* getOutputData(cmd, outputData, key, optConn, optAdditionalOutput, opt_ let row = selectRes[0]; status = row.status; statusInfo = row.status_info; - password = row.password; + password = sqlBase.DocumentPassword.prototype.getCurPassword(key, row.password); creationDate = row.created_at && row.created_at.getTime(); } switch (status) { @@ -255,6 +255,15 @@ function* addRandomKeyTaskCmd(cmd) { var task = yield* taskResult.addRandomKeyTask(cmd.getDocId()); cmd.setSaveKey(task.key); } +function addPasswordToCmd(cmd, docPasswordStr) { + let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(cmd.getDocId(), docPasswordStr); + if (docPassword.current) { + cmd.setSavePassword(docPassword.current); + } + if (docPassword.change) { + cmd.setExternalChangeInfo(docPassword.change); + } +} function* saveParts(cmd, filename) { var result = false; var saveType = cmd.getSaveType(); @@ -419,7 +428,7 @@ function* commandReopen(conn, cmd, outputData) { let selectRes = yield taskResult.select(cmd.getDocId()); if (selectRes.length > 0) { let row = selectRes[0]; - if (row.password) { + if (sqlBase.DocumentPassword.prototype.getCurPassword(cmd.getDocId(), row.password)) { logger.debug('commandReopen has password: docId = %s', cmd.getDocId()); yield* commandOpenFillOutput(conn, cmd, outputData, false); docsCoServer.modifyConnectionForPassword(conn, constants.FILE_STATUS_OK === outputData.getStatus()); @@ -502,9 +511,7 @@ function* commandSfctByCmd(cmd, opt_priority, opt_expiration, opt_queue) { yield* addRandomKeyTaskCmd(cmd); var selectRes = yield taskResult.select(cmd.getDocId()); var row = selectRes.length > 0 ? selectRes[0] : null; - if (row && row.password) { - cmd.setSavePassword(row.password); - } + addPasswordToCmd(cmd, row && row.password); var queueData = getSaveTask(cmd); queueData.setFromChanges(true); let priority = null != opt_priority ? opt_priority : constants.QUEUE_PRIORITY_LOW; @@ -699,7 +706,7 @@ function* commandSetPassword(conn, cmd, outputData) { if (selectRes.length > 0) { let row = selectRes[0]; hasPasswordCol = undefined !== row.password; - if (taskResult.FileStatus.Ok === row.status && row.password) { + if (taskResult.FileStatus.Ok === row.status && sqlBase.DocumentPassword.prototype.getCurPassword(cmd.getDocId(), row.password)) { hasDocumentPassword = true; } } @@ -709,9 +716,16 @@ function* commandSetPassword(conn, cmd, outputData) { updateMask.key = cmd.getDocId(); updateMask.status = taskResult.FileStatus.Ok; + let newChangesLastDate = new Date(); + newChangesLastDate.setMilliseconds(0);//remove milliseconds avoid issues with MySQL datetime rounding + var task = new taskResult.TaskResultData(); task.key = cmd.getDocId(); task.password = cmd.getPassword() || ""; + let changeInfo = null; + if (conn.user) { + changeInfo = task.innerPasswordChange = docsCoServer.getExternalChangeInfo(conn.user, newChangesLastDate.getTime()); + } var upsertRes = yield taskResult.updateIf(task, updateMask); if (upsertRes.affectedRows > 0) { @@ -719,6 +733,7 @@ function* commandSetPassword(conn, cmd, outputData) { if (!conn.isEnterCorrectPassword) { docsCoServer.modifyConnectionForPassword(conn, true); } + yield docsCoServer.resetForceSaveAfterChanges(cmd.getDocId(), newChangesLastDate.getTime(), 0, utils.getBaseUrlByConnection(conn), changeInfo); } else { logger.debug('commandSetPassword sql update error: docId = %s', cmd.getDocId()); outputData.setStatus('err'); @@ -815,7 +830,7 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { } else { isError = true; } - if (getRes) { + if (getRes && userLastChangeId) { logger.debug('Callback commandSfcCallback: docId = %s callback = %s', docId, getRes.server.href); var outputSfc = new commonDefines.OutputSfcData(); outputSfc.setKey(docId); @@ -954,7 +969,7 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { } } } else { - logger.warn('Empty Callback commandSfcCallback: docId = %s', docId); + logger.warn('Empty Callback or userLastChangeId=%s commandSfcCallback: docId = %s', userLastChangeId, docId); storeForgotten = true; } if (undefined !== updateIfTask && !isSfcm) { @@ -1172,8 +1187,8 @@ exports.downloadAs = function(req, res) { } var selectRes = yield taskResult.select(docId); var row = selectRes.length > 0 ? selectRes[0] : null; - if (row && row.password) { - cmd.setSavePassword(row.password); + if (!cmd.getWithoutPassword()) { + addPasswordToCmd(cmd, row && row.password); } cmd.setData(req.body); var outputData = new OutputData(cmd.getCommand()); @@ -1289,9 +1304,7 @@ exports.saveFromChanges = function(docId, statusInfo, optFormat, opt_userId, opt cmd.setStatusInfoIn(statusInfo); cmd.setUserActionId(opt_userId); cmd.setUserActionIndex(opt_userIndex); - if (row.password) { - cmd.setSavePassword(row.password); - } + addPasswordToCmd(cmd, row && row.password); yield* addRandomKeyTaskCmd(cmd); var queueData = getSaveTask(cmd); queueData.setFromChanges(true); diff --git a/DocService/sources/converterservice.js b/DocService/sources/converterservice.js index fef043a24..a539d1676 100644 --- a/DocService/sources/converterservice.js +++ b/DocService/sources/converterservice.js @@ -148,8 +148,8 @@ function* convertByCmd(cmd, async, baseUrl, opt_fileTo, opt_taskExist, opt_prior return status; } -function* convertFromChanges(docId, baseUrl, forceSave, opt_userdata, opt_userConnectionId, opt_responseKey, opt_priority, - opt_expiration, opt_queue, opt_redisKey) { +function* convertFromChanges(docId, baseUrl, forceSave, externalChangeInfo, opt_userdata, opt_userConnectionId, + opt_responseKey, opt_priority, opt_expiration, opt_queue, opt_redisKey) { var cmd = new commonDefines.InputCommand(); cmd.setCommand('sfcm'); cmd.setDocId(docId); @@ -158,6 +158,7 @@ function* convertFromChanges(docId, baseUrl, forceSave, opt_userdata, opt_userCo cmd.setCodepage(commonDefines.c_oAscCodePageUtf8); cmd.setDelimiter(commonDefines.c_oAscCsvDelimiter.Comma); cmd.setForceSave(forceSave); + cmd.setExternalChangeInfo(externalChangeInfo); if (opt_userdata) { cmd.setUserData(opt_userdata); } diff --git a/DocService/sources/editorDataMemory.js b/DocService/sources/editorDataMemory.js index 22b560339..2915c4545 100644 --- a/DocService/sources/editorDataMemory.js +++ b/DocService/sources/editorDataMemory.js @@ -162,9 +162,9 @@ EditorData.prototype.getdelSaved = function(docId) { data.saved = undefined; return Promise.resolve(res); }; -EditorData.prototype.setForceSave = function(docId, time, index, baseUrl) { +EditorData.prototype.setForceSave = function(docId, time, index, baseUrl, changeInfo) { let data = this._getDocumentData(docId); - data.forceSave = {time: time, index: index, baseUrl: baseUrl, started: false, ended: false}; + data.forceSave = {time: time, index: index, baseUrl: baseUrl, changeInfo: changeInfo, started: false, ended: false}; return Promise.resolve(); }; EditorData.prototype.getForceSave = function(docId) { @@ -226,12 +226,15 @@ EditorData.prototype.addPresenceUniqueUser = function(userId, expireAt) { this.uniqueUser[userId] = expireAt; return Promise.resolve(); }; -EditorData.prototype.getPresenceUniqueUser = function(nowUTC) { +EditorData.prototype.getPresenceUniqueUser = function(nowUTC, opt_scores) { let res = []; for (let userId in this.uniqueUser) { if (this.uniqueUser.hasOwnProperty(userId)) { if (this.uniqueUser[userId] > nowUTC) { res.push(userId); + if(opt_scores) { + opt_scores.push(this.uniqueUser[userId]); + } } else { delete this.uniqueUser[userId]; } diff --git a/DocService/sources/gc.js b/DocService/sources/gc.js index c9c983751..bff66a8d3 100644 --- a/DocService/sources/gc.js +++ b/DocService/sources/gc.js @@ -47,7 +47,6 @@ var constants = require('./../../Common/sources/constants'); var commondefines = require('./../../Common/sources/commondefines'); var queueService = require('./../../Common/sources/taskqueueRabbitMQ'); var pubsubService = require('./pubsubRabbitMQ'); -const editorDataStorage = require('./' + configCommon.get('services.CoAuthoring.server.editorDataStorage')); var cfgExpFilesCron = config.get('expire.filesCron'); var cfgExpDocumentsCron = config.get('expire.documentsCron'); @@ -56,6 +55,14 @@ var cfgExpFilesRemovedAtOnce = config.get('expire.filesremovedatonce'); var cfgForceSaveEnable = config.get('autoAssembly.enable'); var cfgForceSaveStep = ms(config.get('autoAssembly.step')); +function getCronStep(cronTime){ + let cronJob = new cron.CronJob(cronTime, function(){}); + let dates = cronJob.nextDates(2); + return dates[1] - dates[0]; +} +let expFilesStep = getCronStep(cfgExpFilesCron); +let expDocumentsStep = getCronStep(cfgExpDocumentsCron); + var checkFileExpire = function() { return co(function* () { try { @@ -83,6 +90,8 @@ var checkFileExpire = function() { logger.debug('checkFileExpire end: removedCount = %d', removedCount); } catch (e) { logger.error('checkFileExpire error:\r\n%s', e.stack); + } finally { + setTimeout(checkFileExpire, expFilesStep); } }); }; @@ -102,8 +111,8 @@ var checkDocumentExpire = function() { for (var i = 0; i < expiredKeys.length; ++i) { var docId = expiredKeys[i]; if (docId) { - var puckerIndex = yield docsCoServer.getChangesIndexPromise(docId); - if (puckerIndex > 0) { + var hasChanges = yield docsCoServer.hasChanges(docId); + if (hasChanges) { yield docsCoServer.createSaveTimerPromise(docId, null, null, queue, true); startSaveCount++; } else { @@ -124,6 +133,7 @@ var checkDocumentExpire = function() { logger.error('checkDocumentExpire error:\r\n%s', e.stack); } logger.debug('checkDocumentExpire end: startSaveCount = %d, removedCount = %d', startSaveCount, removedCount); + setTimeout(checkDocumentExpire, expDocumentsStep); } }); }; @@ -172,24 +182,11 @@ let forceSaveTimeout = function() { }); }; -var documentExpireJob = function(opt_isStart) { - if (!opt_isStart) { - logger.warn('checkDocumentExpire restart'); - } - new cron.CronJob(cfgExpDocumentsCron, checkDocumentExpire, documentExpireJob, true); -}; - -var fileExpireJob = function(opt_isStart) { - if (!opt_isStart) { - logger.warn('checkFileExpire restart'); - } - new cron.CronJob(cfgExpFilesCron, checkFileExpire, fileExpireJob, true); -}; - exports.startGC = function() { - documentExpireJob(true); - fileExpireJob(true); + setTimeout(checkDocumentExpire, expDocumentsStep); + setTimeout(checkFileExpire, expFilesStep); if (cfgForceSaveEnable) { setTimeout(forceSaveTimeout, cfgForceSaveStep); } }; +exports.getCronStep = getCronStep; diff --git a/DocService/sources/mySqlBaseConnector.js b/DocService/sources/mySqlBaseConnector.js index 1c04fd7d1..69c594bb2 100644 --- a/DocService/sources/mySqlBaseConnector.js +++ b/DocService/sources/mySqlBaseConnector.js @@ -78,7 +78,7 @@ let addSqlParam = function (val, values) { }; exports.addSqlParameter = addSqlParam; let concatParams = function (val1, val2) { - return `CONCAT(${val1}, ${val2})`; + return `CONCAT(COALESCE(${val1}, ''), COALESCE(${val2}, ''))`; }; exports.concatParams = concatParams; diff --git a/DocService/sources/postgreSqlBaseConnector.js b/DocService/sources/postgreSqlBaseConnector.js index 0c54b5d0d..204af6690 100644 --- a/DocService/sources/postgreSqlBaseConnector.js +++ b/DocService/sources/postgreSqlBaseConnector.js @@ -96,7 +96,7 @@ let addSqlParam = function (val, values) { }; exports.addSqlParameter = addSqlParam; let concatParams = function (val1, val2) { - return `${val1} || ${val2}`; + return `COALESCE(${val1}, '') || COALESCE(${val2}, '')`; }; exports.concatParams = concatParams; var isSupportOnConflict = true; diff --git a/DocService/sources/taskresult.js b/DocService/sources/taskresult.js index 9bf15d1f7..708da59a1 100644 --- a/DocService/sources/taskresult.js +++ b/DocService/sources/taskresult.js @@ -66,6 +66,8 @@ function TaskResultData() { this.callback = null; this.baseurl = null; this.password = null; + + this.innerPasswordChange = null;//not a DB field } TaskResultData.prototype.completeDefaults = function() { if (!this.key) { @@ -115,7 +117,7 @@ function select(docId) { }, undefined, undefined, values); }); } -function toUpdateArray(task, updateTime, isMask, values) { +function toUpdateArray(task, updateTime, isMask, values, setPassword) { var res = []; if (null != task.status) { let sqlParam = addSqlParam(task.status, values); @@ -151,17 +153,22 @@ function toUpdateArray(task, updateTime, isMask, values) { let sqlParam = addSqlParam(task.baseurl, values); res.push(`baseurl=${sqlParam}`); } - if (null != task.password) { + if (setPassword) { let sqlParam = addSqlParam(task.password, values); res.push(`password=${sqlParam}`); + } else if (null != task.password || setPassword) { + var documentPassword = new sqlBase.DocumentPassword(); + documentPassword.fromValues(task.password, task.innerPasswordChange); + let sqlParam = addSqlParam(documentPassword.toSQLInsert(), values); + res.push(`password=${concatParams('password', sqlParam)}`); } return res; } -function update(task) { +function update(task, setPassword) { return new Promise(function(resolve, reject) { let values = []; - let updateElems = toUpdateArray(task, true, false, values); + let updateElems = toUpdateArray(task, true, false, values, setPassword); let sqlSet = updateElems.join(', '); let sqlParam = addSqlParam(task.key, values); let sqlCommand = `UPDATE task_result SET ${sqlSet} WHERE id=${sqlParam};`; @@ -178,8 +185,8 @@ function update(task) { function updateIf(task, mask) { return new Promise(function(resolve, reject) { let values = []; - let commandArg = toUpdateArray(task, true, false, values); - let commandArgMask = toUpdateArray(mask, false, true, values); + let commandArg = toUpdateArray(task, true, false, values, false); + let commandArgMask = toUpdateArray(mask, false, true, values, false); commandArgMask.push('id=' + addSqlParam(mask.key, values)); let sqlSet = commandArg.join(', '); let sqlWhere = commandArgMask.join(' AND '); @@ -193,6 +200,25 @@ function updateIf(task, mask) { }, undefined, undefined, values); }); } +function restoreInitialPassword(docId) { + return select(docId).then(function(selectRes) { + if (selectRes.length > 0) { + var row = selectRes[0]; + let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(docId, row.password); + var updateTask = new TaskResultData(); + updateTask.key = docId; + if (docPassword.initial) { + var documentPassword = new sqlBase.DocumentPassword(); + documentPassword.fromValues(docPassword.initial); + updateTask.password = documentPassword.toSQLInsert(); + return update(updateTask, true); + } else if (docPassword.current) { + updateTask.password = null; + return update(updateTask, true); + } + } + }); +} function addRandomKey(task, opt_prefix, opt_size) { return new Promise(function(resolve, reject) { @@ -286,6 +312,7 @@ exports.upsert = upsert; exports.select = select; exports.update = update; exports.updateIf = updateIf; +exports.restoreInitialPassword = restoreInitialPassword; exports.addRandomKeyTask = addRandomKeyTask; exports.remove = remove; exports.getExpired = getExpired; diff --git a/FileConverter/sources/converter.js b/FileConverter/sources/converter.js index 24a77b39c..8ac3df229 100644 --- a/FileConverter/sources/converter.js +++ b/FileConverter/sources/converter.js @@ -60,6 +60,7 @@ var cfgPresentationThemesDir = configConverter.get('presentationThemesDir'); var cfgX2tPath = configConverter.get('x2tPath'); var cfgDocbuilderPath = configConverter.get('docbuilderPath'); var cfgDocbuilderAllFontsPath = configConverter.get('docbuilderAllFontsPath'); +var cfgDocbuilderCoreFontsPath = configConverter.get('docbuilderCoreFontsPath'); var cfgArgs = configConverter.get('args'); var cfgSpawnOptions = configConverter.get('spawnOptions'); if (cfgSpawnOptions.env) { @@ -91,6 +92,7 @@ function TaskQueueDataConvert(task) { this.key = cmd.savekey ? cmd.savekey : cmd.id; this.fileFrom = null; this.fileTo = null; + this.title = cmd.getTitle(); if(constants.AVS_OFFICESTUDIO_FILE_OTHER_PDFA !== cmd.outputformat){ this.formatTo = cmd.outputformat; } else { @@ -127,6 +129,7 @@ TaskQueueDataConvert.prototype = { xml += this.serializeXmlProp('m_sKey', this.key); xml += this.serializeXmlProp('m_sFileFrom', this.fileFrom); xml += this.serializeXmlProp('m_sFileTo', this.fileTo); + xml += this.serializeXmlProp('m_sTitle', this.title); xml += this.serializeXmlProp('m_nFormatTo', this.formatTo); xml += this.serializeXmlProp('m_bIsPDFA', this.isPDFA); xml += this.serializeXmlProp('m_nCsvTxtEncoding', this.csvTxtEncoding); @@ -404,11 +407,28 @@ function* processChanges(tempDirs, cmd, authorProps) { forceSaveTime = forceSave.getTime(); forceSaveIndex = forceSave.getIndex(); } + let extChangeInfo = cmd.getExternalChangeInfo(); + let extChanges; + if (extChangeInfo) { + extChanges = [{ + id: cmd.getDocId(), change_id: 0, change_data: "", user_id: extChangeInfo.user_id, + user_id_original: extChangeInfo.user_id_original, user_name: extChangeInfo.user_name, + change_date: new Date(extChangeInfo.change_date) + }]; + } + let streamObj = yield* streamCreate(cmd.getDocId(), changesDir, indexFile++, {highWaterMark: cfgStreamWriterBufferSize}); let curIndexStart = 0; let curIndexEnd = Math.min(curIndexStart + cfgMaxRequestChanges, forceSaveIndex); - while (curIndexStart < curIndexEnd) { - let changes = yield baseConnector.getChangesPromise(cmd.getDocId(), curIndexStart, curIndexEnd, forceSaveTime); + while (curIndexStart < curIndexEnd || extChanges) { + let changes = []; + if (curIndexStart < curIndexEnd) { + changes = yield baseConnector.getChangesPromise(cmd.getDocId(), curIndexStart, curIndexEnd, forceSaveTime); + } + if (0 === changes.length && extChanges) { + changes = extChanges; + } + extChanges = undefined; for (let i = 0; i < changes.length; ++i) { let change = changes[i]; if (change.change_data.startsWith('ENCRYPTED;')) { @@ -424,8 +444,7 @@ function* processChanges(tempDirs, cmd, authorProps) { } changesAuthor = change.user_id_original; changesIndex = utils.getIndexFromUserId(change.user_id, change.user_id_original); - authorProps.lastModifiedBy = change.user_name; - authorProps.modified = change.change_date.toISOString().slice(0, 19) + 'Z'; + let strDate = baseConnector.getDateTime(change.change_date); changesHistory.changes.push({'created': strDate, 'user': {'id': changesAuthor, 'name': change.user_name}}); yield* streamWrite(streamObj, '['); @@ -435,6 +454,10 @@ function* processChanges(tempDirs, cmd, authorProps) { yield* streamWrite(streamObj, change.change_data); streamObj.isNoChangesInFile = false; } + if (changes.length > 0) { + authorProps.lastModifiedBy = changes[changes.length - 1].user_name; + authorProps.modified = changes[changes.length - 1].change_date.toISOString().slice(0, 19) + 'Z'; + } if (changes.length === curIndexEnd - curIndexStart) { curIndexStart += cfgMaxRequestChanges; curIndexEnd = Math.min(curIndexStart + cfgMaxRequestChanges, forceSaveIndex); @@ -678,6 +701,9 @@ function* ExecuteTask(task) { fs.mkdirSync(path.join(tempDirs.result, 'output')); processPath = cfgDocbuilderPath; childArgs.push('--all-fonts-path=' + cfgDocbuilderAllFontsPath); + if (cfgDocbuilderCoreFontsPath) { + childArgs.push('--fonts-dir=' + cfgDocbuilderCoreFontsPath); + } childArgs.push('--save-use-only-names=' + tempDirs.result + '/output'); childArgs.push(dataConvert.fileFrom); } diff --git a/Makefile b/Makefile index 40bf93efc..c105f17e5 100644 --- a/Makefile +++ b/Makefile @@ -34,11 +34,25 @@ else PLATFORM := linux SHARED_EXT := .so* LIB_PREFIX := lib + SED_FLAGS := + USER_ADD := useradd -m -d /var/www/onlyoffice -r -U onlyoffice + USER_DEL := userdel onlyoffice + endif + ifeq ($(UNAME_S),FreeBSD) + PLATFORM := linux + SHARED_EXT := .so* + LIB_PREFIX := lib + SED_FLAGS := \"\" + USER_ADD := pw user add -m -d /var/www/onlyoffice -n onlyoffice + USER_DEL := pw user del -n onlyoffice endif UNAME_M := $(shell uname -m) ifeq ($(UNAME_M),x86_64) ARCHITECTURE := 64 endif + ifeq ($(UNAME_M),amd64) + ARCHITECTURE := 64 + endif ifneq ($(filter %86,$(UNAME_M)),) ARCHITECTURE := 32 endif @@ -86,66 +100,66 @@ DEBUG = $(BRANDING_DIR)/debug.js all: $(SPELLCHECKER_DICTIONARIES) $(TOOLS) $(SCHEMA) $(CORE_FONTS) $(LICENSE) $(WELCOME) $(INFO) build-date build-date: $(GRUNT_FILES) - sed "s|\(const buildVersion = \).*|\1'${PRODUCT_VERSION}';|" -i $(COMMON_DEFINES_JS) - sed "s|\(const buildNumber = \).*|\1${BUILD_NUMBER};|" -i $(COMMON_DEFINES_JS) - sed "s|\(const buildDate = \).*|\1'$$(date +%F)';|" -i $(LICENSE_JS) + sed -e "s|\(const buildVersion = \).*|\1'${PRODUCT_VERSION}';|" -i$(SED_FLAGS) $(COMMON_DEFINES_JS) + sed -e "s|\(const buildNumber = \).*|\1${BUILD_NUMBER};|" -i$(SED_FLAGS) $(COMMON_DEFINES_JS) + sed -e "s|\(const buildDate = \).*|\1'$$(date +%F)';|" -i$(SED_FLAGS) $(LICENSE_JS) test -e $(DEBUG) && \ cp $(DEBUG) $(OUTPUT)/Common/sources || true $(SPELLCHECKER_DICTIONARIES): $(GRUNT_FILES) mkdir -p $(SPELLCHECKER_DICTIONARIES) && \ - cp -r -t $(SPELLCHECKER_DICTIONARIES) $(SPELLCHECKER_DICTIONARY_FILES) + cp -r $(SPELLCHECKER_DICTIONARY_FILES) $(SPELLCHECKER_DICTIONARIES) $(SCHEMA): mkdir -p $(SCHEMA) && \ - cp -r -t $(SCHEMA) $(SCHEMA_FILES) + cp -r $(SCHEMA_FILES) $(SCHEMA) $(TOOLS): mkdir -p $(TOOLS) && \ - cp -r -t $(TOOLS) $(TOOLS_FILES) + cp -r $(TOOLS_FILES) $(TOOLS) $(LICENSE): mkdir -p $(OUTPUT) && \ - cp -r -t $(OUTPUT) $(LICENSE_FILES) + cp -r $(LICENSE_FILES) $(OUTPUT) $(GRUNT_FILES): cd $(@D) && \ npm install && \ $(GRUNT_ENV) $(GRUNT) $(GRUNT_FLAGS) mkdir -p $(OUTPUT) - cp -r -t $(OUTPUT) ./build/server/* + cp -r ./build/server/* $(OUTPUT) echo "Done" > $@ $(WELCOME): mkdir -p $(WELCOME) && \ - cp -r -t $(WELCOME) $(WELCOME_FILES) + cp -r $(WELCOME_FILES) $(WELCOME) $(INFO): mkdir -p $(INFO) && \ - cp -r -t $(INFO) $(INFO_FILES) + cp -r $(INFO_FILES) $(INFO) $(CORE_FONTS): mkdir -p $(CORE_FONTS) && \ - cp -r -t $(CORE_FONTS) $(CORE_FONTS_FILES) + cp -r $(CORE_FONTS_FILES) $(CORE_FONTS) clean: rm -rf $(GRUNT_FILES) install: mkdir -pv ${DESTDIR}/var/www/onlyoffice - if ! id -u onlyoffice > /dev/null 2>&1; then useradd -m -d /var/www/onlyoffice -r -U onlyoffice; fi + if ! id -u onlyoffice > /dev/null 2>&1; then $(USER_ADD); fi mkdir -p ${DESTDIR}${DOCUMENT_ROOT}/fonts mkdir -p ${DESTDIR}/var/log/onlyoffice/documentserver mkdir -p ${DESTDIR}/var/lib/onlyoffice/documentserver/App_Data - cp -fr -t ${DESTDIR}${DOCUMENT_ROOT} build/* ../web-apps/deploy/* + cp -fr build/* ../web-apps/deploy/* ${DESTDIR}${DOCUMENT_ROOT} mkdir -p ${DESTDIR}/etc/onlyoffice/documentserver mv ${DESTDIR}${DOCUMENT_ROOT}/server/Common/config/* ${DESTDIR}/etc/onlyoffice/documentserver - chown onlyoffice:onlyoffice -R ${DESTDIR}/var/www/onlyoffice - chown onlyoffice:onlyoffice -R ${DESTDIR}/var/log/onlyoffice - chown onlyoffice:onlyoffice -R ${DESTDIR}/var/lib/onlyoffice + chown -R onlyoffice:onlyoffice ${DESTDIR}/var/www/onlyoffice + chown -R onlyoffice:onlyoffice ${DESTDIR}/var/log/onlyoffice + chown -R onlyoffice:onlyoffice ${DESTDIR}/var/lib/onlyoffice # Make symlinks for shared libs find \ @@ -153,6 +167,7 @@ install: -maxdepth 1 \ -name *$(SHARED_EXT) \ -exec sh -c 'ln -sf {} ${DESTDIR}/lib/$$(basename {})' \; + ldconfig sudo -u onlyoffice "${DESTDIR}${DOCUMENT_ROOT}/server/tools/allfontsgen"\ --input="${DESTDIR}${DOCUMENT_ROOT}/core-fonts"\ @@ -169,7 +184,7 @@ install: --output="${DESTDIR}${DOCUMENT_ROOT}/sdkjs/common/Images" uninstall: - userdel onlyoffice + $(USER_DEL) # Unlink installed shared libs find /lib -type l | while IFS= read -r lnk; do if (readlink "$$lnk" | grep -q '^${DOCUMENT_ROOT}/server/FileConverter/bin/'); then rm "$$lnk"; fi; done