From 41399fa3d46a19a9bdbb72bbf6e71eec18c905c5 Mon Sep 17 00:00:00 2001 From: Sergey Konovalov Date: Tue, 2 Aug 2022 17:34:06 +0300 Subject: [PATCH] Feature/multi tenant (#377) * [feature] Add tenantManager * [fix] Fix export * [schema] Change schema for tenants * [de] For auth * [config] Remove unused secret.browser param to unify with multitenancy * [feature] Add OperationContext to store state of request * [log] Remove docId and userId from log message * [feature] Add OperationContext class * [feature] For logging * [feature] Add content to some methods * [feature] For multitenancy * [feature] For multitenancy * [feature] For multitenancy * [feature] For multitenancy * [feature] For multitenancy * [feature] For multitenancy * [feature] Move all tenant logic to tenantManager * [feature] Fix reading of tenant license * [feature] Move tenant logic to EditorData interface * [feature] Use context in SQL queries * [feature] Refactoring * [feature] Fix editorDataMemory * [feature] Fix before merge --- Common/config/default.json | 13 +- Common/config/log4js/development.json | 2 +- Common/config/log4js/production.json | 2 +- Common/sources/activeMQCore.js | 9 +- Common/sources/commondefines.js | 8 + Common/sources/constants.js | 3 + Common/sources/formatchecker.js | 7 +- Common/sources/logger.js | 4 +- Common/sources/operationContext.js | 91 ++ Common/sources/rabbitMQCore.js | 9 +- Common/sources/storage-base.js | 95 +- Common/sources/taskqueueRabbitMQ.js | 9 +- Common/sources/tenantManager.js | 138 ++ Common/sources/utils.js | 90 +- DocService/sources/DocsCoServer.js | 1318 +++++++++-------- DocService/sources/baseConnector.js | 133 +- DocService/sources/canvasservice.js | 540 +++---- DocService/sources/converterservice.js | 116 +- DocService/sources/editorDataMemory.js | 291 ++-- DocService/sources/fileuploaderservice.js | 228 +-- DocService/sources/gc.js | 62 +- DocService/sources/mySqlBaseConnector.js | 22 +- DocService/sources/postgreSqlBaseConnector.js | 43 +- DocService/sources/pubsubRabbitMQ.js | 1 - DocService/sources/server.js | 84 +- DocService/sources/shutdown.js | 20 +- DocService/sources/taskresult.js | 79 +- DocService/sources/wopiClient.js | 182 +-- FileConverter/sources/converter.js | 170 ++- FileConverter/sources/convertermaster.js | 31 +- schema/mysql/createdb.sql | 6 +- schema/postgresql/createdb.sql | 14 +- 32 files changed, 2159 insertions(+), 1661 deletions(-) create mode 100644 Common/sources/operationContext.js create mode 100644 Common/sources/tenantManager.js diff --git a/Common/config/default.json b/Common/config/default.json index df6b04d5..3be64ae5 100644 --- a/Common/config/default.json +++ b/Common/config/default.json @@ -97,6 +97,13 @@ "privateKeyOld": "MIIEowIBAAKCAQEAqnro3nUUjvZK1i7UqeOlXmCrVPiDtHlRgIPReAjt2nKL1GG3SBXO6N0aPbiM5rtK0XRPUoLmKu2rYvSJ/Kmkdp14a/3uiEl788VVn0hb/l9OuQtH3HBjmM0/LKRgJQuU3LgHI67uRVZYtSJ/n9fYdZqnLfveLsrgZpgRCoabrp+H5Uem9N+x0OJR3LpToVRZhzSkYQrxnERJmF3bhR5yF8Zn+3BoSiUpVOCAvJRAYl8cAIs3BwQcTEyXJjnt+wW5Q1VyKr+bXp/39+tnugQeTe1jjdPy6rOTftQwzjro81oZpOMazwwR1aeQuQWCrmHQZqyV3Rvo6X3xYlOQnlo1/wIDAQABAoIBAQCKtUSBs8tNYrGTQTlBHXrwpkDg+u7WSZt5sEcfnkxA39BLtlHU8gGO0E9Ihr8GAL+oWjUsEltJ9GTtN8CJ9lFdPVS8sTiCZR/YQOggmFRZTJyVzMrkXgF7Uwwiu3+KxLiTOZx9eRhfDBlTD8W9fXaegX2i2Xp2ohUhBHthEBLdaZTWFi5Sid/Y0dDzBeP6UIJorZ5D+1ybaeIVHjndpwNsIEUGUxPFLrkeiU8Rm4MJ9ahxfywcP7DjQoPGY9Ge5cBhpxfzERWf732wUD6o3+L9tvOBU00CLVjULbGZKTVE2FJMyXK9jr6Zor9Mkhomp6/8Agkr9rp+TPyelFGYEz8hAoGBAOEc09CrL3eYBkhNEcaMQzxBLvOGpg8kaDX5SaArHfl9+U9yzRqss4ARECanp9HuHfjMQo7iejao0ngDjL7BNMSaH74QlSsPOY2iOm8Qvx8/zb7g4h9r1zLjFZb3mpSA4snRZvvdiZ9ugbuVPmhXnDzRRMg45MibJeeOTJNylofRAoGBAMHfF/WutqKDoX25qZo9m74W4bttOj6oIDk1N4/c6M1Z1v/aptYSE06bkWngj9P46kqjaay4hgMtzyGruc5aojPx5MHHf5bo14+Jv4NzYtR2llrUxO+UJX7aCfUYXI7RC93GUmhpeQ414j7SNAXec58d7e+ETw+6cHiAWO4uOSTPAoGATPq5qDLR4Zi4FUNdn8LZPyKfNqHF6YmupT5hIgd8kZO1jKiaYNPL8jBjkIRmjBBcaXcYD5p85nImvumf2J9jNxPpZOpwyC/Fo5xlVROp97qu1eY7DTmodntXJ6/2SXAlnZQhHmHsrPtyG752f+HtyJJbbgiem8cKWDu+DfHybfECgYBbSLo1WiBwgN4nHqZ3E48jgA6le5azLeKOTTpuKKwNFMIhEkj//t7MYn+jhLL0Mf3PSwZU50Vidc1To1IHkbFSGBGIFHFFEzl8QnXEZS4hr/y3o/teezj0c6HAn8nlDRUzRVBEDXWMdV6kCcGpCccTIrqHzpqTY0vV0UkOTQFnDQKBgAxSEhm/gtCYJIMCBe+KBJT9uECV5xDQopTTjsGOkd4306EN2dyPOIlAfwM6K/0qWisa0Ei5i8TbRRuBeTTdLEYLqXCJ7fj5tdD1begBdSVtHQ2WHqzPJSuImTkFi9NXxd1XUyZFM3y6YQvlssSuL7QSxUIEtZHnrJTt3QDd10dj", "refreshLockInterval": "10m" }, + "tenants": { + "baseDir": "", + "baseDomain": "", + "filenameSecret": "secret.key", + "filenameLicense": "license.lic", + "defaultTetant": "tetant" + }, "services": { "CoAuthoring": { "server": { @@ -200,8 +207,7 @@ "allowMetaIPAddress": true }, "secret": { - "browser": {"string": "secret", "file": "", "tenants": {}}, - "inbox": {"string": "secret", "file": "", "tenants": {}}, + "inbox": {"string": "secret", "file": ""}, "outbox": {"string": "secret", "file": ""}, "session": {"string": "secret", "file": ""} }, @@ -213,9 +219,6 @@ "outbox": false } }, - "browser": { - "secretFromInbox": true - }, "inbox": { "header": "Authorization", "prefix": "Bearer ", diff --git a/Common/config/log4js/development.json b/Common/config/log4js/development.json index 9062b2ff..67134f6a 100644 --- a/Common/config/log4js/development.json +++ b/Common/config/log4js/development.json @@ -4,7 +4,7 @@ "type": "console", "layout": { "type": "pattern", - "pattern": "%[[%d] [%p] %c -%] %.10000m" + "pattern": "%[[%d] [%p] [%X{TENANT}] [%X{DOCID}] [%X{USERID}] %c -%] %.10000m" } } }, diff --git a/Common/config/log4js/production.json b/Common/config/log4js/production.json index c8e2cf73..84943039 100644 --- a/Common/config/log4js/production.json +++ b/Common/config/log4js/production.json @@ -4,7 +4,7 @@ "type": "console", "layout": { "type": "pattern", - "pattern": "[%d] [%p] %c - %.10000m" + "pattern": "[%d] [%p] [%X{TENANT}] [%X{DOCID}] [%X{USERID}] %c - %.10000m" } } }, diff --git a/Common/sources/activeMQCore.js b/Common/sources/activeMQCore.js index 79a6920c..26c37475 100644 --- a/Common/sources/activeMQCore.js +++ b/Common/sources/activeMQCore.js @@ -34,6 +34,7 @@ var config = require('config'); var container = require('rhea'); var logger = require('./logger'); +const operationContext = require('./operationContext'); const cfgRabbitSocketOptions = config.get('activemq.connectOptions'); @@ -45,20 +46,20 @@ function connetPromise(reconnectOnConnectionError, closeCallback) { let conn = container.create_container().connect(cfgRabbitSocketOptions); let isConnected = false; conn.on('connection_open', function(context) { - logger.debug('[AMQP] connected'); + operationContext.global.logger.debug('[AMQP] connected'); isConnected = true; resolve(conn); }); conn.on('connection_error', function(context) { //todo - logger.debug('[AMQP] connection_error %s', context.error); + operationContext.global.logger.debug('[AMQP] connection_error %s', context.error); }); conn.on('connection_close', function() { //todo - logger.debug('[AMQP] conn close'); + operationContext.global.logger.debug('[AMQP] conn close'); }); conn.on('disconnected', function(context) { - logger.error('[AMQP] disconnected %s', context.error && context.error.stack); + operationContext.global.logger.error('[AMQP] disconnected %s', context.error && context.error.stack); if (isConnected) { closeCallback(); } else { diff --git a/Common/sources/commondefines.js b/Common/sources/commondefines.js index a592abcf..693b1dd7 100644 --- a/Common/sources/commondefines.js +++ b/Common/sources/commondefines.js @@ -708,6 +708,7 @@ CMailMergeSendData.prototype.setIsJsonKey = function(v) { }; function TaskQueueData(data) { if (data) { + this['ctx'] = data['ctx']; this['cmd'] = new InputCommand(data['cmd'], true); this['toFile'] = data['toFile']; this['fromOrigin'] = data['fromOrigin']; @@ -718,6 +719,7 @@ function TaskQueueData(data) { this['dataKey'] = data['dataKey']; this['visibilityTimeout'] = data['visibilityTimeout']; } else { + this['ctx'] = undefined; this['cmd'] = undefined; this['toFile'] = undefined; this['fromOrigin'] = undefined; @@ -730,6 +732,12 @@ function TaskQueueData(data) { } } TaskQueueData.prototype = { + getCtx : function() { + return this['ctx']; + }, + setCtx : function(data) { + return this['ctx'] = data; + }, getCmd : function() { return this['cmd']; }, diff --git a/Common/sources/constants.js b/Common/sources/constants.js index 61c5c680..65d4c6f3 100644 --- a/Common/sources/constants.js +++ b/Common/sources/constants.js @@ -35,6 +35,7 @@ exports.DOC_ID_PATTERN = '0-9-.a-zA-Z_='; exports.DOC_ID_REGEX = new RegExp("^[" + exports.DOC_ID_PATTERN + "]*$", 'i'); exports.DOC_ID_REPLACE_REGEX = new RegExp("[^" + exports.DOC_ID_PATTERN + "]", 'g'); +exports.DOC_ID_SOCKET_PATTERN = new RegExp("^/doc/([" + exports.DOC_ID_PATTERN + "]*)/c.+", 'i'); exports.DOC_ID_MAX_LENGTH = 240; exports.USER_ID_MAX_LENGTH = 240;//255-240=15 symbols to make user id unique exports.USER_NAME_MAX_LENGTH = 255; @@ -46,6 +47,8 @@ exports.ONLY_OFFICE_URL_PARAM = 'ooname'; exports.DISPLAY_PREFIX = 'display'; exports.CHANGES_NAME = 'changes'; exports.VIEWER_ONLY = /^(?:(pdf|djvu|xps|oxps))$/; +exports.DEFAULT_DOC_ID = 'docId'; +exports.DEFAULT_USER_ID = 'userId'; exports.RIGHTS = { None : 0, diff --git a/Common/sources/formatchecker.js b/Common/sources/formatchecker.js index 62fcabe2..db249cca 100644 --- a/Common/sources/formatchecker.js +++ b/Common/sources/formatchecker.js @@ -34,7 +34,6 @@ var path = require('path'); var constants = require('./constants'); -var logger = require('./logger'); function getImageFormatBySignature(buffer) { var length = buffer.length; @@ -500,7 +499,7 @@ exports.getStringFromFormat = function(format) { return ''; } }; -exports.getImageFormat = function(buffer, optExt) { +exports.getImageFormat = function(ctx, buffer, optExt) { var format = constants.AVS_OFFICESTUDIO_FILE_UNKNOWN; try { //signature @@ -519,8 +518,7 @@ exports.getImageFormat = function(buffer, optExt) { } } catch (e) { - logger.error(optExt); - logger.error('error getImageFormat:\r\n%s', e.stack); + ctx.logger.error('error getImageFormat ext=%s: %s', optExt, e.stack); } return format; }; @@ -540,7 +538,6 @@ exports.isPresentationFormat = function(format) { format === constants.AVS_OFFICESTUDIO_FILE_TEAMLAB_PPTY; }; exports.isOOXFormat = function(format) { - console.log('isOOXFormat'+format); return constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX === format || constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCM === format || constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOTX === format diff --git a/Common/sources/logger.js b/Common/sources/logger.js index 8bcd87f3..ec61de4d 100644 --- a/Common/sources/logger.js +++ b/Common/sources/logger.js @@ -73,7 +73,9 @@ if (config.get('log.options.replaceConsole')) { console.error = logger.error.bind(logger); console.debug = logger.debug.bind(logger); } - +exports.getLogger = function (){ + return log4js.getLogger.apply(log4js, Array.prototype.slice.call(arguments)); +}; exports.trace = function (){ return logger.trace.apply(logger, Array.prototype.slice.call(arguments)); }; diff --git a/Common/sources/operationContext.js b/Common/sources/operationContext.js new file mode 100644 index 00000000..73a6ccdf --- /dev/null +++ b/Common/sources/operationContext.js @@ -0,0 +1,91 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2019 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * + */ + +'use strict'; + +const utils = require('./utils'); +const logger = require('./logger'); +const constants = require('./constants'); +const tenantManager = require('./tenantManager'); + +function Context(){ + this.logger = logger.getLogger('nodeJS'); + this.initDefault(); +} +Context.prototype.init = function(tenant, docId, userId) { + this.setTenant(tenant); + this.setDocId(docId); + this.setUserId(userId); +}; +Context.prototype.initDefault = function() { + this.init(tenantManager.getDefautTenant(), constants.DEFAULT_DOC_ID, constants.DEFAULT_USER_ID); +}; +Context.prototype.initFromConnection = function(conn) { + let tenant = tenantManager.getTenantByConnection(this, conn); + let docId = conn.docid; + if (!docId) { + const docIdParsed = constants.DOC_ID_SOCKET_PATTERN.exec(conn.url); + if (docIdParsed && 1 < docIdParsed.length) { + docId = docIdParsed[1]; + } + } + let userId = conn.user?.id; + this.init(tenant, docId || this.docId, userId || this.userId); +}; +Context.prototype.initFromRequest = function(req) { + let tenant = tenantManager.getTenantByRequest(this, req); + this.init(tenant, this.docId, this.userId); +}; +Context.prototype.initFromTaskQueueData = function(task) { + let ctx = task.getCtx(); + this.init(ctx.tenant, ctx.docId, ctx.userId); +}; +Context.prototype.initFromPubSub = function(data) { + let ctx = data.ctx; + this.init(ctx.tenant, ctx.docId, ctx.userId); +}; + +Context.prototype.setTenant = function(tenant) { + this.tenant = tenant; + this.logger.addContext('TENANT', tenant); +}; +Context.prototype.setDocId = function(docId) { + this.docId = docId; + this.logger.addContext('DOCID', docId); +}; +Context.prototype.setUserId = function(userId) { + this.userId = userId; + this.logger.addContext('USERID', userId); +}; + +exports.Context = Context; +exports.global = new Context(); diff --git a/Common/sources/rabbitMQCore.js b/Common/sources/rabbitMQCore.js index 9d139389..7e35e040 100644 --- a/Common/sources/rabbitMQCore.js +++ b/Common/sources/rabbitMQCore.js @@ -34,6 +34,7 @@ var config = require('config'); var amqp = require('amqplib/callback_api'); var logger = require('./logger'); +const operationContext = require('./operationContext'); var cfgRabbitUrl = config.get('rabbitmq.url'); var cfgRabbitSocketOptions = config.get('rabbitmq.socketOptions'); @@ -45,7 +46,7 @@ function connetPromise(reconnectOnConnectionError, closeCallback) { function startConnect() { amqp.connect(cfgRabbitUrl, cfgRabbitSocketOptions, function(err, conn) { if (null != err) { - logger.error('[AMQP] %s', err.stack); + operationContext.global.logger.error('[AMQP] %s', err.stack); if (reconnectOnConnectionError) { setTimeout(startConnect, RECONNECT_TIMEOUT); } else { @@ -53,16 +54,16 @@ function connetPromise(reconnectOnConnectionError, closeCallback) { } } else { conn.on('error', function(err) { - logger.error('[AMQP] conn error', err.stack); + operationContext.global.logger.error('[AMQP] conn error', err.stack); }); var closeEventCallback = function() { //in some case receive multiple close events conn.removeListener('close', closeEventCallback); - logger.debug('[AMQP] conn close'); + operationContext.global.logger.debug('[AMQP] conn close'); closeCallback(); }; conn.on('close', closeEventCallback); - logger.debug('[AMQP] connected'); + operationContext.global.logger.debug('[AMQP] connected'); resolve(conn); } }); diff --git a/Common/sources/storage-base.js b/Common/sources/storage-base.js index e3f1061c..14f2efb3 100644 --- a/Common/sources/storage-base.js +++ b/Common/sources/storage-base.js @@ -33,84 +33,99 @@ 'use strict'; var config = require('config'); var utils = require('./utils'); -var logger = require('./logger'); var storage = require('./' + config.get('storage.name')); -function getStoragePath(strPath) { - return strPath.replace(/\\/g, '/'); +var tenantManager = require('./tenantManager'); + +function getStoragePath(ctx, strPath) { + return tenantManager.getTenantPathPrefix(ctx) + strPath.replace(/\\/g, '/') } -exports.headObject = function(strPath) { - return storage.headObject(getStoragePath(strPath)); + +exports.headObject = function(ctx, strPath) { + return storage.headObject(getStoragePath(ctx, strPath)); }; -exports.getObject = function(strPath) { - return storage.getObject(getStoragePath(strPath)); +exports.getObject = function(ctx, strPath) { + return storage.getObject(getStoragePath(ctx, strPath)); }; -exports.createReadStream = function(strPath) { - return storage.createReadStream(getStoragePath(strPath)); +exports.createReadStream = function(ctx, strPath) { + return storage.createReadStream(getStoragePath(ctx, strPath)); }; -exports.putObject = function(strPath, buffer, contentLength) { - return storage.putObject(getStoragePath(strPath), buffer, contentLength); +exports.putObject = function(ctx, strPath, buffer, contentLength) { + return storage.putObject(getStoragePath(ctx, strPath), buffer, contentLength); }; -exports.uploadObject = function(strPath, filePath) { - return storage.uploadObject(strPath, filePath); +exports.uploadObject = function(ctx, strPath, filePath) { + return storage.uploadObject(getStoragePath(ctx, strPath), filePath); }; -exports.copyObject = function(sourceKey, destinationKey) { - return storage.copyObject(sourceKey, destinationKey); +exports.copyObject = function(ctx, sourceKey, destinationKey) { + let storageSrc = getStoragePath(ctx, sourceKey); + let storageDst = getStoragePath(ctx, destinationKey); + return storage.copyObject(storageSrc, storageDst); }; -exports.copyPath = function(sourcePath, destinationPath) { - return exports.listObjects(getStoragePath(sourcePath)).then(function(list) { +exports.copyPath = function(ctx, sourcePath, destinationPath) { + let storageSrc = getStoragePath(ctx, sourcePath); + let storageDst = getStoragePath(ctx, destinationPath); + return storage.listObjects(storageSrc).then(function(list) { return Promise.all(list.map(function(curValue) { - return exports.copyObject(curValue, destinationPath + '/' + exports.getRelativePath(sourcePath, curValue)); + return storage.copyObject(curValue, storageDst + '/' + exports.getRelativePath(storageSrc, curValue)); })); }); }; -exports.listObjects = function(strPath) { - return storage.listObjects(getStoragePath(strPath)).catch(function(e) { - logger.error('storage.listObjects:\r\n%s', e.stack); +exports.listObjects = function(ctx, strPath) { + let prefix = getStoragePath(ctx, ""); + return storage.listObjects(getStoragePath(ctx, strPath)).then(function(list) { + return list.map((currentValue) => { + return currentValue.substring(prefix.length); + }); + }).catch(function(e) { + ctx.logger.error('storage.listObjects: %s', e.stack); return []; }); }; -exports.deleteObject = function(strPath) { - return storage.deleteObject(getStoragePath(strPath)); +exports.deleteObject = function(ctx, strPath) { + return storage.deleteObject(getStoragePath(ctx, strPath)); }; -exports.deleteObjects = function(strPaths) { +exports.deleteObjects = function(ctx, strPaths) { var StoragePaths = strPaths.map(function(curValue) { - return getStoragePath(curValue); + return getStoragePath(ctx, curValue); }); return storage.deleteObjects(StoragePaths); }; -exports.deletePath = function(strPath) { - return exports.listObjects(getStoragePath(strPath)).then(function(list) { - return exports.deleteObjects(list); +exports.deletePath = function(ctx, strPath) { + let storageSrc = getStoragePath(ctx, strPath); + return storage.listObjects(storageSrc).then(function(list) { + return storage.deleteObjects(list); }); }; -exports.getSignedUrl = function(baseUrl, strPath, urlType, optFilename, opt_creationDate) { - return storage.getSignedUrl(baseUrl, getStoragePath(strPath), urlType, optFilename, opt_creationDate); +exports.getSignedUrl = function(ctx, baseUrl, strPath, urlType, optFilename, opt_creationDate) { + return storage.getSignedUrl(baseUrl, getStoragePath(ctx, strPath), urlType, optFilename, opt_creationDate); }; -exports.getSignedUrls = function(baseUrl, strPath, urlType, opt_creationDate) { - return exports.listObjects(getStoragePath(strPath)).then(function(list) { +exports.getSignedUrls = function(ctx, baseUrl, strPath, urlType, opt_creationDate) { + let storageSrc = getStoragePath(ctx, strPath); + return storage.listObjects(storageSrc).then(function(list) { return Promise.all(list.map(function(curValue) { - return exports.getSignedUrl(baseUrl, curValue, urlType, undefined, opt_creationDate); + return storage.getSignedUrl(baseUrl, curValue, urlType, undefined, opt_creationDate); })).then(function(urls) { var outputMap = {}; for (var i = 0; i < list.length && i < urls.length; ++i) { - outputMap[exports.getRelativePath(strPath, list[i])] = urls[i]; + outputMap[exports.getRelativePath(storageSrc, list[i])] = urls[i]; } return outputMap; }); }); }; -exports.getSignedUrlsArrayByArray = function(baseUrl, list, urlType) { - return Promise.all(list.map(function(curValue) { - return exports.getSignedUrl(baseUrl, curValue, urlType, undefined); +exports.getSignedUrlsArrayByArray = function(ctx, baseUrl, list, urlType) { + return Promise.all(list.map(function(curValue) { + let storageSrc = getStoragePath(ctx, curValue); + return storage.getSignedUrl(baseUrl, storageSrc, urlType, undefined); })); }; -exports.getSignedUrlsByArray = function(baseUrl, list, optPath, urlType) { - return exports.getSignedUrlsArrayByArray(baseUrl, list, urlType).then(function(urls) { +exports.getSignedUrlsByArray = function(ctx, baseUrl, list, optPath, urlType) { + return exports.getSignedUrlsArrayByArray(ctx, baseUrl, list, urlType).then(function(urls) { var outputMap = {}; for (var i = 0; i < list.length && i < urls.length; ++i) { if (optPath) { - outputMap[exports.getRelativePath(optPath, list[i])] = urls[i]; + let storageSrc = getStoragePath(ctx, optPath); + outputMap[exports.getRelativePath(storageSrc, list[i])] = urls[i]; } else { outputMap[list[i]] = urls[i]; } diff --git a/Common/sources/taskqueueRabbitMQ.js b/Common/sources/taskqueueRabbitMQ.js index b2216309..6345f6fb 100644 --- a/Common/sources/taskqueueRabbitMQ.js +++ b/Common/sources/taskqueueRabbitMQ.js @@ -41,6 +41,7 @@ var rabbitMQCore = require('./rabbitMQCore'); var activeMQCore = require('./activeMQCore'); const logger = require('./logger'); const commonDefines = require('./commondefines'); +const operationContext = require('./operationContext'); const cfgMaxRedeliveredCount = config.get('FileConverter.converter.maxRedeliveredCount'); const cfgQueueType = config.get('queue.type'); @@ -253,7 +254,7 @@ function clear(taskqueue) { function* pushBackRedeliveredRabbit(taskqueue, message, ack) { if (message?.fields?.redelivered) { try { - logger.warn('checkRedelivered redelivered data=%j', message); + operationContext.global.logger.warn('checkRedelivered redelivered data=%j', message); //remove current task and add new into tail of queue to remove redelivered flag let data = message.content.toString(); let redeliveredCount = message.properties.headers['x-redelivered-count']; @@ -264,7 +265,7 @@ function* pushBackRedeliveredRabbit(taskqueue, message, ack) { yield taskqueue.addResponse(taskqueue.simulateErrorResponse(data)); } } catch (err) { - logger.error('checkRedelivered error: %s', err.stack); + operationContext.global.logger.error('checkRedelivered error: %s', err.stack); } finally{ ack(); } @@ -274,14 +275,14 @@ function* pushBackRedeliveredRabbit(taskqueue, message, ack) { } function* pushBackRedeliveredActive(taskqueue, context, ack) { if (undefined !== context.message.delivery_count) { - logger.warn('checkRedelivered redelivered data=%j', context.message); + operationContext.global.logger.warn('checkRedelivered redelivered data=%j', context.message); if (context.message.delivery_count > cfgMaxRedeliveredCount) { try { if (taskqueue.simulateErrorResponse) { yield taskqueue.addResponse(taskqueue.simulateErrorResponse(context.message.body)); } } catch (err) { - logger.error('checkRedelivered error: %s', err.stack); + operationContext.global.logger.error('checkRedelivered error: %s', err.stack); } finally { ack(); } diff --git a/Common/sources/tenantManager.js b/Common/sources/tenantManager.js new file mode 100644 index 00000000..aec9166b --- /dev/null +++ b/Common/sources/tenantManager.js @@ -0,0 +1,138 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2019 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * + */ + +'use strict'; + +const config = require('config'); +const co = require('co'); +const license = require('./../../Common/sources/license'); +const constants = require('./../../Common/sources/constants'); +const commonDefines = require('./../../Common/sources/commondefines'); +const utils = require('./../../Common/sources/utils'); +const { readFile } = require('fs/promises'); +const path = require('path'); + +const oPackageType = config.get('license.packageType'); +const cfgTenantsBaseDomain = config.get('tenants.baseDomain'); +const cfgTenantsBaseDir = config.get('tenants.baseDir'); +const cfgTenantsFilenameSecret = config.get('tenants.filenameSecret'); +const cfgTenantsFilenameLicense = config.get('tenants.filenameLicense'); +const cfgTenantsDefaultTetant = config.get('tenants.defaultTetant'); +const cfgSecretInbox = config.get('services.CoAuthoring.secret.inbox'); +const cfgSecretOutbox = config.get('services.CoAuthoring.secret.outbox'); +const cfgSecretSession = config.get('services.CoAuthoring.secret.session'); + +let licenseInfo; +let licenseOriginal; + +function getDefautTenant() { + return cfgTenantsDefaultTetant; +} +function getTenant(ctx, domain) { + let tenant = getDefautTenant(); + if (domain) { + //remove port + domain = domain.substring(0, domain.indexOf(':')); + let index = domain.indexOf('.' + cfgTenantsBaseDomain); + if (-1 !== index) { + tenant = domain.substring(0, index); + } else { + ctx.logger.warn('getTenant invalid domain=%s', domain); + } + } + return tenant; +} +function getTenantByConnection(ctx, conn) { + return isMultitenantMode() ? getTenant(ctx, utils.getDomainByConnection(ctx, conn)) : getDefautTenant(); +} +function getTenantByRequest(ctx, req) { + return isMultitenantMode() ? getTenant(ctx, utils.getDomainByRequest(ctx, req)) : getDefautTenant(); +} +function getTenantPathPrefix(ctx) { + return isMultitenantMode() ? utils.removeIllegalCharacters(ctx.tenant) + '/' : ''; +} +function getTenantSecret(ctx, type) { + return co(function*() { + let res = undefined; + if (isMultitenantMode()) { + let tenantPath = utils.removeIllegalCharacters(ctx.tenant); + let secretPath = path.join(cfgTenantsBaseDir, tenantPath, cfgTenantsFilenameSecret); + ctx.logger.debug('getTenantSecret path=%s', secretPath); + res = yield readFile(secretPath, {encoding: 'utf8'}); + } else { + switch (type) { + case commonDefines.c_oAscSecretType.Browser: + case commonDefines.c_oAscSecretType.Inbox: + res = utils.getSecretByElem(cfgSecretInbox); + break; + case commonDefines.c_oAscSecretType.Outbox: + res = utils.getSecretByElem(cfgSecretOutbox); + break; + case commonDefines.c_oAscSecretType.Session: + res = utils.getSecretByElem(cfgSecretSession); + break; + } + } + return res; + }); +} + +function setDefLicense(data, original) { + licenseInfo = data; + licenseOriginal = original; +} +function getTenantLicense(ctx) { + return co(function*() { + let res = undefined; + if (isMultitenantMode()) { + let tenantPath = utils.removeIllegalCharacters(ctx.tenant); + let licensePath = path.join(cfgTenantsBaseDir, tenantPath, cfgTenantsFilenameLicense); + ctx.logger.debug('getTenantLicense path=%s', licensePath); + [res] = yield* license.readLicense(licensePath); + } else { + res = licenseInfo; + } + return res; + }); +} +function isMultitenantMode() { + return !!cfgTenantsBaseDir && !!cfgTenantsBaseDomain; +} + +exports.getDefautTenant = getDefautTenant; +exports.getTenantByConnection = getTenantByConnection; +exports.getTenantByRequest = getTenantByRequest; +exports.getTenantPathPrefix = getTenantPathPrefix; +exports.getTenantSecret = getTenantSecret; +exports.getTenantLicense = getTenantLicense; +exports.setDefLicense = setDefLicense; +exports.isMultitenantMode = isMultitenantMode; diff --git a/Common/sources/utils.js b/Common/sources/utils.js index c05842ad..9bdaff76 100644 --- a/Common/sources/utils.js +++ b/Common/sources/utils.js @@ -72,7 +72,6 @@ var cfgTokenOutboxHeader = config.get('services.CoAuthoring.token.outbox.header' var cfgTokenOutboxPrefix = config.get('services.CoAuthoring.token.outbox.prefix'); var cfgTokenOutboxAlgorithm = config.get('services.CoAuthoring.token.outbox.algorithm'); var cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires'); -var cfgSignatureSecretOutbox = config.get('services.CoAuthoring.secret.outbox'); var cfgVisibilityTimeout = config.get('queue.visibilityTimeout'); var cfgQueueRetentionPeriod = config.get('queue.retentionPeriod'); var cfgRequestDefaults = config.get('services.CoAuthoring.requestDefaults'); @@ -261,13 +260,13 @@ function raiseErrorObj(ro, error) { function isRedirectResponse(response) { return response && response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location'); } -function downloadUrlPromise(uri, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) { +function downloadUrlPromise(ctx, uri, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) { //todo replace deprecated request module 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, opt_filterPrivate, opt_headers, opt_streamWriter) + return downloadUrlPromiseWithoutRedirect(ctx, curUrl, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) .catch(function(err) { let response = err.response; if (isRedirectResponse(response)) { @@ -277,7 +276,7 @@ function downloadUrlPromise(uri, optTimeout, optLimit, opt_Authorization, opt_fi redirectTo = url.resolve(err.request.uri.href, redirectTo) } - logger.debug('downloadUrlPromise redirectsFollowed:%d redirectTo: %s', redirectsFollowed, redirectTo); + ctx.logger.debug('downloadUrlPromise redirectsFollowed:%d redirectTo: %s', redirectsFollowed, redirectTo); redirectsFollowed++; return doRequest(redirectTo); } @@ -287,7 +286,7 @@ function downloadUrlPromise(uri, optTimeout, optLimit, opt_Authorization, opt_fi }; return doRequest(uri); } -function downloadUrlPromiseWithoutRedirect(uri, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) { +function downloadUrlPromiseWithoutRedirect(ctx, uri, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) { return new Promise(function (resolve, reject) { //IRI to URI uri = URI.serialize(URI.parse(uri)); @@ -323,7 +322,7 @@ function downloadUrlPromiseWithoutRedirect(uri, optTimeout, optLimit, opt_Author } else { var contentLength = response.caseless.get('content-length'); if (contentLength && body.length !== (contentLength - 0)) { - logger.warn('downloadUrlPromise body size mismatch: uri=%s; content-length=%s; body.length=%d', uri, contentLength, body.length); + ctx.logger.warn('downloadUrlPromise body size mismatch: uri=%s; content-length=%s; body.length=%d', uri, contentLength, body.length); } resolve({response: response, body: body}); } @@ -654,6 +653,9 @@ function containsAllAsciiNP(str) { return /^[\040-\176]*$/.test(str);//non-printing characters } exports.containsAllAsciiNP = containsAllAsciiNP; +function getDomain(hostHeader, forwardedHostHeader) { + return forwardedHostHeader || hostHeader || 'localhost'; +}; function getBaseUrl(protocol, hostHeader, forwardedProtoHeader, forwardedHostHeader, forwardedPrefixHeader) { var url = ''; if (forwardedProtoHeader) { @@ -664,13 +666,7 @@ function getBaseUrl(protocol, hostHeader, forwardedProtoHeader, forwardedHostHea url += 'http'; } url += '://'; - if (forwardedHostHeader) { - url += forwardedHostHeader; - } else if (hostHeader) { - url += hostHeader; - } else { - url += 'localhost'; - } + url += getDomain(hostHeader, forwardedHostHeader); if (forwardedPrefixHeader) { url += forwardedPrefixHeader; } @@ -684,6 +680,20 @@ function getBaseUrlByRequest(req) { } exports.getBaseUrlByConnection = getBaseUrlByConnection; exports.getBaseUrlByRequest = getBaseUrlByRequest; +function getDomainByConnection(ctx, conn) { + let host = conn.headers['host']; + let forwardedHost = conn.headers['x-forwarded-host']; + ctx.logger.debug("getDomainByConnection headers['host']=%s headers['x-forwarded-host']=%s", host, forwardedHost); + return getDomain(host, forwardedHost); +} +function getDomainByRequest(ctx, req) { + let host = req.get('host'); + let forwardedHost = req.get('x-forwarded-host'); + ctx.logger.debug("getDomainByRequest headers['host']=%s headers['x-forwarded-host']=%s", host, forwardedHost); + return getDomain(req.get('host'), req.get('x-forwarded-host')); +} +exports.getDomainByConnection = getDomainByConnection; +exports.getDomainByRequest = getDomainByRequest; function stream2Buffer(stream) { return new Promise(function(resolve, reject) { if (!stream.readable) { @@ -761,14 +771,14 @@ function checkIpFilter(ipString, opt_hostname) { return status; } exports.checkIpFilter = checkIpFilter; -function* checkHostFilter(hostname) { +function* checkHostFilter(ctx, hostname) { let status = 0; let hostIp; try { hostIp = yield dnsLookup(hostname); } catch (e) { status = cfgIpFilterErrorCode; - logger.error('dnsLookup error: hostname = %s\r\n%s', hostname, e.stack); + ctx.logger.error('dnsLookup error: hostname = %s %s', hostname, e.stack); } if (0 === status) { status = checkIpFilter(hostIp, hostname); @@ -832,29 +842,7 @@ function getSecretByElem(secretElem) { return secret; } exports.getSecretByElem = getSecretByElem; -function getSecret(docId, secretElem, opt_iss, opt_token) { - if (!isEmptyObject(secretElem.tenants)) { - var iss; - if (opt_token) { - //look for issuer - var decodedTemp = jwt.decode(opt_token); - if (decodedTemp && decodedTemp.iss) { - iss = decodedTemp.iss; - } - } else { - iss = opt_iss; - } - if (iss) { - secretElem = secretElem.tenants[iss]; - if (!secretElem) { - logger.error('getSecret unknown issuer: docId = %s iss = %s', docId, iss); - } - } - } - return getSecretByElem(secretElem); -} -exports.getSecret = getSecret; -function fillJwtForRequest(payload, opt_inBody) { +function fillJwtForRequest(payload, secret, opt_inBody) { //todo refuse prototypes in payload(they are simple getter/setter). //JSON.parse/stringify is more universal but Object.assign is enough for our inputs payload = Object.assign(Object.create(null), payload); @@ -866,7 +854,6 @@ function fillJwtForRequest(payload, opt_inBody) { } let options = {algorithm: cfgTokenOutboxAlgorithm, expiresIn: cfgTokenOutboxExpires}; - let secret = getSecretByElem(cfgSignatureSecretOutbox); return jwt.sign(data, secret, options); } exports.fillJwtForRequest = fillJwtForRequest; @@ -874,13 +861,13 @@ exports.forwarded = forwarded; exports.getIndexFromUserId = function(userId, userIdOriginal){ return parseInt(userId.substring(userIdOriginal.length)); }; -exports.checkPathTraversal = function(docId, rootDirectory, filename) { +exports.checkPathTraversal = function(ctx, docId, rootDirectory, filename) { if (filename.indexOf('\0') !== -1) { - logger.warn('checkPathTraversal Poison Null Bytes docId=%s filename=%s', docId, filename); + ctx.logger.warn('checkPathTraversal Poison Null Bytes filename=%s', filename); return false; } if (!filename.startsWith(rootDirectory)) { - logger.warn('checkPathTraversal Path Traversal docId=%s filename=%s', docId, filename); + ctx.logger.warn('checkPathTraversal Path Traversal filename=%s', filename); return false; } return true; @@ -905,14 +892,14 @@ exports.getConnectionInfoStr = function(conn){ exports.isLiveViewer = function(conn){ return conn.user?.view && "fast" === conn.coEditingMode; }; -exports.canIncludeOutboxAuthorization = function (url) { +exports.canIncludeOutboxAuthorization = function (ctx, url) { if (cfgTokenEnableRequestOutbox) { if (!outboxUrlExclusionRegex) { return true; } else if (!outboxUrlExclusionRegex.test(url)) { return true; } else { - logger.debug('canIncludeOutboxAuthorization excluded by token.outbox.urlExclusionRegex url=%s', url); + ctx.logger.debug('canIncludeOutboxAuthorization excluded by token.outbox.urlExclusionRegex url=%s', url); } } return false; @@ -981,8 +968,7 @@ exports.checkBaseUrl = function(baseUrl) { }; exports.resolvePath = function(object, path, defaultValue) { return path.split('.').reduce((o, p) => o ? o[p] : defaultValue, object); -} - +}; Date.isLeapYear = function (year) { return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); }; @@ -1023,3 +1009,15 @@ exports.getLicensePeriod = function(startDate, now) { startDate.setUTCHours(0,0,0,0); return startDate.getTime(); }; + +exports.removeIllegalCharacters = function(filename) { + return filename?.replace(/[/\\?%*:|"<>]/g, '-') || filename; +} +exports.getFunctionArguments = function(func) { + return func.toString(). + replace(/[\r\n\s]+/g, ' '). + match(/(?:function\s*\w*)?\s*(?:\((.*?)\)|([^\s]+))/). + slice(1, 3). + join(''). + split(/\s*,\s*/); +}; diff --git a/DocService/sources/DocsCoServer.js b/DocService/sources/DocsCoServer.js index de38fc0e..dc8477c4 100644 --- a/DocService/sources/DocsCoServer.js +++ b/DocService/sources/DocsCoServer.js @@ -105,6 +105,8 @@ const wopiClient = require('./wopiClient'); const queueService = require('./../../Common/sources/taskqueueRabbitMQ'); const rabbitMQCore = require('./../../Common/sources/rabbitMQCore'); const activeMQCore = require('./../../Common/sources/activeMQCore'); +const operationContext = require('./../../Common/sources/operationContext'); +const tenantManager = require('./../../Common/sources/tenantManager'); const editorDataStorage = require('./' + configCommon.get('services.CoAuthoring.server.editorDataStorage')); let cfgEditor = JSON.parse(JSON.stringify(config.get('editor'))); @@ -134,10 +136,7 @@ const cfgTokenSessionAlgorithm = config.get('token.session.algorithm'); const cfgTokenSessionExpires = ms(config.get('token.session.expires')); const cfgTokenInboxHeader = config.get('token.inbox.header'); const cfgTokenInboxPrefix = config.get('token.inbox.prefix'); -const cfgTokenBrowserSecretFromInbox = config.get('token.browser.secretFromInbox'); const cfgTokenVerifyOptions = config.get('token.verifyOptions'); -const cfgSecretBrowser = config.get('secret.browser'); -const cfgSecretInbox = config.get('secret.inbox'); const cfgSecretSession = config.get('secret.session'); const cfgForceSaveEnable = config.get('autoAssembly.enable'); const cfgForceSaveInterval = ms(config.get('autoAssembly.interval')); @@ -165,7 +164,7 @@ let connections = []; // Активные соединения let lockDocumentsTimerId = {};//to drop connection that can't unlockDocument let pubsub; let queue; -let licenseInfo = {type: constants.LICENSE_RESULT.Error, light: false, branding: false, customization: false, plugins: false}; +let f = {type: constants.LICENSE_RESULT.Error, light: false, branding: false, customization: false, plugins: false}; let licenseOriginal = null; let shutdownFlag = false; let expDocumentsStep = gc.getCronStep(cfgExpDocumentsCron); @@ -421,47 +420,47 @@ CRecalcIndex.prototype = { } }; -function updatePresenceCounters(conn, val) { +function updatePresenceCounters(ctx, conn, val) { return co(function* () { if (utils.isLiveViewer(conn)) { - yield editorData.incrLiveViewerConnectionsCountByShard(SHARD_ID, val); + yield editorData.incrLiveViewerConnectionsCountByShard(ctx, SHARD_ID, val); if (clientStatsD) { - let countLiveView = yield editorData.getLiveViewerConnectionsCount(connections); + let countLiveView = yield editorData.getLiveViewerConnectionsCount(ctx, connections); clientStatsD.gauge('expireDoc.connections.liveview', countLiveView); } } else if (conn.isCloseCoAuthoring || (conn.user && conn.user.view)) { - yield editorData.incrViewerConnectionsCountByShard(SHARD_ID, val); + yield editorData.incrViewerConnectionsCountByShard(ctx, SHARD_ID, val); if (clientStatsD) { - let countView = yield editorData.getViewerConnectionsCount(connections); + let countView = yield editorData.getViewerConnectionsCount(ctx, connections); clientStatsD.gauge('expireDoc.connections.view', countView); } } else { - yield editorData.incrEditorConnectionsCountByShard(SHARD_ID, val); + yield editorData.incrEditorConnectionsCountByShard(ctx, SHARD_ID, val); if (clientStatsD) { - let countEditors = yield editorData.getEditorConnectionsCount(connections); + let countEditors = yield editorData.getEditorConnectionsCount(ctx, connections); clientStatsD.gauge('expireDoc.connections.edit', countEditors); } } }); } -function addPresence(conn, updateCunters) { +function addPresence(ctx, conn, updateCunters) { return co(function* () { - yield editorData.addPresence(conn.docId, conn.user.id, utils.getConnectionInfoStr(conn)); + yield editorData.addPresence(ctx, conn.docId, conn.user.id, utils.getConnectionInfoStr(conn)); if (updateCunters) { - yield updatePresenceCounters(conn, 1); + yield updatePresenceCounters(ctx, conn, 1); } }); } -function removePresence(conn) { +function removePresence(ctx, conn) { return co(function* () { - yield editorData.removePresence(conn.docId, conn.user.id); - yield updatePresenceCounters(conn, -1); + yield editorData.removePresence(ctx, conn.docId, conn.user.id); + yield updatePresenceCounters(ctx, conn, -1); }); } -let changeConnectionInfo = co.wrap(function*(conn, cmd) { +let changeConnectionInfo = co.wrap(function*(ctx, conn, cmd) { if (!conn.denyChangeName && conn.user) { - yield* publish({type: commonDefines.c_oPublishType.changeConnecitonInfo, docId: conn.docId, useridoriginal: conn.user.idOriginal, cmd: cmd}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.changeConnecitonInfo, ctx: ctx, docId: conn.docId, useridoriginal: conn.user.idOriginal, cmd: cmd}); return true; } return false; @@ -475,7 +474,6 @@ function needSendChanges (conn){ return !conn.user?.view || utils.isLiveViewer(conn); } function fillJwtByConnection(conn) { - var docId = conn.docId; var payload = {document: {}, editorConfig: {user: {}}}; var doc = payload.document; doc.key = conn.docId; @@ -504,38 +502,38 @@ function fillJwtByConnection(conn) { return signToken(payload, cfgTokenSessionAlgorithm, cfgTokenSessionExpires / 1000, cfgSecretSession); } -function sendData(conn, data) { +function sendData(ctx, conn, data) { conn.write(JSON.stringify(data)); const type = data ? data.type : null; - logger.debug('sendData: docId = %s;type = %s', conn.docId, type); + ctx.logger.debug('sendData: type = %s', type); } -function sendDataWarning(conn, msg) { - sendData(conn, {type: "warning", message: msg}); +function sendDataWarning(ctx, conn, msg) { + sendData(ctx, conn, {type: "warning", message: msg}); } -function sendDataMessage(conn, msg) { +function sendDataMessage(ctx, conn, msg) { if (!conn.permissions || false !== conn.permissions.chat) { - sendData(conn, {type: "message", messages: msg}); + sendData(ctx, conn, {type: "message", messages: msg}); } else { - logger.debug("sendDataMessage permissions.chat==false: userId = %s docId = %s", conn.user && conn.user.id, conn.docId); + ctx.logger.debug("sendDataMessage permissions.chat==false"); } } -function sendDataCursor(conn, msg) { - sendData(conn, {type: "cursor", messages: msg}); +function sendDataCursor(ctx, conn, msg) { + sendData(ctx, conn, {type: "cursor", messages: msg}); } -function sendDataMeta(conn, msg) { - sendData(conn, {type: "meta", messages: msg}); +function sendDataMeta(ctx, conn, msg) { + sendData(ctx, conn, {type: "meta", messages: msg}); } -function sendDataSession(conn, msg) { - sendData(conn, {type: "session", messages: msg}); +function sendDataSession(ctx, conn, msg) { + sendData(ctx, conn, {type: "session", messages: msg}); } -function sendDataRefreshToken(conn) { - sendData(conn, {type: "refreshToken", messages: fillJwtByConnection(conn)}); +function sendDataRefreshToken(ctx, conn) { + sendData(ctx, conn, {type: "refreshToken", messages: fillJwtByConnection(conn)}); } -function sendDataRpc(conn, responseKey, data) { - sendData(conn, {type: "rpc", responseKey: responseKey, data: data}); +function sendDataRpc(ctx, conn, responseKey, data) { + sendData(ctx, conn, {type: "rpc", responseKey: responseKey, data: data}); } -function sendReleaseLock(conn, userLocks) { - sendData(conn, {type: "releaseLock", locks: _.map(userLocks, function(e) { +function sendReleaseLock(ctx, conn, userLocks) { + sendData(ctx, conn, {type: "releaseLock", locks: _.map(userLocks, function(e) { return { block: e.block, user: e.user, @@ -544,11 +542,11 @@ function sendReleaseLock(conn, userLocks) { }; })}); } -function modifyConnectionForPassword(conn, isEnterCorrectPassword) { +function modifyConnectionForPassword(ctx, conn, isEnterCorrectPassword) { if (isEnterCorrectPassword) { conn.isEnterCorrectPassword = true; if (cfgTokenEnableBrowser) { - sendDataRefreshToken(conn); + sendDataRefreshToken(ctx, conn); } } } @@ -565,7 +563,7 @@ function getParticipantUser(docId, includeUserId) { } -function* updateEditUsers(userId, anonym, isLiveViewer) { +function* updateEditUsers(ctx, licenseInfo, userId, anonym, isLiveViewer) { if (!licenseInfo.usersCount) { return; } @@ -574,20 +572,20 @@ function* updateEditUsers(userId, anonym, isLiveViewer) { licenseInfo.usersExpire - 1; let period = utils.getLicensePeriod(licenseInfo.startDate, now); if (isLiveViewer) { - yield editorData.addPresenceUniqueViewUser(userId, expireAt, {anonym: anonym}); - yield editorData.addPresenceUniqueViewUsersOfMonth(userId, period, {anonym: anonym, firstOpenDate: now.toISOString()}); + yield editorData.addPresenceUniqueViewUser(ctx, userId, expireAt, {anonym: anonym}); + yield editorData.addPresenceUniqueViewUsersOfMonth(ctx, userId, period, {anonym: anonym, firstOpenDate: now.toISOString()}); } else { - yield editorData.addPresenceUniqueUser(userId, expireAt, {anonym: anonym}); - yield editorData.addPresenceUniqueUsersOfMonth(userId, period, {anonym: anonym, firstOpenDate: now.toISOString()}); + yield editorData.addPresenceUniqueUser(ctx, userId, expireAt, {anonym: anonym}); + yield editorData.addPresenceUniqueUsersOfMonth(ctx, userId, period, {anonym: anonym, firstOpenDate: now.toISOString()}); } } -function* getEditorsCount(docId, opt_hvals) { +function* getEditorsCount(ctx, docId, opt_hvals) { var elem, editorsCount = 0; var hvals; if(opt_hvals){ hvals = opt_hvals; } else { - hvals = yield editorData.getPresence(docId, connections); + hvals = yield editorData.getPresence(ctx, docId, connections); } for (var i = 0; i < hvals.length; ++i) { elem = JSON.parse(hvals[i]); @@ -598,13 +596,13 @@ function* getEditorsCount(docId, opt_hvals) { } return editorsCount; } -function* hasEditors(docId, opt_hvals) { - let editorsCount = yield* getEditorsCount(docId, opt_hvals); +function* hasEditors(ctx, docId, opt_hvals) { + let editorsCount = yield* getEditorsCount(ctx, docId, opt_hvals); return editorsCount > 0; } -function* isUserReconnect(docId, userId, connectionId) { +function* isUserReconnect(ctx, docId, userId, connectionId) { var elem; - var hvals = yield editorData.getPresence(docId, connections); + var hvals = yield editorData.getPresence(ctx, docId, connections); for (var i = 0; i < hvals.length; ++i) { elem = JSON.parse(hvals[i]); if (userId === elem.id && connectionId !== elem.connectionId) { @@ -613,11 +611,11 @@ function* isUserReconnect(docId, userId, connectionId) { } return false; } -function* publish(data, optDocId, optUserId, opt_pubsub) { +function* publish(ctx, data, optDocId, optUserId, opt_pubsub) { var needPublish = true; if(optDocId && optUserId) { needPublish = false; - var hvals = yield editorData.getPresence(optDocId, connections); + var hvals = yield editorData.getPresence(ctx, optDocId, connections); for (var i = 0; i < hvals.length; ++i) { var elem = JSON.parse(hvals[i]); if(optUserId != elem.id) { @@ -651,9 +649,9 @@ function* removeResponse(data) { yield queue.removeResponse(data); } -function* getOriginalParticipantsId(docId) { +function* getOriginalParticipantsId(ctx, docId) { var result = [], tmpObject = {}; - var hvals = yield editorData.getPresence(docId, connections); + var hvals = yield editorData.getPresence(ctx, docId, connections); for (var i = 0; i < hvals.length; ++i) { var elem = JSON.parse(hvals[i]); if (!elem.view && !elem.isCloseCoAuthoring) { @@ -666,26 +664,27 @@ function* getOriginalParticipantsId(docId) { return result; } -function* sendServerRequest(docId, uri, dataObject, opt_checkAndFixAuthorizationLength) { - logger.debug('postData request: docId = %s;url = %s;data = %j', docId, uri, dataObject); +function* sendServerRequest(ctx, uri, dataObject, opt_checkAndFixAuthorizationLength) { + ctx.logger.debug('postData request: url = %s;data = %j', uri, dataObject); let auth; - if (utils.canIncludeOutboxAuthorization(uri)) { - let bodyToken = utils.fillJwtForRequest(dataObject, true); - auth = utils.fillJwtForRequest(dataObject, false); + if (utils.canIncludeOutboxAuthorization(ctx, uri)) { + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox); + let bodyToken = utils.fillJwtForRequest(dataObject, secret, true); + auth = utils.fillJwtForRequest(dataObject, secret, false); let authLen = auth.length; if (opt_checkAndFixAuthorizationLength && !opt_checkAndFixAuthorizationLength(auth, dataObject)) { - auth = utils.fillJwtForRequest(dataObject, false); - logger.warn('authorization too large. Use body token instead. size reduced from %d to %d: docId = %s', authLen, auth.length, docId); + auth = utils.fillJwtForRequest(dataObject, secret, false); + ctx.logger.warn('authorization too large. Use body token instead. size reduced from %d to %d', authLen, auth.length); } dataObject.setToken(bodyToken); } let postRes = yield utils.postRequestPromise(uri, JSON.stringify(dataObject), undefined, undefined, cfgCallbackRequestTimeout, auth); - logger.debug('postData response: docId = %s;data = %s', docId, postRes.body); + ctx.logger.debug('postData response: data = %s', postRes.body); return postRes.body; } // Парсинг ссылки -function parseUrl(callbackUrl) { +function parseUrl(ctx, callbackUrl) { var result = null; try { //делать decodeURIComponent не нужно http://expressjs.com/en/4x/api.html#app.settings.table @@ -705,93 +704,93 @@ function parseUrl(callbackUrl) { 'href': parseObject.href }; } catch (e) { - logger.error("error parseUrl %s:\r\n%s", callbackUrl, e.stack); + ctx.logger.error("error parseUrl %s: %s", callbackUrl, e.stack); result = null; } return result; } -function* getCallback(id, opt_userIndex) { +function* getCallback(ctx, id, opt_userIndex) { var callbackUrl = null; var baseUrl = null; let wopiParams = null; - var selectRes = yield taskResult.select(id); + var selectRes = yield taskResult.select(ctx, id); if (selectRes.length > 0) { var row = selectRes[0]; if (row.callback) { - callbackUrl = sqlBase.UserCallback.prototype.getCallbackByUserIndex(id, row.callback, opt_userIndex); - wopiParams = wopiClient.parseWopiCallback(id, callbackUrl, row.callback); + callbackUrl = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback, opt_userIndex); + wopiParams = wopiClient.parseWopiCallback(ctx, callbackUrl, row.callback); } if (row.baseurl) { baseUrl = row.baseurl; } } if (null != callbackUrl && null != baseUrl) { - return {server: parseUrl(callbackUrl), baseUrl: baseUrl, wopiParams: wopiParams}; + return {server: parseUrl(ctx, callbackUrl), baseUrl: baseUrl, wopiParams: wopiParams}; } else { return null; } } -function* getChangesIndex(docId) { +function* getChangesIndex(ctx, docId) { var res = 0; - var getRes = yield sqlBase.getChangesIndexPromise(docId); + var getRes = yield sqlBase.getChangesIndexPromise(ctx, docId); if (getRes && getRes.length > 0 && null != getRes[0]['change_id']) { res = getRes[0]['change_id'] + 1; } return res; } -const hasChanges = co.wrap(function*(docId) { +const hasChanges = co.wrap(function*(ctx, docId) { //todo check editorData.getForceSave in case of "undo all changes" - let puckerIndex = yield* getChangesIndex(docId); + let puckerIndex = yield* getChangesIndex(ctx, docId); if (0 === puckerIndex) { - let selectRes = yield taskResult.select(docId); + let selectRes = yield taskResult.select(ctx, docId); if (selectRes.length > 0 && selectRes[0].password) { - return sqlBase.DocumentPassword.prototype.hasPasswordChanges(docId, selectRes[0].password); + return sqlBase.DocumentPassword.prototype.hasPasswordChanges(ctx, selectRes[0].password); } return false; } return true; }); -function* setForceSave(docId, forceSave, cmd, success) { +function* setForceSave(ctx, docId, forceSave, cmd, success) { let forceSaveType = forceSave.getType(); if (commonDefines.c_oAscForceSaveTypes.Form !== forceSaveType) { if (success) { - yield editorData.checkAndSetForceSave(docId, forceSave.getTime(), forceSave.getIndex(), true, true); + yield editorData.checkAndSetForceSave(ctx, docId, forceSave.getTime(), forceSave.getIndex(), true, true); } else { - yield editorData.checkAndSetForceSave(docId, forceSave.getTime(), forceSave.getIndex(), false, false); + yield editorData.checkAndSetForceSave(ctx, docId, forceSave.getTime(), forceSave.getIndex(), false, false); } } if (commonDefines.c_oAscForceSaveTypes.Command !== forceSaveType) { let data = {type: forceSaveType, time: forceSave.getTime(), success: success}; if(commonDefines.c_oAscForceSaveTypes.Form === forceSaveType) { - yield* publish({type: commonDefines.c_oPublishType.rpc, docId: docId, data: data, responseKey: cmd.getResponseKey()}, cmd.getUserConnectionId()); + yield* publish(ctx, {type: commonDefines.c_oPublishType.rpc, ctx: ctx, docId: docId, data: data, responseKey: cmd.getResponseKey()}, cmd.getUserConnectionId()); } else { - yield* publish({type: commonDefines.c_oPublishType.forceSave, docId: docId, data: data}, cmd.getUserConnectionId()); + yield* publish(ctx, {type: commonDefines.c_oPublishType.forceSave, ctx: ctx, docId: docId, data: data}, cmd.getUserConnectionId()); } } } -let startForceSave = co.wrap(function*(docId, type, opt_userdata, opt_userId, opt_userConnectionId, opt_userIndex, opt_responseKey, opt_baseUrl, opt_queue, opt_pubsub) { - logger.debug('startForceSave start:docId = %s', docId); +let startForceSave = co.wrap(function*(ctx, docId, type, opt_userdata, opt_userId, opt_userConnectionId, opt_userIndex, opt_responseKey, opt_baseUrl, opt_queue, opt_pubsub) { + ctx.logger.debug('startForceSave start'); let res = {code: commonDefines.c_oAscServerCommandErrors.NoError, time: null}; let startedForceSave; let hasEncrypted = false; if (!shutdownFlag) { - let hvals = yield editorData.getPresence(docId, connections); + let hvals = yield editorData.getPresence(ctx, docId, connections); hasEncrypted = hvals.some((currentValue) => { return !!JSON.parse(currentValue).encrypted; }); if (!hasEncrypted) { if (commonDefines.c_oAscForceSaveTypes.Form === type) { - startedForceSave = yield editorData.getForceSave(docId); + startedForceSave = yield editorData.getForceSave(ctx, docId); } else { - startedForceSave = yield editorData.checkAndStartForceSave(docId); + startedForceSave = yield editorData.checkAndStartForceSave(ctx, docId); } } } - logger.debug('startForceSave canStart:docId = %s; hasEncrypted = %s; startedForceSave = %j', docId, hasEncrypted, startedForceSave); + ctx.logger.debug('startForceSave canStart: hasEncrypted = %s; startedForceSave = %j', hasEncrypted, startedForceSave); if (startedForceSave) { let baseUrl = opt_baseUrl || startedForceSave.baseUrl; let forceSave = new commonDefines.CForceSaveData(startedForceSave); @@ -800,8 +799,8 @@ let startForceSave = co.wrap(function*(docId, type, opt_userdata, opt_userId, op forceSave.setAuthorUserIndex(opt_userIndex); if (commonDefines.c_oAscForceSaveTypes.Timeout === type) { - yield* publish({ - type: commonDefines.c_oPublishType.forceSave, docId: docId, + yield* publish(ctx, { + type: commonDefines.c_oPublishType.forceSave, ctx: ctx, docId: docId, data: {type: type, time: forceSave.getTime(), start: true} }, undefined, undefined, opt_pubsub); } @@ -815,77 +814,77 @@ 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, startedForceSave.changeInfo, opt_userdata, + let status = yield converterService.convertFromChanges(ctx, 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(); } else { res.code = commonDefines.c_oAscServerCommandErrors.UnknownError; } - logger.debug('startForceSave convertFromChanges:docId = %s; status = %d', docId, status.err); + ctx.logger.debug('startForceSave convertFromChanges: status = %d', status.err); } else { res.code = commonDefines.c_oAscServerCommandErrors.NotModified; } - logger.debug('startForceSave end:docId = %s', docId); + ctx.logger.debug('startForceSave end'); 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) { +let resetForceSaveAfterChanges = co.wrap(function*(ctx, docId, newChangesLastTime, puckerIndex, baseUrl, changeInfo) { //last save if (newChangesLastTime) { - yield editorData.setForceSave(docId, newChangesLastTime, puckerIndex, baseUrl, changeInfo); + yield editorData.setForceSave(ctx, docId, newChangesLastTime, puckerIndex, baseUrl, changeInfo); if (cfgForceSaveEnable) { let expireAt = newChangesLastTime + cfgForceSaveInterval; - yield editorData.addForceSaveTimerNX(docId, expireAt); + yield editorData.addForceSaveTimerNX(ctx, docId, expireAt); } } }); -function* startRPC(conn, responseKey, data) { +function* startRPC(ctx, conn, responseKey, data) { let docId = conn.docId; - logger.debug('startRPC start responseKey:%s , %j:docId = %s', responseKey, data, docId); + ctx.logger.debug('startRPC start responseKey:%s , %j', responseKey, data); switch (data.type) { case 'sendForm': var forceSaveRes; if (conn.user) { - forceSaveRes = yield startForceSave(docId, commonDefines.c_oAscForceSaveTypes.Form, undefined, conn.user.idOriginal, conn.user.id, conn.user.indexUser, responseKey); + forceSaveRes = yield startForceSave(ctx, docId, commonDefines.c_oAscForceSaveTypes.Form, undefined, conn.user.idOriginal, conn.user.id, conn.user.indexUser, responseKey); } else { - sendDataRpc(conn, responseKey); + sendDataRpc(ctx, conn, responseKey); } break; case 'wopi_RenameFile': let renameRes; - let selectRes = yield taskResult.select(docId); + let selectRes = yield taskResult.select(ctx, docId); let row = selectRes.length > 0 ? selectRes[0] : null; if (row) { if (row.callback) { let userIndex = utils.getIndexFromUserId(conn.user.id, conn.user.idOriginal); - let uri = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, row.callback, userIndex); - let wopiParams = wopiClient.parseWopiCallback(docId, uri, row.callback); + let uri = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback, userIndex); + let wopiParams = wopiClient.parseWopiCallback(ctx, uri, row.callback); if (wopiParams) { - renameRes = yield wopiClient.renameFile(wopiParams, data.name); + renameRes = yield wopiClient.renameFile(ctx, wopiParams, data.name); } } } - sendDataRpc(conn, responseKey, renameRes); + sendDataRpc(ctx, conn, responseKey, renameRes); break; } - logger.debug('startRPC end:docId = %s', docId); + ctx.logger.debug('startRPC end'); } function handleDeadLetter(data, ack) { return co(function*() { - let docId = 'null'; + let ctx = new operationContext.Context(); try { var isRequeued = false; let task = new commonDefines.TaskQueueData(JSON.parse(data)); if (task) { + ctx.initFromTaskQueueData(task); let cmd = task.getCmd(); - docId = cmd.getDocId(); - logger.warn('handleDeadLetter start: docId = %s %s', docId, data); + ctx.logger.warn('handleDeadLetter start: %s', data); let forceSave = cmd.getForceSave(); if (forceSave && commonDefines.c_oAscForceSaveTypes.Timeout == forceSave.getType()) { - let actualForceSave = yield editorData.getForceSave(docId); + let actualForceSave = yield editorData.getForceSave(ctx, cmd.getDocId()); //check that there are no new changes if (actualForceSave && forceSave.getTime() === actualForceSave.time && forceSave.getIndex() === actualForceSave.index) { //requeue task @@ -896,7 +895,7 @@ function handleDeadLetter(data, ack) { yield* addTask(task, constants.QUEUE_PRIORITY_NORMAL, undefined); isRequeued = true; } else if(cmd.getAttempt()) { - logger.warn('handleDeadLetter addResponse delayed = %d: docId = %s', cmd.getAttempt(), docId); + ctx.logger.warn('handleDeadLetter addResponse delayed = %d', cmd.getAttempt()); yield* addResponse(task); } else { //simulate error response @@ -904,9 +903,9 @@ function handleDeadLetter(data, ack) { canvasService.receiveTask(JSON.stringify(task), function(){}); } } - logger.warn('handleDeadLetter end: docId = %s; requeue = %s', docId, isRequeued); + ctx.logger.warn('handleDeadLetter end: requeue = %s', isRequeued); } catch (err) { - logger.error('handleDeadLetter error: docId = %s\r\n%s', docId, err.stack); + ctx.logger.error('handleDeadLetter error: %s', err.stack); } finally { ack(); } @@ -919,16 +918,16 @@ function handleDeadLetter(data, ack) { * @param callback * @param baseUrl */ -function* sendStatusDocument(docId, bChangeBase, opt_userAction, opt_userIndex, opt_callback, opt_baseUrl, opt_userData, opt_forceClose) { +function* sendStatusDocument(ctx, docId, bChangeBase, opt_userAction, opt_userIndex, opt_callback, opt_baseUrl, opt_userData, opt_forceClose) { if (!opt_callback) { - var getRes = yield* getCallback(docId, opt_userIndex); + var getRes = yield* getCallback(ctx, docId, opt_userIndex); if (getRes) { opt_callback = getRes.server; if (!opt_baseUrl) { opt_baseUrl = getRes.baseUrl; } if (getRes.wopiParams) { - logger.debug('sendStatusDocument wopi stub: docId = %s', docId); + ctx.logger.debug('sendStatusDocument wopi stub'); return opt_callback; } } @@ -938,9 +937,9 @@ function* sendStatusDocument(docId, bChangeBase, opt_userAction, opt_userIndex, } var status = c_oAscServerStatus.Editing; - var participants = yield* getOriginalParticipantsId(docId); + var participants = yield* getOriginalParticipantsId(ctx, docId); if (0 === participants.length) { - let bHasChanges = yield hasChanges(docId); + let bHasChanges = yield hasChanges(ctx, docId); if (!bHasChanges || opt_forceClose) { status = c_oAscServerStatus.Closed; } @@ -952,14 +951,15 @@ function* sendStatusDocument(docId, bChangeBase, opt_userAction, opt_userIndex, if (c_oAscChangeBase.All === bChangeBase) { //always override callback to avoid expired callbacks var updateTask = new taskResult.TaskResultData(); + updateTask.tenant = ctx.tenant; updateTask.key = docId; updateTask.callback = opt_callback.href; updateTask.baseurl = opt_baseUrl; - var updateIfRes = yield taskResult.update(updateTask); + var updateIfRes = yield taskResult.update(ctx, updateTask); if (updateIfRes.affectedRows > 0) { - logger.debug('sendStatusDocument updateIf: docId = %s', docId); + ctx.logger.debug('sendStatusDocument updateIf'); } else { - logger.debug('sendStatusDocument updateIf no effect: docId = %s', docId); + ctx.logger.debug('sendStatusDocument updateIf no effect'); } } } @@ -978,41 +978,41 @@ function* sendStatusDocument(docId, bChangeBase, opt_userAction, opt_userIndex, var uri = opt_callback.href; var replyData = null; try { - replyData = yield* sendServerRequest(docId, uri, sendData); + replyData = yield* sendServerRequest(ctx, uri, sendData); } catch (err) { replyData = null; - logger.error('postData error: docId = %s;url = %s;data = %j\r\n%s', docId, uri, sendData, err.stack); + ctx.logger.error('postData error: url = %s;data = %j %s', uri, sendData, err.stack); } - yield* onReplySendStatusDocument(docId, replyData); + yield* onReplySendStatusDocument(ctx, docId, replyData); return opt_callback; } -function parseReplyData(docId, replyData) { +function parseReplyData(ctx, replyData) { var res = null; if (replyData) { try { res = JSON.parse(replyData); } catch (e) { - logger.error("error parseReplyData: docId = %s; data = %s\r\n%s", docId, replyData, e.stack); + ctx.logger.error("error parseReplyData: data = %s %s", replyData, e.stack); res = null; } } return res; } -function* onReplySendStatusDocument(docId, replyData) { - var oData = parseReplyData(docId, replyData); +function* onReplySendStatusDocument(ctx, docId, replyData) { + var oData = parseReplyData(ctx, replyData); if (!(oData && commonDefines.c_oAscServerCommandErrors.NoError == oData.error)) { // Ошибка подписки на callback, посылаем warning - yield* publish({type: commonDefines.c_oPublishType.warning, docId: docId, description: 'Error on save server subscription!'}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.warning, ctx: ctx, docId: docId, description: 'Error on save server subscription!'}); } } -function* publishCloseUsersConnection(docId, users, isOriginalId, code, description) { +function* publishCloseUsersConnection(ctx, docId, users, isOriginalId, code, description) { if (Array.isArray(users)) { let usersMap = users.reduce(function(map, val) { map[val] = 1; return map; }, {}); - yield* publish({ - type: commonDefines.c_oPublishType.closeConnection, docId: docId, usersMap: usersMap, + yield* publish(ctx, { + type: commonDefines.c_oPublishType.closeConnection, ctx: ctx, docId: docId, usersMap: usersMap, isOriginalId: isOriginalId, code: code, description: description }); } @@ -1028,18 +1028,18 @@ function closeUsersConnection(docId, usersMap, isOriginalId, code, description) } } } -function* dropUsersFromDocument(docId, users) { +function* dropUsersFromDocument(ctx, docId, users) { if (Array.isArray(users)) { - yield* publish({type: commonDefines.c_oPublishType.drop, docId: docId, users: users, description: ''}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.drop, ctx: ctx, docId: docId, users: users, description: ''}); } } -function dropUserFromDocument(docId, userId, description) { +function dropUserFromDocument(ctx, docId, userId, description) { var elConnection; for (var i = 0, length = connections.length; i < length; ++i) { elConnection = connections[i]; if (elConnection.docId === docId && userId === elConnection.user.idOriginal && !elConnection.isCloseCoAuthoring) { - sendData(elConnection, + sendData(ctx, elConnection, { type: "drop", description: description @@ -1049,7 +1049,7 @@ function dropUserFromDocument(docId, userId, description) { } // Подписка на эвенты: -function* bindEvents(docId, callback, baseUrl, opt_userAction, opt_userData) { +function* bindEvents(ctx, docId, callback, baseUrl, opt_userAction, opt_userData) { // Подписка на эвенты: // - если пользователей нет и изменений нет, то отсылаем статус "закрыто" и в базу не добавляем // - если пользователей нет, а изменения есть, то отсылаем статус "редактируем" без пользователей, но добавляем в базу @@ -1057,18 +1057,18 @@ function* bindEvents(docId, callback, baseUrl, opt_userAction, opt_userData) { var bChangeBase; var oCallbackUrl; if (!callback) { - var getRes = yield* getCallback(docId); + var getRes = yield* getCallback(ctx, docId); if (getRes && !getRes.wopiParams) { oCallbackUrl = getRes.server; bChangeBase = c_oAscChangeBase.Delete; } } else { - oCallbackUrl = parseUrl(callback); + oCallbackUrl = parseUrl(ctx, callback); bChangeBase = c_oAscChangeBase.All; if (null !== oCallbackUrl) { - let filterStatus = yield* utils.checkHostFilter(oCallbackUrl.host); + let filterStatus = yield* utils.checkHostFilter(ctx, oCallbackUrl.host); if (filterStatus > 0) { - logger.warn('checkIpFilter error: docId = %s;url = %s', docId, callback); + ctx.logger.warn('checkIpFilter error: url = %s', callback); //todo add new error type oCallbackUrl = null; } @@ -1077,143 +1077,141 @@ function* bindEvents(docId, callback, baseUrl, opt_userAction, opt_userData) { if (null === oCallbackUrl) { return commonDefines.c_oAscServerCommandErrors.ParseError; } else { - yield* sendStatusDocument(docId, bChangeBase, opt_userAction, undefined, oCallbackUrl, baseUrl, opt_userData); + yield* sendStatusDocument(ctx, docId, bChangeBase, opt_userAction, undefined, oCallbackUrl, baseUrl, opt_userData); return commonDefines.c_oAscServerCommandErrors.NoError; } } -let unlockWopiDoc = co.wrap(function*(docId, opt_userIndex) { +let unlockWopiDoc = co.wrap(function*(ctx, docId, opt_userIndex) { //wopi unlock - var getRes = yield* getCallback(docId, opt_userIndex); + var getRes = yield* getCallback(ctx, docId, opt_userIndex); if (getRes && getRes.wopiParams && getRes.wopiParams.userAuth && 'view' !== getRes.wopiParams.userAuth.mode) { - yield wopiClient.unlock(getRes.wopiParams); + yield wopiClient.unlock(ctx, getRes.wopiParams); let unlockInfo = wopiClient.getWopiUnlockMarker(getRes.wopiParams); - yield canvasService.commandOpenStartPromise(docId, undefined, true, unlockInfo); + yield canvasService.commandOpenStartPromise(ctx, docId, undefined, true, unlockInfo); } }); -function* cleanDocumentOnExit(docId, deleteChanges, opt_userIndex) { +function* cleanDocumentOnExit(ctx, docId, deleteChanges, opt_userIndex) { //clean redis (redisKeyPresenceSet and redisKeyPresenceHash removed with last element) - yield editorData.cleanDocumentOnExit(docId); + yield editorData.cleanDocumentOnExit(ctx, docId); //remove changes if (deleteChanges) { - yield taskResult.restoreInitialPassword(docId); - sqlBase.deleteChanges(docId, null); + yield taskResult.restoreInitialPassword(ctx.tenant, docId); + sqlBase.deleteChanges(ctx, docId, null); //delete forgotten after successful send on callbackUrl - yield storage.deletePath(cfgForgottenFiles + '/' + docId); + yield storage.deletePath(ctx, cfgForgottenFiles + '/' + docId); } - yield unlockWopiDoc(docId, opt_userIndex); + yield unlockWopiDoc(ctx, docId, opt_userIndex); } -function* cleanDocumentOnExitNoChanges(docId, opt_userId, opt_userIndex, opt_forceClose) { +function* cleanDocumentOnExitNoChanges(ctx, docId, opt_userId, opt_userIndex, opt_forceClose) { var userAction = opt_userId ? new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, opt_userId) : null; // Отправляем, что все ушли и нет изменений (чтобы выставить статус на сервере об окончании редактирования) - yield* sendStatusDocument(docId, c_oAscChangeBase.No, userAction, opt_userIndex, undefined, undefined, undefined, opt_forceClose); + yield* sendStatusDocument(ctx, docId, c_oAscChangeBase.No, userAction, opt_userIndex, undefined, undefined, undefined, opt_forceClose); //если пользователь зашел в документ, соединение порвалось, на сервере удалилась вся информация, //при восстановлении соединения userIndex сохранится и он совпадет с userIndex следующего пользователя - yield* cleanDocumentOnExit(docId, false, opt_userIndex); + yield* cleanDocumentOnExit(ctx, docId, false, opt_userIndex); } -function* _createSaveTimer(docId, opt_userId, opt_userIndex, opt_queue, opt_noDelay) { - var updateMask = new taskResult.TaskResultData(); - updateMask.key = docId; - updateMask.status = taskResult.FileStatus.Ok; - var updateTask = new taskResult.TaskResultData(); - updateTask.status = taskResult.FileStatus.SaveVersion; - updateTask.statusInfo = utils.getMillisecondsOfHour(new Date()); - var updateIfRes = yield taskResult.updateIf(updateTask, updateMask); - if (updateIfRes.affectedRows > 0) { - if(!opt_noDelay){ - yield utils.sleep(cfgAscSaveTimeOutDelay); - } - while (true) { - if (!sqlBase.isLockCriticalSection(docId)) { - canvasService.saveFromChanges(docId, updateTask.statusInfo, null, opt_userId, opt_userIndex, opt_queue); - break; +function createSaveTimer(ctx, docId, opt_userId, opt_userIndex, opt_queue, opt_noDelay) { + return co(function*(){ + var updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; + updateMask.key = docId; + updateMask.status = taskResult.FileStatus.Ok; + var updateTask = new taskResult.TaskResultData(); + updateTask.status = taskResult.FileStatus.SaveVersion; + updateTask.statusInfo = utils.getMillisecondsOfHour(new Date()); + var updateIfRes = yield taskResult.updateIf(ctx, updateTask, updateMask); + if (updateIfRes.affectedRows > 0) { + if(!opt_noDelay){ + yield utils.sleep(cfgAscSaveTimeOutDelay); + } + while (true) { + if (!sqlBase.isLockCriticalSection(docId)) { + canvasService.saveFromChanges(ctx, docId, updateTask.statusInfo, null, opt_userId, opt_userIndex, opt_queue); + break; + } + yield utils.sleep(c_oAscLockTimeOutDelay); } - yield utils.sleep(c_oAscLockTimeOutDelay); - } - } else { - //если не получилось - значит FileStatus=SaveVersion(кто-то другой начал сборку) или UpdateVersion(сборка закончена) - //в этом случае ничего делать не надо - logger.debug('_createSaveTimer updateIf no effect'); - } -} - -function checkJwt(docId, token, type) { - var res = {decoded: null, description: null, code: null, token: token}; - var secret; - switch (type) { - case commonDefines.c_oAscSecretType.Browser: - secret = utils.getSecret(docId, cfgTokenBrowserSecretFromInbox ? cfgSecretInbox : cfgSecretBrowser, null, token); - break; - case commonDefines.c_oAscSecretType.Inbox: - secret = utils.getSecret(docId, cfgSecretInbox, null, token); - break; - case commonDefines.c_oAscSecretType.Session: - secret = utils.getSecretByElem(cfgSecretSession); - break; - } - if (undefined == secret) { - logger.warn('empty secret: docId = %s token = %s', docId, token); - } - try { - res.decoded = jwt.verify(token, secret, cfgTokenVerifyOptions); - logger.debug('checkJwt success: docId = %s decoded = %j', docId, res.decoded); - } catch (err) { - logger.warn('checkJwt error: docId = %s name = %s message = %s token = %s', docId, err.name, err.message, token); - if ('TokenExpiredError' === err.name) { - res.code = constants.JWT_EXPIRED_CODE; - res.description = constants.JWT_EXPIRED_REASON + err.message; - } else if ('JsonWebTokenError' === err.name) { - res.code = constants.JWT_ERROR_CODE; - res.description = constants.JWT_ERROR_REASON + err.message; - } - } - return res; -} -function checkJwtHeader(docId, req, opt_header, opt_prefix, opt_secretType) { - let header = opt_header || cfgTokenInboxHeader; - let prefix = opt_prefix || cfgTokenInboxPrefix; - let secretType = opt_secretType || commonDefines.c_oAscSecretType.Inbox; - let authorization = req.get(header); - if (authorization && authorization.startsWith(prefix)) { - var token = authorization.substring(prefix.length); - return checkJwt(docId, token, secretType); - } - return null; -} -function getRequestParams(docId, req, opt_isNotInBody) { - let res = {code: constants.NO_ERROR, params: undefined}; - if (req.body && Buffer.isBuffer(req.body) && req.body.length > 0 && !opt_isNotInBody) { - res.params = JSON.parse(req.body.toString('utf8')); - } else { - res.params = req.query; - } - if (cfgTokenEnableRequestInbox) { - res.code = constants.VKEY; - let checkJwtRes; - if (res.params.token) { - checkJwtRes = checkJwt(docId, res.params.token, commonDefines.c_oAscSecretType.Inbox); } else { - checkJwtRes = checkJwtHeader(docId, req); + //если не получилось - значит FileStatus=SaveVersion(кто-то другой начал сборку) или UpdateVersion(сборка закончена) + //в этом случае ничего делать не надо + ctx.logger.debug('createSaveTimer updateIf no effect'); } - if (checkJwtRes) { - if (checkJwtRes.decoded) { - res.code = constants.NO_ERROR; - if (cfgTokenRequiredParams) { - res.params = {}; - } - Object.assign(res.params, checkJwtRes.decoded); - if (!utils.isEmptyObject(checkJwtRes.decoded.payload)) { - Object.assign(res.params, checkJwtRes.decoded.payload); - } - if (!utils.isEmptyObject(checkJwtRes.decoded.query)) { - Object.assign(res.params, checkJwtRes.decoded.query); - } - } else if (constants.JWT_EXPIRED_CODE == checkJwtRes.code) { - res.code = constants.VKEY_KEY_EXPIRE; + }); +} + +function checkJwt(ctx, token, type) { + return co(function*() { + var res = {decoded: null, description: null, code: null, token: token}; + let secret = yield tenantManager.getTenantSecret(ctx, type); + if (undefined == secret) { + ctx.logger.warn('empty secret: token = %s', token); + } + try { + res.decoded = jwt.verify(token, secret, cfgTokenVerifyOptions); + ctx.logger.debug('checkJwt success: decoded = %j', res.decoded); + } catch (err) { + ctx.logger.warn('checkJwt error: name = %s message = %s token = %s', err.name, err.message, token); + if ('TokenExpiredError' === err.name) { + res.code = constants.JWT_EXPIRED_CODE; + res.description = constants.JWT_EXPIRED_REASON + err.message; + } else if ('JsonWebTokenError' === err.name) { + res.code = constants.JWT_ERROR_CODE; + res.description = constants.JWT_ERROR_REASON + err.message; } } - } - return res; + return res; + }); +} +function checkJwtHeader(ctx, req, opt_header, opt_prefix, opt_secretType) { + return co(function*() { + let header = opt_header || cfgTokenInboxHeader; + let prefix = opt_prefix || cfgTokenInboxPrefix; + let secretType = opt_secretType || commonDefines.c_oAscSecretType.Inbox; + let authorization = req.get(header); + if (authorization && authorization.startsWith(prefix)) { + var token = authorization.substring(prefix.length); + return yield checkJwt(ctx, token, secretType); + } + return null; + }); +} +function getRequestParams(ctx, req, opt_isNotInBody) { + return co(function*(){ + let res = {code: constants.NO_ERROR, params: undefined}; + if (req.body && Buffer.isBuffer(req.body) && req.body.length > 0 && !opt_isNotInBody) { + res.params = JSON.parse(req.body.toString('utf8')); + } else { + res.params = req.query; + } + if (cfgTokenEnableRequestInbox) { + res.code = constants.VKEY; + let checkJwtRes; + if (res.params.token) { + checkJwtRes = yield checkJwt(ctx, res.params.token, commonDefines.c_oAscSecretType.Inbox); + } else { + checkJwtRes = yield checkJwtHeader(ctx, req); + } + if (checkJwtRes) { + if (checkJwtRes.decoded) { + res.code = constants.NO_ERROR; + if (cfgTokenRequiredParams) { + res.params = {}; + } + Object.assign(res.params, checkJwtRes.decoded); + if (!utils.isEmptyObject(checkJwtRes.decoded.payload)) { + Object.assign(res.params, checkJwtRes.decoded.payload); + } + if (!utils.isEmptyObject(checkJwtRes.decoded.query)) { + Object.assign(res.params, checkJwtRes.decoded.query); + } + } else if (constants.JWT_EXPIRED_CODE == checkJwtRes.code) { + res.code = constants.VKEY_KEY_EXPIRE; + } + } + } + return res; + }); } function getLicenseNowUtc() { @@ -1221,13 +1219,13 @@ function getLicenseNowUtc() { return Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds()) / 1000; } -let getParticipantMap = co.wrap(function*(docId, opt_hvals) { +let getParticipantMap = co.wrap(function*(ctx, docId, opt_hvals) { const participantsMap = []; let hvals; if (opt_hvals) { hvals = opt_hvals; } else { - hvals = yield editorData.getPresence(docId, connections); + hvals = yield editorData.getPresence(ctx, docId, connections); } for (let i = 0; i < hvals.length; ++i) { const elem = JSON.parse(hvals[i]); @@ -1245,7 +1243,7 @@ exports.modifyConnectionForPassword = modifyConnectionForPassword; exports.parseUrl = parseUrl; exports.parseReplyData = parseReplyData; exports.sendServerRequest = sendServerRequest; -exports.createSaveTimerPromise = co.wrap(_createSaveTimer); +exports.createSaveTimer = createSaveTimer; exports.changeConnectionInfo = changeConnectionInfo; exports.signToken = signToken; exports.publish = publish; @@ -1268,16 +1266,17 @@ exports.checkJwt = checkJwt; exports.getRequestParams = getRequestParams; exports.checkJwtHeader = checkJwtHeader; exports.install = function(server, callbackFunction) { - var sockjs_echo = sockjs.createServer(cfgSockjs), - urlParse = new RegExp("^/doc/([" + constants.DOC_ID_PATTERN + "]*)/c.+", 'i'); + var sockjs_echo = sockjs.createServer(cfgSockjs); sockjs_echo.on('connection', function(conn) { + let ctx = new operationContext.Context(); + ctx.initFromConnection(conn); if (!conn) { - logger.error("null == conn"); + operationContext.global.logger.error("null == conn"); return; } if (getIsShutdown()) { - sendFileError(conn, 'Server shutdow'); + sendFileError(ctx, conn, 'Server shutdow'); return; } conn.baseUrl = utils.getBaseUrlByConnection(conn); @@ -1287,80 +1286,83 @@ exports.install = function(server, callbackFunction) { conn.on('data', function(message) { return co(function* () { var docId = 'null'; + let ctx = new operationContext.Context(); try { + ctx.initFromConnection(conn); var startDate = null; if(clientStatsD) { startDate = new Date(); } + var data = JSON.parse(message); docId = conn.docId; - logger.info('data.type = ' + data.type + ' id = ' + docId); + ctx.logger.info('data.type = %s', data.type); if(getIsShutdown()) { - logger.debug('Server shutdown receive data'); + ctx.logger.debug('Server shutdown receive data'); return; } if (conn.isCiriticalError && ('message' == data.type || 'getLock' == data.type || 'saveChanges' == data.type || 'isSaveLock' == data.type)) { - logger.warn("conn.isCiriticalError send command: docId = %s type = %s", docId, data.type); + ctx.logger.warn("conn.isCiriticalError send command: type = %s", data.type); conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); return; } if ((conn.isCloseCoAuthoring || (conn.user && conn.user.view)) && ('getLock' == data.type || 'saveChanges' == data.type || 'isSaveLock' == data.type)) { - logger.warn("conn.user.view||isCloseCoAuthoring access deny: docId = %s type = %s", docId, data.type); + ctx.logger.warn("conn.user.view||isCloseCoAuthoring access deny: type = %s", data.type); conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); return; } - yield* encryptPasswordParams(data); + yield* encryptPasswordParams(ctx, data); switch (data.type) { case 'auth' : - yield* auth(conn, data); + yield* auth(ctx, conn, data); break; case 'message' : - yield* onMessage(conn, data); + yield* onMessage(ctx, conn, data); break; case 'cursor' : - yield* onCursor(conn, data); + yield* onCursor(ctx, conn, data); break; case 'getLock' : - yield* getLock(conn, data, false); + yield* getLock(ctx, conn, data, false); break; case 'saveChanges' : - yield* saveChanges(conn, data); + yield* saveChanges(ctx, conn, data); break; case 'isSaveLock' : - yield* isSaveLock(conn, data); + yield* isSaveLock(ctx, conn, data); break; case 'unSaveLock' : - yield* unSaveLock(conn, -1, -1); + yield* unSaveLock(ctx, conn, -1, -1); break; // Индекс отправляем -1, т.к. это экстренное снятие без сохранения case 'getMessages' : - yield* getMessages(conn, data); + yield* getMessages(ctx, conn, data); break; case 'unLockDocument' : - yield* checkEndAuthLock(data.unlock, data.isSave, docId, conn.user.id, data.releaseLocks, data.deleteIndex, conn); + yield* checkEndAuthLock(ctx, data.unlock, data.isSave, docId, conn.user.id, data.releaseLocks, data.deleteIndex, conn); break; case 'close': - yield* closeDocument(conn, false); + yield* closeDocument(ctx, conn, false); break; case 'versionHistory' : { let cmd = new commonDefines.InputCommand(data.cmd); - yield* versionHistory(conn, cmd); + yield* versionHistory(ctx, conn, cmd); break; } case 'openDocument' : { var cmd = new commonDefines.InputCommand(data.message); cmd.fillFromConnection(conn); - yield canvasService.openDocument(conn, cmd); + yield canvasService.openDocument(ctx, conn, cmd); break; } case 'changesError': - logger.error("changesError: docId = %s %s", docId, data.stack); + ctx.logger.error("changesError: %s", data.stack); if (cfgErrorFiles && docId) { let destDir = cfgErrorFiles + '/browser/' + docId; - yield storage.copyPath(docId, destDir); - yield* saveErrorChanges(docId, destDir); + yield storage.copyPath(ctx, docId, destDir); + yield* saveErrorChanges(ctx, docId, destDir); } break; case 'extendSession' : @@ -1370,17 +1372,17 @@ exports.install = function(server, callbackFunction) { case 'forceSaveStart' : var forceSaveRes; if (conn.user) { - forceSaveRes = yield startForceSave(docId, commonDefines.c_oAscForceSaveTypes.Button, undefined, conn.user.idOriginal, conn.user.id, conn.user.indexUser); + forceSaveRes = yield startForceSave(ctx, docId, commonDefines.c_oAscForceSaveTypes.Button, undefined, conn.user.idOriginal, conn.user.id, conn.user.indexUser); } else { forceSaveRes = {code: commonDefines.c_oAscServerCommandErrors.UnknownError, time: null}; } - sendData(conn, {type: "forceSaveStart", messages: forceSaveRes}); + sendData(ctx, conn, {type: "forceSaveStart", messages: forceSaveRes}); break; case 'rpc' : - yield* startRPC(conn, data.responseKey, data.data); + yield* startRPC(ctx, conn, data.responseKey, data.data); break; default: - logger.debug("unknown command %s", message); + ctx.logger.debug("unknown command %s", message); break; } if(clientStatsD) { @@ -1389,33 +1391,35 @@ exports.install = function(server, callbackFunction) { } } } catch (e) { - logger.error("error receiving response: docId = %s type = %s\r\n%s", docId, (data && data.type) ? data.type : 'null', e.stack); + ctx.logger.error("error receiving response: type = %s %s", (data && data.type) ? data.type : 'null', e.stack); } }); }); conn.on('error', function() { - logger.error("On error"); + let ctx = new operationContext.Context(); + ctx.initFromConnection(conn); + ctx.logger.error("On error"); }); conn.on('close', function() { return co(function* () { - var docId = 'null'; + let ctx = new operationContext.Context(); try { - docId = conn.docId; - yield* closeDocument(conn, true); + ctx.initFromConnection(conn); + yield* closeDocument(ctx, conn, true); } catch (err) { - logger.error('Error conn close: docId = %s\r\n%s', docId, err.stack); + ctx.logger.error('Error conn close: %s', err.stack); } }); }); - _checkLicense(conn); + _checkLicense(ctx, conn); }); /** * * @param conn * @param isCloseConnection - закрываем ли мы окончательно соединение */ - function* closeDocument(conn, isCloseConnection) { + function* closeDocument(ctx, conn, isCloseConnection) { var userLocks, reconnected = false, bHasEditors, bHasChanges; var docId = conn.docId; if (null == docId) { @@ -1425,7 +1429,7 @@ exports.install = function(server, callbackFunction) { let participantsTimestamp; var tmpUser = conn.user; var isView = tmpUser.view; - logger.info("Connection closed or timed out: userId = %s isCloseConnection = %s docId = %s", tmpUser.id, isCloseConnection, docId); + ctx.logger.info("Connection closed or timed out: isCloseConnection = %s", isCloseConnection); var isCloseCoAuthoringTmp = conn.isCloseCoAuthoring; if (isCloseConnection) { //Notify that participant has gone @@ -1433,24 +1437,24 @@ exports.install = function(server, callbackFunction) { return el.id === conn.id;//Delete this connection }); //Check if it's not already reconnected - reconnected = yield* isUserReconnect(docId, tmpUser.id, conn.id); + reconnected = yield* isUserReconnect(ctx, docId, tmpUser.id, conn.id); if (reconnected) { - logger.info("reconnected: userId = %s docId = %s", tmpUser.id, docId); + ctx.logger.info("reconnected"); } else { - yield removePresence(conn); - hvals = yield editorData.getPresence(docId, connections); + yield removePresence(ctx, conn); + hvals = yield editorData.getPresence(ctx, docId, connections); participantsTimestamp = Date.now(); if (hvals.length <= 0) { - yield editorData.removePresenceDocument(docId); + yield editorData.removePresenceDocument(ctx, docId); } } } else { if (!conn.isCloseCoAuthoring) { tmpUser.view = true; conn.isCloseCoAuthoring = true; - yield addPresence(conn, true); + yield addPresence(ctx, conn, true); if (cfgTokenEnableBrowser) { - sendDataRefreshToken(conn); + sendDataRefreshToken(ctx, conn); } } } @@ -1464,24 +1468,24 @@ exports.install = function(server, callbackFunction) { //revert old view to send event var tmpView = tmpUser.view; tmpUser.view = isView; - let participants = yield getParticipantMap(docId, hvals); + let participants = yield getParticipantMap(ctx, docId, hvals); if (!participantsTimestamp) { participantsTimestamp = Date.now(); } - yield* publish({type: commonDefines.c_oPublishType.participantsState, docId: docId, userId: tmpUser.id, participantsTimestamp: participantsTimestamp, participants: participants}, docId, tmpUser.id); + yield* publish(ctx, {type: commonDefines.c_oPublishType.participantsState, ctx: ctx, docId: docId, userId: tmpUser.id, participantsTimestamp: participantsTimestamp, participants: participants}, docId, tmpUser.id); tmpUser.view = tmpView; // Для данного пользователя снимаем лок с сохранения - yield editorData.unlockSave(docId, conn.user.id); + yield editorData.unlockSave(ctx, docId, conn.user.id); // Только если редактируем if (false === isView) { - bHasEditors = yield* hasEditors(docId, hvals); - bHasChanges = yield hasChanges(docId); + bHasEditors = yield* hasEditors(ctx, docId, hvals); + bHasChanges = yield hasChanges(ctx, docId); let needSendStatus = true; if (conn.encrypted) { - let selectRes = yield taskResult.select(docId); + let selectRes = yield taskResult.select(ctx, docId); if (selectRes.length > 0) { var row = selectRes[0]; if (taskResult.FileStatus.UpdateVersion === row.status) { @@ -1491,82 +1495,82 @@ exports.install = function(server, callbackFunction) { } //Давайдосвиданья! //Release locks - userLocks = yield* removeUserLocks(docId, conn.user.id); + userLocks = yield* removeUserLocks(ctx, docId, conn.user.id); if (0 < userLocks.length) { //todo на close себе ничего не шлем //sendReleaseLock(conn, userLocks); - yield* publish({type: commonDefines.c_oPublishType.releaseLock, docId: docId, userId: conn.user.id, locks: userLocks}, docId, conn.user.id); + yield* publish(ctx, {type: commonDefines.c_oPublishType.releaseLock, ctx: ctx, docId: docId, userId: conn.user.id, locks: userLocks}, docId, conn.user.id); } // Для данного пользователя снимаем Lock с документа - yield* checkEndAuthLock(true, false, docId, conn.user.id); + yield* checkEndAuthLock(ctx, true, false, docId, conn.user.id); let userIndex = utils.getIndexFromUserId(tmpUser.id, tmpUser.idOriginal); // Если у нас нет пользователей, то удаляем все сообщения if (!bHasEditors) { // На всякий случай снимаем lock - yield editorData.unlockSave(docId, tmpUser.id); + yield editorData.unlockSave(ctx, docId, tmpUser.id); let needSaveChanges = bHasChanges; if (!needSaveChanges) { //start save changes if forgotten file exists. //more effective to send file without sfc, but this method is simpler by code - let forgotten = yield storage.listObjects(cfgForgottenFiles + '/' + docId); + let forgotten = yield storage.listObjects(ctx, cfgForgottenFiles + '/' + docId); needSaveChanges = forgotten.length > 0; - logger.debug('closeDocument hasForgotten %s: docId = %s', needSaveChanges, docId); + ctx.logger.debug('closeDocument hasForgotten %s', needSaveChanges); } if (needSaveChanges && !conn.encrypted) { // Send changes to save server - yield* _createSaveTimer(docId, tmpUser.idOriginal, userIndex); + yield createSaveTimer(ctx, docId, tmpUser.idOriginal, userIndex); } else if (needSendStatus) { - yield* cleanDocumentOnExitNoChanges(docId, tmpUser.idOriginal, userIndex); + yield* cleanDocumentOnExitNoChanges(ctx, docId, tmpUser.idOriginal, userIndex); } else { - yield* cleanDocumentOnExit(docId, false, userIndex); + yield* cleanDocumentOnExit(ctx, docId, false, userIndex); } } else if (needSendStatus) { - yield* sendStatusDocument(docId, c_oAscChangeBase.No, new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, tmpUser.idOriginal), userIndex); + yield* sendStatusDocument(ctx, docId, c_oAscChangeBase.No, new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, tmpUser.idOriginal), userIndex); } } } } - function* versionHistory(conn, cmd) { + function* versionHistory(ctx, conn, cmd) { var docIdOld = conn.docId; var docIdNew = cmd.getDocId(); //check jwt if (cfgTokenEnableBrowser) { - var checkJwtRes = checkJwt(docIdNew, cmd.getTokenHistory(), commonDefines.c_oAscSecretType.Browser); + var checkJwtRes = yield checkJwt(ctx, cmd.getTokenHistory(), commonDefines.c_oAscSecretType.Browser); if (checkJwtRes.decoded) { fillVersionHistoryFromJwt(checkJwtRes.decoded, cmd); docIdNew = cmd.getDocId(); cmd.setWithAuthorization(true); } else { - sendData(conn, {type: "expiredToken", code: checkJwtRes.code, description: checkJwtRes.description}); + sendData(ctx, conn, {type: "expiredToken", code: checkJwtRes.code, description: checkJwtRes.description}); return; } } if (docIdOld !== docIdNew) { //remove presence(other data was removed before in closeDocument) - yield removePresence(conn); - var hvals = yield editorData.getPresence(docIdOld, connections); + yield removePresence(ctx, conn); + var hvals = yield editorData.getPresence(ctx, docIdOld, connections); if (hvals.length <= 0) { - yield editorData.removePresenceDocument(docIdOld); + yield editorData.removePresenceDocument(ctx, docIdOld); } //apply new conn.docId = docIdNew; - yield addPresence(conn, true); + yield addPresence(ctx, conn, true); if (cfgTokenEnableBrowser) { - sendDataRefreshToken(conn); + sendDataRefreshToken(ctx, conn); } } //open - yield canvasService.openDocument(conn, cmd, null); + yield canvasService.openDocument(ctx, conn, cmd, null); } // Получение изменений для документа (либо из кэша, либо обращаемся к базе, но только если были сохранения) - function* getDocumentChanges(docId, optStartIndex, optEndIndex) { + function* getDocumentChanges(ctx, docId, optStartIndex, optEndIndex) { // Если за тот момент, пока мы ждали из базы ответа, все ушли, то отправлять ничего не нужно - var arrayElements = yield sqlBase.getChangesPromise(docId, optStartIndex, optEndIndex); + var arrayElements = yield sqlBase.getChangesPromise(ctx, docId, optStartIndex, optEndIndex); var j, element; var objChangesDocument = new DocumentChanges(docId); for (j = 0; j < arrayElements.length; ++j) { @@ -1580,18 +1584,18 @@ exports.install = function(server, callbackFunction) { return objChangesDocument; } - function* getAllLocks(docId) { + function* getAllLocks(ctx, docId) { var docLockRes = []; - var docLock = yield editorData.getLocks(docId); + var docLock = yield editorData.getLocks(ctx, docId); for (var i = 0; i < docLock.length; ++i) { docLockRes.push(docLock[i]); } return docLockRes; } - function* removeUserLocks(docId, userId) { + function* removeUserLocks(ctx, docId, userId) { var userLocks = [], i; var toCache = []; - var docLock = yield* getAllLocks(docId); + var docLock = yield* getAllLocks(ctx, docId); for (i = 0; i < docLock.length; ++i) { var elem = docLock[i]; if (elem.user === userId) { @@ -1601,33 +1605,34 @@ exports.install = function(server, callbackFunction) { } } //remove all - yield editorData.removeLocks(docId); + yield editorData.removeLocks(ctx, docId); //set all - yield editorData.addLocks(docId, toCache); + yield editorData.addLocks(ctx, docId, toCache); return userLocks; } - function* checkEndAuthLock(unlock, isSave, docId, userId, releaseLocks, deleteIndex, conn) { + function* checkEndAuthLock(ctx, unlock, isSave, docId, userId, releaseLocks, deleteIndex, conn) { let result = false; if (null != deleteIndex && -1 !== deleteIndex) { - let puckerIndex = yield* getChangesIndex(docId); + let puckerIndex = yield* getChangesIndex(ctx, docId); const deleteCount = puckerIndex - deleteIndex; if (0 < deleteCount) { puckerIndex -= deleteCount; - yield sqlBase.deleteChangesPromise(docId, deleteIndex); + yield sqlBase.deleteChangesPromise(ctx, docId, deleteIndex); } else if (0 > deleteCount) { - logger.error("Error checkEndAuthLock docid: %s ; deleteIndex: %s ; startIndex: %s ; deleteCount: %s", docId, + ctx.logger.error("Error checkEndAuthLock: deleteIndex: %s ; startIndex: %s ; deleteCount: %s", deleteIndex, puckerIndex, deleteCount); } } if (unlock) { - var unlockRes = yield editorData.unlockAuth(docId, userId); + var unlockRes = yield editorData.unlockAuth(ctx, docId, userId); if (commonDefines.c_oAscUnlockRes.Unlocked === unlockRes) { - const participantsMap = yield getParticipantMap(docId); - yield* publish({ + const participantsMap = yield getParticipantMap(ctx, docId); + yield* publish(ctx, { type: commonDefines.c_oPublishType.auth, + ctx: ctx, docId: docId, userId: userId, participantsMap: participantsMap @@ -1639,11 +1644,12 @@ exports.install = function(server, callbackFunction) { //Release locks if (releaseLocks && conn) { - const userLocks = yield* removeUserLocks(docId, userId); + const userLocks = yield* removeUserLocks(ctx, docId, userId); if (0 < userLocks.length) { - sendReleaseLock(conn, userLocks); - yield* publish({ + sendReleaseLock(ctx, conn, userLocks); + yield* publish(ctx, { type: commonDefines.c_oPublishType.releaseLock, + ctx: ctx, docId: docId, userId: userId, locks: userLocks @@ -1652,37 +1658,37 @@ exports.install = function(server, callbackFunction) { } if (isSave && conn) { // Автоматически снимаем lock сами - yield* unSaveLock(conn, -1, -1); + yield* unSaveLock(ctx, conn, -1, -1); } return result; } - function* setLockDocumentTimer(docId, userId) { + function* setLockDocumentTimer(ctx, docId, userId) { let timerId = setTimeout(function() { return co(function*() { try { - logger.warn("lockDocumentsTimerId timeout: docId = %s", docId); + ctx.logger.warn("lockDocumentsTimerId timeout"); delete lockDocumentsTimerId[docId]; //todo remove checkEndAuthLock(only needed for lost connections in redis) - yield* checkEndAuthLock(true, false, docId, userId); - yield* publishCloseUsersConnection(docId, [userId], false, constants.DROP_CODE, constants.DROP_REASON); + yield* checkEndAuthLock(ctx, true, false, docId, userId); + yield* publishCloseUsersConnection(ctx, docId, [userId], false, constants.DROP_CODE, constants.DROP_REASON); } catch (e) { - logger.error("lockDocumentsTimerId error:\r\n%s", e.stack); + ctx.logger.error("lockDocumentsTimerId error: %s", e.stack); } }); }, 1000 * cfgExpLockDoc); lockDocumentsTimerId[docId] = {timerId: timerId, userId: userId}; - logger.debug("lockDocumentsTimerId set userId = %s: docId = %s", userId, docId); + ctx.logger.debug("lockDocumentsTimerId set"); } function cleanLockDocumentTimer(docId, lockDocumentTimer) { clearTimeout(lockDocumentTimer.timerId); delete lockDocumentsTimerId[docId]; } - function sendParticipantsState(participants, data) { + function sendParticipantsState(ctx, participants, data) { _.each(participants, function(participant) { - sendData(participant, { + sendData(ctx, participant, { type: "connectState", participantsTimestamp: data.participantsTimestamp, participants: data.participants, @@ -1691,13 +1697,13 @@ exports.install = function(server, callbackFunction) { }); } - function sendFileError(conn, errorId, code) { - logger.warn('error description: docId = %s errorId = %s', conn.docId, errorId); + function sendFileError(ctx, conn, errorId, code) { + ctx.logger.warn('error description: errorId = %s', errorId); conn.isCiriticalError = true; - sendData(conn, {type: 'error', description: errorId, code: code}); + sendData(ctx, conn, {type: 'error', description: errorId, code: code}); } - function* sendFileErrorAuth(conn, sessionId, errorId, code) { + function* sendFileErrorAuth(ctx, conn, sessionId, errorId, code) { conn.isCloseCoAuthoring = true; conn.sessionId = sessionId;//restore old //Kill previous connections @@ -1708,9 +1714,9 @@ exports.install = function(server, callbackFunction) { if (constants.CONN_CLOSED !== conn.readyState) { // Кладем в массив, т.к. нам нужно отправлять данные для открытия/сохранения документа connections.push(conn); - yield addPresence(conn, true); + yield addPresence(ctx, conn, true); - sendFileError(conn, errorId, code); + sendFileError(ctx, conn, errorId, code); } } @@ -1861,17 +1867,17 @@ exports.install = function(server, callbackFunction) { return resultLock; } - function* authRestore(conn, sessionId) { + function* authRestore(ctx, conn, sessionId) { conn.sessionId = sessionId;//restore old //Kill previous connections connections = _.reject(connections, function(el) { return el.sessionId === sessionId;//Delete this connection }); - yield* endAuth(conn, true); + yield* endAuth(ctx, conn, true); } - function fillUsername(data) { + function fillUsername(ctx, data) { let name; let user = data.user; if (user.firstname && user.lastname) { @@ -1882,16 +1888,16 @@ exports.install = function(server, callbackFunction) { name = user.username || "Anonymous"; } if (name.length > constants.USER_NAME_MAX_LENGTH) { - logger.warn('fillUsername user name too long actual = %s; max = %s', name.length, constants.USER_NAME_MAX_LENGTH); + ctx.logger.warn('fillUsername user name too long actual = %s; max = %s', name.length, constants.USER_NAME_MAX_LENGTH); name = name.substr(0, constants.USER_NAME_MAX_LENGTH); } return name; } function isEditMode(permissions, mode) { - //as in web-apps/apps/documenteditor/main/app/controller/Main.js + //as in web-apps/apps/documenteditor/main/app/controller/Main.js return (!mode || mode !== 'view') && (!permissions || permissions.edit !== false || permissions.review === true || permissions.comment === true || permissions.fillForms === true); - } + } function fillDataFromWopiJwt(decoded, data) { let res = true; var openCmd = data.openCmd; @@ -1962,7 +1968,7 @@ exports.install = function(server, callbackFunction) { } return res; } - function fillDataFromJwt(decoded, data) { + function fillDataFromJwt(ctx, decoded, data) { let res = true; var openCmd = data.openCmd; if (decoded.document) { @@ -1976,7 +1982,7 @@ exports.install = function(server, callbackFunction) { if(doc.permissions) { res = deepEqual(data.permissions, doc.permissions, {strict: true}); if (!res) { - logger.warn('fillDataFromJwt token has modified permissions docId = %s', data.docid); + ctx.logger.warn('fillDataFromJwt token has modified permissions'); } if(!data.permissions){ data.permissions = {}; @@ -2064,7 +2070,7 @@ exports.install = function(server, callbackFunction) { //todo make required fields if (decoded.url || decoded.payload|| (decoded.key && !decoded.fileInfo)) { - logger.warn('fillDataFromJwt token has invalid format docId = %s', data.docid); + ctx.logger.warn('fillDataFromJwt token has invalid format'); res = false; } @@ -2084,7 +2090,7 @@ exports.install = function(server, callbackFunction) { } } - function* encryptPasswordParams(data) { + function* encryptPasswordParams(ctx, data) { let dataWithPassword; if (data.type === 'openDocument' && data.message) { dataWithPassword = data.message; @@ -2094,7 +2100,7 @@ exports.install = function(server, callbackFunction) { if (dataWithPassword && dataWithPassword.password) { if (dataWithPassword.password.length > constants.PASSWORD_MAX_LENGTH) { //todo send back error - logger.warn('encryptPasswordParams password too long actual = %s; max = %s', dataWithPassword.password.length, constants.PASSWORD_MAX_LENGTH); + ctx.logger.warn('encryptPasswordParams password too long actual = %s; max = %s', dataWithPassword.password.length, constants.PASSWORD_MAX_LENGTH); dataWithPassword.password = null; } else { dataWithPassword.password = yield utils.encryptPassword(dataWithPassword.password); @@ -2102,15 +2108,14 @@ exports.install = function(server, callbackFunction) { } } - function* auth(conn, data) { + function* auth(ctx, conn, data) { //TODO: Do authorization etc. check md5 or query db if (data.token && data.user) { - let docId = data.docid; + let licenseInfo = yield tenantManager.getTenantLicense(ctx); //check jwt if (cfgTokenEnableBrowser) { - let secretType = !!data.jwtSession ? commonDefines.c_oAscSecretType.Session : - commonDefines.c_oAscSecretType.Browser; - const checkJwtRes = checkJwt(docId, data.jwtSession || data.jwtOpen, secretType); + let secretType = !!data.jwtSession ? commonDefines.c_oAscSecretType.Session : commonDefines.c_oAscSecretType.Browser; + const checkJwtRes = yield checkJwt(ctx, data.jwtSession || data.jwtOpen, secretType); if (checkJwtRes.decoded) { let decoded = checkJwtRes.decoded; let fillDataFromJwtRes = false; @@ -2119,23 +2124,23 @@ exports.install = function(server, callbackFunction) { fillDataFromJwtRes = fillDataFromWopiJwt(decoded, data); } else if (decoded.editorConfig && undefined !== decoded.editorConfig.ds_view) { //reconnection - fillDataFromJwtRes = fillDataFromJwt(decoded, data); + fillDataFromJwtRes = fillDataFromJwt(ctx, decoded, data); } else { //opening let validationErr = validateAuthToken(data, decoded); if (!validationErr) { - fillDataFromJwtRes = fillDataFromJwt(decoded, data); + fillDataFromJwtRes = fillDataFromJwt(ctx, decoded, data); } else if (cfgTokenRequiredParams) { - logger.error("auth missing required parameter %s (since 7.1 version): docId = %s ", validationErr, docId); + ctx.logger.error("auth missing required parameter %s (since 7.1 version)", validationErr); conn.close(constants.JWT_ERROR_CODE, constants.JWT_ERROR_REASON); return; } else { - logger.warn("auth missing required parameter %s (since 7.1 version): docId = %s ", validationErr, docId); - fillDataFromJwtRes = fillDataFromJwt(decoded, data); + ctx.logger.warn("auth missing required parameter %s (since 7.1 version)", validationErr); + fillDataFromJwtRes = fillDataFromJwt(ctx, decoded, data); } } if(!fillDataFromJwtRes) { - logger.warn("fillDataFromJwt return false: docId = %s", docId); + ctx.logger.warn("fillDataFromJwt return false"); conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); return; } @@ -2145,12 +2150,12 @@ exports.install = function(server, callbackFunction) { } } - docId = data.docid; + let docId = data.docid; const user = data.user; let wopiParams = null; if (data.documentCallbackUrl) { - wopiParams = wopiClient.parseWopiCallback(docId, data.documentCallbackUrl); + wopiParams = wopiClient.parseWopiCallback(ctx, data.documentCallbackUrl); if (wopiParams && wopiParams.userAuth) { conn.access_token_ttl = wopiParams.userAuth.access_token_ttl; } @@ -2165,31 +2170,32 @@ exports.install = function(server, callbackFunction) { } else { if (data.documentCallbackUrl && !wopiParams) { documentCallback = url.parse(data.documentCallbackUrl); - let filterStatus = yield* utils.checkHostFilter(documentCallback.hostname); + let filterStatus = yield* utils.checkHostFilter(ctx, documentCallback.hostname); if (0 !== filterStatus) { - logger.warn('checkIpFilter error: docId = %s;url = %s', docId, data.documentCallbackUrl); + ctx.logger.warn('checkIpFilter error: url = %s', data.documentCallbackUrl); conn.close(constants.DROP_CODE, constants.DROP_REASON); return; } } let format = data.openCmd && data.openCmd.format; - upsertRes = yield canvasService.commandOpenStartPromise(docId, utils.getBaseUrlByConnection(conn), true, data.documentCallbackUrl, format); + upsertRes = yield canvasService.commandOpenStartPromise(ctx, docId, utils.getBaseUrlByConnection(conn), true, data.documentCallbackUrl, format); let isInserted = upsertRes.affectedRows == 1; curIndexUser = isInserted ? 1 : upsertRes.insertId; if (isInserted && undefined !== data.timezoneOffset) { //todo insert in commandOpenStartPromise. insert here for database compatibility if (false === canvasService.hasAdditionalCol) { - let selectRes = yield taskResult.select(docId); + let selectRes = yield taskResult.select(ctx, docId); canvasService.hasAdditionalCol = selectRes.length > 0 && undefined !== selectRes[0].additional; } if (canvasService.hasAdditionalCol) { let task = new taskResult.TaskResultData(); + task.tenant = ctx.tenant; task.key = docId; //todo duplicate created_at because CURRENT_TIMESTAMP uses server timezone task.additional = sqlBase.DocumentAdditional.prototype.setOpenedAt(Date.now(), data.timezoneOffset); - yield taskResult.update(task); + yield taskResult.update(ctx, task); } else { - logger.warn('auth unknown column "additional": docId = %s', docId); + ctx.logger.warn('auth unknown column "additional"'); } } } @@ -2205,7 +2211,7 @@ exports.install = function(server, callbackFunction) { conn.user = { id: curUserId, idOriginal: curUserIdOriginal, - username: fillUsername(data), + username: fillUsername(ctx, data), indexUser: curIndexUser, view: !isEditMode(data.permissions, data.mode) }; @@ -2229,13 +2235,13 @@ exports.install = function(server, callbackFunction) { let isLiveViewer = utils.isLiveViewer(conn); if (!conn.user.view || isLiveViewer) { //todo - let licenceType = conn.licenseType = yield* _checkLicenseAuth(conn.user.idOriginal, isLiveViewer); - if (c_LR.Success !== licenceType && c_LR.SuccessLimit !== licenceType) { + let licenseType = conn.licenseType = yield* _checkLicenseAuth(ctx, licenseInfo, conn.user.idOriginal, isLiveViewer); + if (c_LR.Success !== licenseType && c_LR.SuccessLimit !== licenseType) { conn.user.view = true; delete conn.coEditingMode; } else { //don't check IsAnonymousUser via jwt because substituting it doesn't lead to any trouble - yield* updateEditUsers(conn.user.idOriginal, !!data.IsAnonymousUser, isLiveViewer); + yield* updateEditUsers(ctx, licenseInfo, conn.user.idOriginal, !!data.IsAnonymousUser, isLiveViewer); } } @@ -2257,33 +2263,33 @@ exports.install = function(server, callbackFunction) { if (constants.CONN_CLOSED !== conn.readyState) { // Кладем в массив, т.к. нам нужно отправлять данные для открытия/сохранения документа connections.push(conn); - yield addPresence(conn, true); + yield addPresence(ctx, conn, true); // Посылаем формальную авторизацию, чтобы подтвердить соединение - yield* sendAuthInfo(conn, bIsRestore, undefined); + yield* sendAuthInfo(ctx, conn, bIsRestore, undefined); if (cmd) { - yield canvasService.openDocument(conn, cmd, upsertRes, bIsRestore); + yield canvasService.openDocument(ctx, conn, cmd, upsertRes, bIsRestore); } } return; } - let result = yield taskResult.select(docId); + let result = yield taskResult.select(ctx, docId); let resultRow = result.length > 0 ? result[0] : null; if (cmd && resultRow && resultRow.callback) { - let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, resultRow.callback, curIndexUser); - let wopiParams = wopiClient.parseWopiCallback(docId, userAuthStr, resultRow.callback); + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, resultRow.callback, curIndexUser); + let wopiParams = wopiClient.parseWopiCallback(ctx, userAuthStr, resultRow.callback); cmd.setWopiParams(wopiParams); if (wopiParams) { documentCallback = null; if (!wopiParams.userAuth) { - yield* sendFileErrorAuth(conn, data.sessionId, 'Wopi without userAuth'); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Wopi without userAuth'); return; } } } if (conn.user.idOriginal.length > constants.USER_ID_MAX_LENGTH) { //todo refactor DB and remove restrictions - logger.warn('auth user id too long actual = %s; max = %s; docId = %s', curUserIdOriginal.length, constants.USER_ID_MAX_LENGTH, docId); - yield* sendFileErrorAuth(conn, data.sessionId, 'User id too long'); + ctx.logger.warn('auth user id too long actual = %s; max = %s', curUserIdOriginal.length, constants.USER_ID_MAX_LENGTH); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'User id too long'); return; } if (!conn.user.view) { @@ -2295,49 +2301,50 @@ exports.install = function(server, callbackFunction) { Date.now() - result[0]['status_info'] * 60000 > cfgExpUpdateVersionStatus)) { let newStatus = taskResult.FileStatus.Ok; if (taskResult.FileStatus.UpdateVersion === status) { - logger.warn("UpdateVersion expired: docId = %s", docId); + ctx.logger.warn("UpdateVersion expired"); //FileStatus.None to open file again from new url newStatus = taskResult.FileStatus.None; } // Обновим статус файла (идет сборка, нужно ее остановить) var updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; updateMask.key = docId; updateMask.status = status; updateMask.statusInfo = result[0]['status_info']; var updateTask = new taskResult.TaskResultData(); updateTask.status = newStatus; updateTask.statusInfo = constants.NO_ERROR; - var updateIfRes = yield taskResult.updateIf(updateTask, updateMask); + var updateIfRes = yield taskResult.updateIf(ctx, updateTask, updateMask); if (!(updateIfRes.affectedRows > 0)) { // error version - yield* sendFileErrorAuth(conn, data.sessionId, 'Update Version error', constants.UPDATE_VERSION_CODE); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Update Version error', constants.UPDATE_VERSION_CODE); return; } } else if (bIsRestore && taskResult.FileStatus.UpdateVersion === status) { // error version - yield* sendFileErrorAuth(conn, data.sessionId, 'Update Version error', constants.UPDATE_VERSION_CODE); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Update Version error', constants.UPDATE_VERSION_CODE); return; } else if (taskResult.FileStatus.None === status && conn.encrypted) { //ok } else if (bIsRestore) { // Other error let code = null === status ? constants.NO_CACHE_CODE : undefined; - yield* sendFileErrorAuth(conn, data.sessionId, 'Other error', code); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Other error', code); return; } } //Set the unique ID if (bIsRestore) { - logger.info("restored old session: docId = %s id = %s", docId, data.sessionId); + ctx.logger.info("restored old session: id = %s", data.sessionId); if (!conn.user.view) { // Останавливаем сборку (вдруг она началась) // Когда переподсоединение, нам нужна проверка на сборку файла try { - var puckerIndex = yield* getChangesIndex(docId); + var puckerIndex = yield* getChangesIndex(ctx, docId); var bIsSuccessRestore = true; if (puckerIndex > 0) { - let objChangesDocument = yield* getDocumentChanges(docId, puckerIndex - 1, puckerIndex); + let objChangesDocument = yield* getDocumentChanges(ctx, docId, puckerIndex - 1, puckerIndex); var change = objChangesDocument.arrChanges[objChangesDocument.getLength() - 1]; if (change) { if (change['change']) { @@ -2353,33 +2360,33 @@ exports.install = function(server, callbackFunction) { if (bIsSuccessRestore) { // Проверяем lock-и var arrayBlocks = data['block']; - var getLockRes = yield* getLock(conn, data, true); + var getLockRes = yield* getLock(ctx, conn, data, true); if (arrayBlocks && (0 === arrayBlocks.length || getLockRes)) { - yield* authRestore(conn, data.sessionId); + yield* authRestore(ctx, conn, data.sessionId); } else { - yield* sendFileErrorAuth(conn, data.sessionId, 'Restore error. Locks not checked.', constants.RESTORE_CODE); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Restore error. Locks not checked.', constants.RESTORE_CODE); } } else { - yield* sendFileErrorAuth(conn, data.sessionId, 'Restore error. Document modified.', constants.RESTORE_CODE); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Restore error. Document modified.', constants.RESTORE_CODE); } } catch (err) { - logger.error("DataBase error: docId = %s %s", docId, err.stack); - yield* sendFileErrorAuth(conn, data.sessionId, 'DataBase error', constants.RESTORE_CODE); + ctx.logger.error("DataBase error: %s", err.stack); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'DataBase error', constants.RESTORE_CODE); } } else { - yield* authRestore(conn, data.sessionId); + yield* authRestore(ctx, conn, data.sessionId); } } else { conn.sessionId = conn.id; - const endAuthRes = yield* endAuth(conn, false, documentCallback, canvasService.getOpenedAt(resultRow)); + const endAuthRes = yield* endAuth(ctx, conn, false, documentCallback, canvasService.getOpenedAt(resultRow)); if (endAuthRes && cmd) { - yield canvasService.openDocument(conn, cmd, upsertRes, bIsRestore); + yield canvasService.openDocument(ctx, conn, cmd, upsertRes, bIsRestore); } } } } - function* endAuth(conn, bIsRestore, documentCallback, opt_openedAt) { + function* endAuth(ctx, conn, bIsRestore, documentCallback, opt_openedAt) { let res = true; const docId = conn.docId; const tmpUser = conn.user; @@ -2390,8 +2397,8 @@ exports.install = function(server, callbackFunction) { } connections.push(conn); let firstParticipantNoView, countNoView = 0; - yield addPresence(conn, true); - let participantsMap = yield getParticipantMap(docId); + yield addPresence(ctx, conn, true); + let participantsMap = yield getParticipantMap(ctx, docId); const participantsTimestamp = Date.now(); for (let i = 0; i < participantsMap.length; ++i) { const elem = participantsMap[i]; @@ -2410,12 +2417,12 @@ exports.install = function(server, callbackFunction) { if (!tmpUser.view) { const userIndex = utils.getIndexFromUserId(tmpUser.id, tmpUser.idOriginal); const userAction = new commonDefines.OutputAction(commonDefines.c_oAscUserAction.In, tmpUser.idOriginal); - let callback = yield* sendStatusDocument(docId, c_oAscChangeBase.No, userAction, userIndex, documentCallback, conn.baseUrl); + let callback = yield* sendStatusDocument(ctx, docId, c_oAscChangeBase.No, userAction, userIndex, documentCallback, conn.baseUrl); if (!callback && !bIsRestore) { //check forgotten file - let forgotten = yield storage.listObjects(cfgForgottenFiles + '/' + docId); + let forgotten = yield storage.listObjects(ctx, cfgForgottenFiles + '/' + docId); hasForgotten = forgotten.length > 0; - logger.debug('endAuth hasForgotten %s: docId = %s', hasForgotten, docId); + ctx.logger.debug('endAuth hasForgotten %s', hasForgotten); } } @@ -2427,7 +2434,7 @@ exports.install = function(server, callbackFunction) { let waitAuthUserId; if (!bIsRestore && 2 === countNoView && !tmpUser.view) { // Ставим lock на документ - const lockRes = yield editorData.lockAuth(docId, firstParticipantNoView.id, 2 * cfgExpLockDoc); + const lockRes = yield editorData.lockAuth(ctx, docId, firstParticipantNoView.id, 2 * cfgExpLockDoc); if (constants.CONN_CLOSED === conn.readyState) { //closing could happen during async action return false; @@ -2439,7 +2446,7 @@ exports.install = function(server, callbackFunction) { if (lockDocumentTimer) { cleanLockDocumentTimer(docId, lockDocumentTimer); } - yield* setLockDocumentTimer(docId, lockDocument.id); + yield* setLockDocumentTimer(ctx, docId, lockDocument.id); } } if (constants.CONN_CLOSED === conn.readyState) { @@ -2452,32 +2459,32 @@ exports.install = function(server, callbackFunction) { type: "waitAuth", lockDocument: lockDocument }; - sendData(conn, sendObject);//Or 0 if fails + sendData(ctx, conn, sendObject);//Or 0 if fails } else { if (!bIsRestore && needSendChanges(conn)) { - yield* sendAuthChanges(conn.docId, [conn]); + yield* sendAuthChanges(ctx, conn.docId, [conn]); } if (constants.CONN_CLOSED === conn.readyState) { //closing could happen during async action return false; } - yield* sendAuthInfo(conn, bIsRestore, participantsMap, hasForgotten, opt_openedAt); + yield* sendAuthInfo(ctx, conn, bIsRestore, participantsMap, hasForgotten, opt_openedAt); } if (constants.CONN_CLOSED === conn.readyState) { //closing could happen during async action return false; } - yield* publish({type: commonDefines.c_oPublishType.participantsState, docId: docId, userId: tmpUser.id, participantsTimestamp: participantsTimestamp, participants: participantsMap, waitAuthUserId: waitAuthUserId}, docId, tmpUser.id); + yield* publish(ctx, {type: commonDefines.c_oPublishType.participantsState, ctx: ctx, docId: docId, userId: tmpUser.id, participantsTimestamp: participantsTimestamp, participants: participantsMap, waitAuthUserId: waitAuthUserId}, docId, tmpUser.id); return res; } - function* saveErrorChanges(docId, destDir) { + function* saveErrorChanges(ctx, docId, destDir) { let index = 0; let indexChunk = 1; let changes; let changesPrefix = destDir + '/' + constants.CHANGES_NAME + '/' + constants.CHANGES_NAME + '.json.'; do { - changes = yield sqlBase.getChangesPromise(docId, index, index + cfgMaxRequestChanges); + changes = yield sqlBase.getChangesPromise(ctx, docId, index, index + cfgMaxRequestChanges); if (changes.length > 0) { let changesJSON = indexChunk > 1 ? ',[' : '['; changesJSON += changes[0].change_data; @@ -2487,13 +2494,13 @@ exports.install = function(server, callbackFunction) { } changesJSON += ']\r\n'; let buffer = Buffer.from(changesJSON, 'utf8'); - yield storage.putObject(changesPrefix + (indexChunk++).toString().padStart(3, '0'), buffer, buffer.length); + yield storage.putObject(ctx, changesPrefix + (indexChunk++).toString().padStart(3, '0'), buffer, buffer.length); } index += cfgMaxRequestChanges; } while (changes && cfgMaxRequestChanges === changes.length); } - function sendAuthChangesByChunks(changes, connections) { + function sendAuthChangesByChunks(ctx, changes, connections) { let startIndex = 0; let endIndex = 0; while (endIndex < changes.length) { @@ -2509,36 +2516,36 @@ exports.install = function(server, callbackFunction) { }; for (let i = 0; i < connections.length; ++i) { if(needSendChanges(connections[i])) { - sendData(connections[i], sendObject);//Or 0 if fails + sendData(ctx, connections[i], sendObject);//Or 0 if fails } } } } - function* sendAuthChanges(docId, connections) { + function* sendAuthChanges(ctx, docId, connections) { let index = 0; let changes; do { - let objChangesDocument = yield getDocumentChanges(docId, index, index + cfgMaxRequestChanges); + let objChangesDocument = yield getDocumentChanges(ctx, docId, index, index + cfgMaxRequestChanges); changes = objChangesDocument.arrChanges; - sendAuthChangesByChunks(changes, connections); + sendAuthChangesByChunks(ctx, changes, connections); index += cfgMaxRequestChanges; } while (changes && cfgMaxRequestChanges === changes.length); } - function* sendAuthInfo(conn, bIsRestore, participantsMap, opt_hasForgotten, opt_openedAt) { + function* sendAuthInfo(ctx, conn, bIsRestore, participantsMap, opt_hasForgotten, opt_openedAt) { const docId = conn.docId; let docLock; if(EditorTypes.document == conn.editorType){ docLock = {}; let elem; - const allLocks = yield* getAllLocks(docId); + const allLocks = yield* getAllLocks(ctx, docId); for(let i = 0 ; i < allLocks.length; ++i) { elem = allLocks[i]; docLock[elem.block] = elem; } } else { - docLock = yield* getAllLocks(docId); + docLock = yield* getAllLocks(ctx, docId); } - let allMessages = yield editorData.getMessages(docId); + let allMessages = yield editorData.getMessages(ctx, docId); allMessages = allMessages.length > 0 ? allMessages : undefined;//todo client side const sendObject = { type: 'auth', @@ -2558,39 +2565,39 @@ exports.install = function(server, callbackFunction) { settings: cfgEditor, openedAt: opt_openedAt }; - sendData(conn, sendObject);//Or 0 if fails + sendData(ctx, conn, sendObject);//Or 0 if fails } - function* onMessage(conn, data) { + function* onMessage(ctx, conn, data) { if (false === conn.permissions.chat) { - logger.warn("insert message permissions.chat==false: userId = %s docId = %s", conn.user && conn.user.id, conn.docId); + ctx.logger.warn("insert message permissions.chat==false"); return; } var docId = conn.docId; var userId = conn.user.id; var msg = {docid: docId, message: data.message, time: Date.now(), user: userId, useridoriginal: conn.user.idOriginal, username: conn.user.username}; - yield editorData.addMessage(docId, msg); + yield editorData.addMessage(ctx, docId, msg); // insert - logger.info("insert message: docId = %s %j", docId, msg); + ctx.logger.info("insert message: %j", msg); var messages = [msg]; - sendDataMessage(conn, messages); - yield* publish({type: commonDefines.c_oPublishType.message, docId: docId, userId: userId, messages: messages}, docId, userId); + sendDataMessage(ctx, conn, messages); + yield* publish(ctx, {type: commonDefines.c_oPublishType.message, ctx: ctx, docId: docId, userId: userId, messages: messages}, docId, userId); } - function* onCursor(conn, data) { + function* onCursor(ctx, conn, data) { var docId = conn.docId; var userId = conn.user.id; var msg = {cursor: data.cursor, time: Date.now(), user: userId, useridoriginal: conn.user.idOriginal}; - logger.info("send cursor: docId = %s %s", docId, msg); + ctx.logger.info("send cursor: %s", msg); var messages = [msg]; - yield* publish({type: commonDefines.c_oPublishType.cursor, docId: docId, userId: userId, messages: messages}, docId, userId); + yield* publish(ctx, {type: commonDefines.c_oPublishType.cursor, ctx: ctx, docId: docId, userId: userId, messages: messages}, docId, userId); } - function* getLock(conn, data, bIsRestore) { - logger.info("getLock docid: %s", conn.docId); + function* getLock(ctx, conn, data, bIsRestore) { + ctx.logger.info("getLock"); var fLock = null; switch (conn.editorType) { case EditorTypes.document: @@ -2606,13 +2613,13 @@ exports.install = function(server, callbackFunction) { fLock = getLockPresentation; break; } - return fLock ? yield* fLock(conn, data, bIsRestore) : false; + return fLock ? yield* fLock(ctx, conn, data, bIsRestore) : false; } - function* getLockWord(conn, data, bIsRestore) { + function* getLockWord(ctx, conn, data, bIsRestore) { var docId = conn.docId, userId = conn.user.id, arrayBlocks = data.block; var i; - var checkRes = yield* _checkLock(docId, arrayBlocks); + var checkRes = yield* _checkLock(ctx, docId, arrayBlocks); var documentLocks = checkRes.documentLocks; if (checkRes.res) { //Ok. take lock @@ -2623,21 +2630,21 @@ exports.install = function(server, callbackFunction) { documentLocks[block] = elem; toCache.push(elem); } - yield editorData.addLocks(docId, toCache); + yield editorData.addLocks(ctx, docId, toCache); } else if (bIsRestore) { return false; } //тому кто зделал запрос возвращаем максимально быстро - sendData(conn, {type: "getLock", locks: documentLocks}); - yield* publish({type: commonDefines.c_oPublishType.getLock, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); + sendData(ctx, conn, {type: "getLock", locks: documentLocks}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.getLock, ctx: ctx, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); return true; } // Для Excel block теперь это объект { sheetId, type, rangeOrObjectId, guid } - function* getLockExcel(conn, data, bIsRestore) { + function* getLockExcel(ctx, conn, data, bIsRestore) { var docId = conn.docId, userId = conn.user.id, arrayBlocks = data.block; var i; - var checkRes = yield* _checkLockExcel(docId, arrayBlocks, userId); + var checkRes = yield* _checkLockExcel(ctx, docId, arrayBlocks, userId); var documentLocks = checkRes.documentLocks; if (checkRes.res) { //Ok. take lock @@ -2648,21 +2655,21 @@ exports.install = function(server, callbackFunction) { documentLocks.push(elem); toCache.push(elem); } - yield editorData.addLocks(docId, toCache); + yield editorData.addLocks(ctx, docId, toCache); } else if (bIsRestore) { return false; } //тому кто зделал запрос возвращаем максимально быстро - sendData(conn, {type: "getLock", locks: documentLocks}); - yield* publish({type: commonDefines.c_oPublishType.getLock, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); + sendData(ctx, conn, {type: "getLock", locks: documentLocks}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.getLock, ctx: ctx, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); return true; } // Для презентаций это объект { type, val } или { type, slideId, objId } - function* getLockPresentation(conn, data, bIsRestore) { + function* getLockPresentation(ctx, conn, data, bIsRestore) { var docId = conn.docId, userId = conn.user.id, arrayBlocks = data.block; var i; - var checkRes = yield* _checkLockPresentation(docId, arrayBlocks, userId); + var checkRes = yield* _checkLockPresentation(ctx, docId, arrayBlocks, userId); var documentLocks = checkRes.documentLocks; if (checkRes.res) { //Ok. take lock @@ -2673,35 +2680,35 @@ exports.install = function(server, callbackFunction) { documentLocks.push(elem); toCache.push(elem); } - yield editorData.addLocks(docId, toCache); + yield editorData.addLocks(ctx, docId, toCache); } else if (bIsRestore) { return false; } //тому кто зделал запрос возвращаем максимально быстро - sendData(conn, {type: "getLock", locks: documentLocks}); - yield* publish({type: commonDefines.c_oPublishType.getLock, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); + sendData(ctx, conn, {type: "getLock", locks: documentLocks}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.getLock, ctx: ctx, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); return true; } - function sendGetLock(participants, documentLocks) { + function sendGetLock(ctx, participants, documentLocks) { _.each(participants, function(participant) { - sendData(participant, {type: "getLock", locks: documentLocks}); + sendData(ctx, participant, {type: "getLock", locks: documentLocks}); }); } // Для Excel необходимо делать пересчет lock-ов при добавлении/удалении строк/столбцов - function* saveChanges(conn, data) { + function* saveChanges(ctx, conn, data) { const docId = conn.docId, userId = conn.user.id; - logger.info("Start saveChanges docid: %s; reSave: %s", docId, data.reSave); + ctx.logger.info("Start saveChanges: reSave: %s", data.reSave); - let lockRes = yield editorData.lockSave(docId, userId, cfgExpSaveLock); + let lockRes = yield editorData.lockSave(ctx, docId, userId, cfgExpSaveLock); if (!lockRes) { //should not be here. cfgExpSaveLock - 60sec, sockjs disconnects after 25sec - logger.warn("saveChanges lockSave error docid: %s", docId); + ctx.logger.warn("saveChanges lockSave error"); return; } - let puckerIndex = yield* getChangesIndex(docId); + let puckerIndex = yield* getChangesIndex(ctx, docId); let deleteIndex = -1; if (data.startSaveChanges && null != data.deleteIndex) { @@ -2710,9 +2717,9 @@ exports.install = function(server, callbackFunction) { const deleteCount = puckerIndex - deleteIndex; if (0 < deleteCount) { puckerIndex -= deleteCount; - yield sqlBase.deleteChangesPromise(docId, deleteIndex); + yield sqlBase.deleteChangesPromise(ctx, docId, deleteIndex); } else if (0 > deleteCount) { - logger.error("Error saveChanges docid: %s ; deleteIndex: %s ; startIndex: %s ; deleteCount: %s", docId, deleteIndex, puckerIndex, deleteCount); + ctx.logger.error("Error saveChanges: deleteIndex: %s ; startIndex: %s ; deleteCount: %s", deleteIndex, puckerIndex, deleteCount); } } } @@ -2725,7 +2732,7 @@ exports.install = function(server, callbackFunction) { newChangesLastDate.setMilliseconds(0);//remove milliseconds avoid issues with MySQL datetime rounding let newChangesLastTime = newChangesLastDate.getTime(); let arrNewDocumentChanges = []; - logger.info("saveChanges docid: %s ; deleteIndex: %s ; startIndex: %s ; length: %s", docId, deleteIndex, startIndex, newChanges.length); + ctx.logger.info("saveChanges: deleteIndex: %s ; startIndex: %s ; length: %s", deleteIndex, startIndex, newChanges.length); if (0 < newChanges.length) { let oElement = null; @@ -2736,7 +2743,7 @@ exports.install = function(server, callbackFunction) { } puckerIndex += arrNewDocumentChanges.length; - yield sqlBase.insertChangesPromise(arrNewDocumentChanges, docId, startIndex, conn.user); + yield sqlBase.insertChangesPromise(ctx, arrNewDocumentChanges, docId, startIndex, conn.user); } const changesIndex = (-1 === deleteIndex && data.startSaveChanges) ? startIndex : -1; if (data.endSaveChanges) { @@ -2748,14 +2755,14 @@ exports.install = function(server, callbackFunction) { const oRecalcIndexRows = _addRecalcIndex(tmpAdditionalInfo["indexRows"]); // Теперь нужно пересчитать индексы для lock-элементов if (null !== oRecalcIndexColumns || null !== oRecalcIndexRows) { - const docLock = yield* getAllLocks(docId); + const docLock = yield* getAllLocks(ctx, docId); if (_recalcLockArray(userId, docLock, oRecalcIndexColumns, oRecalcIndexRows)) { let toCache = []; for (let i = 0; i < docLock.length; ++i) { toCache.push(docLock[i]); } - yield editorData.removeLocks(docId); - yield editorData.addLocks(docId, toCache); + yield editorData.removeLocks(ctx, docId); + yield editorData.addLocks(ctx, docId, toCache); } } } @@ -2763,10 +2770,10 @@ exports.install = function(server, callbackFunction) { let userLocks = []; if (data.releaseLocks) { //Release locks - userLocks = yield* removeUserLocks(docId, userId); + userLocks = yield* removeUserLocks(ctx, docId, userId); } // Для данного пользователя снимаем Lock с документа, если пришел флаг unlock - const checkEndAuthLockRes = yield* checkEndAuthLock(data.unlock, false, docId, userId); + const checkEndAuthLockRes = yield* checkEndAuthLock(ctx, data.unlock, false, docId, userId); if (!checkEndAuthLockRes) { const arrLocks = _.map(userLocks, function(e) { return { @@ -2784,15 +2791,15 @@ exports.install = function(server, callbackFunction) { value.time = value.time.getTime(); }) } - yield* publish({type: commonDefines.c_oPublishType.changes, docId: docId, userId: userId, + yield* publish(ctx, {type: commonDefines.c_oPublishType.changes, ctx: ctx, docId: docId, userId: userId, changes: changesToSend, startIndex: startIndex, changesIndex: puckerIndex, locks: arrLocks, excelAdditionalInfo: data.excelAdditionalInfo, endSaveChanges: data.endSaveChanges}, docId, userId); } // Автоматически снимаем lock сами и посылаем индекс для сохранения - yield* unSaveLock(conn, changesIndex, newChangesLastTime); + yield* unSaveLock(ctx, conn, changesIndex, newChangesLastTime); //last save let changeInfo = getExternalChangeInfo(conn.user, newChangesLastTime); - yield resetForceSaveAfterChanges(docId, newChangesLastTime, puckerIndex, utils.getBaseUrlByConnection(conn), changeInfo); + yield resetForceSaveAfterChanges(ctx, docId, newChangesLastTime, puckerIndex, utils.getBaseUrlByConnection(conn), changeInfo); } else { let changesToSend = arrNewDocumentChanges; if(changesToSend.length > cfgPubSubMaxChanges) { @@ -2802,47 +2809,47 @@ exports.install = function(server, callbackFunction) { value.time = value.time.getTime(); }) } - let isPublished = yield* publish({type: commonDefines.c_oPublishType.changes, docId: docId, userId: userId, + let isPublished = yield* publish(ctx, {type: commonDefines.c_oPublishType.changes, ctx: ctx, docId: docId, userId: userId, changes: changesToSend, startIndex: startIndex, changesIndex: puckerIndex, locks: [], excelAdditionalInfo: undefined, endSaveChanges: data.endSaveChanges}, docId, userId); - sendData(conn, {type: 'savePartChanges', changesIndex: changesIndex}); + sendData(ctx, conn, {type: 'savePartChanges', changesIndex: changesIndex}); if (!isPublished) { //stub for lockDocumentsTimerId - yield* publish({type: commonDefines.c_oPublishType.changesNotify, docId: docId}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.changesNotify, ctx: ctx, docId: docId}); } } } // Можем ли мы сохранять ? - function* isSaveLock(conn) { - let lockRes = yield editorData.lockSave(conn.docId, conn.user.id, cfgExpSaveLock); - logger.debug("isSaveLock: docId = %s; lockRes: %s", conn.docId, lockRes); + function* isSaveLock(ctx, conn) { + let lockRes = yield editorData.lockSave(ctx, conn.docId, conn.user.id, cfgExpSaveLock); + ctx.logger.debug("isSaveLock lockRes: %s", lockRes); // Отправляем только тому, кто спрашивал (всем отправлять нельзя) - sendData(conn, {type: "saveLock", saveLock: !lockRes}); + sendData(ctx, conn, {type: "saveLock", saveLock: !lockRes}); } // Снимаем лок с сохранения - function* unSaveLock(conn, index, time) { - var unlockRes = yield editorData.unlockSave(conn.docId, conn.user.id); + function* unSaveLock(ctx, conn, index, time) { + var unlockRes = yield editorData.unlockSave(ctx, conn.docId, conn.user.id); if (commonDefines.c_oAscUnlockRes.Locked !== unlockRes) { - sendData(conn, {type: 'unSaveLock', index: index, time: time}); + sendData(ctx, conn, {type: 'unSaveLock', index: index, time: time}); } else { - logger.warn("unSaveLock failure: docId = %s; conn.user.id: %s", conn.docId, conn.user.id); + ctx.logger.warn("unSaveLock failure"); } } // Возвращаем все сообщения для документа - function* getMessages(conn) { - let allMessages = yield editorData.getMessages(conn.docId); + function* getMessages(ctx, conn) { + let allMessages = yield editorData.getMessages(ctx, conn.docId); allMessages = allMessages.length > 0 ? allMessages : undefined;//todo client side - sendDataMessage(conn, allMessages); + sendDataMessage(ctx, conn, allMessages); } - function* _checkLock(docId, arrayBlocks) { + function* _checkLock(ctx, docId, arrayBlocks) { // Data is array now var isLock = false; - var allLocks = yield* getAllLocks(docId); + var allLocks = yield* getAllLocks(ctx, docId); var documentLocks = {}; for(var i = 0 ; i < allLocks.length; ++i) { var elem = allLocks[i]; @@ -2851,7 +2858,7 @@ exports.install = function(server, callbackFunction) { if (arrayBlocks.length > 0) { for (var i = 0; i < arrayBlocks.length; ++i) { var block = arrayBlocks[i]; - logger.info("getLock id: docId = %s %s", docId, block); + ctx.logger.info("getLock id: %s", block); if (documentLocks.hasOwnProperty(block) && documentLocks[block] !== null) { isLock = true; break; @@ -2863,13 +2870,13 @@ exports.install = function(server, callbackFunction) { return {res: !isLock, documentLocks: documentLocks}; } - function* _checkLockExcel(docId, arrayBlocks, userId) { + function* _checkLockExcel(ctx, docId, arrayBlocks, userId) { // Data is array now var documentLock; var isLock = false; var isExistInArray = false; var i, blockRange; - var documentLocks = yield* getAllLocks(docId); + var documentLocks = yield* getAllLocks(ctx, docId); var lengthArray = (arrayBlocks) ? arrayBlocks.length : 0; for (i = 0; i < lengthArray && false === isLock; ++i) { blockRange = arrayBlocks[i]; @@ -2923,11 +2930,11 @@ exports.install = function(server, callbackFunction) { return {res: !isLock && !isExistInArray, documentLocks: documentLocks}; } - function* _checkLockPresentation(docId, arrayBlocks, userId) { + function* _checkLockPresentation(ctx, docId, arrayBlocks, userId) { // Data is array now var isLock = false; var i, documentLock, blockRange; - var documentLocks = yield* getAllLocks(docId); + var documentLocks = yield* getAllLocks(ctx, docId); var lengthArray = (arrayBlocks) ? arrayBlocks.length : 0; for (i = 0; i < lengthArray && false === isLock; ++i) { blockRange = arrayBlocks[i]; @@ -2952,15 +2959,15 @@ exports.install = function(server, callbackFunction) { return {res: !isLock, documentLocks: documentLocks}; } - function _checkLicense(conn) { + function _checkLicense(ctx, conn) { return co(function* () { try { let rights = constants.RIGHTS.Edit; if (config.get('server.edit_singleton')) { // ToDo docId from url ? - const docIdParsed = urlParse.exec(conn.url); + const docIdParsed = constants.DOC_ID_SOCKET_PATTERN.exec(conn.url); if (docIdParsed && 1 < docIdParsed.length) { - const participantsMap = yield getParticipantMap(docIdParsed[1]); + const participantsMap = yield getParticipantMap(ctx, docIdParsed[1]); for (let i = 0; i < participantsMap.length; ++i) { const elem = participantsMap[i]; if (!elem.view) { @@ -2971,7 +2978,9 @@ exports.install = function(server, callbackFunction) { } } - sendData(conn, { + let licenseInfo = yield tenantManager.getTenantLicense(ctx); + + sendData(ctx, conn, { type: 'license', license: { type: licenseInfo.type, light: licenseInfo.light, @@ -2987,12 +2996,12 @@ exports.install = function(server, callbackFunction) { } }); } catch (err) { - logger.error('_checkLicense error:\r\n%s', err.stack); + ctx.logger.error('_checkLicense error: %s', err.stack); } }); } - function* _checkLicenseAuth(userId, isLiveViewer) { + function* _checkLicenseAuth(ctx, licenseInfo, userId, isLiveViewer) { let licenseWarningLimitUsers = false; let licenseWarningLimitUsersView = false; let licenseWarningLimitConnections = false; @@ -3003,13 +3012,13 @@ exports.install = function(server, callbackFunction) { if (licenseInfo.usersCount) { const nowUTC = getLicenseNowUtc(); if(isLiveViewer) { - const arrUsers = yield editorData.getPresenceUniqueViewUser(nowUTC); + const arrUsers = yield editorData.getPresenceUniqueViewUser(ctx, nowUTC); if (arrUsers.length >= licenseInfo.usersViewCount && (-1 === arrUsers.findIndex((element) => {return element.userid === userId}))) { licenseType = c_LR.UsersViewCount; } licenseWarningLimitUsersView = licenseInfo.usersViewCount * cfgWarningLimitPercents <= arrUsers.length; } else { - const arrUsers = yield editorData.getPresenceUniqueUser(nowUTC); + const arrUsers = yield editorData.getPresenceUniqueUser(ctx, nowUTC); if (arrUsers.length >= licenseInfo.usersCount && (-1 === arrUsers.findIndex((element) => {return element.userid === userId}))) { licenseType = c_LR.UsersCount; } @@ -3017,14 +3026,14 @@ exports.install = function(server, callbackFunction) { } } else if(isLiveViewer) { const connectionsLiveCount = licenseInfo.connectionsView; - const liveViewerConnectionsCount = yield editorData.getLiveViewerConnectionsCount(connections); + const liveViewerConnectionsCount = yield editorData.getLiveViewerConnectionsCount(ctx, connections); if (liveViewerConnectionsCount >= connectionsLiveCount) { licenseType = c_LR.ConnectionsLive; } licenseWarningLimitConnectionsLive = connectionsLiveCount * cfgWarningLimitPercents <= liveViewerConnectionsCount; } else { const connectionsCount = licenseInfo.connections; - const editConnectionsCount = yield editorData.getEditorConnectionsCount(connections); + const editConnectionsCount = yield editorData.getEditorConnectionsCount(ctx, connections); if (editConnectionsCount >= connectionsCount) { licenseType = c_LR.Connections; } @@ -3036,34 +3045,34 @@ exports.install = function(server, callbackFunction) { if (!licenseInfo.hasLicense) { licenseType = c_LR.UsersCountOS; } - logger.error('License: User limit exceeded!!!'); + ctx.logger.error('License: User limit exceeded!!!'); } else if (c_LR.UsersViewCount === licenseType) { if (!licenseInfo.hasLicense) { licenseType = c_LR.UsersViewCountOS; } - logger.error('License: User Live Viewer limit exceeded!!!'); + ctx.logger.error('License: User Live Viewer limit exceeded!!!'); } else if (c_LR.Connections === licenseType) { if (!licenseInfo.hasLicense) { licenseType = c_LR.ConnectionsOS; } - logger.error('License: Connection limit exceeded!!!'); + ctx.logger.error('License: Connection limit exceeded!!!'); } else if (c_LR.ConnectionsLive === licenseType) { if (!licenseInfo.hasLicense) { licenseType = c_LR.ConnectionsLiveOS; } - logger.error('License: Connection Live Viewer limit exceeded!!!'); + ctx.logger.error('License: Connection Live Viewer limit exceeded!!!'); } else { if (licenseWarningLimitUsers) { - logger.warn('License: Warning User limit exceeded!!!'); + ctx.logger.warn('License: Warning User limit exceeded!!!'); } if (licenseWarningLimitUsersView) { - logger.warn('License: Warning User Live Viewer limit exceeded!!!'); + ctx.logger.warn('License: Warning User Live Viewer limit exceeded!!!'); } if (licenseWarningLimitConnections) { - logger.warn('License: Warning Connection limit exceeded!!!'); + ctx.logger.warn('License: Warning Connection limit exceeded!!!'); } if (licenseWarningLimitConnectionsLive) { - logger.warn('License: Warning Connection Live Viewer limit exceeded!!!'); + ctx.logger.warn('License: Warning Connection Live Viewer limit exceeded!!!'); } } return licenseType; @@ -3071,15 +3080,17 @@ exports.install = function(server, callbackFunction) { sockjs_echo.installHandlers(server, {prefix: '/doc/['+constants.DOC_ID_PATTERN+']*/c', log: function(severity, message) { //TODO: handle severity - logger.info(message); + operationContext.global.logger.info(message); }}); //publish subscribe message brocker function pubsubOnMessage(msg) { return co(function* () { + let ctx = new operationContext.Context(); try { - logger.debug('pubsub message start:%s', msg); var data = JSON.parse(msg); + ctx.initFromPubSub(data); + ctx.logger.debug('pubsub message start:%s', msg); var participants; var participant; var objChangesDocument; @@ -3088,7 +3099,7 @@ exports.install = function(server, callbackFunction) { switch (data.type) { case commonDefines.c_oPublishType.drop: for (i = 0; i < data.users.length; ++i) { - dropUserFromDocument(data.docId, data.users[i], data.description); + dropUserFromDocument(ctx, data.docId, data.users[i], data.description); } break; case commonDefines.c_oPublishType.closeConnection: @@ -3097,42 +3108,42 @@ exports.install = function(server, callbackFunction) { case commonDefines.c_oPublishType.releaseLock: participants = getParticipants(data.docId, true, data.userId, true); _.each(participants, function(participant) { - sendReleaseLock(participant, data.locks); + sendReleaseLock(ctx, participant, data.locks); }); break; case commonDefines.c_oPublishType.participantsState: participants = getParticipants(data.docId, true, data.userId); - sendParticipantsState(participants, data); + sendParticipantsState(ctx, participants, data); break; case commonDefines.c_oPublishType.message: participants = getParticipants(data.docId, true, data.userId); _.each(participants, function(participant) { - sendDataMessage(participant, data.messages); + sendDataMessage(ctx, participant, data.messages); }); break; case commonDefines.c_oPublishType.getLock: participants = getParticipants(data.docId, true, data.userId, true); - sendGetLock(participants, data.documentLocks); + sendGetLock(ctx, participants, data.documentLocks); break; case commonDefines.c_oPublishType.changes: lockDocumentTimer = lockDocumentsTimerId[data.docId]; if (lockDocumentTimer) { - logger.debug("lockDocumentsTimerId update c_oPublishType.changes: docId = %s", data.docId); + ctx.logger.debug("lockDocumentsTimerId update c_oPublishType.changes"); cleanLockDocumentTimer(data.docId, lockDocumentTimer); - yield* setLockDocumentTimer(data.docId, lockDocumentTimer.userId); + yield* setLockDocumentTimer(ctx, data.docId, lockDocumentTimer.userId); } participants = getParticipants(data.docId, true, data.userId); if(participants.length > 0) { var changes = data.changes; if (null == changes) { - objChangesDocument = yield* getDocumentChanges(data.docId, data.startIndex, data.changesIndex); + objChangesDocument = yield* getDocumentChanges(ctx, data.docId, data.startIndex, data.changesIndex); changes = objChangesDocument.arrChanges; } _.each(participants, function(participant) { if (!needSendChanges(participant)) { return; } - sendData(participant, {type: 'saveChanges', changes: changes, + sendData(ctx, participant, {type: 'saveChanges', changes: changes, changesIndex: data.changesIndex, endSaveChanges: data.endSaveChanges, locks: data.locks, excelAdditionalInfo: data.excelAdditionalInfo}); }); @@ -3141,23 +3152,23 @@ exports.install = function(server, callbackFunction) { case commonDefines.c_oPublishType.changesNotify: lockDocumentTimer = lockDocumentsTimerId[data.docId]; if (lockDocumentTimer) { - logger.debug("lockDocumentsTimerId update c_oPublishType.changesNotify: docId = %s", data.docId); + ctx.logger.debug("lockDocumentsTimerId update c_oPublishType.changesNotify"); cleanLockDocumentTimer(data.docId, lockDocumentTimer); - yield* setLockDocumentTimer(data.docId, lockDocumentTimer.userId); + yield* setLockDocumentTimer(ctx, data.docId, lockDocumentTimer.userId); } break; case commonDefines.c_oPublishType.auth: lockDocumentTimer = lockDocumentsTimerId[data.docId]; if (lockDocumentTimer) { - logger.debug("lockDocumentsTimerId clear: docId = %s", data.docId); + ctx.logger.debug("lockDocumentsTimerId clear"); cleanLockDocumentTimer(data.docId, lockDocumentTimer); } participants = getParticipants(data.docId, true, data.userId, true); if(participants.length > 0) { - yield* sendAuthChanges(data.docId, participants); + yield* sendAuthChanges(ctx, data.docId, participants); for (i = 0; i < participants.length; ++i) { participant = participants[i]; - yield* sendAuthInfo(participant, false, data.participantsMap); + yield* sendAuthInfo(ctx, participant, false, data.participantsMap); } } break; @@ -3183,16 +3194,16 @@ exports.install = function(server, callbackFunction) { participant = participants[i]; if (data.needUrlKey) { if (0 == data.needUrlMethod) { - outputData.setData(yield storage.getSignedUrls(participant.baseUrl, data.needUrlKey, data.needUrlType, data.creationDate)); + outputData.setData(yield storage.getSignedUrls(ctx, participant.baseUrl, data.needUrlKey, data.needUrlType, data.creationDate)); } else if (1 == data.needUrlMethod) { - outputData.setData(yield storage.getSignedUrl(participant.baseUrl, data.needUrlKey, data.needUrlType, undefined, data.creationDate)); + outputData.setData(yield storage.getSignedUrl(ctx, participant.baseUrl, data.needUrlKey, data.needUrlType, undefined, data.creationDate)); } else { let url; if (cmd.getInline()) { url = canvasService.getPrintFileUrl(data.needUrlKey, participant.baseUrl, cmd.getTitle()); outputData.setExtName('.pdf'); } else { - url = yield storage.getSignedUrl(participant.baseUrl, data.needUrlKey, data.needUrlType, cmd.getTitle(), data.creationDate); + url = yield storage.getSignedUrl(ctx, participant.baseUrl, data.needUrlKey, data.needUrlType, cmd.getTitle(), data.creationDate); outputData.setExtName(pathModule.extname(data.needUrlKey)); } outputData.setData(url); @@ -3200,29 +3211,29 @@ exports.install = function(server, callbackFunction) { if (undefined !== data.openedAt) { outputData.setOpenedAt(data.openedAt); } - modifyConnectionForPassword(participant, data.needUrlIsCorrectPassword); + modifyConnectionForPassword(ctx, participant, data.needUrlIsCorrectPassword); } - sendData(participant, output); + sendData(ctx, participant, output); } break; case commonDefines.c_oPublishType.warning: participants = getParticipants(data.docId); _.each(participants, function(participant) { - sendDataWarning(participant, data.description); + sendDataWarning(ctx, participant, data.description); }); break; case commonDefines.c_oPublishType.cursor: participants = getParticipants(data.docId, true, data.userId); _.each(participants, function(participant) { - sendDataCursor(participant, data.messages); + sendDataCursor(ctx, participant, data.messages); }); break; case commonDefines.c_oPublishType.shutdown: //flag prevent new socket connections and receive data from exist connections shutdownFlag = data.status; - logger.warn('start shutdown:%b', shutdownFlag); + ctx.logger.warn('start shutdown:%b', shutdownFlag); if (shutdownFlag) { - logger.warn('active connections: %d', connections.length); + ctx.logger.warn('active connections: %d', connections.length); //не останавливаем сервер, т.к. будут недоступны сокеты и все запросы //плохо тем, что может понадобится конвертация выходного файла и то что не будут обработаны запросы на CommandService //server.close(); @@ -3233,18 +3244,18 @@ exports.install = function(server, callbackFunction) { connectionsTmp[i].close(constants.SHUTDOWN_CODE, constants.SHUTDOWN_REASON); } } - logger.warn('end shutdown'); + ctx.logger.warn('end shutdown'); break; case commonDefines.c_oPublishType.meta: participants = getParticipants(data.docId); _.each(participants, function(participant) { - sendDataMeta(participant, data.meta); + sendDataMeta(ctx, participant, data.meta); }); break; case commonDefines.c_oPublishType.forceSave: participants = getParticipants(data.docId, true, data.userId, true); _.each(participants, function(participant) { - sendData(participant, {type: "forceSave", messages: data.data}); + sendData(ctx, participant, {type: "forceSave", messages: data.data}); }); break; case commonDefines.c_oPublishType.changeConnecitonInfo: @@ -3255,56 +3266,58 @@ exports.install = function(server, callbackFunction) { participant = participants[i]; if (!participant.denyChangeName && participant.user.idOriginal === data.useridoriginal) { hasChanges = true; - logger.debug('changeConnectionInfo: docId = %s, userId = %s', data.docId, data.useridoriginal); + ctx.logger.debug('changeConnectionInfo: userId = %s', data.useridoriginal); participant.user.username = cmd.getUserName(); - yield addPresence(participant, false); + yield addPresence(ctx, participant, false); if (cfgTokenEnableBrowser) { - sendDataRefreshToken(participant); + sendDataRefreshToken(ctx, participant); } } } if (hasChanges) { - let participants = yield getParticipantMap(data.docId); + let participants = yield getParticipantMap(ctx, data.docId); let participantsTimestamp = Date.now(); - yield* publish({type: commonDefines.c_oPublishType.participantsState, docId: data.docId, userId: null, participantsTimestamp: participantsTimestamp, participants: participants}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.participantsState, ctx: ctx, docId: data.docId, userId: null, participantsTimestamp: participantsTimestamp, participants: participants}); } break; case commonDefines.c_oPublishType.rpc: participants = getParticipants(data.docId, true, data.userId, true); _.each(participants, function(participant) { - sendDataRpc(participant, data.responseKey, data.data); + sendDataRpc(ctx, participant, data.responseKey, data.data); }); break; default: - logger.debug('pubsub unknown message type:%s', msg); + ctx.logger.debug('pubsub unknown message type:%s', msg); } } catch (err) { - logger.error('pubsub message error:\r\n%s', err.stack); + ctx.logger.error('pubsub message error: %s', err.stack); } }); } - function* collectStats(countEdit, countLiveView, countView) { + function* collectStats(ctx, countEdit, countLiveView, countView) { let now = Date.now(); - yield editorData.setEditorConnections(countEdit, countLiveView, countView, now, PRECISION); + yield editorData.setEditorConnections(ctx, countEdit, countLiveView, countView, now, PRECISION); } function expireDoc() { return co(function* () { + let ctx = new operationContext.Context(); try { let countEditByShard = 0; let countLiveViewByShard = 0; let countViewByShard = 0; - logger.debug('expireDoc connections.length = %d', connections.length); + ctx.logger.debug('expireDoc connections.length = %d', connections.length); var nowMs = new Date().getTime(); var maxMs = nowMs + Math.max(cfgExpSessionCloseCommand, expDocumentsStep); for (var i = 0; i < connections.length; ++i) { var conn = connections[i]; + ctx.initFromConnection(conn); //wopi access_token_ttl; if (cfgExpSessionAbsolute > 0 || conn.access_token_ttl) { if ((cfgExpSessionAbsolute > 0 && maxMs - conn.sessionTimeConnect > cfgExpSessionAbsolute || (conn.access_token_ttl && maxMs > conn.access_token_ttl)) && !conn.sessionIsSendWarning) { conn.sessionIsSendWarning = true; - sendDataSession(conn, { + sendDataSession(ctx, conn, { code: constants.SESSION_ABSOLUTE_CODE, reason: constants.SESSION_ABSOLUTE_REASON }); @@ -3316,7 +3329,7 @@ exports.install = function(server, callbackFunction) { if (cfgExpSessionIdle > 0) { if (maxMs - conn.sessionTimeLastAction > cfgExpSessionIdle && !conn.sessionIsSendWarning) { conn.sessionIsSendWarning = true; - sendDataSession(conn, { + sendDataSession(ctx, conn, { code: constants.SESSION_IDLE_CODE, reason: constants.SESSION_IDLE_REASON, interval: cfgExpSessionIdle @@ -3327,9 +3340,9 @@ exports.install = function(server, callbackFunction) { } } if (constants.CONN_CLOSED === conn.readyState) { - logger.error('expireDoc connection closed docId = %s', conn.docId); + ctx.logger.error('expireDoc connection closed'); } - yield addPresence(conn, false); + yield addPresence(ctx, conn, false); if (utils.isLiveViewer(conn)) { countLiveViewByShard++; } else if(conn.isCloseCoAuthoring || (conn.user && conn.user.view)) { @@ -3338,20 +3351,21 @@ exports.install = function(server, callbackFunction) { countEditByShard++; } } - yield* collectStats(countEditByShard, countLiveViewByShard, countViewByShard); - yield editorData.setEditorConnectionsCountByShard(SHARD_ID, countEditByShard); - yield editorData.setLiveViewerConnectionsCountByShard(SHARD_ID, countLiveViewByShard); - yield editorData.setViewerConnectionsCountByShard(SHARD_ID, countViewByShard); + ctx.initDefault(); + yield* collectStats(ctx, countEditByShard, countLiveViewByShard, countViewByShard); + yield editorData.setEditorConnectionsCountByShard(ctx, SHARD_ID, countEditByShard); + yield editorData.setLiveViewerConnectionsCountByShard(ctx, SHARD_ID, countLiveViewByShard); + yield editorData.setViewerConnectionsCountByShard(ctx, SHARD_ID, countViewByShard); if (clientStatsD) { - let countEdit = yield editorData.getEditorConnectionsCount(connections); + let countEdit = yield editorData.getEditorConnectionsCount(ctx, connections); clientStatsD.gauge('expireDoc.connections.edit', countEdit); - let countLiveView = yield editorData.getLiveViewerConnectionsCount(connections); + let countLiveView = yield editorData.getLiveViewerConnectionsCount(ctx, connections); clientStatsD.gauge('expireDoc.connections.liveview', countLiveView); - let countView = yield editorData.getViewerConnectionsCount(connections); + let countView = yield editorData.getViewerConnectionsCount(ctx, connections); clientStatsD.gauge('expireDoc.connections.view', countView); } } catch (err) { - logger.error('expireDoc error:\r\n%s', err.stack); + ctx.logger.error('expireDoc error: %s', err.stack); } finally { setTimeout(expireDoc, expDocumentsStep); } @@ -3360,11 +3374,13 @@ exports.install = function(server, callbackFunction) { setTimeout(expireDoc, expDocumentsStep); function refreshWopiLock() { return co(function* () { + let ctx = new operationContext.Context(); try { - logger.info('refreshWopiLock start'); + ctx.logger.info('refreshWopiLock start'); let docIds = new Map(); for (let i = 0; i < connections.length; ++i) { let conn = connections[i]; + ctx.initFromConnection(conn); let docId = conn.docId; if ((conn.user && conn.user.view) || docIds.has(docId)) { continue; @@ -3373,21 +3389,22 @@ exports.install = function(server, callbackFunction) { if (undefined === conn.access_token_ttl) { continue; } - let selectRes = yield taskResult.select(docId); + let selectRes = yield taskResult.select(ctx, docId); if (selectRes.length > 0 && selectRes[0] && selectRes[0].callback) { let callback = selectRes[0].callback; - let callbackUrl = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, callback); - let wopiParams = wopiClient.parseWopiCallback(docId, callbackUrl, callback); + let callbackUrl = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, callback); + let wopiParams = wopiClient.parseWopiCallback(ctx, callbackUrl, callback); if (wopiParams) { - yield wopiClient.lock('REFRESH_LOCK', wopiParams.commonInfo.lockId, + yield wopiClient.lock(ctx, 'REFRESH_LOCK', wopiParams.commonInfo.lockId, wopiParams.commonInfo.fileInfo, wopiParams.userAuth); } } } + ctx.initDefault(); + ctx.logger.info('refreshWopiLock end'); } catch (err) { - logger.error('refreshWopiLock error:%s', err.stack); + ctx.logger.error('refreshWopiLock error:%s', err.stack); } finally { - logger.info('refreshWopiLock end'); setTimeout(refreshWopiLock, cfgRefreshLockInterval); } }); @@ -3398,7 +3415,7 @@ exports.install = function(server, callbackFunction) { pubsub.on('message', pubsubOnMessage); pubsub.init(function(err) { if (null != err) { - logger.error('createPubSub error :\r\n%s', err.stack); + operationContext.global.logger.error('createPubSub error: %s', err.stack); } queue = new queueService(); @@ -3406,28 +3423,52 @@ exports.install = function(server, callbackFunction) { queue.on('response', canvasService.receiveTask); queue.init(true, true, false, true, true, true, function(err){ if (null != err) { - logger.error('createTaskQueue error :\r\n%s', err.stack); + operationContext.global.logger.error('createTaskQueue error: %s', err.stack); } gc.startGC(); - callbackFunction(); + + let tableName = 'task_result'; + const tableRequiredColumn = 'tenant'; + //check data base compatibility + sqlBase.getTableColumns(operationContext.global, tableName).then(function(res) { + let index = res.findIndex((currentValue) => { + for (let key in currentValue) { + if (currentValue.hasOwnProperty(key) && 'column_name' === key.toLowerCase()) { + return tableRequiredColumn === currentValue[key]; + } + } + }); + if (-1 !== index) { + //check redis storage compatibility + let editorDataArgs = utils.getFunctionArguments(editorData.addPresence); + if (editorDataArgs && 'ctx' === editorDataArgs[0]) { + callbackFunction(); + } else { + operationContext.global.logger.error('server-lockstorage repository on wrong branch. args: %j', editorDataArgs); + } + } else { + operationContext.global.logger.error('DB table "%s" does not contain %s column, columns info: %j', tableName, tableRequiredColumn, res); + } + }).catch(err => { + operationContext.global.logger.error('getTableColumns error: %s', err.stack); + }); + }); }); }; exports.setLicenseInfo = function(data, original ) { - licenseInfo = data; - licenseOriginal = original; -}; -exports.getLicenseInfo = function() { - return licenseInfo; + tenantManager.setDefLicense(data, original); }; exports.healthCheck = function(req, res) { return co(function*() { let output = false; + let ctx = new operationContext.Context(); try { - logger.debug('healthCheck start'); + ctx.initFromRequest(req); + ctx.logger.debug('healthCheck start'); let promises = []; //database - promises.push(sqlBase.healthCheck()); + promises.push(sqlBase.healthCheck(ctx)); //redis if (editorData.isConnected()) { promises.push(editorData.ping()); @@ -3448,18 +3489,18 @@ exports.healthCheck = function(req, res) { const tempName = 'hc_' + os.hostname() + '_' + clusterId + '_' + Math.round(Math.random() * HEALTH_CHECK_KEY_MAX); const tempBuffer = Buffer.from([1, 2, 3, 4, 5]); //It's proper to putObject one tempName - yield storage.putObject(tempName, tempBuffer, tempBuffer.length); + yield storage.putObject(ctx, tempName, tempBuffer, tempBuffer.length); try { //try to prevent case, when another process can remove same tempName - yield storage.deleteObject(tempName); + yield storage.deleteObject(ctx, tempName); } catch (err) { - logger.warn('healthCheck error\r\n%s', err.stack); + ctx.logger.warn('healthCheck error %s', err.stack); } output = true; - logger.debug('healthCheck end'); + ctx.logger.debug('healthCheck end'); } catch (err) { - logger.error('healthCheck error\r\n%s', err.stack); + ctx.logger.error('healthCheck error %s', err.stack); } finally { res.setHeader('Content-Type', 'text/plain'); res.send(output.toString()); @@ -3490,9 +3531,15 @@ exports.licenseInfo = function(req, res) { byMonth: [] } }; - Object.assign(output.licenseInfo, licenseInfo); + + let ctx = new operationContext.Context(); try { - logger.debug('licenseInfo start'); + ctx.initFromRequest(req); + ctx.logger.debug('licenseInfo start'); + + let licenseInfo = yield tenantManager.getTenantLicense(ctx); + Object.assign(output.licenseInfo, licenseInfo); + var precisionSum = {}; for (let i = 0; i < PRECISION.length; ++i) { precisionSum[PRECISION[i].name] = { @@ -3506,7 +3553,7 @@ exports.licenseInfo = function(req, res) { view: {min: 0, avr: 0, max: 0} }; } - var redisRes = yield editorData.getEditorConnections(); + var redisRes = yield editorData.getEditorConnections(ctx); const now = Date.now(); if (redisRes.length > 0) { let expDocumentsStep95 = expDocumentsStep * 0.95; @@ -3562,8 +3609,8 @@ exports.licenseInfo = function(req, res) { } const nowUTC = getLicenseNowUtc(); let execRes; - execRes = yield editorData.getPresenceUniqueUser(nowUTC); - output.quota.edit.connectionsCount = yield editorData.getEditorConnectionsCount(connections); + execRes = yield editorData.getPresenceUniqueUser(ctx, nowUTC); + output.quota.edit.connectionsCount = yield editorData.getEditorConnectionsCount(ctx, connections); output.quota.edit.usersCount.unique = execRes.length; execRes.forEach(function(elem) { if (elem.anonym) { @@ -3571,8 +3618,8 @@ exports.licenseInfo = function(req, res) { } }); - execRes = yield editorData.getPresenceUniqueViewUser(nowUTC); - output.quota.view.connectionsCount = yield editorData.getLiveViewerConnectionsCount(connections); + execRes = yield editorData.getPresenceUniqueViewUser(ctx, nowUTC); + output.quota.view.connectionsCount = yield editorData.getLiveViewerConnectionsCount(ctx, connections); output.quota.view.usersCount.unique = execRes.length; execRes.forEach(function(elem) { if (elem.anonym) { @@ -3580,8 +3627,8 @@ exports.licenseInfo = function(req, res) { } }); - let byMonth = yield editorData.getPresenceUniqueUsersOfMonth(); - let byMonthView = yield editorData.getPresenceUniqueViewUsersOfMonth(); + let byMonth = yield editorData.getPresenceUniqueUsersOfMonth(ctx); + let byMonthView = yield editorData.getPresenceUniqueViewUsersOfMonth(ctx); let byMonthMerged = []; for (let i in byMonth) { if (byMonth.hasOwnProperty(i)) { @@ -3602,10 +3649,10 @@ exports.licenseInfo = function(req, res) { return a.date.localeCompare(b.date); }); - logger.debug('licenseInfo end'); + ctx.logger.debug('licenseInfo end'); } catch (err) { isError = true; - logger.error('licenseInfo error\r\n%s', err.stack); + ctx.logger.error('licenseInfo error %s', err.stack); } finally { if (!isError) { res.setHeader('Content-Type', 'application/json'); @@ -3616,10 +3663,11 @@ exports.licenseInfo = function(req, res) { } }); }; -let commandLicense = co.wrap(function*() { +let commandLicense = co.wrap(function*(ctx) { const nowUTC = getLicenseNowUtc(); - let users = yield editorData.getPresenceUniqueUser(nowUTC); - let users_view = yield editorData.getPresenceUniqueViewUser(nowUTC); + let users = yield editorData.getPresenceUniqueUser(ctx, nowUTC); + let users_view = yield editorData.getPresenceUniqueViewUser(ctx, nowUTC); + let licenseInfo = yield tenantManager.getTenantLicense(ctx); return { license: licenseOriginal || utils.convertLicenseInfoToFileParams(licenseInfo), server: utils.convertLicenseInfoToServerParams(licenseInfo), @@ -3633,8 +3681,11 @@ exports.commandFromServer = function (req, res) { let docId = 'commandFromServer'; let version = undefined; let outputLicense = undefined; + let ctx = new operationContext.Context(); try { - let authRes = getRequestParams(docId, req); + ctx.initFromRequest(req); + ctx.logger.info('commandFromServer start'); + let authRes = yield getRequestParams(ctx, req); let params = authRes.params; if(authRes.code === constants.VKEY_KEY_EXPIRE){ result = commonDefines.c_oAscServerCommandErrors.TokenExpire; @@ -3643,26 +3694,27 @@ exports.commandFromServer = function (req, res) { } // Ключ id-документа docId = params.key; + ctx.setDocId(docId); 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); + ctx.logger.debug('commandFromServer: c = %s', params.c); switch (params.c) { case 'info': //If no files in the database means they have not been edited. - const selectRes = yield taskResult.select(docId); + const selectRes = yield taskResult.select(ctx, docId); if (selectRes.length > 0) { - result = yield* bindEvents(docId, params.callback, utils.getBaseUrlByRequest(req), undefined, params.userdata); + result = yield* bindEvents(ctx, docId, params.callback, utils.getBaseUrlByRequest(req), undefined, params.userdata); } else { result = commonDefines.c_oAscServerCommandErrors.DocumentIdError; } break; case 'drop': if (params.userid) { - yield* publish({type: commonDefines.c_oPublishType.drop, docId: docId, users: [params.userid], description: params.description}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.drop, ctx: ctx, docId: docId, users: [params.userid], description: params.description}); } else if (params.users) { const users = (typeof params.users === 'string') ? JSON.parse(params.users) : params.users; - yield* dropUsersFromDocument(docId, users); + yield* dropUsersFromDocument(ctx, docId, users); } else { result = commonDefines.c_oAscServerCommandErrors.UnknownCommand; } @@ -3671,19 +3723,19 @@ exports.commandFromServer = function (req, res) { // Результат от менеджера документов о статусе обработки сохранения файла после сборки if ('1' !== params.status) { //запрос saved выполняется синхронно, поэтому заполняем переменную чтобы проверить ее после sendServerRequest - yield editorData.setSaved(docId, params.status); - logger.warn('saved corrupted id = %s status = %s conv = %s', docId, params.status, params.conv); + yield editorData.setSaved(ctx, docId, params.status); + ctx.logger.warn('saved corrupted id = %s status = %s conv = %s', docId, params.status, params.conv); } else { - logger.info('saved id = %s status = %s conv = %s', docId, params.status, params.conv); + ctx.logger.info('saved id = %s status = %s conv = %s', docId, params.status, params.conv); } break; case 'forcesave': - let forceSaveRes = yield startForceSave(docId, commonDefines.c_oAscForceSaveTypes.Command, params.userdata, undefined, undefined, undefined, undefined, utils.getBaseUrlByRequest(req)); + let forceSaveRes = yield startForceSave(ctx, docId, commonDefines.c_oAscForceSaveTypes.Command, params.userdata, undefined, undefined, undefined, undefined, utils.getBaseUrlByRequest(req)); result = forceSaveRes.code; break; case 'meta': if (params.meta) { - yield* publish({type: commonDefines.c_oPublishType.meta, docId: docId, meta: params.meta}); + yield* publish(ctx, {type: commonDefines.c_oPublishType.meta, ctx: ctx, docId: docId, meta: params.meta}); } else { result = commonDefines.c_oAscServerCommandErrors.UnknownCommand; } @@ -3692,7 +3744,7 @@ exports.commandFromServer = function (req, res) { version = commonDefines.buildVersion + '.' + commonDefines.buildNumber; break; case 'license': - outputLicense = yield commandLicense(); + outputLicense = yield commandLicense(ctx); break; default: result = commonDefines.c_oAscServerCommandErrors.UnknownCommand; @@ -3701,18 +3753,18 @@ exports.commandFromServer = function (req, res) { } } catch (err) { result = commonDefines.c_oAscServerCommandErrors.UnknownError; - logger.error('Error commandFromServer: docId = %s\r\n%s', docId, err.stack); + ctx.logger.error('Error commandFromServer: %s', err.stack); } finally { //undefined value are excluded in JSON.stringify 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); + ctx.logger.info('commandFromServer end : %j', output); } }); }; @@ -3720,13 +3772,17 @@ exports.commandFromServer = function (req, res) { exports.shutdown = function(req, res) { return co(function*() { let output = false; + let ctx = new operationContext.Context(); try { - output = yield shutdown.shutdown(editorData, req.method === 'PUT'); + ctx.initFromRequest(req); + ctx.logger.info('shutdown start'); + output = yield shutdown.shutdown(ctx, editorData, req.method === 'PUT'); } catch (err) { - logger.error('shutdown error\r\n%s', err.stack); + ctx.logger.error('shutdown error %s', err.stack); } finally { res.setHeader('Content-Type', 'text/plain'); res.send(output.toString()); + ctx.logger.info('shutdown end'); } }); }; diff --git a/DocService/sources/baseConnector.js b/DocService/sources/baseConnector.js index 6ba30141..32b117ef 100644 --- a/DocService/sources/baseConnector.js +++ b/DocService/sources/baseConnector.js @@ -40,7 +40,6 @@ var sqlDataBaseType = { var config = require('config').get('services.CoAuthoring.sql'); var baseConnector = (sqlDataBaseType.mySql === config.get('type') || sqlDataBaseType.mariaDB === config.get('type')) ? require('./mySqlBaseConnector') : require('./postgreSqlBaseConnector'); -var logger = require('./../../Common/sources/logger'); let constants = require('./../../Common/sources/constants'); const tableChanges = config.get('tableChanges'), @@ -52,9 +51,9 @@ let addSqlParam = baseConnector.addSqlParameter; var maxPacketSize = config.get('max_allowed_packet'); // Размер по умолчанию для запроса в базу данных 1Mb - 1 (т.к. он не пишет 1048575, а пишет 1048574) exports.baseConnector = baseConnector; -exports.insertChangesPromiseCompatibility = function (objChanges, docId, index, user) { +exports.insertChangesPromiseCompatibility = function (ctx, objChanges, docId, index, user) { return new Promise(function(resolve, reject) { - _insertChangesCallback(0, objChanges, docId, index, user, function(error, result) { + _insertChangesCallback(ctx, 0, objChanges, docId, index, user, function(error, result) { if (error) { reject(error); } else { @@ -63,13 +62,13 @@ exports.insertChangesPromiseCompatibility = function (objChanges, docId, index, }); }); }; -exports.insertChangesPromiseFast = function (objChanges, docId, index, user) { +exports.insertChangesPromiseFast = function (ctx, objChanges, docId, index, user) { return new Promise(function(resolve, reject) { - baseConnector.insertChanges(tableChanges, 0, objChanges, docId, index, user, function(error, result, isSupported) { + baseConnector.insertChanges(ctx, tableChanges, 0, objChanges, docId, index, user, function(error, result, isSupported) { isSupportFastInsert = isSupported; if (error) { if (!isSupportFastInsert) { - resolve(exports.insertChangesPromiseCompatibility(objChanges, docId, index, user)); + resolve(exports.insertChangesPromiseCompatibility(ctx, objChanges, docId, index, user)); } else { reject(error); } @@ -79,11 +78,11 @@ exports.insertChangesPromiseFast = function (objChanges, docId, index, user) { }); }); }; -exports.insertChangesPromise = function (objChanges, docId, index, user) { +exports.insertChangesPromise = function (ctx, objChanges, docId, index, user) { if (isSupportFastInsert) { - return exports.insertChangesPromiseFast(objChanges, docId, index, user); + return exports.insertChangesPromiseFast(ctx, objChanges, docId, index, user); } else { - return exports.insertChangesPromiseCompatibility(objChanges, docId, index, user); + return exports.insertChangesPromiseCompatibility(ctx, objChanges, docId, index, user); } }; @@ -93,7 +92,7 @@ function _getDateTime2(oDate) { exports.getDateTime = _getDateTime2; -function _insertChangesCallback (startIndex, objChanges, docId, index, user, callback) { +function _insertChangesCallback (ctx, startIndex, objChanges, docId, index, user, callback) { var sqlCommand = `INSERT INTO ${tableChanges} VALUES`; var i = startIndex, l = objChanges.length, lengthUtf8Current = sqlCommand.length, lengthUtf8Row = 0, values = []; if (i === l) @@ -106,13 +105,14 @@ function _insertChangesCallback (startIndex, objChanges, docId, index, user, cal if (lengthUtf8Row + lengthUtf8Current >= maxPacketSize && i > startIndex) { sqlCommand += ';'; (function(tmpStart, tmpIndex) { - baseConnector.sqlQuery(sqlCommand, function() { + baseConnector.sqlQuery(ctx, sqlCommand, function() { // lock не снимаем, а продолжаем добавлять - _insertChangesCallback(tmpStart, objChanges, docId, tmpIndex, user, callback); + _insertChangesCallback(ctx, tmpStart, objChanges, docId, tmpIndex, user, callback); }, undefined, undefined, values); })(i, index); return; } + let p0 = addSqlParam(ctx.tenant, values); let p1 = addSqlParam(docId, values); let p2 = addSqlParam(index, values); let p3 = addSqlParam(user.id, values); @@ -123,27 +123,28 @@ function _insertChangesCallback (startIndex, objChanges, docId, index, user, cal if (i > startIndex) { sqlCommand += ','; } - sqlCommand += `(${p1},${p2},${p3},${p4},${p5},${p6},${p7})`; + sqlCommand += `(${p0},${p1},${p2},${p3},${p4},${p5},${p6},${p7})`; lengthUtf8Current += lengthUtf8Row; } sqlCommand += ';'; - baseConnector.sqlQuery(sqlCommand, callback, undefined, undefined, values); + baseConnector.sqlQuery(ctx, sqlCommand, callback, undefined, undefined, values); } -exports.deleteChangesCallback = function(docId, deleteIndex, callback) { +exports.deleteChangesCallback = function(ctx, docId, deleteIndex, callback) { let sqlCommand, values = []; - let sqlParam1 = addSqlParam(docId, values); + let p1 = addSqlParam(ctx.tenant, values); + let p2 = addSqlParam(docId, values); if (null !== deleteIndex) { let sqlParam2 = addSqlParam(deleteIndex, values); - sqlCommand = `DELETE FROM ${tableChanges} WHERE id=${sqlParam1} AND change_id >= ${sqlParam2};`; + sqlCommand = `DELETE FROM ${tableChanges} WHERE tenant=${p1} AND id=${p2} AND change_id >= ${sqlParam2};`; } else { - sqlCommand = `DELETE FROM ${tableChanges} WHERE id=${sqlParam1};`; + sqlCommand = `DELETE FROM ${tableChanges} WHERE tenant=${p1} AND id=${p2};`; } - baseConnector.sqlQuery(sqlCommand, callback, undefined, undefined, values); + baseConnector.sqlQuery(ctx, sqlCommand, callback, undefined, undefined, values); }; -exports.deleteChangesPromise = function (docId, deleteIndex) { +exports.deleteChangesPromise = function (ctx, docId, deleteIndex) { return new Promise(function(resolve, reject) { - exports.deleteChangesCallback(docId, deleteIndex, function(error, result) { + exports.deleteChangesCallback(ctx, docId, deleteIndex, function(error, result) { if (error) { reject(error); } else { @@ -152,21 +153,22 @@ exports.deleteChangesPromise = function (docId, deleteIndex) { }); }); }; -exports.deleteChanges = function (docId, deleteIndex) { - lockCriticalSection(docId, function () {_deleteChanges(docId, deleteIndex);}); +exports.deleteChanges = function (ctx, docId, deleteIndex) { + lockCriticalSection(docId, function () {_deleteChanges(ctx, docId, deleteIndex);}); }; -function _deleteChanges (docId, deleteIndex) { - exports.deleteChangesCallback(docId, deleteIndex, function () {unLockCriticalSection(docId);}); +function _deleteChanges (ctx, docId, deleteIndex) { + exports.deleteChangesCallback(ctx, docId, deleteIndex, function () {unLockCriticalSection(docId);}); } -exports.getChangesIndex = function(docId, callback) { +exports.getChangesIndex = function(ctx, docId, callback) { let values = []; - let sqlParam = addSqlParam(docId, values); - var sqlCommand = `SELECT MAX(change_id) as change_id FROM ${tableChanges} WHERE id=${sqlParam};`; - baseConnector.sqlQuery(sqlCommand, callback, undefined, undefined, values); + let p1 = addSqlParam(ctx.tenant, values); + let p2 = addSqlParam(docId, values); + var sqlCommand = `SELECT MAX(change_id) as change_id FROM ${tableChanges} WHERE tenant=${p1} AND id=${p2};`; + baseConnector.sqlQuery(ctx, sqlCommand, callback, undefined, undefined, values); }; -exports.getChangesIndexPromise = function(docId) { +exports.getChangesIndexPromise = function(ctx, docId) { return new Promise(function(resolve, reject) { - exports.getChangesIndex(docId, function(error, result) { + exports.getChangesIndex(ctx, docId, function(error, result) { if (error) { reject(error); } else { @@ -175,11 +177,13 @@ exports.getChangesIndexPromise = function(docId) { }); }); }; -exports.getChangesPromise = function (docId, optStartIndex, optEndIndex, opt_time) { +exports.getChangesPromise = function (ctx, docId, optStartIndex, optEndIndex, opt_time) { return new Promise(function(resolve, reject) { let values = []; - let sqlParam = addSqlParam(docId, values); - let sqlWhere = `id=${sqlParam}`; + let sqlParam = addSqlParam(ctx.tenant, values); + let sqlWhere = `tenant=${sqlParam}`; + sqlParam = addSqlParam(docId, values); + sqlWhere += ` AND id=${sqlParam}`; if (null != optStartIndex) { sqlParam = addSqlParam(optStartIndex, values); sqlWhere += ` AND change_id>=${sqlParam}`; @@ -198,7 +202,7 @@ exports.getChangesPromise = function (docId, optStartIndex, optEndIndex, opt_tim sqlWhere += ' ORDER BY change_id ASC'; var sqlCommand = `SELECT * FROM ${tableChanges} WHERE ${sqlWhere};`; - baseConnector.sqlQuery(sqlCommand, function(error, result) { + baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { @@ -207,23 +211,6 @@ exports.getChangesPromise = function (docId, optStartIndex, optEndIndex, opt_tim }, undefined, undefined, values); }); }; -exports.checkStatusFile = function (docId, callbackFunction) { - let values = []; - let sqlParam = addSqlParam(docId, values); - var sqlCommand = `SELECT status, status_info FROM ${tableResult} WHERE id=${sqlParam};`; - baseConnector.sqlQuery(sqlCommand, callbackFunction, undefined, undefined, values); -}; -exports.checkStatusFilePromise = function (docId) { - return new Promise(function(resolve, reject) { - exports.checkStatusFile(docId, function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; exports.isLockCriticalSection = function (id) { return !!(g_oCriticalSection[id]); @@ -249,11 +236,11 @@ function unLockCriticalSection (id) { else delete g_oCriticalSection[id]; } -exports.healthCheck = function () { +exports.healthCheck = function (ctx) { return new Promise(function(resolve, reject) { //SELECT 1; usefull for H2, MySQL, Microsoft SQL Server, PostgreSQL, SQLite //http://stackoverflow.com/questions/3668506/efficient-sql-test-query-or-validation-query-that-will-work-across-all-or-most - baseConnector.sqlQuery('SELECT 1;', function(error, result) { + baseConnector.sqlQuery(ctx, 'SELECT 1;', function(error, result) { if (error) { reject(error); } else { @@ -263,10 +250,22 @@ exports.healthCheck = function () { }); }; -exports.getEmptyCallbacks = function() { +exports.getEmptyCallbacks = function(ctx) { return new Promise(function(resolve, reject) { - const sqlCommand = "SELECT DISTINCT t1.id FROM doc_changes t1 LEFT JOIN task_result t2 ON t2.id = t1.id WHERE t2.callback = '';"; - baseConnector.sqlQuery(sqlCommand, function(error, result) { + const sqlCommand = "SELECT DISTINCT t1.tenant, t1.id FROM doc_changes t1 LEFT JOIN task_result t2 ON t2.tenant = t1.tenant AND t2.id = t1.id WHERE t2.callback = '';"; + baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +}; +exports.getTableColumns = function(ctx, tableName) { + return new Promise(function(resolve, reject) { + const sqlCommand = `SELECT column_name FROM information_schema.COLUMNS WHERE TABLE_NAME = '${tableName}';`; + baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { @@ -291,8 +290,8 @@ UserCallback.prototype.delimiter = constants.CHAR_DELIMITER; UserCallback.prototype.toSQLInsert = function(){ return this.delimiter + JSON.stringify(this); }; -UserCallback.prototype.getCallbackByUserIndex = function(docId, callbacksStr, opt_userIndex) { - logger.debug("getCallbackByUserIndex: docId = %s userIndex = %s callbacks = %s", docId, opt_userIndex, callbacksStr); +UserCallback.prototype.getCallbackByUserIndex = function(ctx, callbacksStr, opt_userIndex) { + ctx.logger.debug("getCallbackByUserIndex: userIndex = %s callbacks = %s", opt_userIndex, callbacksStr); if (!callbacksStr || !callbacksStr.startsWith(UserCallback.prototype.delimiter)) { let index = callbacksStr.indexOf(UserCallback.prototype.delimiter); if (-1 === index) { @@ -314,8 +313,8 @@ UserCallback.prototype.getCallbackByUserIndex = function(docId, callbacksStr, op } return callbackUrl; }; -UserCallback.prototype.getCallbacks = function(docId, callbacksStr) { - logger.debug("getCallbacks: docId = %s callbacks = %s", docId, callbacksStr); +UserCallback.prototype.getCallbacks = function(ctx, callbacksStr) { + ctx.logger.debug("getCallbacks: callbacks = %s", callbacksStr); if (!callbacksStr || !callbacksStr.startsWith(UserCallback.prototype.delimiter)) { let index = callbacksStr.indexOf(UserCallback.prototype.delimiter); if (-1 === index) { @@ -359,10 +358,10 @@ DocumentPassword.prototype.toSQLInsert = function(){ DocumentPassword.prototype.isInitial = function(){ return !this.change; }; -DocumentPassword.prototype.getDocPassword = function(docId, docPasswordStr) { +DocumentPassword.prototype.getDocPassword = function(ctx, docPasswordStr) { let res = {initial: undefined, current: undefined, change: undefined}; if (docPasswordStr) { - logger.debug("getDocPassword: docId = %s passwords = %s", docId, docPasswordStr); + ctx.logger.debug("getDocPassword: passwords = %s", docPasswordStr); let passwords = docPasswordStr.split(UserCallback.prototype.delimiter); for (let i = 1; i < passwords.length; ++i) { @@ -378,12 +377,12 @@ DocumentPassword.prototype.getDocPassword = function(docId, docPasswordStr) { } return res; }; -DocumentPassword.prototype.getCurPassword = function(docId, docPasswordStr) { - let docPassword = this.getDocPassword(docId, docPasswordStr); +DocumentPassword.prototype.getCurPassword = function(ctx, docPasswordStr) { + let docPassword = this.getDocPassword(ctx, docPasswordStr); return docPassword.current; }; -DocumentPassword.prototype.hasPasswordChanges = function(docId, docPasswordStr) { - let docPassword = this.getDocPassword(docId, docPasswordStr); +DocumentPassword.prototype.hasPasswordChanges = function(ctx, docPasswordStr) { + let docPassword = this.getDocPassword(ctx, docPasswordStr); return docPassword.initial !== docPassword.current; }; exports.DocumentPassword = DocumentPassword; diff --git a/DocService/sources/canvasservice.js b/DocService/sources/canvasservice.js index 63e8eccf..3239e1a9 100644 --- a/DocService/sources/canvasservice.js +++ b/DocService/sources/canvasservice.js @@ -49,6 +49,8 @@ var commonDefines = require('./../../Common/sources/commondefines'); var storage = require('./../../Common/sources/storage-base'); var formatChecker = require('./../../Common/sources/formatchecker'); var statsDClient = require('./../../Common/sources/statsdclient'); +var operationContext = require('./../../Common/sources/operationContext'); +var tenantManager = require('./../../Common/sources/tenantManager'); var config = require('config'); var config_server = config.get('services.CoAuthoring.server'); var config_utils = config.get('services.CoAuthoring.utils'); @@ -169,14 +171,14 @@ function getOpenedAtJSONParams(row) { return undefined; } -var getOutputData = co.wrap(function* (cmd, outputData, key, optConn, optAdditionalOutput, opt_bIsRestore) { +var getOutputData = co.wrap(function* (ctx, cmd, outputData, key, optConn, optAdditionalOutput, opt_bIsRestore) { let status, statusInfo, password, creationDate, openedAt, row; - let selectRes = yield taskResult.select(key); + let selectRes = yield taskResult.select(ctx, key); if (selectRes.length > 0) { row = selectRes[0]; status = row.status; statusInfo = row.status_info; - password = sqlBase.DocumentPassword.prototype.getCurPassword(key, row.password); + password = sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password); creationDate = row.created_at && row.created_at.getTime(); openedAt = getOpenedAt(row); } @@ -193,16 +195,17 @@ var getOutputData = co.wrap(function* (cmd, outputData, key, optConn, optAdditio outputData.setStatus(constants.FILE_STATUS_UPDATE_VERSION); } else { if (taskResult.FileStatus.UpdateVersion === status) { - logger.warn("UpdateVersion expired: docId = %s", key); + ctx.logger.warn("UpdateVersion expired"); } var updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; updateMask.key = key; updateMask.status = status; updateMask.statusInfo = statusInfo; var updateTask = new taskResult.TaskResultData(); updateTask.status = taskResult.FileStatus.Ok; updateTask.statusInfo = constants.NO_ERROR; - var updateIfRes = yield taskResult.updateIf(updateTask, updateMask); + var updateIfRes = yield taskResult.updateIf(ctx, updateTask, updateMask); if (updateIfRes.affectedRows > 0) { outputData.setStatus('ok'); } else { @@ -220,7 +223,7 @@ var getOutputData = co.wrap(function* (cmd, outputData, key, optConn, optAdditio if(cmd.getInline()) { url = getPrintFileUrl(key, optConn.baseUrl, cmd.getTitle()); } else { - url = yield storage.getSignedUrl(optConn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Temporary, + url = yield storage.getSignedUrl(ctx, optConn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Temporary, cmd.getTitle()); } outputData.setData(url); @@ -241,7 +244,7 @@ var getOutputData = co.wrap(function* (cmd, outputData, key, optConn, optAdditio isCorrectPassword = decryptedPassword === userPassword; } if(password && !isCorrectPassword) { - logger.debug("getOutputData password mismatch: docId = %s", key); + ctx.logger.debug("getOutputData password mismatch"); if(encryptedUserPassword) { outputData.setStatus('needpassword'); outputData.setData(constants.CONVERT_PASSWORD); @@ -251,7 +254,7 @@ var getOutputData = co.wrap(function* (cmd, outputData, key, optConn, optAdditio } } else if (optConn) { outputData.setOpenedAt(openedAt); - outputData.setData(yield storage.getSignedUrls(optConn.baseUrl, key, commonDefines.c_oAscUrlTypes.Session, creationDate)); + outputData.setData(yield storage.getSignedUrls(ctx, optConn.baseUrl, key, commonDefines.c_oAscUrlTypes.Session, creationDate)); } else if (optAdditionalOutput) { optAdditionalOutput.needUrlKey = key; optAdditionalOutput.needUrlMethod = 0; @@ -266,7 +269,7 @@ var getOutputData = co.wrap(function* (cmd, outputData, key, optConn, optAdditio outputData.setStatus('needparams'); var settingsPath = key + '/' + 'origin.' + cmd.getFormat(); if (optConn) { - let url = yield storage.getSignedUrl(optConn.baseUrl, settingsPath, commonDefines.c_oAscUrlTypes.Temporary); + let url = yield storage.getSignedUrl(ctx, optConn.baseUrl, settingsPath, commonDefines.c_oAscUrlTypes.Temporary); outputData.setData(url); } else if (optAdditionalOutput) { optAdditionalOutput.needUrlKey = settingsPath; @@ -283,11 +286,11 @@ var getOutputData = co.wrap(function* (cmd, outputData, key, optConn, optAdditio outputData.setStatus('err'); outputData.setData(statusInfo); if (taskResult.FileStatus.ErrToReload == status) { - let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(key, row.callback); - let wopiParams = wopiClient.parseWopiCallback(key, userAuthStr); + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + let wopiParams = wopiClient.parseWopiCallback(ctx, userAuthStr); if (!wopiParams) { //todo rework ErrToReload to clean up on next open - yield cleanupCache(key); + yield cleanupCache(ctx); } } break; @@ -303,12 +306,12 @@ var getOutputData = co.wrap(function* (cmd, outputData, key, optConn, optAdditio break; } }); -function* addRandomKeyTaskCmd(cmd) { - var task = yield* taskResult.addRandomKeyTask(cmd.getDocId()); +function* addRandomKeyTaskCmd(ctx, cmd) { + var task = yield* taskResult.addRandomKeyTask(ctx, cmd.getDocId()); cmd.setSaveKey(task.key); } -function addPasswordToCmd(cmd, docPasswordStr) { - let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(cmd.getDocId(), docPasswordStr); +function addPasswordToCmd(ctx, cmd, docPasswordStr) { + let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(ctx, docPasswordStr); if (docPassword.current) { cmd.setSavePassword(docPassword.current); } @@ -317,15 +320,15 @@ function addPasswordToCmd(cmd, docPasswordStr) { } } -function changeFormatByOrigin(docId, row, format) { +function changeFormatByOrigin(ctx, row, format) { let originFormat = row && row.change_id; if (originFormat && constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== originFormat) { if (cfgAssemblyFormatAsOrigin) { format = originFormat; } else { //for wopi always save origin - let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, row.callback); - let wopiParams = wopiClient.parseWopiCallback(docId, userAuthStr, row.callback); + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + let wopiParams = wopiClient.parseWopiCallback(ctx, userAuthStr, row.callback); if (wopiParams) { format = originFormat; } @@ -333,7 +336,7 @@ function changeFormatByOrigin(docId, row, format) { } return format; } -function* saveParts(cmd, filename) { +function* saveParts(ctx, cmd, filename) { var result = false; var saveType = cmd.getSaveType(); if (SAVE_TYPE_COMPLETE_ALL !== saveType) { @@ -342,22 +345,23 @@ function* saveParts(cmd, filename) { filename = pathModule.basename(filename, ext) + saveIndex + ext; } if ((SAVE_TYPE_PART_START === saveType || SAVE_TYPE_COMPLETE_ALL === saveType) && !cmd.getSaveKey()) { - yield* addRandomKeyTaskCmd(cmd); + yield* addRandomKeyTaskCmd(ctx, cmd); } if (cmd.getUrl()) { result = true; } else { var buffer = cmd.getData(); - yield storage.putObject(cmd.getSaveKey() + '/' + filename, buffer, buffer.length); + yield storage.putObject(ctx, cmd.getSaveKey() + '/' + filename, buffer, buffer.length); //delete data to prevent serialize into json cmd.data = null; result = (SAVE_TYPE_COMPLETE_ALL === saveType || SAVE_TYPE_COMPLETE === saveType); } return result; } -function getSaveTask(cmd) { +function getSaveTask(ctx, cmd) { cmd.setData(null); var queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); queueData.setCmd(cmd); queueData.setToFile(constants.OUTPUT_NAME + '.' + formatChecker.getStringFromFormat(cmd.getOutputFormat())); //todo paid @@ -369,8 +373,9 @@ function getSaveTask(cmd) { //} return queueData; } -function* getUpdateResponse(cmd) { +function* getUpdateResponse(ctx, cmd) { var updateTask = new taskResult.TaskResultData(); + updateTask.tenant = ctx.tenant; updateTask.key = cmd.getSaveKey() ? cmd.getSaveKey() : cmd.getDocId(); var statusInfo = cmd.getStatusInfo(); if (constants.NO_ERROR == statusInfo) { @@ -378,7 +383,7 @@ function* getUpdateResponse(cmd) { let password = cmd.getPassword(); if (password) { if (false === hasPasswordCol) { - let selectRes = yield taskResult.select(updateTask.key); + let selectRes = yield taskResult.select(ctx, updateTask.key); hasPasswordCol = selectRes.length > 0 && undefined !== selectRes[0].password; } if(hasPasswordCol) { @@ -405,36 +410,38 @@ function* getUpdateResponse(cmd) { updateTask.statusInfo = statusInfo; return updateTask; } -var cleanupCache = co.wrap(function* (docId) { +var cleanupCache = co.wrap(function* (ctx) { //todo redis ? var res = false; + let docId = ctx.docId; let list = []; - var removeRes = yield taskResult.remove(docId); + var removeRes = yield taskResult.remove(ctx, docId); if (removeRes.affectedRows > 0) { - list = yield storage.listObjects(docId); - yield storage.deleteObjects(list); + list = yield storage.listObjects(ctx, docId); + yield storage.deleteObjects(ctx, list); res = true; } - logger.debug("cleanupCache db.affectedRows=%d list.length=%d: docId = %s", removeRes.affectedRows, list.length, docId); + ctx.logger.debug("cleanupCache db.affectedRows=%d list.length=%d", removeRes.affectedRows, list.length); return res; }); -var cleanupCacheIf = co.wrap(function* (mask) { +var cleanupCacheIf = co.wrap(function* (ctx, mask) { //todo redis ? var res = false; let list = []; - var removeRes = yield taskResult.removeIf(mask); + var removeRes = yield taskResult.removeIf(ctx, mask); if (removeRes.affectedRows > 0) { - sqlBase.deleteChanges(mask.key, null); - list = yield storage.listObjects(mask.key); - yield storage.deleteObjects(list); + sqlBase.deleteChanges(ctx, mask.key, null); + list = yield storage.listObjects(ctx, mask.key); + yield storage.deleteObjects(ctx, list); res = true; } - logger.debug("cleanupCacheIf db.affectedRows=%d list.length=%d: docId = %s", removeRes.affectedRows, list.length, mask.key); + ctx.logger.debug("cleanupCacheIf db.affectedRows=%d list.length=%d", removeRes.affectedRows, list.length); return res; }); -function commandOpenStartPromise(docId, baseUrl, opt_updateUserIndex, opt_documentCallbackUrl, opt_format) { +function commandOpenStartPromise(ctx, docId, baseUrl, opt_updateUserIndex, opt_documentCallbackUrl, opt_format) { var task = new taskResult.TaskResultData(); + task.tenant = ctx.tenant; task.key = docId; //None instead WaitQueue to prevent: conversion task is lost when entering and leaving the editor quickly(that leads to an endless opening) task.status = taskResult.FileStatus.None; @@ -446,45 +453,45 @@ function commandOpenStartPromise(docId, baseUrl, opt_updateUserIndex, opt_docume if (opt_format) { task.changeId = formatChecker.getFormatFromString(opt_format); } - return taskResult.upsert(task, opt_updateUserIndex); + return taskResult.upsert(ctx, task, opt_updateUserIndex); } -function* commandOpen(conn, cmd, outputData, opt_upsertRes, opt_bIsRestore) { +function* commandOpen(ctx, conn, cmd, outputData, opt_upsertRes, opt_bIsRestore) { var upsertRes; if (opt_upsertRes) { upsertRes = opt_upsertRes; } else { - upsertRes = yield commandOpenStartPromise(cmd.getDocId(), utils.getBaseUrlByConnection(conn)); + upsertRes = yield commandOpenStartPromise(ctx, cmd.getDocId(), utils.getBaseUrlByConnection(conn)); } //if CLIENT_FOUND_ROWS don't specify 1 row is inserted , 2 row is updated, and 0 row is set to its current values //http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html let bCreate = upsertRes.affectedRows == 1; let needAddTask = bCreate; if (!bCreate) { - needAddTask = yield* commandOpenFillOutput(conn, cmd, outputData, opt_bIsRestore); + needAddTask = yield* commandOpenFillOutput(ctx, conn, cmd, outputData, opt_bIsRestore); } if (conn.encrypted) { - logger.debug("commandOpen encrypted %j: docId = %s", outputData, cmd.getDocId()); + ctx.logger.debug("commandOpen encrypted %j", outputData); if (constants.FILE_STATUS_UPDATE_VERSION !== outputData.getStatus()) { //don't send output data outputData.setStatus(undefined); } } else if (needAddTask) { let updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; updateMask.key = cmd.getDocId(); updateMask.status = taskResult.FileStatus.None; let task = new taskResult.TaskResultData(); - task.key = cmd.getDocId(); task.status = taskResult.FileStatus.WaitQueue; task.statusInfo = constants.NO_ERROR; - let updateIfRes = yield taskResult.updateIf(task, updateMask); + let updateIfRes = yield taskResult.updateIf(ctx, task, updateMask); if (updateIfRes.affectedRows > 0) { let forgottenId = cfgForgottenFiles + '/' + cmd.getDocId(); - let forgotten = yield storage.listObjects(forgottenId); + let forgotten = yield storage.listObjects(ctx, forgottenId); //replace url with forgotten file because it absorbed all lost changes if (forgotten.length > 0) { - logger.debug("commandOpen from forgotten: docId = %s", cmd.getDocId()); + ctx.logger.debug("commandOpen from forgotten"); cmd.setUrl(undefined); cmd.setForgotten(forgottenId); } @@ -492,6 +499,7 @@ function* commandOpen(conn, cmd, outputData, opt_upsertRes, opt_bIsRestore) { cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_CANVAS); cmd.setEmbeddedFonts(false); var dataQueue = new commonDefines.TaskQueueData(); + dataQueue.setCtx(ctx); dataQueue.setCmd(cmd); dataQueue.setToFile('Editor.bin'); var priority = constants.QUEUE_PRIORITY_HIGH; @@ -504,40 +512,40 @@ function* commandOpen(conn, cmd, outputData, opt_upsertRes, opt_bIsRestore) { } yield* docsCoServer.addTask(dataQueue, priority); } else { - yield* commandOpenFillOutput(conn, cmd, outputData, opt_bIsRestore); + yield* commandOpenFillOutput(ctx, conn, cmd, outputData, opt_bIsRestore); } } } -function* commandOpenFillOutput(conn, cmd, outputData, opt_bIsRestore) { - yield getOutputData(cmd, outputData, cmd.getDocId(), conn, undefined, opt_bIsRestore); +function* commandOpenFillOutput(ctx, conn, cmd, outputData, opt_bIsRestore) { + yield getOutputData(ctx, cmd, outputData, cmd.getDocId(), conn, undefined, opt_bIsRestore); return 'none' === outputData.getStatus(); } -function* commandReopen(conn, cmd, outputData) { +function* commandReopen(ctx, conn, cmd, outputData) { let res = true; let isPassword = undefined !== cmd.getPassword(); if (isPassword) { - let selectRes = yield taskResult.select(cmd.getDocId()); + let selectRes = yield taskResult.select(ctx, cmd.getDocId()); if (selectRes.length > 0) { let row = selectRes[0]; - 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()); + if (sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password)) { + ctx.logger.debug('commandReopen has password'); + yield* commandOpenFillOutput(ctx, conn, cmd, outputData, false); + docsCoServer.modifyConnectionForPassword(ctx, conn, constants.FILE_STATUS_OK === outputData.getStatus()); return res; } } } if (!isPassword || cfgOpenProtectedFile) { let updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; updateMask.key = cmd.getDocId(); updateMask.status = isPassword ? taskResult.FileStatus.NeedPassword : taskResult.FileStatus.NeedParams; var task = new taskResult.TaskResultData(); - task.key = cmd.getDocId(); task.status = taskResult.FileStatus.WaitQueue; task.statusInfo = constants.NO_ERROR; - var upsertRes = yield taskResult.updateIf(task, updateMask); + var upsertRes = yield taskResult.updateIf(ctx, task, updateMask); if (upsertRes.affectedRows > 0) { //add task cmd.setUrl(null);//url may expire @@ -548,6 +556,7 @@ function* commandReopen(conn, cmd, outputData) { cmd.setUserConnectionId(conn.user.id); } var dataQueue = new commonDefines.TaskQueueData(); + dataQueue.setCtx(ctx); dataQueue.setCmd(cmd); dataQueue.setToFile('Editor.bin'); dataQueue.setFromSettings(true); @@ -561,36 +570,36 @@ function* commandReopen(conn, cmd, outputData) { } return res; } -function* commandSave(cmd, outputData) { +function* commandSave(ctx, cmd, outputData) { let format = cmd.getFormat() || 'bin'; - var completeParts = yield* saveParts(cmd, "Editor." + format); + var completeParts = yield* saveParts(ctx, cmd, "Editor." + format); if (completeParts) { - var queueData = getSaveTask(cmd); + var queueData = getSaveTask(ctx, cmd); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); } outputData.setStatus('ok'); outputData.setData(cmd.getSaveKey()); } -function* commandSendMailMerge(cmd, outputData) { +function* commandSendMailMerge(ctx, cmd, outputData) { let mailMergeSend = cmd.getMailMergeSend(); let isJson = mailMergeSend.getIsJsonKey(); - var completeParts = yield* saveParts(cmd, isJson ? "Editor.json" : "Editor.bin"); + var completeParts = yield* saveParts(ctx, cmd, isJson ? "Editor.json" : "Editor.bin"); var isErr = false; if (completeParts && !isJson) { isErr = true; - var getRes = yield* docsCoServer.getCallback(cmd.getDocId(), cmd.getUserIndex()); + var getRes = yield* docsCoServer.getCallback(ctx, cmd.getDocId(), cmd.getUserIndex()); if (getRes && !getRes.wopiParams) { mailMergeSend.setUrl(getRes.server.href); mailMergeSend.setBaseUrl(getRes.baseUrl); //меняем JsonKey и SaveKey, новый key нужет потому что за одну конвертацию делается часть, а json нужен всегда mailMergeSend.setJsonKey(cmd.getSaveKey()); mailMergeSend.setRecordErrorCount(0); - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); + yield* addRandomKeyTaskCmd(ctx, cmd); + var queueData = getSaveTask(ctx, cmd); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); isErr = false; } else if (getRes.wopiParams) { - logger.warn('commandSendMailMerge unexpected with wopi: docId = %s', cmd.getDocId()); + ctx.logger.warn('commandSendMailMerge unexpected with wopi'); } } if (isErr) { @@ -601,25 +610,25 @@ function* commandSendMailMerge(cmd, outputData) { outputData.setData(cmd.getSaveKey()); } } -function* commandSfctByCmd(cmd, opt_priority, opt_expiration, opt_queue) { - var selectRes = yield taskResult.select(cmd.getDocId()); +let commandSfctByCmd = co.wrap(function*(ctx, cmd, opt_priority, opt_expiration, opt_queue) { + var selectRes = yield taskResult.select(ctx, cmd.getDocId()); var row = selectRes.length > 0 ? selectRes[0] : null; if (!row) { return; } - yield* addRandomKeyTaskCmd(cmd); - addPasswordToCmd(cmd, row.password); - let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(cmd.getDocId(), row.callback); - cmd.setWopiParams(wopiClient.parseWopiCallback(cmd.getDocId(), userAuthStr)); - cmd.setOutputFormat(changeFormatByOrigin(cmd.getDocId(), row, cmd.getOutputFormat())); + yield* addRandomKeyTaskCmd(ctx, cmd); + addPasswordToCmd(ctx, cmd, row.password); + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + cmd.setWopiParams(wopiClient.parseWopiCallback(ctx, userAuthStr)); + cmd.setOutputFormat(changeFormatByOrigin(ctx, row, cmd.getOutputFormat())); cmd.setJsonParams(getOpenedAtJSONParams(row)); - var queueData = getSaveTask(cmd); + var queueData = getSaveTask(ctx, cmd); queueData.setFromChanges(true); let priority = null != opt_priority ? opt_priority : constants.QUEUE_PRIORITY_LOW; yield* docsCoServer.addTask(queueData, priority, opt_queue, opt_expiration); -} -function* commandSfct(cmd, outputData) { - yield* commandSfctByCmd(cmd); +}); +function* commandSfct(ctx, cmd, outputData) { + yield commandSfctByCmd(ctx, cmd); outputData.setStatus('ok'); } function isDisplayedImage(strName) { @@ -641,14 +650,13 @@ function isDisplayedImage(strName) { } return res; } -function* commandImgurls(conn, cmd, outputData) { - let docId = cmd.getDocId(); +function* commandImgurls(ctx, conn, cmd, outputData) { var errorCode = constants.NO_ERROR; let urls = cmd.getData(); let authorizations = []; let token = cmd.getTokenDownload(); if (cfgTokenEnableBrowser && token) { - let checkJwtRes = docsCoServer.checkJwt(docId, token, commonDefines.c_oAscSecretType.Browser); + let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser); if (checkJwtRes.decoded) { //todo multiple url case if (checkJwtRes.decoded.images) { @@ -659,12 +667,13 @@ function* commandImgurls(conn, cmd, outputData) { urls = [checkJwtRes.decoded.url]; } for (let i = 0; i < urls.length; ++i) { - if (utils.canIncludeOutboxAuthorization(urls[i])) { - authorizations[i] = [utils.fillJwtForRequest({url: urls[i]}, false)]; + if (utils.canIncludeOutboxAuthorization(ctx, urls[i])) { + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox); + authorizations[i] = [utils.fillJwtForRequest({url: urls[i]}, secret, false)]; } } } else { - logger.warn('Error commandImgurls jwt: docId = %s\r\n%s', docId, checkJwtRes.description); + ctx.logger.warn('Error commandImgurls jwt: %s', checkJwtRes.description); errorCode = constants.VKEY_ENCRYPT; } } @@ -698,12 +707,12 @@ function* commandImgurls(conn, cmd, outputData) { } else if (urlSource) { try { //todo stream - let getRes = yield utils.downloadUrlPromise(urlSource, cfgImageDownloadTimeout, cfgImageSize, authorizations[i], !authorizations[i]); + let getRes = yield utils.downloadUrlPromise(ctx, urlSource, cfgImageDownloadTimeout, cfgImageSize, authorizations[i], !authorizations[i]); data = getRes.body; urlParsed = urlModule.parse(urlSource); } catch (e) { data = undefined; - logger.error('error commandImgurls download: url = %s; docId = %s\r\n%s', urlSource, docId, e.stack); + ctx.logger.error('error commandImgurls download: url = %s; %s', urlSource, e.stack); if (e.code === 'EMSGSIZE') { errorCode = constants.UPLOAD_CONTENT_LENGTH; } else { @@ -713,7 +722,7 @@ function* commandImgurls(conn, cmd, outputData) { } var outputUrl = {url: 'error', path: 'error'}; if (data) { - let format = formatChecker.getImageFormat(data); + let format = formatChecker.getImageFormat(ctx, data); let formatStr; let isAllow = false; if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== format) { @@ -753,8 +762,8 @@ function* commandImgurls(conn, cmd, outputData) { } strLocalPath += 'image1' + '.' + formatStr; var strPath = cmd.getDocId() + '/' + strLocalPath; - yield storage.putObject(strPath, data, data.length); - var imgUrl = yield storage.getSignedUrl(conn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Session); + yield storage.putObject(ctx, strPath, data, data.length); + var imgUrl = yield storage.getSignedUrl(ctx, conn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Session); outputUrl = {url: imgUrl, path: strLocalPath}; } } @@ -764,7 +773,7 @@ function* commandImgurls(conn, cmd, outputData) { outputUrls.push(outputUrl); } } else if(constants.NO_ERROR === errorCode) { - logger.warn('error commandImgurls: docId = %s access deny', docId); + ctx.logger.warn('error commandImgurls: access deny'); errorCode = constants.UPLOAD; } if (constants.NO_ERROR !== errorCode && 0 == outputUrls.length) { @@ -775,17 +784,17 @@ function* commandImgurls(conn, cmd, outputData) { outputData.setData({error: errorCode, urls: outputUrls}); } } -function* commandPathUrls(conn, cmd, outputData) { +function* commandPathUrls(ctx, conn, cmd, outputData) { let listImages = cmd.getData().map(function callback(currentValue) { return conn.docId + '/' + currentValue; }); - let urls = yield storage.getSignedUrlsArrayByArray(conn.baseUrl, listImages, commonDefines.c_oAscUrlTypes.Session); + let urls = yield storage.getSignedUrlsArrayByArray(ctx, conn.baseUrl, listImages, commonDefines.c_oAscUrlTypes.Session); outputData.setStatus('ok'); outputData.setData(urls); } -function* commandPathUrl(conn, cmd, outputData) { +function* commandPathUrl(ctx, conn, cmd, outputData) { var strPath = conn.docId + '/' + cmd.getData(); - var url = yield storage.getSignedUrl(conn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Temporary, cmd.getTitle()); + var url = yield storage.getSignedUrl(ctx, conn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Temporary, cmd.getTitle()); var errorCode = constants.NO_ERROR; if (constants.NO_ERROR !== errorCode) { outputData.setStatus('err'); @@ -796,31 +805,32 @@ function* commandPathUrl(conn, cmd, outputData) { outputData.setExtName(pathModule.extname(strPath)); } } -function* commandSaveFromOrigin(cmd, outputData, password) { - let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(cmd.getDocId(), password); +function* commandSaveFromOrigin(ctx, cmd, outputData, password) { + let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(ctx, password); if (docPassword.initial) { cmd.setPassword(docPassword.initial); } - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); + yield* addRandomKeyTaskCmd(ctx, cmd); + var queueData = getSaveTask(ctx, cmd); queueData.setFromOrigin(true); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); outputData.setStatus('ok'); outputData.setData(cmd.getSaveKey()); } -function* commandSetPassword(conn, cmd, outputData) { +function* commandSetPassword(ctx, conn, cmd, outputData) { let hasDocumentPassword = false; - let selectRes = yield taskResult.select(cmd.getDocId()); + let selectRes = yield taskResult.select(ctx, cmd.getDocId()); if (selectRes.length > 0) { let row = selectRes[0]; hasPasswordCol = undefined !== row.password; - if (taskResult.FileStatus.Ok === row.status && sqlBase.DocumentPassword.prototype.getCurPassword(cmd.getDocId(), row.password)) { + if (taskResult.FileStatus.Ok === row.status && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password)) { hasDocumentPassword = true; } } - logger.debug('commandSetPassword isEnterCorrectPassword=%s, hasDocumentPassword=%s, hasPasswordCol=%s: docId = %s', conn.isEnterCorrectPassword, hasDocumentPassword, hasPasswordCol, cmd.getDocId()); + ctx.logger.debug('commandSetPassword isEnterCorrectPassword=%s, hasDocumentPassword=%s, hasPasswordCol=%s', conn.isEnterCorrectPassword, hasDocumentPassword, hasPasswordCol); if (cfgOpenProtectedFile && (conn.isEnterCorrectPassword || !hasDocumentPassword) && hasPasswordCol) { let updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; updateMask.key = cmd.getDocId(); updateMask.status = taskResult.FileStatus.Ok; @@ -828,22 +838,21 @@ function* commandSetPassword(conn, cmd, outputData) { 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); + var upsertRes = yield taskResult.updateIf(ctx, task, updateMask); if (upsertRes.affectedRows > 0) { outputData.setStatus('ok'); if (!conn.isEnterCorrectPassword) { - docsCoServer.modifyConnectionForPassword(conn, true); + docsCoServer.modifyConnectionForPassword(ctx, conn, true); } - yield docsCoServer.resetForceSaveAfterChanges(cmd.getDocId(), newChangesLastDate.getTime(), 0, utils.getBaseUrlByConnection(conn), changeInfo); + yield docsCoServer.resetForceSaveAfterChanges(ctx, cmd.getDocId(), newChangesLastDate.getTime(), 0, utils.getBaseUrlByConnection(conn), changeInfo); } else { - logger.debug('commandSetPassword sql update error: docId = %s', cmd.getDocId()); + ctx.logger.debug('commandSetPassword sql update error'); outputData.setStatus('err'); outputData.setData(constants.PASSWORD); } @@ -852,8 +861,8 @@ function* commandSetPassword(conn, cmd, outputData) { outputData.setData(constants.PASSWORD); } } -function* commandChangeDocInfo(conn, cmd, outputData) { - let res = yield docsCoServer.changeConnectionInfo(conn, cmd); +function* commandChangeDocInfo(ctx, conn, cmd, outputData) { + let res = yield docsCoServer.changeConnectionInfo(ctx, conn, cmd); if(res) { outputData.setStatus('ok'); } else { @@ -871,9 +880,9 @@ function checkAndFixAuthorizationLength(authorization, data){ } return res; } -function* commandSfcCallback(cmd, isSfcm, isEncrypted) { +function* commandSfcCallback(ctx, cmd, isSfcm, isEncrypted) { var docId = cmd.getDocId(); - logger.debug('Start commandSfcCallback: docId = %s', docId); + ctx.logger.debug('Start commandSfcCallback'); var statusInfo = cmd.getStatusInfo(); //setUserId - set from changes in convert //setUserActionId - used in case of save without changes(forgotten files) @@ -893,12 +902,12 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { let forceSaveUserIndex = forceSave ? forceSave.getAuthorUserIndex() : undefined; let callbackUserIndex = (forceSaveUserIndex || 0 === forceSaveUserIndex) ? forceSaveUserIndex : userLastChangeIndex; let uri, baseUrl, wopiParams; - let selectRes = yield taskResult.select(docId); + let selectRes = yield taskResult.select(ctx, docId); let row = selectRes.length > 0 ? selectRes[0] : null; if (row) { if (row.callback) { - uri = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, row.callback, callbackUserIndex); - wopiParams = wopiClient.parseWopiCallback(docId, uri, row.callback); + uri = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback, callbackUserIndex); + wopiParams = wopiClient.parseWopiCallback(ctx, uri, row.callback); } if (row.baseurl) { baseUrl = row.baseurl; @@ -925,6 +934,7 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { let updateIfRes; let updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; updateMask.key = docId; if (row) { if (isEncrypted) { @@ -946,7 +956,7 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { isError = true; } if (uri && baseUrl && userLastChangeId) { - logger.debug('Callback commandSfcCallback: docId = %s callback = %s', docId, uri); + ctx.logger.debug('Callback commandSfcCallback: callback = %s', uri); var outputSfc = new commonDefines.OutputSfcData(docId); outputSfc.setEncrypted(isEncrypted); var users = []; @@ -970,32 +980,32 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { if (!isError || isErrorCorrupted) { try { let forgottenId = cfgForgottenFiles + '/' + docId; - let forgotten = yield storage.listObjects(forgottenId); + let forgotten = yield storage.listObjects(ctx, forgottenId); let isSendHistory = 0 === forgotten.length; if (!isSendHistory) { //check indicator file to determine if opening was from the forgotten file var forgottenMarkPath = docId + '/' + cfgForgottenFilesName + '.txt'; - var forgottenMark = yield storage.listObjects(forgottenMarkPath); + var forgottenMark = yield storage.listObjects(ctx, forgottenMarkPath); isOpenFromForgotten = 0 !== forgottenMark.length; isSendHistory = !isOpenFromForgotten; - logger.debug('commandSfcCallback forgotten no empty: docId = %s isSendHistory = %s', docId, isSendHistory); + ctx.logger.debug('commandSfcCallback forgotten no empty: isSendHistory = %s', isSendHistory); } if (isSendHistory && !isEncrypted) { //don't send history info because changes isn't from file in storage - var data = yield storage.getObject(savePathHistory); + var data = yield storage.getObject(ctx, savePathHistory); outputSfc.setChangeHistory(JSON.parse(data.toString('utf-8'))); - let changeUrl = yield storage.getSignedUrl(baseUrl, savePathChanges, + let changeUrl = yield storage.getSignedUrl(ctx, baseUrl, savePathChanges, commonDefines.c_oAscUrlTypes.Temporary); outputSfc.setChangeUrl(changeUrl); } else { //for backward compatibility. remove this when Community is ready outputSfc.setChangeHistory({}); } - let url = yield storage.getSignedUrl(baseUrl, savePathDoc, commonDefines.c_oAscUrlTypes.Temporary); + let url = yield storage.getSignedUrl(ctx, baseUrl, savePathDoc, commonDefines.c_oAscUrlTypes.Temporary); outputSfc.setUrl(url); outputSfc.setExtName(pathModule.extname(savePathDoc)); } catch (e) { - logger.error('Error commandSfcCallback: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error commandSfcCallback: %s', e.stack); } if (outputSfc.getUrl() && outputSfc.getUsers().length > 0) { outputSfc.setStatus(statusOk); @@ -1007,7 +1017,7 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { outputSfc.setStatus(statusErr); } if (isSfcm) { - let selectRes = yield taskResult.select(docId); + let selectRes = yield taskResult.select(ctx, docId); let row = selectRes.length > 0 ? selectRes[0] : null; //send only if FileStatus.Ok to prevent forcesave after final save if (row && row.status == taskResult.FileStatus.Ok) { @@ -1018,29 +1028,29 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { } try { if (wopiParams) { - replyStr = yield processWopiPutFile(docId, wopiParams, savePathDoc, userLastChangeId, true, forceSaveType !== commonDefines.c_oAscForceSaveTypes.Button, false); + replyStr = yield processWopiPutFile(ctx, docId, wopiParams, savePathDoc, userLastChangeId, true, forceSaveType !== commonDefines.c_oAscForceSaveTypes.Button, false); } else { - replyStr = yield* docsCoServer.sendServerRequest(docId, uri, outputSfc, checkAndFixAuthorizationLength); + replyStr = yield* docsCoServer.sendServerRequest(ctx, uri, outputSfc, checkAndFixAuthorizationLength); } - let replyData = docsCoServer.parseReplyData(docId, replyStr); + let replyData = docsCoServer.parseReplyData(ctx, replyStr); isSfcmSuccess = replyData && commonDefines.c_oAscServerCommandErrors.NoError == replyData.error; if (replyData && commonDefines.c_oAscServerCommandErrors.NoError != replyData.error) { - logger.warn('sendServerRequest returned an error: docId = %s; data = %s', docId, replyStr); + ctx.logger.warn('sendServerRequest returned an error: data = %s', replyStr); } } catch (err) { - logger.error('sendServerRequest error: docId = %s;url = %s;data = %j\r\n%s', docId, uri, outputSfc, err.stack); + ctx.logger.error('sendServerRequest error: url = %s;data = %j %s', uri, outputSfc, err.stack); } } } else { //if anybody in document stop save - let editorsCount = yield docsCoServer.getEditorsCountPromise(docId); - logger.debug('commandSfcCallback presence: docId = %s count = %d', docId, editorsCount); + let editorsCount = yield docsCoServer.getEditorsCountPromise(ctx, docId); + ctx.logger.debug('commandSfcCallback presence: count = %d', editorsCount); if (0 === editorsCount || (isEncrypted && 1 === editorsCount)) { if (!updateIfRes) { - updateIfRes = yield taskResult.updateIf(updateIfTask, updateMask); + updateIfRes = yield taskResult.updateIf(ctx, updateIfTask, updateMask); } if (updateIfRes.affectedRows > 0) { - let actualForceSave = yield docsCoServer.editorData.getForceSave(docId); + let actualForceSave = yield docsCoServer.editorData.getForceSave(ctx, docId); let forceSaveDate = (actualForceSave && actualForceSave.time) ? new Date(actualForceSave.time) : new Date(); let notModified = actualForceSave && true === actualForceSave.ended; outputSfc.setLastSave(forceSaveDate.toISOString()); @@ -1050,37 +1060,37 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { updateMask.statusInfo = updateIfTask.statusInfo; try { if (wopiParams) { - replyStr = yield processWopiPutFile(docId, wopiParams, savePathDoc, userLastChangeId, !notModified, false, true); + replyStr = yield processWopiPutFile(ctx, docId, wopiParams, savePathDoc, userLastChangeId, !notModified, false, true); } else { - replyStr = yield* docsCoServer.sendServerRequest(docId, uri, outputSfc, checkAndFixAuthorizationLength); + replyStr = yield* docsCoServer.sendServerRequest(ctx, uri, outputSfc, checkAndFixAuthorizationLength); } } catch (err) { - logger.error('sendServerRequest error: docId = %s;url = %s;data = %j\r\n%s', docId, uri, outputSfc, err.stack); + ctx.logger.error('sendServerRequest error: url = %s;data = %j %s', uri, outputSfc, err.stack); if (!isEncrypted && !docsCoServer.getIsShutdown() && (!err.statusCode || retryHttpStatus.has(err.statusCode.toString()))) { let attempt = cmd.getAttempt() || 0; if (attempt < cfgCallbackBackoffOptions.retries) { needRetry = true; } else { - logger.warn('commandSfcCallback backoff limit exceeded: docId = %s', docId); + ctx.logger.warn('commandSfcCallback backoff limit exceeded'); } } } var requestRes = false; - var replyData = docsCoServer.parseReplyData(docId, replyStr); + var replyData = docsCoServer.parseReplyData(ctx, replyStr); if (replyData && commonDefines.c_oAscServerCommandErrors.NoError == replyData.error) { //в случае comunity server придет запрос в CommandService проверяем результат - var savedVal = yield docsCoServer.editorData.getdelSaved(docId); + var savedVal = yield docsCoServer.editorData.getdelSaved(ctx, docId); requestRes = (null == savedVal || '1' === savedVal); } if (replyData && commonDefines.c_oAscServerCommandErrors.NoError != replyData.error) { - logger.warn('sendServerRequest returned an error: docId = %s; data = %s', docId, replyStr); + ctx.logger.warn('sendServerRequest returned an error: data = %s', replyStr); } if (requestRes) { updateIfTask = undefined; - yield docsCoServer.cleanDocumentOnExitPromise(docId, true, callbackUserIndex); + yield docsCoServer.cleanDocumentOnExitPromise(ctx, docId, true, callbackUserIndex); if (isOpenFromForgotten) { //remove forgotten file in cache - yield cleanupCache(docId); + yield cleanupCache(ctx); } } else { storeForgotten = true; @@ -1091,67 +1101,68 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) { } } } else { - logger.warn('Empty Callback=%s or baseUrl=%s or userLastChangeId=%s commandSfcCallback: docId = %s', uri, baseUrl, userLastChangeId, docId); + ctx.logger.warn('Empty Callback=%s or baseUrl=%s or userLastChangeId=%s commandSfcCallback', uri, baseUrl, userLastChangeId); storeForgotten = true; } if (undefined !== updateIfTask && !isSfcm) { - logger.debug('commandSfcCallback restore %d status: docId = %s', recoverTask.status, docId); + ctx.logger.debug('commandSfcCallback restore %d status', recoverTask.status); updateIfTask.status = recoverTask.status; updateIfTask.statusInfo = recoverTask.statusInfo; - updateIfRes = yield taskResult.updateIf(updateIfTask, updateMask); + updateIfRes = yield taskResult.updateIf(ctx, updateIfTask, updateMask); if (updateIfRes.affectedRows > 0) { updateMask.status = updateIfTask.status; updateMask.statusInfo = updateIfTask.statusInfo; } else { - logger.debug('commandSfcCallback restore %d status failed: docId = %s', recoverTask.status, docId); + ctx.logger.debug('commandSfcCallback restore %d status failed', recoverTask.status); } } if (storeForgotten && !needRetry && !isEncrypted && (!isError || isErrorCorrupted)) { try { - logger.warn("storeForgotten: docId = %s", docId); + ctx.logger.warn("storeForgotten"); let forgottenName = cfgForgottenFilesName + pathModule.extname(cmd.getOutputPath()); - yield storage.copyObject(savePathDoc, cfgForgottenFiles + '/' + docId + '/' + forgottenName); + yield storage.copyObject(ctx, savePathDoc, cfgForgottenFiles + '/' + docId + '/' + forgottenName); } catch (err) { - logger.error('Error storeForgotten: docId = %s\r\n%s', docId, err.stack); + ctx.logger.error('Error storeForgotten: %s', err.stack); } if (!isSfcm) { //todo simultaneous opening //to unlock wopi file - yield docsCoServer.unlockWopiDoc(docId, callbackUserIndex); + yield docsCoServer.unlockWopiDoc(ctx, docId, callbackUserIndex); //cleanupRes can be false in case of simultaneous opening. it is OK - let cleanupRes = yield cleanupCacheIf(updateMask); - logger.debug('storeForgotten cleanupRes=%s: docId = %s', cleanupRes, docId); + let cleanupRes = yield cleanupCacheIf(ctx, updateMask); + ctx.logger.debug('storeForgotten cleanupRes=%s', cleanupRes); } } if (forceSave) { - yield* docsCoServer.setForceSave(docId, forceSave, cmd, isSfcmSuccess && !isError); + yield* docsCoServer.setForceSave(ctx, docId, forceSave, cmd, isSfcmSuccess && !isError); } if (needRetry) { let attempt = cmd.getAttempt() || 0; cmd.setAttempt(attempt + 1); let queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); queueData.setCmd(cmd); let timeout = retry.createTimeout(attempt, cfgCallbackBackoffOptions.timeout); - logger.debug('commandSfcCallback backoff timeout = %d : docId = %s', timeout, docId); + ctx.logger.debug('commandSfcCallback backoff timeout = %d', timeout); yield* docsCoServer.addDelayed(queueData, timeout); } } else { - logger.debug('commandSfcCallback cleanDocumentOnExitNoChangesPromise: docId = %s', docId); - yield docsCoServer.cleanDocumentOnExitNoChangesPromise(docId, undefined, userLastChangeIndex, true); + ctx.logger.debug('commandSfcCallback cleanDocumentOnExitNoChangesPromise'); + yield docsCoServer.cleanDocumentOnExitNoChangesPromise(ctx, docId, undefined, userLastChangeIndex, true); } if ((docsCoServer.getIsShutdown() && !isSfcm) || cmd.getRedisKey()) { let keyRedis = cmd.getRedisKey() ? cmd.getRedisKey() : redisKeyShutdown; yield docsCoServer.editorData.removeShutdown(keyRedis, docId); } - logger.debug('End commandSfcCallback: docId = %s', docId); + ctx.logger.debug('End commandSfcCallback'); return replyStr; } -function* processWopiPutFile(docId, wopiParams, savePathDoc, userLastChangeId, isModifiedByUser, isAutosave, isExitSave) { +function* processWopiPutFile(ctx, docId, wopiParams, savePathDoc, userLastChangeId, isModifiedByUser, isAutosave, isExitSave) { let res = '{"error": 1}'; - let metadata = yield storage.headObject(savePathDoc); - let streamObj = yield storage.createReadStream(savePathDoc); - let postRes = yield wopiClient.putFile(wopiParams, null, streamObj.readStream, metadata.ContentLength, userLastChangeId, isModifiedByUser, isAutosave, isExitSave); + let metadata = yield storage.headObject(ctx, savePathDoc); + let streamObj = yield storage.createReadStream(ctx, savePathDoc); + let postRes = yield wopiClient.putFile(ctx, wopiParams, null, streamObj.readStream, metadata.ContentLength, userLastChangeId, isModifiedByUser, isAutosave, isExitSave); if (postRes) { if (postRes.body) { try { @@ -1159,19 +1170,19 @@ function* processWopiPutFile(docId, wopiParams, savePathDoc, userLastChangeId, i //collabora nexcloud connector if (body.LastModifiedTime) { let lastModifiedTimeInfo = wopiClient.getWopiModifiedMarker(wopiParams, body.LastModifiedTime); - yield commandOpenStartPromise(docId, undefined, true, lastModifiedTimeInfo); + yield commandOpenStartPromise(ctx, docId, undefined, true, lastModifiedTimeInfo); } } catch (e) { - logger.debug('processWopiPutFile error: docId = %s %s', docId, e.stack); + ctx.logger.debug('processWopiPutFile error: %s', e.stack); } } res = '{"error": 0}'; } return res; } -function* commandSendMMCallback(cmd) { +function* commandSendMMCallback(ctx, cmd) { var docId = cmd.getDocId(); - logger.debug('Start commandSendMMCallback: docId = %s', docId); + ctx.logger.debug('Start commandSendMMCallback'); var saveKey = cmd.getSaveKey(); var statusInfo = cmd.getStatusInfo(); var outputSfc = new commonDefines.OutputSfcData(docId); @@ -1184,7 +1195,7 @@ function* commandSendMMCallback(cmd) { var outputMailMerge = new commonDefines.OutputMailMerge(mailMergeSendData); outputSfc.setMailMerge(outputMailMerge); outputSfc.setUsers([mailMergeSendData.getUserId()]); - var data = yield storage.getObject(saveKey + '/' + cmd.getOutputPath()); + var data = yield storage.getObject(ctx, saveKey + '/' + cmd.getOutputPath()); var xml = data.toString('utf8'); var files = xml.match(/[< ]file.*?\/>/g); var recordRemain = (mailMergeSendData.getRecordTo() - mailMergeSendData.getRecordFrom() + 1); @@ -1195,19 +1206,19 @@ function* commandSendMMCallback(cmd) { outputMailMerge.setTo(fieldRes[1]); outputMailMerge.setRecordIndex(recordIndexStart + i); var pathRes = /path=["'](.*?)["']/.exec(file); - var signedUrl = yield storage.getSignedUrl(mailMergeSendData.getBaseUrl(), saveKey + '/' + pathRes[1], + var signedUrl = yield storage.getSignedUrl(ctx, mailMergeSendData.getBaseUrl(), saveKey + '/' + pathRes[1], commonDefines.c_oAscUrlTypes.Temporary); outputSfc.setUrl(signedUrl); outputSfc.setExtName(pathModule.extname(pathRes[1])); var uri = mailMergeSendData.getUrl(); var replyStr = null; try { - replyStr = yield* docsCoServer.sendServerRequest(docId, uri, outputSfc); + replyStr = yield* docsCoServer.sendServerRequest(ctx, uri, outputSfc); } catch (err) { replyStr = null; - logger.error('sendServerRequest error: docId = %s;url = %s;data = %j\r\n%s', docId, uri, outputSfc, err.stack); + ctx.logger.error('sendServerRequest error: url = %s;data = %j %s', uri, outputSfc, err.stack); } - var replyData = docsCoServer.parseReplyData(docId, replyStr); + var replyData = docsCoServer.parseReplyData(ctx, replyStr); if (!(replyData && commonDefines.c_oAscServerCommandErrors.NoError == replyData.error)) { var recordErrorCount = mailMergeSendData.getRecordErrorCount(); recordErrorCount++; @@ -1215,54 +1226,53 @@ function* commandSendMMCallback(cmd) { mailMergeSendData.setRecordErrorCount(recordErrorCount); } if (replyData && commonDefines.c_oAscServerCommandErrors.NoError != replyData.error) { - logger.warn('sendServerRequest returned an error: docId = %s; data = %s', docId, replyStr); + ctx.logger.warn('sendServerRequest returned an error: data = %s', docId, replyStr); } } var newRecordFrom = mailMergeSendData.getRecordFrom() + Math.max(files.length, 1); if (newRecordFrom <= mailMergeSendData.getRecordTo()) { mailMergeSendData.setRecordFrom(newRecordFrom); - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); + yield* addRandomKeyTaskCmd(ctx, cmd); + var queueData = getSaveTask(ctx, cmd); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); } else { - logger.debug('End MailMerge: docId = %s', docId); + ctx.logger.debug('End MailMerge'); } - logger.debug('End commandSendMMCallback: docId = %s', docId); + ctx.logger.debug('End commandSendMMCallback'); } -exports.openDocument = function(conn, cmd, opt_upsertRes, opt_bIsRestore) { +exports.openDocument = function(ctx, conn, cmd, opt_upsertRes, opt_bIsRestore) { return co(function* () { var outputData; - var docId = conn ? conn.docId : 'null'; try { var startDate = null; if(clientStatsD) { startDate = new Date(); } - logger.debug('Start command: docId = %s %s', docId, JSON.stringify(cmd)); + ctx.logger.debug('Start command: %s', JSON.stringify(cmd)); outputData = new OutputData(cmd.getCommand()); let res = true; switch (cmd.getCommand()) { case 'open': - yield* commandOpen(conn, cmd, outputData, opt_upsertRes, opt_bIsRestore); + yield* commandOpen(ctx, conn, cmd, outputData, opt_upsertRes, opt_bIsRestore); break; case 'reopen': - res = yield* commandReopen(conn, cmd, outputData); + res = yield* commandReopen(ctx, conn, cmd, outputData); break; case 'imgurls': - yield* commandImgurls(conn, cmd, outputData); + yield* commandImgurls(ctx, conn, cmd, outputData); break; case 'pathurl': - yield* commandPathUrl(conn, cmd, outputData); + yield* commandPathUrl(ctx, conn, cmd, outputData); break; case 'pathurls': - yield* commandPathUrls(conn, cmd, outputData); + yield* commandPathUrls(ctx, conn, cmd, outputData); break; case 'setpassword': - yield* commandSetPassword(conn, cmd, outputData); + yield* commandSetPassword(ctx, conn, cmd, outputData); break; case 'changedocinfo': - yield* commandChangeDocInfo(conn, cmd, outputData); + yield* commandChangeDocInfo(ctx, conn, cmd, outputData); break; default: res = false; @@ -1277,7 +1287,7 @@ exports.openDocument = function(conn, cmd, opt_upsertRes, opt_bIsRestore) { } } catch (e) { - logger.error('Error openDocument: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error openDocument: %s', e.stack); if (!outputData) { outputData = new OutputData(); } @@ -1286,40 +1296,43 @@ exports.openDocument = function(conn, cmd, opt_upsertRes, opt_bIsRestore) { } finally { if (outputData && outputData.getStatus()) { - logger.debug('Response command: docId = %s %s', docId, JSON.stringify(outputData)); - docsCoServer.sendData(conn, new OutputDataWrap('documentOpen', outputData)); + ctx.logger.debug('Response command: %s', JSON.stringify(outputData)); + docsCoServer.sendData(ctx, conn, new OutputDataWrap('documentOpen', outputData)); } - logger.debug('End command: docId = %s', docId); + ctx.logger.debug('End command'); } }); }; exports.downloadAs = function(req, res) { return co(function* () { var docId = 'null'; + let ctx = new operationContext.Context(); try { var startDate = null; if(clientStatsD) { startDate = new Date(); } + ctx.initFromRequest(req); var strCmd = req.query['cmd']; var cmd = new commonDefines.InputCommand(JSON.parse(strCmd)); docId = cmd.getDocId(); - logger.debug('Start downloadAs: docId = %s %s', docId, strCmd); + ctx.setDocId(docId); + ctx.logger.debug('Start downloadAs: %s', strCmd); if (cfgTokenEnableBrowser) { var isValidJwt = false; if (cmd.getTokenDownload()) { - let checkJwtRes = docsCoServer.checkJwt(docId, cmd.getTokenDownload(), commonDefines.c_oAscSecretType.Browser); + let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenDownload(), commonDefines.c_oAscSecretType.Browser); if (checkJwtRes.decoded) { isValidJwt = true; cmd.setFormat(checkJwtRes.decoded.fileType); cmd.setUrl(checkJwtRes.decoded.url); cmd.setWithAuthorization(true); } else { - logger.warn('Error downloadAs jwt: docId = %s\r\n%s', docId, checkJwtRes.description); + ctx.logger.warn('Error downloadAs jwt: %s', checkJwtRes.description); } } else { - let checkJwtRes = docsCoServer.checkJwt(docId, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session); + let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session); if (checkJwtRes.decoded) { let decoded = checkJwtRes.decoded; var doc = checkJwtRes.decoded.document; @@ -1329,10 +1342,10 @@ exports.downloadAs = function(req, res) { cmd.setDocId(doc.key); cmd.setUserIndex(decoded.editorConfig && decoded.editorConfig.user && decoded.editorConfig.user.index); } else { - logger.warn('Error downloadAs jwt: docId = %s\r\n%s', docId, 'access deny'); + ctx.logger.warn('Error downloadAs jwt: %s', 'access deny'); } } else { - logger.warn('Error downloadAs jwt: docId = %s\r\n%s', docId, checkJwtRes.description); + ctx.logger.warn('Error downloadAs jwt: %s', checkJwtRes.description); } } if (!isValidJwt) { @@ -1340,25 +1353,26 @@ exports.downloadAs = function(req, res) { return; } } - var selectRes = yield taskResult.select(docId); + ctx.setDocId(docId); + var selectRes = yield taskResult.select(ctx, docId); var row = selectRes.length > 0 ? selectRes[0] : null; if (!cmd.getWithoutPassword()) { - addPasswordToCmd(cmd, row && row.password); + addPasswordToCmd(ctx, cmd, row && row.password); } cmd.setData(req.body); var outputData = new OutputData(cmd.getCommand()); switch (cmd.getCommand()) { case 'save': - yield* commandSave(cmd, outputData); + yield* commandSave(ctx, cmd, outputData); break; case 'savefromorigin': - yield* commandSaveFromOrigin(cmd, outputData, row && row.password); + yield* commandSaveFromOrigin(ctx, cmd, outputData, row && row.password); break; case 'sendmm': - yield* commandSendMailMerge(cmd, outputData); + yield* commandSendMailMerge(ctx, cmd, outputData); break; case 'sfct': - yield* commandSfct(cmd, outputData); + yield* commandSfct(ctx, cmd, outputData); break; default: outputData.setStatus('err'); @@ -1368,13 +1382,13 @@ exports.downloadAs = function(req, res) { var strRes = JSON.stringify(outputData); res.setHeader('Content-Type', 'application/json'); res.send(strRes); - logger.debug('End downloadAs: docId = %s %s', docId, strRes); + ctx.logger.debug('End downloadAs: %s', strRes); if(clientStatsD) { clientStatsD.timing('coauth.downloadAs.' + cmd.getCommand(), new Date() - startDate); } } catch (e) { - logger.error('Error downloadAs: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error downloadAs: %s', e.stack); res.sendStatus(400); } }); @@ -1382,20 +1396,22 @@ exports.downloadAs = function(req, res) { exports.saveFile = function(req, res) { return co(function*() { let docId = 'null'; + let ctx = new operationContext.Context(); try { let startDate = null; if (clientStatsD) { startDate = new Date(); } - + ctx.initFromRequest(req); let strCmd = req.query['cmd']; let cmd = new commonDefines.InputCommand(JSON.parse(strCmd)); docId = cmd.getDocId(); - logger.debug('Start saveFile: docId = %s', docId); + ctx.setDocId(docId); + ctx.logger.debug('Start saveFile'); if (cfgTokenEnableBrowser) { let isValidJwt = false; - let checkJwtRes = docsCoServer.checkJwt(docId, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session); + let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session); if (checkJwtRes.decoded) { let doc = checkJwtRes.decoded.document; var edit = checkJwtRes.decoded.editorConfig; @@ -1404,33 +1420,34 @@ exports.saveFile = function(req, res) { docId = doc.key; cmd.setDocId(doc.key); } else { - logger.warn('Error saveFile jwt: docId = %s\r\n%s', docId, 'access deny'); + ctx.logger.warn('Error saveFile jwt: %s', 'access deny'); } } else { - logger.warn('Error saveFile jwt: docId = %s\r\n%s', docId, checkJwtRes.description); + ctx.logger.warn('Error saveFile jwt: %s', checkJwtRes.description); } if (!isValidJwt) { res.sendStatus(403); return; } } + ctx.setDocId(docId); cmd.setStatusInfo(constants.NO_ERROR); - yield* addRandomKeyTaskCmd(cmd); + yield* addRandomKeyTaskCmd(ctx, cmd); cmd.setOutputPath(constants.OUTPUT_NAME + pathModule.extname(cmd.getOutputPath())); - yield storage.putObject(cmd.getSaveKey() + '/' + cmd.getOutputPath(), req.body, req.body.length); - let replyStr = yield* commandSfcCallback(cmd, false, true); + yield storage.putObject(ctx, cmd.getSaveKey() + '/' + cmd.getOutputPath(), req.body, req.body.length); + let replyStr = yield* commandSfcCallback(ctx, cmd, false, true); if (replyStr) { utils.fillResponseSimple(res, replyStr, 'application/json'); } else { res.sendStatus(400); } - logger.debug('End saveFile: docId = %s %s', docId, replyStr); + ctx.logger.debug('End saveFile: %s', replyStr); if (clientStatsD) { clientStatsD.timing('coauth.saveFile', new Date() - startDate); } } catch (e) { - logger.error('Error saveFile: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error saveFile: %s', e.stack); res.sendStatus(400); } }); @@ -1451,34 +1468,36 @@ exports.getPrintFileUrl = getPrintFileUrl; exports.printFile = function(req, res) { return co(function*() { let docId = 'null'; + let ctx = new operationContext.Context(); try { let startDate = null; if (clientStatsD) { startDate = new Date(); } - + ctx.initFromRequest(req); let filename = req.query['filename']; let token = req.query['token']; docId = req.params.docid; - logger.info('Start printFile: docId = %s', docId); + ctx.setDocId(docId); + ctx.logger.info('Start printFile'); if (cfgTokenEnableBrowser) { - let checkJwtRes = docsCoServer.checkJwt(docId, token, commonDefines.c_oAscSecretType.Session); + let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session); if (checkJwtRes.decoded) { let docIdBase = checkJwtRes.decoded.document.key; if (!docId.startsWith(docIdBase)) { - logger.warn('Error printFile jwt: docId = %s description = %s', docId, 'access deny'); + ctx.logger.warn('Error printFile jwt: description = %s', 'access deny'); res.sendStatus(403); return; } } else { - logger.warn('Error printFile jwt: docId = %s description = %s', docId, checkJwtRes.description); + ctx.logger.warn('Error printFile jwt: description = %s', checkJwtRes.description); res.sendStatus(403); return; } } - - let streamObj = yield storage.createReadStream(`${docId}/${constants.OUTPUT_NAME}.pdf`); + ctx.setDocId(docId); + let streamObj = yield storage.createReadStream(ctx, `${docId}/${constants.OUTPUT_NAME}.pdf`); res.setHeader('Content-Disposition', utils.getContentDisposition(filename, null, constants.CONTENT_DISPOSITION_INLINE)); res.setHeader('Content-Length', streamObj.contentLength); res.setHeader('Content-Type', 'application/pdf'); @@ -1489,30 +1508,30 @@ exports.printFile = function(req, res) { } } catch (e) { - logger.error('Error printFile: docId = %s %s', docId, e.stack); + ctx.logger.error('Error printFile: %s', e.stack); res.sendStatus(400); } finally { - logger.info('End printFile: docId = %s', docId); + ctx.logger.info('End printFile'); } }); }; exports.downloadFile = function(req, res) { return co(function*() { - let docId = 'null'; + let ctx = new operationContext.Context(); try { let startDate = null; if (clientStatsD) { startDate = new Date(); } - + ctx.initFromRequest(req); let url = req.get('x-url'); - docId = req.params.docid; - logger.info('Start downloadFile: docId = %s', docId); + ctx.setDocId(req.params.docid); + ctx.logger.info('Start downloadFile'); let authorization; if (cfgTokenEnableBrowser) { - let checkJwtRes = docsCoServer.checkJwtHeader(docId, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Browser); + let checkJwtRes = yield docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Browser); let errorDescription; if (checkJwtRes.decoded) { let decoded = checkJwtRes.decoded; @@ -1527,23 +1546,23 @@ exports.downloadFile = function(req, res) { errorDescription = checkJwtRes.description; } if (errorDescription) { - logger.warn('Error downloadFile jwt: docId = %s description = %s', docId, errorDescription); + ctx.logger.warn('Error downloadFile jwt: description = %s', errorDescription); res.sendStatus(403); return; } - if (utils.canIncludeOutboxAuthorization(url)) { - authorization = utils.fillJwtForRequest({url: url}, false); + if (utils.canIncludeOutboxAuthorization(ctx, url)) { + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox); + authorization = utils.fillJwtForRequest({url: url}, secret, false); } } - - yield utils.downloadUrlPromise(url, cfgDownloadTimeout, cfgDownloadMaxBytes, authorization, !authorization, null, res); + yield utils.downloadUrlPromise(ctx, url, cfgDownloadTimeout, cfgDownloadMaxBytes, authorization, !authorization, null, res); if (clientStatsD) { clientStatsD.timing('coauth.downloadFile', new Date() - startDate); } } catch (err) { - logger.error('Error downloadFile: docId = %s %s', docId, err.stack); + ctx.logger.error('Error downloadFile: %s', err.stack); if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') { res.sendStatus(408); } else if (err.code === 'EMSGSIZE') { @@ -1555,26 +1574,24 @@ exports.downloadFile = function(req, res) { } } finally { - logger.info('End downloadFile: docId = %s', docId); + ctx.logger.info('End downloadFile'); } }); }; -exports.saveFromChanges = function(docId, statusInfo, optFormat, opt_userId, opt_userIndex, opt_queue) { +exports.saveFromChanges = function(ctx, docId, statusInfo, optFormat, opt_userId, opt_userIndex, opt_queue) { return co(function* () { try { var startDate = null; if(clientStatsD) { startDate = new Date(); } - logger.debug('Start saveFromChanges: docId = %s', docId); - var task = new taskResult.TaskResultData(); - task.key = docId; + ctx.logger.debug('Start saveFromChanges'); //делаем select, потому что за время timeout информация могла измениться - var selectRes = yield taskResult.select(docId); + var selectRes = yield taskResult.select(ctx, docId); var row = selectRes.length > 0 ? selectRes[0] : null; if (row && row.status == taskResult.FileStatus.SaveVersion && row.status_info == statusInfo) { if (null == optFormat) { - optFormat = changeFormatByOrigin(docId, row, constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML); + optFormat = changeFormatByOrigin(ctx, row, constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML); } var cmd = new commonDefines.InputCommand(); cmd.setCommand('sfc'); @@ -1584,20 +1601,20 @@ exports.saveFromChanges = function(docId, statusInfo, optFormat, opt_userId, opt cmd.setUserActionId(opt_userId); cmd.setUserActionIndex(opt_userIndex); cmd.setJsonParams(getOpenedAtJSONParams(row)); - let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, row.callback); - cmd.setWopiParams(wopiClient.parseWopiCallback(docId, userAuthStr)); - addPasswordToCmd(cmd, row && row.password); - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + cmd.setWopiParams(wopiClient.parseWopiCallback(ctx, userAuthStr)); + addPasswordToCmd(ctx, cmd, row && row.password); + yield* addRandomKeyTaskCmd(ctx, cmd); + var queueData = getSaveTask(ctx, cmd); queueData.setFromChanges(true); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_NORMAL, opt_queue); if (docsCoServer.getIsShutdown()) { yield docsCoServer.editorData.addShutdown(redisKeyShutdown, docId); } - logger.debug('AddTask saveFromChanges: docId = %s', docId); + ctx.logger.debug('AddTask saveFromChanges'); } else { if (row) { - logger.debug('saveFromChanges status mismatch: docId = %s; row: %d; %d; expected: %d', docId, row.status, row.status_info, statusInfo); + ctx.logger.debug('saveFromChanges status mismatch: row: %d; %d; expected: %d', row.status, row.status_info, statusInfo); } } if (clientStatsD) { @@ -1605,43 +1622,43 @@ exports.saveFromChanges = function(docId, statusInfo, optFormat, opt_userId, opt } } catch (e) { - logger.error('Error saveFromChanges: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error saveFromChanges: %s', e.stack); } }); }; exports.receiveTask = function(data, ack) { return co(function* () { - var docId = 'null'; + let ctx = new operationContext.Context(); try { var task = new commonDefines.TaskQueueData(JSON.parse(data)); if (task) { var cmd = task.getCmd(); - docId = cmd.getDocId(); - logger.debug('Start receiveTask: docId = %s %s', docId, data); - var updateTask = yield* getUpdateResponse(cmd); - var updateRes = yield taskResult.update(updateTask); + ctx.initFromTaskQueueData(task); + ctx.logger.info('receiveTask start: %s', data); + var updateTask = yield* getUpdateResponse(ctx, cmd); + var updateRes = yield taskResult.update(ctx, updateTask); if (updateRes.affectedRows > 0) { var outputData = new OutputData(cmd.getCommand()); var command = cmd.getCommand(); var additionalOutput = {needUrlKey: null, needUrlMethod: null, needUrlType: null, needUrlIsCorrectPassword: undefined, creationDate: undefined, openedAt: undefined}; if ('open' == command || 'reopen' == command) { - yield getOutputData(cmd, outputData, cmd.getDocId(), null, additionalOutput); + yield getOutputData(ctx, cmd, outputData, cmd.getDocId(), null, additionalOutput); } else if ('save' == command || 'savefromorigin' == command || 'sfct' == command) { - yield getOutputData(cmd, outputData, cmd.getSaveKey(), null, additionalOutput); + yield getOutputData(ctx, cmd, outputData, cmd.getSaveKey(), null, additionalOutput); } else if ('sfcm' == command) { - yield* commandSfcCallback(cmd, true); + yield* commandSfcCallback(ctx, cmd, true); } else if ('sfc' == command) { - yield* commandSfcCallback(cmd, false); + yield* commandSfcCallback(ctx, cmd, false); } else if ('sendmm' == command) { - yield* commandSendMMCallback(cmd); + yield* commandSendMMCallback(ctx, cmd); } else if ('conv' == command) { //nothing } if (outputData.getStatus()) { - logger.debug('Send receiveTask: docId = %s %s', docId, JSON.stringify(outputData)); + ctx.logger.debug('receiveTask publish: %s', JSON.stringify(outputData)); var output = new OutputDataWrap('documentOpen', outputData); - yield* docsCoServer.publish({ - type: commonDefines.c_oPublishType.receiveTask, cmd: cmd, output: output, + yield* docsCoServer.publish(ctx, { + type: commonDefines.c_oPublishType.receiveTask, ctx: ctx, cmd: cmd, output: output, needUrlKey: additionalOutput.needUrlKey, needUrlMethod: additionalOutput.needUrlMethod, needUrlType: additionalOutput.needUrlType, @@ -1651,11 +1668,11 @@ exports.receiveTask = function(data, ack) { }); } } - logger.debug('End receiveTask: docId = %s', docId); } } catch (err) { - logger.debug('Error receiveTask: docId = %s\r\n%s', docId, err.stack); + ctx.logger.error('receiveTask error: %s', err.stack); } finally { + ctx.logger.info('receiveTask end'); ack(); } }); @@ -1663,7 +1680,6 @@ exports.receiveTask = function(data, ack) { exports.cleanupCache = cleanupCache; exports.cleanupCacheIf = cleanupCacheIf; -exports.getOutputData = getOutputData; exports.getOpenedAt = getOpenedAt; exports.commandSfctByCmd = commandSfctByCmd; exports.commandOpenStartPromise = commandOpenStartPromise; diff --git a/DocService/sources/converterservice.js b/DocService/sources/converterservice.js index dbe11a09..52d6b510 100644 --- a/DocService/sources/converterservice.js +++ b/DocService/sources/converterservice.js @@ -37,7 +37,6 @@ var config = require('config'); var co = require('co'); const locale = require('windows-locale'); var taskResult = require('./taskresult'); -var logger = require('./../../Common/sources/logger'); var utils = require('./../../Common/sources/utils'); var constants = require('./../../Common/sources/constants'); var commonDefines = require('./../../Common/sources/commondefines'); @@ -47,18 +46,19 @@ var storage = require('./../../Common/sources/storage-base'); var formatChecker = require('./../../Common/sources/formatchecker'); var statsDClient = require('./../../Common/sources/statsdclient'); var storageBase = require('./../../Common/sources/storage-base'); +var operationContext = require('./../../Common/sources/operationContext'); const sqlBase = require('./baseConnector'); var CONVERT_ASYNC_DELAY = 1000; var clientStatsD = statsDClient.getClient(); -function* getConvertStatus(cmd, selectRes, opt_checkPassword) { +function* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword) { var status = new commonDefines.ConvertStatus(constants.NO_ERROR); if (selectRes.length > 0) { var docId = cmd.getDocId(); var row = selectRes[0]; - let password = opt_checkPassword && sqlBase.DocumentPassword.prototype.getCurPassword(docId, row.password); + let password = opt_checkPassword && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password); switch (row.status) { case taskResult.FileStatus.Ok: if (password) { @@ -70,10 +70,10 @@ function* getConvertStatus(cmd, selectRes, opt_checkPassword) { isCorrectPassword = decryptedPassword === userPassword; } if (isCorrectPassword) { - logger.debug("getConvertStatus password match: docId = %s", docId); + ctx.logger.debug("getConvertStatus password match"); status.end = true; } else { - logger.debug("getConvertStatus password mismatch: docId = %s", docId); + ctx.logger.debug("getConvertStatus password mismatch"); status.err = constants.CONVERT_PASSWORD; } } else { @@ -85,7 +85,7 @@ function* getConvertStatus(cmd, selectRes, opt_checkPassword) { case taskResult.FileStatus.NeedPassword: status.err = row.status_info; if (taskResult.FileStatus.ErrToReload == row.status || taskResult.FileStatus.NeedPassword == row.status) { - yield canvasService.cleanupCache(docId); + yield canvasService.cleanupCache(ctx); } break; case taskResult.FileStatus.NeedParams: @@ -103,9 +103,9 @@ function* getConvertStatus(cmd, selectRes, opt_checkPassword) { } return status; } -function* getConvertPath(docId, fileTo, formatTo) { +function* getConvertPath(ctx, docId, fileTo, formatTo) { if (constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML === formatTo || constants.AVS_OFFICESTUDIO_FILE_OTHER_ODF === formatTo) { - let list = yield storage.listObjects(docId); + let list = yield storage.listObjects(ctx, docId); let baseName = path.basename(fileTo, path.extname(fileTo)); for (let i = 0; i < list.length; ++i) { if (path.basename(list[i], path.extname(list[i])) === baseName) { @@ -115,28 +115,29 @@ function* getConvertPath(docId, fileTo, formatTo) { } return docId + '/' + fileTo; } -function* getConvertUrl(baseUrl, fileToPath, title) { +function* getConvertUrl(ctx, baseUrl, fileToPath, title) { if (title) { title = path.basename(title, path.extname(title)) + path.extname(fileToPath); } - return yield storage.getSignedUrl(baseUrl, fileToPath, commonDefines.c_oAscUrlTypes.Temporary, title); + return yield storage.getSignedUrl(ctx, baseUrl, fileToPath, commonDefines.c_oAscUrlTypes.Temporary, title); } -function* convertByCmd(cmd, async, opt_fileTo, opt_taskExist, opt_priority, opt_expiration, opt_queue, opt_checkPassword) { +function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority, opt_expiration, opt_queue, opt_checkPassword) { var docId = cmd.getDocId(); var startDate = null; if (clientStatsD) { startDate = new Date(); } - logger.debug('Start convert request docId = %s', docId); + ctx.logger.debug('Start convert request'); let bCreate = false; if (!opt_taskExist) { let task = new taskResult.TaskResultData(); + task.tenant = ctx.tenant; task.key = docId; task.status = taskResult.FileStatus.WaitQueue; task.statusInfo = constants.NO_ERROR; - let upsertRes = yield taskResult.upsert(task); + let upsertRes = yield taskResult.upsert(ctx, task); //if CLIENT_FOUND_ROWS don't specify 1 row is inserted , 2 row is updated, and 0 row is set to its current values //http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html bCreate = upsertRes.affectedRows == 1; @@ -144,10 +145,11 @@ function* convertByCmd(cmd, async, opt_fileTo, opt_taskExist, opt_priority, opt_ var selectRes; var status; if (!bCreate) { - selectRes = yield taskResult.select(docId); - status = yield* getConvertStatus(cmd, selectRes, opt_checkPassword); + selectRes = yield taskResult.select(ctx, docId); + status = yield* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword); } else { var queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); queueData.setCmd(cmd); if (opt_fileTo) { queueData.setToFile(opt_fileTo); @@ -165,22 +167,22 @@ function* convertByCmd(cmd, async, opt_fileTo, opt_taskExist, opt_priority, opt_ break; } yield utils.sleep(CONVERT_ASYNC_DELAY); - selectRes = yield taskResult.select(docId); - status = yield* getConvertStatus(cmd, selectRes, opt_checkPassword); + selectRes = yield taskResult.select(ctx, docId); + status = yield* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword); waitTime += CONVERT_ASYNC_DELAY; if (waitTime > utils.CONVERTION_TIMEOUT) { status.err = constants.CONVERT_TIMEOUT; } } } - logger.debug('End convert request end %s status %s docId = %s', status.end, status.err, docId); + ctx.logger.debug('End convert request end %s status %s', status.end, status.err); if (clientStatsD) { clientStatsD.timing('coauth.convertservice', new Date() - startDate); } return status; } -function* convertFromChanges(docId, baseUrl, forceSave, externalChangeInfo, opt_userdata, opt_userConnectionId, +let convertFromChanges = co.wrap(function*(ctx, 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'); @@ -204,50 +206,56 @@ function* convertFromChanges(docId, baseUrl, forceSave, externalChangeInfo, opt_ cmd.setRedisKey(opt_redisKey); } - yield* canvasService.commandSfctByCmd(cmd, opt_priority, opt_expiration, opt_queue); + yield canvasService.commandSfctByCmd(ctx, cmd, opt_priority, opt_expiration, opt_queue); var fileTo = constants.OUTPUT_NAME; let outputExt = formatChecker.getStringFromFormat(cmd.getOutputFormat()); if (outputExt) { fileTo += '.' + outputExt; } - let status = yield* convertByCmd(cmd, true, fileTo, undefined, opt_priority, opt_expiration, opt_queue); + let status = yield* convertByCmd(ctx, cmd, true, fileTo, undefined, opt_priority, opt_expiration, opt_queue); if (status.end) { - let fileToPath = yield* getConvertPath(docId, fileTo, cmd.getOutputFormat()); + let fileToPath = yield* getConvertPath(ctx, docId, fileTo, cmd.getOutputFormat()); status.setExtName(path.extname(fileToPath)); - status.setUrl(yield* getConvertUrl(baseUrl, fileToPath, cmd.getTitle())); + status.setUrl(yield* getConvertUrl(ctx, baseUrl, fileToPath, cmd.getTitle())); } return status; -} +}); function parseIntParam(val){ return (typeof val === 'string') ? parseInt(val) : val; } function convertRequest(req, res, isJson) { return co(function* () { - var docId = 'convertRequest'; + let ctx = new operationContext.Context(); try { + ctx.initFromRequest(req); + ctx.logger.info('convertRequest start'); let params; - let authRes = docsCoServer.getRequestParams(docId, req); + let authRes = yield docsCoServer.getRequestParams(ctx, req); if(authRes.code === constants.NO_ERROR){ params = authRes.params; } else { + ctx.logger.warn('convertRequest auth failed %j', authRes); utils.fillResponse(req, res, new commonDefines.ConvertStatus(authRes.code), isJson); return; } + let outputtype = params.outputtype || ''; + let docId = 'conv_' + params.key + '_' + outputtype; + ctx.setDocId(docId); + if (params.key && !constants.DOC_ID_REGEX.test(params.key)) { - logger.warn('convertRequest unexpected key = %s: docId = %s', params.key, docId); + ctx.logger.warn('convertRequest unexpected key = %s', params.key); utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); return; } if (params.filetype && !constants.EXTENTION_REGEX.test(params.filetype)) { - logger.warn('convertRequest unexpected filetype = %s: docId = %s', params.filetype, docId); + ctx.logger.warn('convertRequest unexpected filetype = %s', params.filetype); utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); return; } - let outputtype = params.outputtype || ''; let outputFormat = formatChecker.getFormatFromString(outputtype); if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) { - logger.warn('convertRequest unexpected outputtype = %s: docId = %s', outputtype, docId); + ctx.logger.warn('convertRequest unexpected outputtype = %s', outputtype); utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); return; } @@ -256,7 +264,6 @@ function convertRequest(req, res, isJson) { cmd.setUrl(params.url); cmd.setEmbeddedFonts(false);//params.embeddedfonts']; cmd.setFormat(params.filetype); - docId = 'conv_' + params.key + '_' + outputtype; cmd.setDocId(docId); cmd.setOutputFormat(outputFormat); @@ -275,7 +282,7 @@ function convertRequest(req, res, isJson) { } if (params.password) { if (params.password.length > constants.PASSWORD_MAX_LENGTH) { - logger.warn('convertRequest password too long actual = %s; max = %s;docId = %s', params.password.length, constants.PASSWORD_MAX_LENGTH, docId); + ctx.logger.warn('convertRequest password too long actual = %s; max = %s', params.password.length, constants.PASSWORD_MAX_LENGTH); utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); return; } @@ -340,23 +347,24 @@ function convertRequest(req, res, isJson) { if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== cmd.getOutputFormat()) { let fileTo = constants.OUTPUT_NAME + '.' + outputExt; - var status = yield* convertByCmd(cmd, async, fileTo, undefined, undefined, undefined, undefined, true); + var status = yield* convertByCmd(ctx, cmd, async, fileTo, undefined, undefined, undefined, undefined, true); if (status.end) { - let fileToPath = yield* getConvertPath(docId, fileTo, cmd.getOutputFormat()); + let fileToPath = yield* getConvertPath(ctx, docId, fileTo, cmd.getOutputFormat()); status.setExtName(path.extname(fileToPath)); - status.setUrl(yield* getConvertUrl(utils.getBaseUrlByRequest(req), fileToPath, cmd.getTitle())); - logger.debug('convertRequest: url = %s docId = %s', status.url, docId); + status.setUrl(yield* getConvertUrl(ctx, utils.getBaseUrlByRequest(req), fileToPath, cmd.getTitle())); + ctx.logger.debug('convertRequest: url = %s', status.url); } utils.fillResponse(req, res, status, isJson); } else { var addresses = utils.forwarded(req); - logger.warn('Error convert unknown outputtype: query = %j from = %s docId = %s', params, addresses, docId); + ctx.logger.warn('Error convert unknown outputtype: query = %j from = %s', params, addresses); utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson); } - } - catch (e) { - logger.error('Error convert: docId = %s\r\n%s', docId, e.stack); + } catch (e) { + ctx.logger.error('convertRequest error: %s', e.stack); utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson); + } finally { + ctx.logger.info('convertRequest end'); } }); } @@ -369,59 +377,65 @@ function convertRequestXml(req, res) { function builderRequest(req, res) { return co(function* () { - let docId = 'builderRequest'; + let ctx = new operationContext.Context(); try { + ctx.initFromRequest(req); + ctx.logger.info('builderRequest start'); let authRes; if (!utils.isEmptyObject(req.query)) { //todo this is a stub for compatibility. remove in future version - authRes = docsCoServer.getRequestParams(docId, req, true); + authRes = yield docsCoServer.getRequestParams(ctx, req, true); } else { - authRes = docsCoServer.getRequestParams(docId, req); + authRes = yield docsCoServer.getRequestParams(ctx, req); } - let params = authRes.params; + let docId = params.key; + ctx.setDocId(docId); + let error = authRes.code; let urls; let end = false; if (error === constants.NO_ERROR && (params.key || params.url || (req.body && Buffer.isBuffer(req.body) && req.body.length > 0))) { - docId = params.key; let cmd = new commonDefines.InputCommand(); cmd.setCommand('builder'); cmd.setIsBuilder(true); cmd.setWithAuthorization(true); cmd.setDocId(docId); if (!docId) { - let task = yield* taskResult.addRandomKeyTask(undefined, 'bld_', 8); + let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'bld_', 8); docId = task.key; cmd.setDocId(docId); if (params.url) { cmd.setUrl(params.url); cmd.setFormat('docbuilder'); } else { - yield storageBase.putObject(docId + '/script.docbuilder', req.body, req.body.length); + yield storageBase.putObject(ctx, docId + '/script.docbuilder', req.body, req.body.length); } let queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); queueData.setCmd(cmd); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); } let async = (typeof params.async === 'string') ? 'true' === params.async : params.async; - let status = yield* convertByCmd(cmd, async, utils.getBaseUrlByRequest(req), undefined, true); + let status = yield* convertByCmd(ctx, cmd, async, utils.getBaseUrlByRequest(req), undefined, true); end = status.end; error = status.err; if (end) { - urls = yield storageBase.getSignedUrls(utils.getBaseUrlByRequest(req), docId + '/output', + urls = yield storageBase.getSignedUrls(ctx, utils.getBaseUrlByRequest(req), docId + '/output', commonDefines.c_oAscUrlTypes.Temporary); } } else if (error === constants.NO_ERROR) { error = constants.UNKNOWN; } - logger.debug('End builderRequest request: docId = %s urls = %j end = %s error = %s', docId, urls, end, error); + ctx.logger.debug('End builderRequest request: urls = %j end = %s error = %s', urls, end, error); utils.fillResponseBuilder(res, docId, urls, end, error); } catch (e) { - logger.error('Error builderRequest: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error builderRequest: %s', e.stack); utils.fillResponseBuilder(res, undefined, undefined, undefined, constants.UNKNOWN); + } finally { + ctx.logger.info('builderRequest end'); } }); } diff --git a/DocService/sources/editorDataMemory.js b/DocService/sources/editorDataMemory.js index 9ef9cab6..8e673648 100644 --- a/DocService/sources/editorDataMemory.js +++ b/DocService/sources/editorDataMemory.js @@ -35,6 +35,7 @@ const config = require('config'); const ms = require('ms'); const utils = require('./../../Common/sources/utils'); const commonDefines = require('./../../Common/sources/commondefines'); +const tenantManager = require('./../../Common/sources/tenantManager'); const cfgExpMonthUniqueUsers = ms(config.get('services.CoAuthoring.expire.monthUniqueUsers')); @@ -46,18 +47,22 @@ function EditorData() { this.uniqueViewUser = {}; this.uniqueViewUsersOfMonth = {}; this.shutdown = {}; - this.stat = []; + this.stat = {}; } -EditorData.prototype._getDocumentData = function(docId) { - let options = this.data[docId]; +EditorData.prototype._getDocumentData = function(ctx, docId) { + let tenantData = this.data[ctx.tenant]; + if (!tenantData) { + this.data[ctx.tenant] = tenantData = {}; + } + let options = tenantData[docId]; if (!options) { - this.data[docId] = options = {}; + tenantData[docId] = options = {}; } return options; }; -EditorData.prototype._checkAndLock = function(name, docId, fencingToken, ttl) { - let data = this._getDocumentData(docId); +EditorData.prototype._checkAndLock = function(ctx, name, docId, fencingToken, ttl) { + let data = this._getDocumentData(ctx, docId); const now = Date.now(); let res = true; if (data[name] && now < data[name].expireAt && fencingToken !== data[name].fencingToken) { @@ -68,8 +73,8 @@ EditorData.prototype._checkAndLock = function(name, docId, fencingToken, ttl) { } return Promise.resolve(res); }; -EditorData.prototype._checkAndUnlock = function(name, docId, fencingToken) { - let data = this._getDocumentData(docId); +EditorData.prototype._checkAndUnlock = function(ctx, name, docId, fencingToken) { + let data = this._getDocumentData(ctx, docId); const now = Date.now(); let res; if (data[name] && now < data[name].expireAt) { @@ -86,100 +91,101 @@ EditorData.prototype._checkAndUnlock = function(name, docId, fencingToken) { return Promise.resolve(res); }; -EditorData.prototype.addPresence = function(docId, userId, userInfo) { +EditorData.prototype.addPresence = function(ctx, docId, userId, userInfo) { return Promise.resolve(); }; -EditorData.prototype.removePresence = function(docId, userId) { +EditorData.prototype.removePresence = function(ctx, docId, userId) { return Promise.resolve(); }; -EditorData.prototype.getPresence = function(docId, connections) { +EditorData.prototype.getPresence = function(ctx, docId, connections) { let hvals = []; for (let i = 0; i < connections.length; ++i) { - if (connections[i].docId === docId) { - hvals.push(utils.getConnectionInfoStr(connections[i])); + let conn = connections[i]; + if (conn.docId === docId && ctx.tenant === tenantManager.getTenantByConnection(ctx, conn)) { + hvals.push(utils.getConnectionInfoStr(conn)); } } return Promise.resolve(hvals); }; -EditorData.prototype.lockSave = function(docId, userId, ttl) { - return this._checkAndLock('lockSave', docId, userId, ttl); +EditorData.prototype.lockSave = function(ctx, docId, userId, ttl) { + return this._checkAndLock(ctx, 'lockSave', docId, userId, ttl); }; -EditorData.prototype.unlockSave = function(docId, userId) { - return this._checkAndUnlock('lockSave', docId, userId); +EditorData.prototype.unlockSave = function(ctx, docId, userId) { + return this._checkAndUnlock(ctx, 'lockSave', docId, userId); }; -EditorData.prototype.lockAuth = function(docId, userId, ttl) { - return this._checkAndLock('lockAuth', docId, userId, ttl); +EditorData.prototype.lockAuth = function(ctx, docId, userId, ttl) { + return this._checkAndLock(ctx, 'lockAuth', docId, userId, ttl); }; -EditorData.prototype.unlockAuth = function(docId, userId) { - return this._checkAndUnlock('lockAuth', docId, userId); +EditorData.prototype.unlockAuth = function(ctx, docId, userId) { + return this._checkAndUnlock(ctx, 'lockAuth', docId, userId); }; EditorData.prototype.getDocumentPresenceExpired = function(now) { return Promise.resolve([]); }; -EditorData.prototype.removePresenceDocument = function(docId) { +EditorData.prototype.removePresenceDocument = function(ctx, docId) { return Promise.resolve(); }; -EditorData.prototype.addLocks = function(docId, locks) { - let data = this._getDocumentData(docId); +EditorData.prototype.addLocks = function(ctx, docId, locks) { + let data = this._getDocumentData(ctx, docId); if (!data.locks) { data.locks = []; } data.locks = data.locks.concat(locks); return Promise.resolve(); }; -EditorData.prototype.removeLocks = function(docId) { - let data = this._getDocumentData(docId); +EditorData.prototype.removeLocks = function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); data.locks = undefined; return Promise.resolve(); }; -EditorData.prototype.getLocks = function(docId) { - let data = this._getDocumentData(docId); +EditorData.prototype.getLocks = function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); return Promise.resolve(data.locks || []); }; -EditorData.prototype.addMessage = function(docId, msg) { - let data = this._getDocumentData(docId); +EditorData.prototype.addMessage = function(ctx, docId, msg) { + let data = this._getDocumentData(ctx, docId); if (!data.messages) { data.messages = []; } data.messages.push(msg); return Promise.resolve(); }; -EditorData.prototype.removeMessages = function(docId) { - let data = this._getDocumentData(docId); +EditorData.prototype.removeMessages = function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); data.messages = undefined; return Promise.resolve(); }; -EditorData.prototype.getMessages = function(docId) { - let data = this._getDocumentData(docId); +EditorData.prototype.getMessages = function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); return Promise.resolve(data.messages || []); }; -EditorData.prototype.setSaved = function(docId, status) { - let data = this._getDocumentData(docId); +EditorData.prototype.setSaved = function(ctx, docId, status) { + let data = this._getDocumentData(ctx, docId); data.saved = status; return Promise.resolve(); }; -EditorData.prototype.getdelSaved = function(docId) { - let data = this._getDocumentData(docId); +EditorData.prototype.getdelSaved = function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); let res = data.saved; data.saved = undefined; return Promise.resolve(res); }; -EditorData.prototype.setForceSave = function(docId, time, index, baseUrl, changeInfo) { - let data = this._getDocumentData(docId); +EditorData.prototype.setForceSave = function(ctx, docId, time, index, baseUrl, changeInfo) { + let data = this._getDocumentData(ctx, docId); data.forceSave = {time: time, index: index, baseUrl: baseUrl, changeInfo: changeInfo, started: false, ended: false}; return Promise.resolve(); }; -EditorData.prototype.getForceSave = function(docId) { - let data = this._getDocumentData(docId); +EditorData.prototype.getForceSave = function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); return Promise.resolve(data.forceSave || null); }; -EditorData.prototype.checkAndStartForceSave = function(docId) { - let data = this._getDocumentData(docId); +EditorData.prototype.checkAndStartForceSave = function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); let res; if (data.forceSave && !data.forceSave.started) { data.forceSave.started = true; @@ -188,8 +194,8 @@ EditorData.prototype.checkAndStartForceSave = function(docId) { } return Promise.resolve(res); }; -EditorData.prototype.checkAndSetForceSave = function(docId, time, index, started, ended) { - let data = this._getDocumentData(docId); +EditorData.prototype.checkAndSetForceSave = function(ctx, docId, time, index, started, ended) { + let data = this._getDocumentData(ctx, docId); let res; if (data.forceSave && time === data.forceSave.time && index === data.forceSave.index) { data.forceSave.started = started; @@ -198,180 +204,235 @@ EditorData.prototype.checkAndSetForceSave = function(docId, time, index, started } return Promise.resolve(res); }; -EditorData.prototype.removeForceSave = function(docId) { - let data = this._getDocumentData(docId); +EditorData.prototype.removeForceSave = function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); data.forceSave = undefined; return Promise.resolve(); }; -EditorData.prototype.cleanDocumentOnExit = function(docId) { - delete this.data[docId]; - delete this.forceSaveTimer[docId]; +EditorData.prototype.cleanDocumentOnExit = function(ctx, docId) { + let tenantData = this.data[ctx.tenant]; + if (tenantData) { + delete tenantData[docId]; + } + let tenantTimer = this.forceSaveTimer[ctx.tenant]; + if (tenantTimer) { + delete tenantTimer[docId]; + } return Promise.resolve(); }; -EditorData.prototype.addForceSaveTimerNX = function(docId, expireAt) { - if (!this.forceSaveTimer[docId]) { - this.forceSaveTimer[docId] = expireAt; +EditorData.prototype.addForceSaveTimerNX = function(ctx, docId, expireAt) { + let tenantTimer = this.forceSaveTimer[ctx.tenant]; + if (!tenantTimer) { + this.forceSaveTimer[ctx.tenant] = tenantTimer = {}; + } + if (!tenantTimer[docId]) { + tenantTimer[docId] = expireAt; } return Promise.resolve(); }; EditorData.prototype.getForceSaveTimer = function(now) { let res = []; - for (let docId in this.forceSaveTimer) { - if (this.forceSaveTimer.hasOwnProperty(docId)) { - if (this.forceSaveTimer[docId] < now) { - res.push(docId); - delete this.forceSaveTimer[docId]; + for (let tenant in this.forceSaveTimer) { + if (this.forceSaveTimer.hasOwnProperty(tenant)) { + let tenantTimer = this.forceSaveTimer[tenant]; + for (let docId in tenantTimer) { + if (tenantTimer.hasOwnProperty(docId)) { + if (tenantTimer[docId] < now) { + res.push([tenant, docId]); + delete tenantTimer[docId]; + } + } } } } return Promise.resolve(res); }; -EditorData.prototype.addPresenceUniqueUser = function(userId, expireAt, userInfo) { - this.uniqueUser[userId] = {expireAt: expireAt, userInfo: userInfo}; +EditorData.prototype.addPresenceUniqueUser = function(ctx, userId, expireAt, userInfo) { + let tenantUser = this.uniqueUser[ctx.tenant]; + if (!tenantUser) { + this.uniqueUser[ctx.tenant] = tenantUser = {}; + } + tenantUser[userId] = {expireAt: expireAt, userInfo: userInfo}; return Promise.resolve(); }; -EditorData.prototype.getPresenceUniqueUser = function(nowUTC) { +EditorData.prototype.getPresenceUniqueUser = function(ctx, nowUTC) { let res = []; - for (let userId in this.uniqueUser) { - if (this.uniqueUser.hasOwnProperty(userId)) { - if (this.uniqueUser[userId].expireAt > nowUTC) { - let elem = this.uniqueUser[userId]; + let tenantUser = this.uniqueUser[ctx.tenant]; + if (!tenantUser) { + this.uniqueUser[ctx.tenant] = tenantUser = {}; + } + for (let userId in tenantUser) { + if (tenantUser.hasOwnProperty(userId)) { + if (tenantUser[userId].expireAt > nowUTC) { + let elem = tenantUser[userId]; let newElem = {userid: userId, expire: new Date(elem.expireAt * 1000)}; Object.assign(newElem, elem.userInfo); res.push(newElem); } else { - delete this.uniqueUser[userId]; + delete tenantUser[userId]; } } } return Promise.resolve(res); }; -EditorData.prototype.addPresenceUniqueUsersOfMonth = function(userId, period, userInfo) { - if(!this.uniqueUsersOfMonth[period]) { - let expireAt = Date.now() + cfgExpMonthUniqueUsers; - this.uniqueUsersOfMonth[period] = {expireAt: expireAt, data: {}}; +EditorData.prototype.addPresenceUniqueUsersOfMonth = function(ctx, userId, period, userInfo) { + let tenantUser = this.uniqueUsersOfMonth[ctx.tenant]; + if (!tenantUser) { + this.uniqueUsersOfMonth[ctx.tenant] = tenantUser = {}; } - this.uniqueUsersOfMonth[period].data[userId] = userInfo; + if(!tenantUser[period]) { + let expireAt = Date.now() + cfgExpMonthUniqueUsers; + tenantUser[period] = {expireAt: expireAt, data: {}}; + } + tenantUser[period].data[userId] = userInfo; return Promise.resolve(); }; -EditorData.prototype.getPresenceUniqueUsersOfMonth = function() { +EditorData.prototype.getPresenceUniqueUsersOfMonth = function(ctx) { let res = {}; let nowUTC = Date.now(); - for (let periodId in this.uniqueUsersOfMonth) { - if (this.uniqueUsersOfMonth.hasOwnProperty(periodId)) { - if (this.uniqueUsersOfMonth[periodId].expireAt <= nowUTC) { - delete this.uniqueUsersOfMonth[periodId]; + let tenantUser = this.uniqueUsersOfMonth[ctx.tenant]; + if (!tenantUser) { + this.uniqueUsersOfMonth[ctx.tenant] = tenantUser = {}; + } + for (let periodId in tenantUser) { + if (tenantUser.hasOwnProperty(periodId)) { + if (tenantUser[periodId].expireAt <= nowUTC) { + delete tenantUser[periodId]; } else { let date = new Date(parseInt(periodId)).toISOString(); - res[date] = this.uniqueUsersOfMonth[periodId].data; + res[date] = tenantUser[periodId].data; } } } return Promise.resolve(res); }; -EditorData.prototype.addPresenceUniqueViewUser = function(userId, expireAt, userInfo) { - this.uniqueViewUser[userId] = {expireAt: expireAt, userInfo: userInfo}; +EditorData.prototype.addPresenceUniqueViewUser = function(ctx, userId, expireAt, userInfo) { + let tenantUser = this.uniqueViewUser[ctx.tenant]; + if (!tenantUser) { + this.uniqueViewUser[ctx.tenant] = tenantUser = {}; + } + tenantUser[userId] = {expireAt: expireAt, userInfo: userInfo}; return Promise.resolve(); }; -EditorData.prototype.getPresenceUniqueViewUser = function(nowUTC) { +EditorData.prototype.getPresenceUniqueViewUser = function(ctx, nowUTC) { let res = []; - for (let userId in this.uniqueViewUser) { - if (this.uniqueViewUser.hasOwnProperty(userId)) { - if (this.uniqueViewUser[userId].expireAt > nowUTC) { - let elem = this.uniqueViewUser[userId]; + let tenantUser = this.uniqueViewUser[ctx.tenant]; + if (!tenantUser) { + this.uniqueViewUser[ctx.tenant] = tenantUser = {}; + } + for (let userId in tenantUser) { + if (tenantUser.hasOwnProperty(userId)) { + if (tenantUser[userId].expireAt > nowUTC) { + let elem = tenantUser[userId]; let newElem = {userid: userId, expire: new Date(elem.expireAt * 1000)}; Object.assign(newElem, elem.userInfo); res.push(newElem); } else { - delete this.uniqueViewUser[userId]; + delete tenantUser[userId]; } } } return Promise.resolve(res); }; -EditorData.prototype.addPresenceUniqueViewUsersOfMonth = function(userId, period, userInfo) { - if(!this.uniqueViewUsersOfMonth[period]) { - let expireAt = Date.now() + cfgExpMonthUniqueUsers; - this.uniqueViewUsersOfMonth[period] = {expireAt: expireAt, data: {}}; +EditorData.prototype.addPresenceUniqueViewUsersOfMonth = function(ctx, userId, period, userInfo) { + let tenantUser = this.uniqueViewUsersOfMonth[ctx.tenant]; + if (!tenantUser) { + this.uniqueViewUsersOfMonth[ctx.tenant] = tenantUser = {}; } - this.uniqueViewUsersOfMonth[period].data[userId] = userInfo; + if(!tenantUser[period]) { + let expireAt = Date.now() + cfgExpMonthUniqueUsers; + tenantUser[period] = {expireAt: expireAt, data: {}}; + } + tenantUser[period].data[userId] = userInfo; return Promise.resolve(); }; -EditorData.prototype.getPresenceUniqueViewUsersOfMonth = function() { +EditorData.prototype.getPresenceUniqueViewUsersOfMonth = function(ctx) { let res = {}; let nowUTC = Date.now(); - for (let periodId in this.uniqueViewUsersOfMonth) { - if (this.uniqueViewUsersOfMonth.hasOwnProperty(periodId)) { - if (this.uniqueViewUsersOfMonth[periodId].expireAt <= nowUTC) { - delete this.uniqueViewUsersOfMonth[periodId]; + let tenantUser = this.uniqueViewUsersOfMonth[ctx.tenant]; + if (!tenantUser) { + this.uniqueViewUsersOfMonth[ctx.tenant] = tenantUser = {}; + } + for (let periodId in tenantUser) { + if (tenantUser.hasOwnProperty(periodId)) { + if (tenantUser[periodId].expireAt <= nowUTC) { + delete tenantUser[periodId]; } else { let date = new Date(parseInt(periodId)).toISOString(); - res[date] = this.uniqueViewUsersOfMonth[periodId].data; + res[date] = tenantUser[periodId].data; } } } return Promise.resolve(res); }; -EditorData.prototype.setEditorConnections = function(countEdit, countLiveView, countView, now, precision) { - this.stat.push({time: now, edit: countEdit, liveview: countLiveView, view: countView}); +EditorData.prototype.setEditorConnections = function(ctx, countEdit, countLiveView, countView, now, precision) { + let tenantStat = this.stat[ctx.tenant]; + if (!tenantStat) { + this.stat[ctx.tenant] = tenantStat = []; + } + tenantStat.push({time: now, edit: countEdit, liveview: countLiveView, view: countView}); let i = 0; - while (i < this.stat.length && this.stat[i] < now - precision[precision.length - 1].val) { + while (i < tenantStat.length && tenantStat[i] < now - precision[precision.length - 1].val) { i++; } - this.stat.splice(0, i); + tenantStat.splice(0, i); return Promise.resolve(); }; -EditorData.prototype.getEditorConnections = function() { - return Promise.resolve(this.stat); +EditorData.prototype.getEditorConnections = function(ctx) { + let tenantStat = this.stat[ctx.tenant]; + if (!tenantStat) { + this.stat[ctx.tenant] = tenantStat = []; + } + return Promise.resolve(tenantStat); }; -EditorData.prototype.setEditorConnectionsCountByShard = function(shardId, count) { +EditorData.prototype.setEditorConnectionsCountByShard = function(ctx, shardId, count) { return Promise.resolve(); }; -EditorData.prototype.incrEditorConnectionsCountByShard = function(shardId, count) { +EditorData.prototype.incrEditorConnectionsCountByShard = function(ctx, shardId, count) { return Promise.resolve(); }; -EditorData.prototype.getEditorConnectionsCount = function(connections) { +EditorData.prototype.getEditorConnectionsCount = function(ctx, connections) { let count = 0; for (let i = 0; i < connections.length; ++i) { let conn = connections[i]; - if (!(conn.isCloseCoAuthoring || (conn.user && conn.user.view))) { + if (!(conn.isCloseCoAuthoring || (conn.user && conn.user.view)) && ctx.tenant === tenantManager.getTenantByConnection(ctx, conn)) { count++; } } return Promise.resolve(count); }; -EditorData.prototype.setViewerConnectionsCountByShard = function(shardId, count) { +EditorData.prototype.setViewerConnectionsCountByShard = function(ctx, shardId, count) { return Promise.resolve(); }; -EditorData.prototype.incrViewerConnectionsCountByShard = function(shardId, count) { +EditorData.prototype.incrViewerConnectionsCountByShard = function(ctx, shardId, count) { return Promise.resolve(); }; -EditorData.prototype.getViewerConnectionsCount = function(connections) { +EditorData.prototype.getViewerConnectionsCount = function(ctx, connections) { let count = 0; for (let i = 0; i < connections.length; ++i) { let conn = connections[i]; - if (conn.isCloseCoAuthoring || (conn.user && conn.user.view)) { + if (conn.isCloseCoAuthoring || (conn.user && conn.user.view) && ctx.tenant === tenantManager.getTenantByConnection(ctx, conn)) { count++; } } return Promise.resolve(count); }; -EditorData.prototype.setLiveViewerConnectionsCountByShard = function(shardId, count) { +EditorData.prototype.setLiveViewerConnectionsCountByShard = function(ctx, shardId, count) { return Promise.resolve(); }; -EditorData.prototype.incrLiveViewerConnectionsCountByShard = function(shardId, count) { +EditorData.prototype.incrLiveViewerConnectionsCountByShard = function(ctx, shardId, count) { return Promise.resolve(); }; -EditorData.prototype.getLiveViewerConnectionsCount = function(connections) { +EditorData.prototype.getLiveViewerConnectionsCount = function(ctx, connections) { let count = 0; for (let i = 0; i < connections.length; ++i) { let conn = connections[i]; - if (utils.isLiveViewer(conn)) { + if (utils.isLiveViewer(conn) && ctx.tenant === tenantManager.getTenantByConnection(ctx, conn)) { count++; } } diff --git a/DocService/sources/fileuploaderservice.js b/DocService/sources/fileuploaderservice.js index f41f3564..49b2cb51 100644 --- a/DocService/sources/fileuploaderservice.js +++ b/DocService/sources/fileuploaderservice.js @@ -41,8 +41,8 @@ var utils = require('./../../Common/sources/utils'); var constants = require('./../../Common/sources/constants'); var storageBase = require('./../../Common/sources/storage-base'); var formatChecker = require('./../../Common/sources/formatchecker'); -var logger = require('./../../Common/sources/logger'); const commonDefines = require('./../../Common/sources/commondefines'); +const operationContext = require('./../../Common/sources/operationContext'); var config = require('config'); var configServer = config.get('services.CoAuthoring.server'); @@ -57,9 +57,12 @@ const PATTERN_ENCRYPTED = 'ENCRYPTED;'; exports.uploadTempFile = function(req, res) { return co(function* () { var docId = 'uploadTempFile'; + let ctx = new operationContext.Context(); try { + ctx.initFromRequest(req); + ctx.logger.info('uploadTempFile start'); let params; - let authRes = docsCoServer.getRequestParams(docId, req, true); + let authRes = yield docsCoServer.getRequestParams(ctx, req, true); if(authRes.code === constants.NO_ERROR){ params = authRes.params; } else { @@ -67,33 +70,34 @@ exports.uploadTempFile = function(req, res) { return; } docId = params.key; - logger.debug('Start uploadTempFile: docId = %s', docId); + ctx.setDocId(docId); + ctx.logger.debug('Start uploadTempFile'); if (docId && constants.DOC_ID_REGEX.test(docId) && req.body && Buffer.isBuffer(req.body)) { - var task = yield* taskResult.addRandomKeyTask(docId); + var task = yield* taskResult.addRandomKeyTask(ctx, docId); var strPath = task.key + '/' + docId + '.tmp'; - yield storageBase.putObject(strPath, req.body, req.body.length); - var url = yield storageBase.getSignedUrl(utils.getBaseUrlByRequest(req), strPath, + yield storageBase.putObject(ctx, strPath, req.body, req.body.length); + var url = yield storageBase.getSignedUrl(ctx, utils.getBaseUrlByRequest(req), strPath, commonDefines.c_oAscUrlTypes.Temporary); utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.NO_ERROR, url), false); } else { if (!constants.DOC_ID_REGEX.test(docId)) { - logger.warn('Error uploadTempFile unexpected key: docId = %s', docId); + ctx.logger.warn('Error uploadTempFile unexpected key'); } utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), false); } - logger.debug('End uploadTempFile: docId = %s', docId); - } - catch (e) { - logger.error('Error uploadTempFile: docId = %s\r\n%s', docId, e.stack); + } catch (e) { + ctx.logger.error('Error uploadTempFile: %s', e.stack); utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), false); + } finally { + ctx.logger.info('uploadTempFile end'); } }); }; -function checkJwtUpload(docId, errorName, token){ - let checkJwtRes = docsCoServer.checkJwt(docId, token, commonDefines.c_oAscSecretType.Session); - return checkJwtUploadTransformRes(docId, errorName, checkJwtRes); +function* checkJwtUpload(ctx, errorName, token){ + let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session); + return checkJwtUploadTransformRes(ctx, errorName, checkJwtRes); } -function checkJwtUploadTransformRes(docId, errorName, checkJwtRes){ +function checkJwtUploadTransformRes(ctx, errorName, checkJwtRes){ var res = {err: true, docId: null, userid: null, encrypted: null}; if (checkJwtRes.decoded) { var doc = checkJwtRes.decoded.document; @@ -106,94 +110,100 @@ function checkJwtUploadTransformRes(docId, errorName, checkJwtRes){ res.userid = edit.user.id; } } else { - logger.warn('Error %s jwt: docId = %s\r\n%s', errorName, docId, 'access deny'); + ctx.logger.warn('Error %s jwt: %s', errorName, 'access deny'); } } else { - logger.warn('Error %s jwt: docId = %s\r\n%s', errorName, docId, checkJwtRes.description); + ctx.logger.warn('Error %s jwt: %s', errorName, checkJwtRes.description); } return res; } exports.uploadImageFileOld = function(req, res) { - var docId = req.params.docid; - logger.debug('Start uploadImageFileOld: docId = %s', docId); - var userid = req.params.userid; - if (cfgTokenEnableBrowser) { - var checkJwtRes = checkJwtUpload(docId, 'uploadImageFileOld', req.query['token']); - if(!checkJwtRes.err){ - docId = checkJwtRes.docId || docId; - userid = checkJwtRes.userid || userid; - } else { - res.sendStatus(403); - return; - } - } - var listImages = []; - //todo userid - if (docId) { - var isError = false; - var form = new multiparty.Form(); - form.on('error', function(err) { - logger.error('Error parsing form: docId = %s\r\n%s', docId, err.toString()); - res.sendStatus(400); - }); - form.on('part', function(part) { - if (!part.filename) { - // ignore field's content - part.resume(); - } - if (part.filename) { - if (part.byteCount > cfgImageSize) { - isError = true; - } - if (isError) { - part.resume(); - } else { - //в начале пишется хеш, чтобы избежать ошибок при параллельном upload в совместном редактировании - var strImageName = crypto.randomBytes(16).toString("hex"); - var strPath = docId + '/media/' + strImageName + '.jpg'; - listImages.push(strPath); - utils.stream2Buffer(part).then(function(buffer) { - return storageBase.putObject(strPath, buffer, buffer.length); - }).then(function() { - part.resume(); - }).catch(function(err) { - logger.error('Upload putObject: docId = %s\r\n%s', docId, err.stack); - isError = true; - part.resume(); - }); - } - } - part.on('error', function(err) { - logger.error('Error parsing form part: docId = %s\r\n%s', docId, err.toString()); - }); - }); - form.on('close', function() { - if (isError) { - res.sendStatus(400); + return co(function* () { + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + var docId = req.params.docid; + var userid = req.params.userid; + ctx.init(ctx.tenant, docId, userid); + ctx.logger.debug('Start uploadImageFileOld'); + if (cfgTokenEnableBrowser) { + var checkJwtRes = yield* checkJwtUpload(ctx, 'uploadImageFileOld', req.query['token']); + if(!checkJwtRes.err){ + docId = checkJwtRes.docId || docId; + userid = checkJwtRes.userid || userid; } else { - storageBase.getSignedUrlsByArray(utils.getBaseUrlByRequest(req), listImages, docId, - commonDefines.c_oAscUrlTypes.Session).then(function(urls) { - var outputData = {'type': 0, 'error': constants.NO_ERROR, 'urls': urls, 'input': req.query}; - var output = ''; - - //res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Content-Type', 'text/html'); - res.send(output); - logger.debug('End uploadImageFileOld: docId = %s %s', docId, output); - } - ).catch(function(err) { - res.sendStatus(400); - logger.error('upload getSignedUrlsByArray: docId = %s\r\n%s', docId, err.stack); - }); + res.sendStatus(403); + return; } - }); - form.parse(req); - } else { - logger.debug('Error params uploadImageFileOld: docId = %s', docId); - res.sendStatus(400); - } + } + ctx.init(ctx.tenant, docId, userid); + var listImages = []; + //todo userid + if (docId) { + var isError = false; + var form = new multiparty.Form(); + form.on('error', function(err) { + ctx.logger.error('Error parsing form:%s', err.toString()); + res.sendStatus(400); + }); + form.on('part', function(part) { + if (!part.filename) { + // ignore field's content + part.resume(); + } + if (part.filename) { + if (part.byteCount > cfgImageSize) { + isError = true; + } + if (isError) { + part.resume(); + } else { + //в начале пишется хеш, чтобы избежать ошибок при параллельном upload в совместном редактировании + var strImageName = crypto.randomBytes(16).toString("hex"); + var strPath = docId + '/media/' + strImageName + '.jpg'; + listImages.push(strPath); + utils.stream2Buffer(part).then(function(buffer) { + return storageBase.putObject(ctx, strPath, buffer, buffer.length); + }).then(function() { + part.resume(); + }).catch(function(err) { + ctx.logger.error('Upload putObject:%s', err.stack); + isError = true; + part.resume(); + }); + } + } + part.on('error', function(err) { + ctx.logger.error('Error parsing form part:%s', err.toString()); + }); + }); + form.on('close', function() { + if (isError) { + res.sendStatus(400); + } else { + storageBase.getSignedUrlsByArray(ctx, utils.getBaseUrlByRequest(req), listImages, docId, + commonDefines.c_oAscUrlTypes.Session).then(function(urls) { + var outputData = {'type': 0, 'error': constants.NO_ERROR, 'urls': urls, 'input': req.query}; + var output = ''; + + //res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Content-Type', 'text/html'); + res.send(output); + ctx.logger.debug('End uploadImageFileOld:%s', output); + } + ).catch(function(err) { + res.sendStatus(400); + ctx.logger.error('upload getSignedUrlsByArray:%s', err.stack); + }); + } + }); + form.parse(req); + } else { + ctx.logger.debug('Error params uploadImageFileOld'); + res.sendStatus(400); + } + }); }; exports.uploadImageFile = function(req, res) { return co(function* () { @@ -201,18 +211,21 @@ exports.uploadImageFile = function(req, res) { var docId = 'null'; let output = {}; let isValidJwt = true; + let ctx = new operationContext.Context(); try { + ctx.initFromRequest(req); docId = req.params.docid; + ctx.setDocId(docId); let encrypted = false; - logger.debug('Start uploadImageFile: docId = %s', docId); + ctx.logger.debug('Start uploadImageFile'); if (cfgTokenEnableBrowser) { - let checkJwtRes = docsCoServer.checkJwtHeader(docId, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session); + let checkJwtRes = yield docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session); if (!checkJwtRes) { //todo remove compatibility with previous versions - checkJwtRes = docsCoServer.checkJwt(docId, req.query['token'], commonDefines.c_oAscSecretType.Session); + checkJwtRes = yield docsCoServer.checkJwt(ctx, req.query['token'], commonDefines.c_oAscSecretType.Session); } - let transformedRes = checkJwtUploadTransformRes(docId, 'uploadImageFile', checkJwtRes); + let transformedRes = checkJwtUploadTransformRes(ctx, 'uploadImageFile', checkJwtRes); if (!transformedRes.err) { docId = transformedRes.docId || docId; encrypted = transformedRes.encrypted; @@ -220,11 +233,12 @@ exports.uploadImageFile = function(req, res) { isValidJwt = false; } } + ctx.setDocId(docId); if (isValidJwt && docId && req.body && Buffer.isBuffer(req.body)) { let buffer = req.body; if (buffer.length <= cfgImageSize) { - var format = formatChecker.getImageFormat(buffer, undefined); + var format = formatChecker.getImageFormat(ctx, buffer); var formatStr = formatChecker.getStringFromFormat(format); if (encrypted && PATTERN_ENCRYPTED === buffer.toString('utf8', 0, PATTERN_ENCRYPTED.length)) { formatStr = buffer.toString('utf8', PATTERN_ENCRYPTED.length, buffer.indexOf(';', PATTERN_ENCRYPTED.length)); @@ -236,20 +250,20 @@ exports.uploadImageFile = function(req, res) { var strImageName = crypto.randomBytes(16).toString("hex"); var strPathRel = 'media/' + strImageName + '.' + formatStr; var strPath = docId + '/' + strPathRel; - yield storageBase.putObject(strPath, buffer, buffer.length); - output[strPathRel] = yield storageBase.getSignedUrl(utils.getBaseUrlByRequest(req), strPath, + yield storageBase.putObject(ctx, strPath, buffer, buffer.length); + output[strPathRel] = yield storageBase.getSignedUrl(ctx, utils.getBaseUrlByRequest(req), strPath, commonDefines.c_oAscUrlTypes.Session); isError = false; } else { - logger.debug('uploadImageFile format is not supported: docId = %s', docId); + ctx.logger.debug('uploadImageFile format is not supported'); } } else { - logger.debug('uploadImageFile size limit exceeded: buffer.length = %d docId = %s', buffer.length, docId); + ctx.logger.debug('uploadImageFile size limit exceeded: buffer.length = %d', buffer.length); } } } catch (e) { isError = true; - logger.error('Error uploadImageFile: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error uploadImageFile:%s', e.stack); } finally { try { if (!isError) { @@ -258,9 +272,9 @@ exports.uploadImageFile = function(req, res) { } else { res.sendStatus(isValidJwt ? 400 : 403); } - logger.debug('End uploadImageFile: isError = %s docId = %s', isError, docId); + ctx.logger.debug('End uploadImageFile: isError = %s', isError); } catch (e) { - logger.error('Error uploadImageFile: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error uploadImageFile:%s', e.stack); } } }); diff --git a/DocService/sources/gc.js b/DocService/sources/gc.js index bff66a8d..e2ce9a4d 100644 --- a/DocService/sources/gc.js +++ b/DocService/sources/gc.js @@ -46,6 +46,7 @@ var logger = require('./../../Common/sources/logger'); var constants = require('./../../Common/sources/constants'); var commondefines = require('./../../Common/sources/commondefines'); var queueService = require('./../../Common/sources/taskqueueRabbitMQ'); +var operationContext = require('./../../Common/sources/operationContext'); var pubsubService = require('./pubsubRabbitMQ'); var cfgExpFilesCron = config.get('expire.filesCron'); @@ -65,31 +66,36 @@ let expDocumentsStep = getCronStep(cfgExpDocumentsCron); var checkFileExpire = function() { return co(function* () { + let ctx = new operationContext.Context(); try { - logger.debug('checkFileExpire start'); + ctx.logger.info('checkFileExpire start'); + let removedCount = 0; var expired; - var removedCount = 0; var currentRemovedCount; do { currentRemovedCount = 0; - expired = yield taskResult.getExpired(cfgExpFilesRemovedAtOnce, cfgExpFiles); + expired = yield taskResult.getExpired(ctx, cfgExpFilesRemovedAtOnce, cfgExpFiles); for (var i = 0; i < expired.length; ++i) { - var docId = expired[i].id; + let tenant = expired[i].tenant; + let docId = expired[i].id; + ctx.init(tenant, docId, ctx.userId); + //todo tenant //проверяем что никто не сидит в документе - let editorsCount = yield docsCoServer.getEditorsCountPromise(docId); + let editorsCount = yield docsCoServer.getEditorsCountPromise(ctx, docId); if(0 === editorsCount){ - if (yield canvasService.cleanupCache(docId)) { + if (yield canvasService.cleanupCache(ctx)) { currentRemovedCount++; } } else { - logger.debug('checkFileExpire expire but presence: editorsCount = %d; docId = %s', editorsCount, docId); + ctx.logger.debug('checkFileExpire expire but presence: editorsCount = %d', editorsCount); } } removedCount += currentRemovedCount; } while (currentRemovedCount > 0); - logger.debug('checkFileExpire end: removedCount = %d', removedCount); + ctx.initDefault(); + ctx.logger.info('checkFileExpire end: removedCount = %d', removedCount); } catch (e) { - logger.error('checkFileExpire error:\r\n%s', e.stack); + ctx.logger.error('checkFileExpire error: %s', e.stack); } finally { setTimeout(checkFileExpire, expFilesStep); } @@ -100,8 +106,9 @@ var checkDocumentExpire = function() { var queue = null; var removedCount = 0; var startSaveCount = 0; + let ctx = new operationContext.Context(); try { - logger.debug('checkDocumentExpire start'); + ctx.logger.info('checkDocumentExpire start'); var now = (new Date()).getTime(); let expiredKeys = yield docsCoServer.editorData.getDocumentPresenceExpired(now); if (expiredKeys.length > 0) { @@ -109,30 +116,33 @@ var checkDocumentExpire = function() { yield queue.initPromise(true, false, false, false, false, false); for (var i = 0; i < expiredKeys.length; ++i) { - var docId = expiredKeys[i]; + let tenant = expiredKeys[i][0]; + let docId = expiredKeys[i][1]; if (docId) { - var hasChanges = yield docsCoServer.hasChanges(docId); + ctx.init(tenant, docId, ctx.userId); + var hasChanges = yield docsCoServer.hasChanges(ctx, docId); if (hasChanges) { - yield docsCoServer.createSaveTimerPromise(docId, null, null, queue, true); + yield docsCoServer.createSaveTimer(ctx, docId, null, null, queue, true); startSaveCount++; } else { - yield docsCoServer.cleanDocumentOnExitNoChangesPromise(docId); + yield docsCoServer.cleanDocumentOnExitNoChangesPromise(ctx, docId); removedCount++; } } } } + ctx.initDefault(); + ctx.logger.info('checkDocumentExpire end: startSaveCount = %d, removedCount = %d', startSaveCount, removedCount); } catch (e) { - logger.error('checkDocumentExpire error:\r\n%s', e.stack); + ctx.logger.error('checkDocumentExpire error: %s', e.stack); } finally { try { if (queue) { yield queue.close(); } } catch (e) { - logger.error('checkDocumentExpire error:\r\n%s', e.stack); + ctx.logger.error('checkDocumentExpire error: %s', e.stack); } - logger.debug('checkDocumentExpire end: startSaveCount = %d, removedCount = %d', startSaveCount, removedCount); setTimeout(checkDocumentExpire, expDocumentsStep); } }); @@ -141,8 +151,9 @@ let forceSaveTimeout = function() { return co(function* () { let queue = null; let pubsub = null; + let ctx = new operationContext.Context(); try { - logger.debug('forceSaveTimeout start'); + ctx.logger.info('forceSaveTimeout start'); let now = (new Date()).getTime(); let expiredKeys = yield docsCoServer.editorData.getForceSaveTimer(now); if (expiredKeys.length > 0) { @@ -154,18 +165,21 @@ let forceSaveTimeout = function() { let actions = []; for (let i = 0; i < expiredKeys.length; ++i) { - let docId = expiredKeys[i]; + let tenant = expiredKeys[i][0]; + let docId = expiredKeys[i][1]; if (docId) { - actions.push(docsCoServer.startForceSave(docId, commondefines.c_oAscForceSaveTypes.Timeout, + ctx.init(tenant, docId, ctx.userId); + actions.push(docsCoServer.startForceSave(ctx, docId, commondefines.c_oAscForceSaveTypes.Timeout, undefined, undefined, undefined, undefined, undefined, undefined, queue, pubsub)); } } yield Promise.all(actions); - logger.debug('forceSaveTimeout actions.length %d', actions.length); + ctx.logger.debug('forceSaveTimeout actions.length %d', actions.length); } - logger.debug('forceSaveTimeout end'); + ctx.initDefault(); + ctx.logger.info('forceSaveTimeout end'); } catch (e) { - logger.error('forceSaveTimeout error:\r\n%s', e.stack); + ctx.logger.error('forceSaveTimeout error: %s', e.stack); } finally { try { if (queue) { @@ -175,7 +189,7 @@ let forceSaveTimeout = function() { yield pubsub.close(); } } catch (e) { - logger.error('checkDocumentExpire error:\r\n%s', e.stack); + ctx.logger.error('checkDocumentExpire error: %s', e.stack); } setTimeout(forceSaveTimeout, cfgForceSaveStep); } diff --git a/DocService/sources/mySqlBaseConnector.js b/DocService/sources/mySqlBaseConnector.js index 3c35f1c6..e5420235 100644 --- a/DocService/sources/mySqlBaseConnector.js +++ b/DocService/sources/mySqlBaseConnector.js @@ -46,22 +46,21 @@ var pool = mysql.createPool({ timezone : 'Z', flags : '-FOUND_ROWS' }); -var logger = require('./../../Common/sources/logger'); -exports.sqlQuery = function (sqlCommand, callbackFunction, opt_noModifyRes, opt_noLog, opt_values) { +exports.sqlQuery = function (ctx, sqlCommand, callbackFunction, opt_noModifyRes, opt_noLog, opt_values) { pool.getConnection(function(err, connection) { if (err) { - logger.error('pool.getConnection error: %s', err); + ctx.logger.error('pool.getConnection error: %s', err); if (callbackFunction) callbackFunction(err, null); return; } let queryCallback = function (error, result) { connection.release(); if (error) { - logger.error('________________________error_____________________'); - logger.error('sqlQuery: %s sqlCommand: %s', error.code, sqlCommand); - logger.error(error); - logger.error('_____________________end_error_____________________'); + ctx.logger.error('________________________error_____________________'); + ctx.logger.error('sqlQuery: %s sqlCommand: %s', error.code, sqlCommand); + ctx.logger.error(error); + ctx.logger.error('_____________________end_error_____________________'); } if (callbackFunction) callbackFunction(error, result); }; @@ -82,7 +81,7 @@ let concatParams = function (val1, val2) { }; exports.concatParams = concatParams; -exports.upsert = function(task, opt_updateUserIndex) { +exports.upsert = function(ctx, task, opt_updateUserIndex) { return new Promise(function(resolve, reject) { task.completeDefaults(); let dateNow = new Date(); @@ -93,6 +92,7 @@ exports.upsert = function(task, opt_updateUserIndex) { userCallback.fromValues(task.userIndex, task.callback); cbInsert = userCallback.toSQLInsert(); } + let p0 = addSqlParam(task.tenant, values); let p1 = addSqlParam(task.key, values); let p2 = addSqlParam(task.status, values); let p3 = addSqlParam(task.statusInfo, values); @@ -102,8 +102,8 @@ exports.upsert = function(task, opt_updateUserIndex) { let p7 = addSqlParam(cbInsert, values); let p8 = addSqlParam(task.baseurl, values); let p9 = addSqlParam(dateNow, values); - var sqlCommand = 'INSERT INTO task_result (id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)'+ - ` VALUES (${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8}) ON DUPLICATE KEY UPDATE` + + var sqlCommand = 'INSERT INTO task_result (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)'+ + ` VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8}) ON DUPLICATE KEY UPDATE` + ` last_open_date = ${p9}`; if (task.callback) { let p10 = addSqlParam(JSON.stringify(task.callback), values); @@ -118,7 +118,7 @@ exports.upsert = function(task, opt_updateUserIndex) { } sqlCommand += ';'; - exports.sqlQuery(sqlCommand, function(error, result) { + exports.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { diff --git a/DocService/sources/postgreSqlBaseConnector.js b/DocService/sources/postgreSqlBaseConnector.js index 30b2c731..3c491f71 100644 --- a/DocService/sources/postgreSqlBaseConnector.js +++ b/DocService/sources/postgreSqlBaseConnector.js @@ -62,11 +62,9 @@ types.setTypeParser(1184, function(stringValue) { return new Date(stringValue + '+0000'); }); -var logger = require('./../../Common/sources/logger'); - var maxPacketSize = configSql.get('max_allowed_packet'); -exports.sqlQuery = function(sqlCommand, callbackFunction, opt_noModifyRes, opt_noLog, opt_values) { +exports.sqlQuery = function(ctx, sqlCommand, callbackFunction, opt_noModifyRes, opt_noLog, opt_values) { co(function *() { var result = null; var error = null; @@ -75,7 +73,7 @@ exports.sqlQuery = function(sqlCommand, callbackFunction, opt_noModifyRes, opt_n } catch (err) { error = err; if (!opt_noLog) { - logger.warn('sqlQuery error sqlCommand: %s:\r\n%s', sqlCommand.slice(0, 50), err.stack); + ctx.logger.warn('sqlQuery error sqlCommand: %s: %s', sqlCommand.slice(0, 50), err.stack); } } finally { if (callbackFunction) { @@ -112,6 +110,7 @@ function getUpsertString(task, values) { userCallback.fromValues(task.userIndex, task.callback); cbInsert = userCallback.toSQLInsert(); } + let p0 = addSqlParam(task.tenant, values); let p1 = addSqlParam(task.key, values); let p2 = addSqlParam(task.status, values); let p3 = addSqlParam(task.statusInfo, values); @@ -123,9 +122,9 @@ function getUpsertString(task, values) { if (isSupportOnConflict) { let p9 = addSqlParam(dateNow, values); //http://stackoverflow.com/questions/34762732/how-to-find-out-if-an-upsert-was-an-update-with-postgresql-9-5-upsert - let sqlCommand = "INSERT INTO task_result (id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)"; - sqlCommand += ` VALUES (${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8})`; - sqlCommand += ` ON CONFLICT (id) DO UPDATE SET last_open_date = ${p9}`; + let sqlCommand = "INSERT INTO task_result (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)"; + sqlCommand += ` VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8})`; + sqlCommand += ` ON CONFLICT (tenant, id) DO UPDATE SET last_open_date = ${p9}`; if (task.callback) { let p10 = addSqlParam(JSON.stringify(task.callback), values); sqlCommand += `, callback = task_result.callback || '${sqlBase.UserCallback.prototype.delimiter}{"userIndex":' `; @@ -138,20 +137,20 @@ function getUpsertString(task, values) { sqlCommand += ", user_index = task_result.user_index + 1 RETURNING user_index as userindex;"; return sqlCommand; } else { - return `SELECT * FROM merge_db(${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8});`; + return `SELECT * FROM merge_db(${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8});`; } } -exports.upsert = function(task) { +exports.upsert = function(ctx, task) { return new Promise(function(resolve, reject) { let values = []; var sqlCommand = getUpsertString(task, values); - exports.sqlQuery(sqlCommand, function(error, result) { + exports.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { if (isSupportOnConflict && '42601' === error.code) { //SYNTAX ERROR isSupportOnConflict = false; - logger.warn('checkIsSupportOnConflict false'); - resolve(exports.upsert(task)); + ctx.logger.warn('checkIsSupportOnConflict false'); + resolve(exports.upsert(ctx, task)); } else { reject(error); } @@ -167,12 +166,13 @@ exports.upsert = function(task) { }, true, undefined, values); }); }; -exports.insertChanges = function(tableChanges, startIndex, objChanges, docId, index, user, callback) { +exports.insertChanges = function(ctx, tableChanges, startIndex, objChanges, docId, index, user, callback) { let i = startIndex; if (i >= objChanges.length) { return; } let isSupported = true; + let tenant = []; let id = []; let changeId = []; let userId = []; @@ -181,27 +181,28 @@ exports.insertChanges = function(tableChanges, startIndex, objChanges, docId, in let change = []; let time = []; //Postgres 9.4 multi-argument unnest - let sqlCommand = `INSERT INTO ${tableChanges} (id, change_id, user_id, user_id_original, user_name, change_data, change_date) `; - sqlCommand += "SELECT * FROM UNNEST ($1::text[], $2::int[], $3::text[], $4::text[], $5::text[], $6::text[], $7::timestamp[]);"; - let values = [id, changeId, userId, userIdOriginal, username, change, time]; + let sqlCommand = `INSERT INTO ${tableChanges} (tenant, id, change_id, user_id, user_id_original, user_name, change_data, change_date) `; + sqlCommand += "SELECT * FROM UNNEST ($1::text[], $2::text[], $3::int[], $4::text[], $5::text[], $6::text[], $7::text[], $8::timestamp[]);"; + let values = [tenant, id, changeId, userId, userIdOriginal, username, change, time]; let curLength = sqlCommand.length; for (; i < objChanges.length; ++i) { //4 is max utf8 bytes per symbol curLength += 4 * (docId.length + user.id.length + user.idOriginal.length + user.username.length + objChanges[i].change.length) + 4 + 8; if (curLength >= maxPacketSize && i > startIndex) { - exports.sqlQuery(sqlCommand, function(error, output) { + exports.sqlQuery(ctx, sqlCommand, function(error, output) { if (error && '42883' == error.code) { isSupported = false; - logger.warn('postgresql does not support UNNEST'); + ctx.logger.warn('postgresql does not support UNNEST'); } if (error) { callback(error, output, isSupported); } else { - exports.insertChanges(tableChanges, i, objChanges, docId, index, user, callback); + exports.insertChanges(ctx, tableChanges, i, objChanges, docId, index, user, callback); } }, undefined, undefined, values); return; } + tenant.push(ctx.tenant); id.push(docId); changeId.push(index++); userId.push(user.id); @@ -210,10 +211,10 @@ exports.insertChanges = function(tableChanges, startIndex, objChanges, docId, in change.push(objChanges[i].change); time.push(objChanges[i].time); } - exports.sqlQuery(sqlCommand, function(error, output) { + exports.sqlQuery(ctx, sqlCommand, function(error, output) { if (error && '42883' == error.code) { isSupported = false; - logger.warn('postgresql does not support UNNEST'); + ctx.logger.warn('postgresql does not support UNNEST'); } callback(error, output, isSupported); }, undefined, undefined, values); diff --git a/DocService/sources/pubsubRabbitMQ.js b/DocService/sources/pubsubRabbitMQ.js index e870c750..f37a5ab2 100644 --- a/DocService/sources/pubsubRabbitMQ.js +++ b/DocService/sources/pubsubRabbitMQ.js @@ -40,7 +40,6 @@ const commonDefines = require('./../../Common/sources/commondefines'); var utils = require('./../../Common/sources/utils'); var rabbitMQCore = require('./../../Common/sources/rabbitMQCore'); var activeMQCore = require('./../../Common/sources/activeMQCore'); -const logger = require('./../../Common/sources/logger'); const cfgQueueType = config.get('queue.type'); var cfgRabbitExchangePubSub = config.get('rabbitmq.exchangepubsub'); diff --git a/DocService/sources/server.js b/DocService/sources/server.js index fc3b0413..aae379ba 100644 --- a/DocService/sources/server.js +++ b/DocService/sources/server.js @@ -56,6 +56,8 @@ const wopiClient = require('./wopiClient'); const constants = require('./../../Common/sources/constants'); const utils = require('./../../Common/sources/utils'); const commonDefines = require('./../../Common/sources/commondefines'); +const operationContext = require('./../../Common/sources/operationContext'); +const tenantManager = require('./../../Common/sources/tenantManager'); const configStorage = configCommon.get('storage'); const cfgWopiEnable = configCommon.get('wopi.enable'); @@ -63,6 +65,7 @@ const cfgHtmlTemplate = configCommon.get('wopi.htmlTemplate'); const cfgTokenEnableBrowser = configCommon.get('services.CoAuthoring.token.enable.browser'); const cfgTokenEnableRequestInbox = configCommon.get('services.CoAuthoring.token.enable.request.inbox'); const cfgTokenEnableRequestOutbox = configCommon.get('services.CoAuthoring.token.enable.request.outbox'); +const cfgLicenseFile = configCommon.get('license.license_file'); const app = express(); app.disable('x-powered-by'); @@ -74,43 +77,49 @@ const server = http.createServer(app); let licenseInfo, licenseOriginal, updatePluginsTime, userPlugins, pluginsLoaded; const updatePlugins = (eventType, filename) => { - console.log('update Folder: %s ; %s', eventType, filename); + operationContext.global.logger.info('update Folder: %s ; %s', eventType, filename); if (updatePluginsTime && 1000 >= (new Date() - updatePluginsTime)) { return; } - console.log('update Folder true: %s ; %s', eventType, filename); + operationContext.global.logger.info('update Folder true: %s ; %s', eventType, filename); updatePluginsTime = new Date(); pluginsLoaded = false; }; const readLicense = function*() { - [licenseInfo, licenseOriginal] = yield* license.readLicense(); + [licenseInfo, licenseOriginal] = yield* license.readLicense(cfgLicenseFile); }; const updateLicense = () => { return co(function*() { try { yield* readLicense(); docsCoServer.setLicenseInfo(licenseInfo, licenseOriginal); - console.log('End updateLicense'); + operationContext.global.logger.info('End updateLicense'); } catch (err) { - logger.error('updateLicense error:\r\n%s', err.stack); + operationContext.global.logger.error('updateLicense error: %s', err.stack); } }); }; -logger.warn('Express server starting...'); +operationContext.global.logger.warn('Express server starting...'); if (!(cfgTokenEnableBrowser && cfgTokenEnableRequestInbox && cfgTokenEnableRequestOutbox)) { - logger.warn('Set services.CoAuthoring.token.enable.browser, services.CoAuthoring.token.enable.request.inbox, ' + + operationContext.global.logger.warn('Set services.CoAuthoring.token.enable.browser, services.CoAuthoring.token.enable.request.inbox, ' + 'services.CoAuthoring.token.enable.request.outbox in the Document Server config ' + 'to prevent an unauthorized access to your documents and the substitution of important parameters in ONLYOFFICE Document Server requests.'); } -updateLicense(); +if (!tenantManager.isMultitenantMode()) { + updateLicense(); + fs.watchFile(cfgLicenseFile, updateLicense); + setInterval(updateLicense, 86400000); +} if (config.has('server.static_content')) { const staticContent = config.get('server.static_content'); for (let i in staticContent) { - app.use(i, express.static(staticContent[i]['path'], staticContent[i]['options'])); + if (staticContent.hasOwnProperty(i)) { + app.use(i, express.static(staticContent[i]['path'], staticContent[i]['options'])); + } } } @@ -133,7 +142,7 @@ if (configStorage.has('fs.folderPath')) { const realUrl = req.url.substring(0, index); res.sendFile(realUrl, sendFileOptions, (err) => { if (err) { - logger.error(err); + operationContext.global.logger.error(err); res.status(400).end(); } }); @@ -146,33 +155,36 @@ if (configStorage.has('fs.folderPath')) { try { fs.watch(config.get('plugins.path'), updatePlugins); } catch (e) { - logger.warn('Failed to subscribe to plugin folder updates. When changing the list of plugins, you must restart the server. https://nodejs.org/docs/latest/api/fs.html#fs_availability'); + operationContext.global.logger.warn('Failed to subscribe to plugin folder updates. When changing the list of plugins, you must restart the server. https://nodejs.org/docs/latest/api/fs.html#fs_availability'); } -fs.watchFile(configCommon.get('license').get('license_file'), updateLicense); -setInterval(updateLicense, 86400000); // Если захочется использовать 'development' и 'production', // то с помощью app.settings.env (https://github.com/strongloop/express/issues/936) // Если нужна обработка ошибок, то теперь она такая https://github.com/expressjs/errorhandler docsCoServer.install(server, () => { - console.log('Start callbackFunction'); + operationContext.global.logger.info('Start callbackFunction'); server.listen(config.get('server.port'), () => { - logger.warn("Express server listening on port %d in %s mode. Version: %s. Build: %s", config.get('server.port'), app.settings.env, commonDefines.buildVersion, commonDefines.buildNumber); + operationContext.global.logger.warn("Express server listening on port %d in %s mode. Version: %s. Build: %s", config.get('server.port'), app.settings.env, commonDefines.buildVersion, commonDefines.buildNumber); }); app.get('/index.html', (req, res) => { - let buildVersion = commonDefines.buildVersion; - let buildNumber = commonDefines.buildNumber; - let buildDate, packageType, customerId = ""; - if (licenseInfo) { - buildDate = licenseInfo.buildDate.toISOString(); - packageType = licenseInfo.packageType; - customerId = licenseInfo.customerId; - } - let output = `Server is functioning normally. Version: ${buildVersion}. Build: ${buildNumber}`; - output += `. Release date: ${buildDate}. Package type: ${packageType}. Customer Id: ${customerId}`; - res.send(output); + return co(function*() { + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + let licenseInfo = yield tenantManager.getTenantLicense(ctx); + let buildVersion = commonDefines.buildVersion; + let buildNumber = commonDefines.buildNumber; + let buildDate, packageType, customerId = ""; + if (licenseInfo) { + buildDate = licenseInfo.buildDate.toISOString(); + packageType = licenseInfo.packageType; + customerId = licenseInfo.customerId; + } + let output = `Server is functioning normally. Version: ${buildVersion}. Build: ${buildNumber}`; + output += `. Release date: ${buildDate}. Package type: ${packageType}. Customer Id: ${customerId}`; + res.send(output); + }); }); const rawFileParser = bodyParser.raw( {inflate: true, limit: config.get('server.limits_tempfile_upload'), type: function() {return true;}}); @@ -237,7 +249,9 @@ docsCoServer.install(server, () => { } app.post('/dummyCallback', utils.checkClientIp, rawFileParser, function(req, res){ - logger.debug(`dummyCallback req.body:%s`, req.body); + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + ctx.logger.debug(`dummyCallback req.body:%s`, req.body); utils.fillResponseSimple(res, JSON.stringify({error: 0}, "application/json")); }); @@ -292,8 +306,10 @@ docsCoServer.install(server, () => { app.get('/themes.json', apicache.middleware("5 minutes"), (req, res) => { return co(function*() { let themes = []; + let ctx = new operationContext.Context(); try { - logger.info('themes.json start'); + ctx.initFromRequest(req); + ctx.logger.info('themes.json start'); if (!config.has('server.static_content') || !config.has('themes.uri')) { return; } @@ -305,8 +321,8 @@ docsCoServer.install(server, () => { if (staticContent.hasOwnProperty(i) && themesUri.startsWith(i)) { let dir = staticContent[i].path + themesUri.substring(i.length); themesList = yield utils.listObjects(dir, true); - logger.debug('themes.json dir:%s', dir); - logger.debug('themes.json themesList:%j', themesList); + ctx.logger.debug('themes.json dir:%s', dir); + ctx.logger.debug('themes.json themesList:%j', themesList); for (let j = 0; j < themesList.length; ++j) { if (themesList[j].endsWith('.json')) { let data = yield utils.readFile(themesList[j], true); @@ -317,7 +333,7 @@ docsCoServer.install(server, () => { } } } catch (err) { - logger.error('themes.json error:%s', err.stack); + ctx.logger.error('themes.json error:%s', err.stack); } finally { if (themes.length > 0) { res.setHeader('Content-Type', 'application/json'); @@ -325,15 +341,15 @@ docsCoServer.install(server, () => { } else { res.sendStatus(404); } - logger.info('themes.json end'); + ctx.logger.info('themes.json end'); } }); }); }); process.on('uncaughtException', (err) => { - logger.error((new Date).toUTCString() + ' uncaughtException:', err.message); - logger.error(err.stack); + operationContext.global.logger.error((new Date).toUTCString() + ' uncaughtException:', err.message); + operationContext.global.logger.error(err.stack); logger.shutdown(() => { process.exit(1); }); diff --git a/DocService/sources/shutdown.js b/DocService/sources/shutdown.js index b24b9d14..49b92411 100644 --- a/DocService/sources/shutdown.js +++ b/DocService/sources/shutdown.js @@ -47,11 +47,11 @@ var WAIT_TIMEOUT = 30000; var LOOP_TIMEOUT = 1000; var EXEC_TIMEOUT = WAIT_TIMEOUT + utils.CONVERTION_TIMEOUT; -exports.shutdown = function(editorData, status) { +exports.shutdown = function(ctx, editorData, status) { return co(function*() { var res = true; try { - logger.debug('shutdown start:' + EXEC_TIMEOUT); + ctx.logger.debug('shutdown start:' + EXEC_TIMEOUT); //redisKeyShutdown не простой счетчик, чтобы его не уменьшала сборка, которая началась перед запуском Shutdown //сбрасываем redisKeyShutdown на всякий случай, если предыдущий запуск не дошел до конца @@ -60,24 +60,24 @@ exports.shutdown = function(editorData, status) { var pubsub = new pubsubService(); yield pubsub.initPromise(); //inner ping to update presence - logger.debug('shutdown pubsub shutdown message'); - pubsub.publish(JSON.stringify({type: commonDefines.c_oPublishType.shutdown, status: status})); + ctx.logger.debug('shutdown pubsub shutdown message'); + pubsub.publish(JSON.stringify({type: commonDefines.c_oPublishType.shutdown, ctx: ctx, status: status})); //wait while pubsub deliver and start conversion - logger.debug('shutdown start wait pubsub deliver'); + ctx.logger.debug('shutdown start wait pubsub deliver'); var startTime = new Date().getTime(); var isStartWait = true; while (true) { var curTime = new Date().getTime() - startTime; if (isStartWait && curTime >= WAIT_TIMEOUT) { isStartWait = false; - logger.debug('shutdown stop wait pubsub deliver'); + ctx.logger.debug('shutdown stop wait pubsub deliver'); } else if (curTime >= EXEC_TIMEOUT) { res = false; - logger.debug('shutdown timeout'); + ctx.logger.debug('shutdown timeout'); break; } var remainingFiles = yield editorData.getShutdownCount(redisKeyShutdown); - logger.debug('shutdown remaining files:%d', remainingFiles); + ctx.logger.debug('shutdown remaining files:%d', remainingFiles); if (!isStartWait && remainingFiles <= 0) { break; } @@ -88,10 +88,10 @@ exports.shutdown = function(editorData, status) { yield editorData.cleanupShutdown(redisKeyShutdown); yield pubsub.close(); - logger.debug('shutdown end'); + ctx.logger.debug('shutdown end'); } catch (e) { res = false; - logger.error('shutdown error:\r\n%s', e.stack); + ctx.logger.error('shutdown error: %s', e.stack); } return res; }); diff --git a/DocService/sources/taskresult.js b/DocService/sources/taskresult.js index 840f1ef5..a1d657ea 100644 --- a/DocService/sources/taskresult.js +++ b/DocService/sources/taskresult.js @@ -34,9 +34,9 @@ const crypto = require('crypto'); var sqlBase = require('./baseConnector'); -var logger = require('./../../Common/sources/logger'); var utils = require('./../../Common/sources/utils'); var constants = require('./../../Common/sources/constants'); +var tenantManager = require('./../../Common/sources/tenantManager'); let addSqlParam = sqlBase.baseConnector.addSqlParameter; let concatParams = sqlBase.baseConnector.concatParams; @@ -56,6 +56,7 @@ var FileStatus = { }; function TaskResultData() { + this.tenant = null; this.key = null; this.status = null; this.statusInfo = null; @@ -71,6 +72,9 @@ function TaskResultData() { this.innerPasswordChange = null;//not a DB field } TaskResultData.prototype.completeDefaults = function() { + if (!this.tenant) { + this.tenant = tenantManager.getDefautTenant(); + } if (!this.key) { this.key = ''; } @@ -100,16 +104,17 @@ TaskResultData.prototype.completeDefaults = function() { } }; -function upsert(task, opt_updateUserIndex) { - return sqlBase.baseConnector.upsert(task, opt_updateUserIndex); +function upsert(ctx, task, opt_updateUserIndex) { + return sqlBase.baseConnector.upsert(ctx, task, opt_updateUserIndex); } -function select(docId) { +function select(ctx, docId) { return new Promise(function(resolve, reject) { let values = []; - let sqlParam = addSqlParam(docId, values); - let sqlCommand = `SELECT * FROM task_result WHERE id=${sqlParam};`; - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let p1 = addSqlParam(ctx.tenant, values); + let p2 = addSqlParam(docId, values); + let sqlCommand = `SELECT * FROM task_result WHERE tenant=${p1} AND id=${p2};`; + sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { @@ -170,14 +175,15 @@ function toUpdateArray(task, updateTime, isMask, values, setPassword) { return res; } -function update(task, setPassword) { +function update(ctx, task, setPassword) { return new Promise(function(resolve, reject) { let 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};`; - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let p1 = addSqlParam(task.tenant, values); + let p2 = addSqlParam(task.key, values); + let sqlCommand = `UPDATE task_result SET ${sqlSet} WHERE tenant=${p1} AND id=${p2};`; + sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { @@ -187,16 +193,17 @@ function update(task, setPassword) { }); } -function updateIf(task, mask) { +function updateIf(ctx, task, mask) { return new Promise(function(resolve, reject) { let values = []; let commandArg = toUpdateArray(task, true, false, values, false); let commandArgMask = toUpdateArray(mask, false, true, values, false); + commandArgMask.push('tenant=' + addSqlParam(mask.tenant, values)); commandArgMask.push('id=' + addSqlParam(mask.key, values)); let sqlSet = commandArg.join(', '); let sqlWhere = commandArgMask.join(' AND '); let sqlCommand = `UPDATE task_result SET ${sqlSet} WHERE ${sqlWhere};`; - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { @@ -205,28 +212,30 @@ function updateIf(task, mask) { }, undefined, undefined, values); }); } -function restoreInitialPassword(docId) { - return select(docId).then(function(selectRes) { +function restoreInitialPassword(ctx, docId) { + return select(ctx, docId).then(function(selectRes) { if (selectRes.length > 0) { var row = selectRes[0]; - let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(docId, row.password); + let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(ctx, row.password); var updateTask = new TaskResultData(); + updateTask.tenant = ctx.tenant; updateTask.key = docId; if (docPassword.initial) { var documentPassword = new sqlBase.DocumentPassword(); documentPassword.fromValues(docPassword.initial); updateTask.password = documentPassword.toSQLInsert(); - return update(updateTask, true); + return update(ctx, updateTask, true); } else if (docPassword.current) { updateTask.password = null; - return update(updateTask, true); + return update(ctx, updateTask, true); } } }); } -function addRandomKey(task, opt_prefix, opt_size) { +function addRandomKey(ctx, task, opt_prefix, opt_size) { return new Promise(function(resolve, reject) { + task.tenant = ctx.tenant; if (undefined !== opt_prefix && undefined !== opt_size) { task.key = opt_prefix + crypto.randomBytes(opt_size).toString("hex"); } else { @@ -234,6 +243,7 @@ function addRandomKey(task, opt_prefix, opt_size) { } task.completeDefaults(); let values = []; + let p0 = addSqlParam(task.tenant, values); let p1 = addSqlParam(task.key, values); let p2 = addSqlParam(task.status, values); let p3 = addSqlParam(task.statusInfo, values); @@ -242,9 +252,9 @@ function addRandomKey(task, opt_prefix, opt_size) { let p6 = addSqlParam(task.changeId, values); let p7 = addSqlParam(task.callback, values); let p8 = addSqlParam(task.baseurl, values); - let sqlCommand = 'INSERT INTO task_result (id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)' + - ` VALUES (${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8});`; - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let sqlCommand = 'INSERT INTO task_result (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)' + + ` VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8});`; + sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { @@ -253,8 +263,9 @@ function addRandomKey(task, opt_prefix, opt_size) { }, undefined, undefined, values); }); } -function* addRandomKeyTask(key, opt_prefix, opt_size) { +function* addRandomKeyTask(ctx, key, opt_prefix, opt_size) { var task = new TaskResultData(); + task.tenant = ctx.tenant; task.key = key; task.status = FileStatus.WaitQueue; //nTryCount чтобы не зависнуть если реально будут проблемы с DB @@ -262,7 +273,7 @@ function* addRandomKeyTask(key, opt_prefix, opt_size) { var addRes = null; while (nTryCount-- > 0) { try { - addRes = yield addRandomKey(task, opt_prefix, opt_size); + addRes = yield addRandomKey(ctx, task, opt_prefix, opt_size); } catch (e) { addRes = null; //key exist, try again @@ -278,12 +289,13 @@ function* addRandomKeyTask(key, opt_prefix, opt_size) { } } -function remove(docId) { +function remove(ctx, docId) { return new Promise(function(resolve, reject) { let values = []; - let sqlParam = addSqlParam(docId, values); - const sqlCommand = `DELETE FROM task_result WHERE id=${sqlParam};`; - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let p1 = addSqlParam(ctx.tenant, values); + let p2 = addSqlParam(docId, values); + const sqlCommand = `DELETE FROM task_result WHERE tenant=${p1} AND id=${p2};`; + sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { @@ -292,14 +304,15 @@ function remove(docId) { }, undefined, undefined, values); }); } -function removeIf(mask) { +function removeIf(ctx, mask) { return new Promise(function(resolve, reject) { let values = []; let commandArgMask = toUpdateArray(mask, false, true, values, false); + commandArgMask.push('tenant=' + addSqlParam(mask.tenant, values)); commandArgMask.push('id=' + addSqlParam(mask.key, values)); let sqlWhere = commandArgMask.join(' AND '); const sqlCommand = `DELETE FROM task_result WHERE ${sqlWhere};`; - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { @@ -308,7 +321,7 @@ function removeIf(mask) { }, undefined, undefined, values); }); } -function getExpired(maxCount, expireSeconds) { +function getExpired(ctx, maxCount, expireSeconds) { return new Promise(function(resolve, reject) { let values = []; let expireDate = new Date(); @@ -316,8 +329,8 @@ function getExpired(maxCount, expireSeconds) { let sqlParam1 = addSqlParam(expireDate, values); let sqlParam2 = addSqlParam(maxCount, values); let sqlCommand = `SELECT * FROM task_result WHERE last_open_date <= ${sqlParam1}` + - ` AND NOT EXISTS(SELECT id FROM doc_changes WHERE doc_changes.id = task_result.id LIMIT 1) LIMIT ${sqlParam2};`; - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + ` AND NOT EXISTS(SELECT tenant, id FROM doc_changes WHERE doc_changes.tenant = task_result.tenant AND doc_changes.id = task_result.id LIMIT 1) LIMIT ${sqlParam2};`; + sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { diff --git a/DocService/sources/wopiClient.js b/DocService/sources/wopiClient.js index c94456b8..bcad1b7b 100644 --- a/DocService/sources/wopiClient.js +++ b/DocService/sources/wopiClient.js @@ -44,13 +44,14 @@ const logger = require('./../../Common/sources/logger'); const utils = require('./../../Common/sources/utils'); const constants = require('./../../Common/sources/constants'); const commonDefines = require('./../../Common/sources/commondefines'); +const operationContext = require('./../../Common/sources/operationContext'); +const tenantManager = require('./../../Common/sources/tenantManager'); const sqlBase = require('./baseConnector'); const taskResult = require('./taskresult'); const canvasService = require('./canvasservice'); const cfgTokenOutboxAlgorithm = config.get('services.CoAuthoring.token.outbox.algorithm'); const cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires'); -const cfgSignatureSecretOutbox = config.get('services.CoAuthoring.secret.outbox'); const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); const cfgCallbackRequestTimeout = config.get('services.CoAuthoring.server.callbackRequestTimeout'); const cfgDownloadTimeout = config.get('FileConverter.converter.downloadTimeout'); @@ -97,8 +98,10 @@ let mimeTypesByExt = (function() { function discovery(req, res) { return co(function*() { let output = ''; + let ctx = new operationContext.Context(); try { - logger.info('wopiDiscovery start'); + ctx.initFromRequest(req); + ctx.logger.info('wopiDiscovery start'); let baseUrl = cfgWopiHost || utils.getBaseUrlByRequest(req); let names = ['Word','Excel','PowerPoint']; let favIconUrls = [cfgWopiFavIconUrlWord, cfgWopiFavIconUrlCell, cfgWopiFavIconUrlSlide]; @@ -179,11 +182,11 @@ function discovery(req, res) { } output += `${proofKey}`; } catch (err) { - logger.error('wopiDiscovery error:%s', err.stack); + ctx.logger.error('wopiDiscovery error:%s', err.stack); } finally { res.setHeader('Content-Type', 'text/xml'); res.send(output); - logger.info('wopiDiscovery end'); + ctx.logger.info('wopiDiscovery end'); } }); } @@ -193,13 +196,15 @@ function collaboraCapabilities(req, res) { "convert-to": {"available": false}, "hasMobileSupport": true, "hasProxyPrefix": false, "hasTemplateSaveAs": false, "hasTemplateSource": true, "productVersion": commonDefines.buildVersion }; + let ctx = new operationContext.Context(); try { - logger.info('collaboraCapabilities start'); + ctx.initFromRequest(req); + ctx.logger.info('collaboraCapabilities start'); } catch (err) { - logger.error('collaboraCapabilities error:%s', err.stack); + ctx.logger.error('collaboraCapabilities error:%s', err.stack); } finally { utils.fillResponseSimple(res, JSON.stringify(output), "application/json"); - logger.info('collaboraCapabilities end'); + ctx.logger.info('collaboraCapabilities end'); } }); } @@ -233,7 +238,7 @@ function getLastModifiedTimeFromCallbacks(callbacks) { function isCorrectUserAuth(userAuth) { return undefined !== userAuth.wopiSrc; } -function parseWopiCallback(docId, userAuthStr, opt_url) { +function parseWopiCallback(ctx, userAuthStr, opt_url) { let wopiParams = null; if (isWopiCallback(userAuthStr)) { let userAuth = JSON.parse(userAuthStr); @@ -243,58 +248,59 @@ function parseWopiCallback(docId, userAuthStr, opt_url) { let commonInfo = null; let lastModifiedTime = null; if (opt_url) { - let commonInfoStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, opt_url, 1); + let commonInfoStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, opt_url, 1); if (isWopiCallback(commonInfoStr)) { commonInfo = JSON.parse(commonInfoStr); lastModifiedTime = commonInfo.fileInfo.LastModifiedTime; if (lastModifiedTime) { - let callbacks = sqlBase.UserCallback.prototype.getCallbacks(docId, opt_url); + let callbacks = sqlBase.UserCallback.prototype.getCallbacks(ctx, opt_url); lastModifiedTime = getLastModifiedTimeFromCallbacks(callbacks); } } } wopiParams = {commonInfo: commonInfo, userAuth: userAuth, LastModifiedTime: lastModifiedTime}; - logger.debug('parseWopiCallback wopiParams:%j', wopiParams); + ctx.logger.debug('parseWopiCallback wopiParams:%j', wopiParams); } return wopiParams; } -function checkAndInvalidateCache(docId, fileInfo) { +function checkAndInvalidateCache(ctx, docId, fileInfo) { return co(function*() { let res = {success: true, lockId: undefined}; - let selectRes = yield taskResult.select(docId); + let selectRes = yield taskResult.select(ctx, docId); if (selectRes.length > 0) { let row = selectRes[0]; if (row.callback) { - let commonInfoStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, row.callback, 1); + let commonInfoStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback, 1); if (isWopiCallback(commonInfoStr)) { let commonInfo = JSON.parse(commonInfoStr); res.lockId = commonInfo.lockId; - logger.debug('wopiEditor lockId from DB lockId=%s', res.lockId); - let unlockMarkStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(docId, row.callback); - logger.debug('wopiEditor commonInfoStr=%s', commonInfoStr); - logger.debug('wopiEditor unlockMarkStr=%s', unlockMarkStr); + ctx.logger.debug('wopiEditor lockId from DB lockId=%s', res.lockId); + let unlockMarkStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + ctx.logger.debug('wopiEditor commonInfoStr=%s', commonInfoStr); + ctx.logger.debug('wopiEditor unlockMarkStr=%s', unlockMarkStr); let hasUnlockMarker = isWopiUnlockMarker(unlockMarkStr); - logger.debug('wopiEditor hasUnlockMarker=%s', hasUnlockMarker); + ctx.logger.debug('wopiEditor hasUnlockMarker=%s', hasUnlockMarker); if (hasUnlockMarker) { let fileInfoVersion = fileInfo.Version; let cacheVersion = commonInfo.fileInfo.Version; let fileInfoModified = fileInfo.LastModifiedTime; let cacheModified = commonInfo.fileInfo.LastModifiedTime; - logger.debug('wopiEditor version fileInfo=%s; cache=%s', fileInfoVersion, cacheVersion); - logger.debug('wopiEditor LastModifiedTime fileInfo=%s; cache=%s', fileInfoModified, cacheModified); + ctx.logger.debug('wopiEditor version fileInfo=%s; cache=%s', fileInfoVersion, cacheVersion); + ctx.logger.debug('wopiEditor LastModifiedTime fileInfo=%s; cache=%s', fileInfoModified, cacheModified); if (fileInfoVersion !== cacheVersion || (fileInfoModified !== cacheModified)) { var mask = new taskResult.TaskResultData(); + mask.tenant = ctx.tenant; mask.key = docId; mask.last_open_date = row.last_open_date; //cleanupRes can be false in case of simultaneous opening. it is OK - let cleanupRes = yield canvasService.cleanupCacheIf(mask); - logger.debug('wopiEditor cleanupRes=%s', cleanupRes); + let cleanupRes = yield canvasService.cleanupCacheIf(ctx, mask); + ctx.logger.debug('wopiEditor cleanupRes=%s', cleanupRes); res.lockId = undefined; } } } else { res.success = false; - logger.warn('wopiEditor attempt to open not wopi record'); + ctx.logger.warn('wopiEditor attempt to open not wopi record'); } } } @@ -304,14 +310,19 @@ function checkAndInvalidateCache(docId, fileInfo) { function getEditorHtml(req, res) { return co(function*() { let params = {key: undefined, fileInfo: {}, userAuth: {}, queryParams: req.query, token: undefined, documentType: undefined}; + let ctx = new operationContext.Context(); try { - logger.info('wopiEditor start'); - logger.debug(`wopiEditor req.url:%s`, req.url); - logger.debug(`wopiEditor req.query:%j`, req.query); - logger.debug(`wopiEditor req.body:%j`, req.body); + ctx.initFromRequest(req); + let wopiSrc = req.query['wopisrc']; + let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1); + ctx.setDocId(fileId); + + ctx.logger.info('wopiEditor start'); + ctx.logger.debug(`wopiEditor req.url:%s`, req.url); + ctx.logger.debug(`wopiEditor req.query:%j`, req.query); + ctx.logger.debug(`wopiEditor req.body:%j`, req.body); params.documentType = req.params.documentType; let mode = req.params.mode; - let wopiSrc = req.query['wopisrc']; let sc = req.query['sc']; let hostSessionId = req.query['hid']; let access_token = req.body['access_token'] || ""; @@ -319,7 +330,7 @@ function getEditorHtml(req, res) { let uri = `${encodeURI(wopiSrc)}?access_token=${encodeURIComponent(access_token)}`; - let fileInfo = params.fileInfo = yield checkFileInfo(uri, access_token, sc); + let fileInfo = params.fileInfo = yield checkFileInfo(ctx, uri, access_token, sc); if (!fileInfo) { params.fileInfo = {}; return; @@ -330,7 +341,6 @@ function getEditorHtml(req, res) { } //docId let docId = undefined; - let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1); if ('view' !== mode) { docId = `${fileId}`; } else { @@ -344,7 +354,7 @@ function getEditorHtml(req, res) { } } docId = docId.replace(constants.DOC_ID_REPLACE_REGEX, '_').substring(0, constants.DOC_ID_MAX_LENGTH); - logger.debug(`wopiEditor docId=%s`, docId); + ctx.logger.debug(`wopiEditor`); params.key = docId; let userAuth = params.userAuth = { wopiSrc: wopiSrc, access_token: access_token, access_token_ttl: access_token_ttl, @@ -352,7 +362,7 @@ function getEditorHtml(req, res) { }; //check and invalidate cache - let checkRes = yield checkAndInvalidateCache(docId, fileInfo); + let checkRes = yield checkAndInvalidateCache(ctx, docId, fileInfo); let lockId = checkRes.lockId; if (!checkRes.success) { params.fileInfo = {}; @@ -364,12 +374,12 @@ function getEditorHtml(req, res) { fileType = fileInfo.FileExtension ? fileInfo.FileExtension.substr(1) : fileType; lockId = crypto.randomBytes(16).toString('base64'); let commonInfo = JSON.stringify({lockId: lockId, fileInfo: fileInfo}); - yield canvasService.commandOpenStartPromise(docId, utils.getBaseUrlByRequest(req), 1, commonInfo, fileType); + yield canvasService.commandOpenStartPromise(ctx, docId, utils.getBaseUrlByRequest(req), 1, commonInfo, fileType); } //Lock if ('view' !== mode) { - let lockRes = yield lock('LOCK', lockId, fileInfo, userAuth); + let lockRes = yield lock(ctx, 'LOCK', lockId, fileInfo, userAuth); if (!lockRes) { params.fileInfo = {}; return; @@ -384,36 +394,36 @@ function getEditorHtml(req, res) { if (cfgTokenEnableBrowser) { let options = {algorithm: cfgTokenOutboxAlgorithm, expiresIn: cfgTokenOutboxExpires}; - let secret = utils.getSecretByElem(cfgSignatureSecretOutbox); + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Inbox); params.token = jwt.sign(params, secret, options); } } catch (err) { - logger.error('wopiEditor error:%s', err.stack); + ctx.logger.error('wopiEditor error:%s', err.stack); params.fileInfo = {}; } finally { - logger.debug('wopiEditor render params=%j', params); + ctx.logger.debug('wopiEditor render params=%j', params); try { res.render("editor-wopi", params); } catch (err) { - logger.error('wopiEditor error:%s', err.stack); + ctx.logger.error('wopiEditor error:%s', err.stack); res.sendStatus(400); } - logger.info('wopiEditor end'); + ctx.logger.info('wopiEditor end'); } }); } -function putFile(wopiParams, data, dataStream, dataSize, userLastChangeId, isModifiedByUser, isAutosave, isExitSave) { +function putFile(ctx, wopiParams, data, dataStream, dataSize, userLastChangeId, isModifiedByUser, isAutosave, isExitSave) { return co(function* () { let postRes = null; try { - logger.info('wopi PutFile start'); + ctx.logger.info('wopi PutFile start'); if (!wopiParams.userAuth) { return postRes; } let fileInfo = wopiParams.commonInfo.fileInfo; let userAuth = wopiParams.userAuth; let uri = `${userAuth.wopiSrc}/contents?access_token=${userAuth.access_token}`; - let filterStatus = yield checkIpFilter(uri); + let filterStatus = yield checkIpFilter(ctx, uri); if (0 !== filterStatus) { return postRes; } @@ -432,33 +442,33 @@ function putFile(wopiParams, data, dataStream, dataSize, userLastChangeId, isMod headers['X-LOOL-WOPI-Timestamp'] = wopiParams.LastModifiedTime; } - logger.debug('wopi PutFile request uri=%s headers=%j', uri, headers); + ctx.logger.debug('wopi PutFile request uri=%s headers=%j', uri, headers); postRes = yield utils.postRequestPromise(uri, data, dataStream, dataSize, cfgCallbackRequestTimeout, undefined, headers); - logger.debug('wopi PutFile response headers=%j', postRes.response.headers); - logger.debug('wopi PutFile response body:%s', postRes.body); + ctx.logger.debug('wopi PutFile response headers=%j', postRes.response.headers); + ctx.logger.debug('wopi PutFile response body:%s', postRes.body); } else { - logger.warn('wopi SupportsUpdate = false or UserCanWrite = false'); + ctx.logger.warn('wopi SupportsUpdate = false or UserCanWrite = false'); } } catch (err) { - logger.error('wopi error PutFile:%s', err.stack); + ctx.logger.error('wopi error PutFile:%s', err.stack); } finally { - logger.info('wopi PutFile end'); + ctx.logger.info('wopi PutFile end'); } return postRes; }); } -function renameFile(wopiParams, name) { +function renameFile(ctx, wopiParams, name) { return co(function* () { - let res; + let res = undefined; try { - logger.info('wopi RenameFile start'); + ctx.logger.info('wopi RenameFile start'); if (!wopiParams.userAuth) { return res; } let fileInfo = wopiParams.commonInfo.fileInfo; let userAuth = wopiParams.userAuth; let uri = `${userAuth.wopiSrc}?access_token=${userAuth.access_token}`; - let filterStatus = yield checkIpFilter(uri); + let filterStatus = yield checkIpFilter(ctx, uri); if (0 !== filterStatus) { return res; } @@ -471,9 +481,9 @@ function renameFile(wopiParams, name) { let headers = {'X-WOPI-Override': 'RENAME_FILE', 'X-WOPI-Lock': commonInfo.lockId, 'X-WOPI-RequestedName': utf7.encode(name)}; fillStandardHeaders(headers, uri, userAuth.access_token); - logger.debug('wopi RenameFile request uri=%s headers=%j', uri, headers); + ctx.logger.debug('wopi RenameFile request uri=%s headers=%j', uri, headers); let postRes = yield utils.postRequestPromise(uri, undefined, undefined, undefined, cfgCallbackRequestTimeout, undefined, headers); - logger.debug('wopi RenameFile response headers=%j body=%s', postRes.response.headers, postRes.body); + ctx.logger.debug('wopi RenameFile response headers=%j body=%s', postRes.response.headers, postRes.body); if (postRes.body) { res = JSON.parse(postRes.body); } else { @@ -481,22 +491,22 @@ function renameFile(wopiParams, name) { res = {"Name": name}; } } else { - logger.info('wopi SupportsRename = false'); + ctx.logger.info('wopi SupportsRename = false'); } } catch (err) { - logger.error('wopi error RenameFile:%s', err.stack); + ctx.logger.error('wopi error RenameFile:%s', err.stack); } finally { - logger.info('wopi RenameFile end'); + ctx.logger.info('wopi RenameFile end'); } return res; }); } -function checkFileInfo(uri, access_token, sc) { +function checkFileInfo(ctx, uri, access_token, sc) { return co(function* () { - let fileInfo; + let fileInfo = undefined; try { - logger.info('wopi checkFileInfo start'); - let filterStatus = yield checkIpFilter(uri); + ctx.logger.info('wopi checkFileInfo start'); + let filterStatus = yield checkIpFilter(ctx, uri); if (0 !== filterStatus) { return fileInfo; } @@ -505,23 +515,23 @@ function checkFileInfo(uri, access_token, sc) { headers['X-WOPI-SessionContext'] = sc; } fillStandardHeaders(headers, uri, access_token); - logger.debug('wopi checkFileInfo request uri=%s headers=%j', uri, headers); - let getRes = yield utils.downloadUrlPromise(uri, cfgDownloadTimeout, undefined, undefined, false, headers); - logger.debug(`wopi checkFileInfo headers=%j body=%s`, getRes.response.headers, getRes.body); + ctx.logger.debug('wopi checkFileInfo request uri=%s headers=%j', uri, headers); + let getRes = yield utils.downloadUrlPromise(ctx, uri, cfgDownloadTimeout, undefined, undefined, false, headers); + ctx.logger.debug(`wopi checkFileInfo headers=%j body=%s`, getRes.response.headers, getRes.body); fileInfo = JSON.parse(getRes.body); } catch (err) { - logger.error('wopi error checkFileInfo:%s', err.stack); + ctx.logger.error('wopi error checkFileInfo:%s', err.stack); } finally { - logger.info('wopi checkFileInfo end'); + ctx.logger.info('wopi checkFileInfo end'); } return fileInfo; }); } -function lock(command, lockId, fileInfo, userAuth) { +function lock(ctx, command, lockId, fileInfo, userAuth) { return co(function* () { let res = true; try { - logger.info('wopi %s start', command); + ctx.logger.info('wopi %s start', command); if (fileInfo && fileInfo.SupportsLocks) { if (!userAuth) { return false; @@ -529,32 +539,32 @@ function lock(command, lockId, fileInfo, userAuth) { let wopiSrc = userAuth.wopiSrc; let access_token = userAuth.access_token; let uri = `${wopiSrc}?access_token=${access_token}`; - let filterStatus = yield checkIpFilter(uri); + let filterStatus = yield checkIpFilter(ctx, uri); if (0 !== filterStatus) { return false; } let headers = {"X-WOPI-Override": command, "X-WOPI-Lock": lockId}; fillStandardHeaders(headers, uri, access_token); - logger.debug('wopi %s request uri=%s headers=%j', command, uri, headers); + ctx.logger.debug('wopi %s request uri=%s headers=%j', command, uri, headers); let postRes = yield utils.postRequestPromise(uri, undefined, undefined, undefined, cfgCallbackRequestTimeout, undefined, headers); - logger.debug('wopi %s response headers=%j', command, postRes.response.headers); + ctx.logger.debug('wopi %s response headers=%j', command, postRes.response.headers); } else { - logger.info('wopi %s SupportsLocks = false', command); + ctx.logger.info('wopi %s SupportsLocks = false', command); } } catch (err) { res = false; - logger.error('wopi error %s:%s', command, err.stack); + ctx.logger.error('wopi error %s:%s', command, err.stack); } finally { - logger.info('wopi %s end', command); + ctx.logger.info('wopi %s end', command); } return res; }); } -function unlock(wopiParams) { +function unlock(ctx, wopiParams) { return co(function* () { try { - logger.info('wopi Unlock start'); + ctx.logger.info('wopi Unlock start'); let fileInfo = wopiParams.commonInfo.fileInfo; if (fileInfo && fileInfo.SupportsLocks) { if (!wopiParams.userAuth) { @@ -564,23 +574,23 @@ function unlock(wopiParams) { let lockId = wopiParams.commonInfo.lockId; let access_token = wopiParams.userAuth.access_token; let uri = `${wopiSrc}?access_token=${access_token}`; - let filterStatus = yield checkIpFilter(uri); + let filterStatus = yield checkIpFilter(ctx, uri); if (0 !== filterStatus) { return; } let headers = {"X-WOPI-Override": "UNLOCK", "X-WOPI-Lock": lockId}; fillStandardHeaders(headers, uri, access_token); - logger.debug('wopi Unlock request uri=%s headers=%j', uri, headers); + ctx.logger.debug('wopi Unlock request uri=%s headers=%j', uri, headers); let postRes = yield utils.postRequestPromise(uri, undefined, undefined, undefined, cfgCallbackRequestTimeout, undefined, headers); - logger.debug('wopi Unlock response headers=%j', postRes.response.headers); + ctx.logger.debug('wopi Unlock response headers=%j', postRes.response.headers); } else { - logger.info('wopi SupportsLocks = false'); + ctx.logger.info('wopi SupportsLocks = false'); } } catch (err) { - logger.error('wopi error Unlock:%s', err.stack); + ctx.logger.error('wopi error Unlock:%s', err.stack); } finally { - logger.info('wopi Unlock end'); + ctx.logger.info('wopi Unlock end'); } }); } @@ -630,12 +640,12 @@ function fillStandardHeaders(headers, url, access_token) { headers['Authorization'] = `Bearer ${access_token}`; } -function checkIpFilter(uri){ +function checkIpFilter(ctx, uri){ return co(function* () { let urlParsed = new URL(uri); - let filterStatus = yield* utils.checkHostFilter(urlParsed.hostname); + let filterStatus = yield* utils.checkHostFilter(ctx, urlParsed.hostname); if (0 !== filterStatus) { - logger.warn('wopi checkIpFilter error: url = %s', uri); + ctx.logger.warn('wopi checkIpFilter error: url = %s', uri); } return filterStatus; }); diff --git a/FileConverter/sources/converter.js b/FileConverter/sources/converter.js index 49b39852..8abc168d 100644 --- a/FileConverter/sources/converter.js +++ b/FileConverter/sources/converter.js @@ -53,6 +53,8 @@ const wopiClient = require('./../../DocService/sources/wopiClient'); var statsDClient = require('./../../Common/sources/statsdclient'); var queueService = require('./../../Common/sources/taskqueueRabbitMQ'); const formatChecker = require('./../../Common/sources/formatchecker'); +const operationContext = require('./../../Common/sources/operationContext'); +const tenantManager = require('./../../Common/sources/tenantManager'); var cfgDownloadMaxBytes = configConverter.has('maxDownloadBytes') ? configConverter.get('maxDownloadBytes') : 100000000; var cfgDownloadTimeout = configConverter.has('downloadTimeout') ? configConverter.get('downloadTimeout') : 60; @@ -278,7 +280,7 @@ function getTempDir() { fs.mkdirSync(resultDir); return {temp: newTemp, source: sourceDir, result: resultDir}; } -function* replaceEmptyFile(docId, fileFrom, ext, _lcid) { +function* replaceEmptyFile(ctx, fileFrom, ext, _lcid) { if (!fs.existsSync(fileFrom) || 0 === fs.lstatSync(fileFrom).size) { let locale = 'en-US'; if (_lcid) { @@ -288,11 +290,11 @@ function* replaceEmptyFile(docId, fileFrom, ext, _lcid) { if (fs.existsSync(path.join(cfgNewFileTemplate, localeNew))) { locale = localeNew; } else { - logger.debug('replaceEmptyFile empty locale dir locale=%s (id=%s)', localeNew, docId); + ctx.logger.debug('replaceEmptyFile empty locale dir locale=%s', localeNew); } } } - logger.debug('replaceEmptyFile format=%s locale=%s (id=%s)', ext, locale, docId); + ctx.logger.debug('replaceEmptyFile format=%s locale=%s', ext, locale); let format = formatChecker.getFormatFromString(ext); if (formatChecker.isDocumentFormat(format)) { fs.copyFileSync(path.join(cfgNewFileTemplate, locale, 'new.docx'), fileFrom); @@ -303,25 +305,26 @@ function* replaceEmptyFile(docId, fileFrom, ext, _lcid) { } } } -function* downloadFile(docId, uri, fileFrom, withAuthorization, filterPrivate, opt_headers) { +function* downloadFile(ctx, uri, fileFrom, withAuthorization, filterPrivate, opt_headers) { var res = constants.CONVERT_DOWNLOAD; var data = null; var downloadAttemptCount = 0; var urlParsed = url.parse(uri); - var filterStatus = yield* utils.checkHostFilter(urlParsed.hostname); + var filterStatus = yield* utils.checkHostFilter(ctx, urlParsed.hostname); if (0 == filterStatus) { while (constants.NO_ERROR !== res && downloadAttemptCount++ < cfgDownloadAttemptMaxCount) { try { let authorization; - if (utils.canIncludeOutboxAuthorization(uri) && withAuthorization) { - authorization = utils.fillJwtForRequest({url: uri}, false); + if (utils.canIncludeOutboxAuthorization(ctx, uri) && withAuthorization) { + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox); + authorization = utils.fillJwtForRequest({url: uri}, secret, false); } - let getRes = yield utils.downloadUrlPromise(uri, cfgDownloadTimeout, cfgDownloadMaxBytes, authorization, filterPrivate, opt_headers); + let getRes = yield utils.downloadUrlPromise(ctx, uri, cfgDownloadTimeout, cfgDownloadMaxBytes, authorization, filterPrivate, opt_headers); data = getRes.body; res = constants.NO_ERROR; } catch (err) { res = constants.CONVERT_DOWNLOAD; - logger.error('error downloadFile:url=%s;attempt=%d;code:%s;connect:%s;(id=%s)\r\n%s', uri, downloadAttemptCount, err.code, err.connect, docId, err.stack); + ctx.logger.error('error downloadFile:url=%s;attempt=%d;code:%s;connect:%s %s', uri, downloadAttemptCount, err.code, err.connect, err.stack); //not continue attempts if timeout if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') { break; @@ -334,18 +337,18 @@ function* downloadFile(docId, uri, fileFrom, withAuthorization, filterPrivate, o } } if (constants.NO_ERROR === res) { - logger.debug('downloadFile complete filesize=%d (id=%s)', data.length, docId); + ctx.logger.debug('downloadFile complete filesize=%d', data.length); fs.writeFileSync(fileFrom, data); } } else { - logger.error('checkIpFilter error:url=%s;code:%s;(id=%s)', uri, filterStatus, docId); + ctx.logger.error('checkIpFilter error:url=%s;code:%s;', uri, filterStatus); res = constants.CONVERT_DOWNLOAD; } return res; } -function* downloadFileFromStorage(id, strPath, dir) { - var list = yield storage.listObjects(strPath); - logger.debug('downloadFileFromStorage list %s (id=%s)', list.toString(), id); +function* downloadFileFromStorage(ctx, strPath, dir) { + var list = yield storage.listObjects(ctx, strPath); + ctx.logger.debug('downloadFileFromStorage list %s', list.toString()); //create dirs var dirsToCreate = []; var dirStruct = {}; @@ -371,36 +374,36 @@ function* downloadFileFromStorage(id, strPath, dir) { for (var i = 0; i < list.length; ++i) { var file = list[i]; var fileRel = storage.getRelativePath(strPath, file); - var data = yield storage.getObject(file); + var data = yield storage.getObject(ctx, file); fs.writeFileSync(path.join(dir, fileRel), data); } } -function* processDownloadFromStorage(dataConvert, cmd, task, tempDirs, authorProps) { +function* processDownloadFromStorage(ctx, dataConvert, cmd, task, tempDirs, authorProps) { let res = constants.NO_ERROR; let needConcatFiles = false; if (task.getFromOrigin() || task.getFromSettings()) { dataConvert.fileFrom = path.join(tempDirs.source, 'origin.' + cmd.getFormat()); } else { //перезаписываем некоторые файлы из m_sKey(например Editor.bin или changes) - yield* downloadFileFromStorage(cmd.getSaveKey(), cmd.getSaveKey(), tempDirs.source); + yield* downloadFileFromStorage(ctx, cmd.getSaveKey(), tempDirs.source); let format = cmd.getFormat() || 'bin'; dataConvert.fileFrom = path.join(tempDirs.source, 'Editor.' + format); needConcatFiles = true; } - if (!utils.checkPathTraversal(dataConvert.key, tempDirs.source, dataConvert.fileFrom)) { + if (!utils.checkPathTraversal(ctx, dataConvert.key, tempDirs.source, dataConvert.fileFrom)) { return constants.CONVERT_PARAMS; } //mail merge let mailMergeSend = cmd.getMailMergeSend(); if (mailMergeSend) { - yield* downloadFileFromStorage(mailMergeSend.getJsonKey(), mailMergeSend.getJsonKey(), tempDirs.source); + yield* downloadFileFromStorage(ctx, mailMergeSend.getJsonKey(), tempDirs.source); needConcatFiles = true; } if (needConcatFiles) { yield* concatFiles(tempDirs.source); } if (task.getFromChanges()) { - res = yield* processChanges(tempDirs, cmd, authorProps); + res = yield* processChanges(ctx, tempDirs, cmd, authorProps); } //todo rework if (!fs.existsSync(dataConvert.fileFrom)) { @@ -443,7 +446,7 @@ function* concatFiles(source) { } } -function* processChanges(tempDirs, cmd, authorProps) { +function* processChanges(ctx, tempDirs, cmd, authorProps) { let res = constants.NO_ERROR; let changesDir = path.join(tempDirs.source, constants.CHANGES_NAME); fs.mkdirSync(changesDir); @@ -472,13 +475,13 @@ function* processChanges(tempDirs, cmd, authorProps) { }]; } - let streamObj = yield* streamCreate(cmd.getDocId(), changesDir, indexFile++, {highWaterMark: cfgStreamWriterBufferSize}); + let streamObj = yield* streamCreate(ctx, changesDir, indexFile++, {highWaterMark: cfgStreamWriterBufferSize}); let curIndexStart = 0; let curIndexEnd = Math.min(curIndexStart + cfgMaxRequestChanges, forceSaveIndex); while (curIndexStart < curIndexEnd || extChanges) { let changes = []; if (curIndexStart < curIndexEnd) { - changes = yield baseConnector.getChangesPromise(cmd.getDocId(), curIndexStart, curIndexEnd, forceSaveTime); + changes = yield baseConnector.getChangesPromise(ctx, cmd.getDocId(), curIndexStart, curIndexEnd, forceSaveTime); } if (0 === changes.length && extChanges) { changes = extChanges; @@ -487,7 +490,7 @@ function* processChanges(tempDirs, cmd, authorProps) { for (let i = 0; i < changes.length; ++i) { let change = changes[i]; if (change.change_data.startsWith('ENCRYPTED;')) { - logger.warn('processChanges encrypted changes (id=%s)', cmd.getDocId()); + ctx.logger.warn('processChanges encrypted changes'); //todo sql request instead? res = constants.EDITOR_CHANGES; break; @@ -495,7 +498,7 @@ function* processChanges(tempDirs, cmd, authorProps) { if (null === changesAuthor || changesAuthor !== change.user_id_original) { if (null !== changesAuthor) { yield* streamEnd(streamObj, ']'); - streamObj = yield* streamCreate(cmd.getDocId(), changesDir, indexFile++); + streamObj = yield* streamCreate(ctx, changesDir, indexFile++); } let strDate = baseConnector.getDateTime(change.change_date); changesHistory.changes.push({'created': strDate, 'user': {'id': change.user_id_original, 'name': change.user_name}}); @@ -537,13 +540,13 @@ function* processChanges(tempDirs, cmd, authorProps) { return res; } -function* streamCreate(docId, changesDir, indexFile, opt_options) { +function* streamCreate(ctx, changesDir, indexFile, opt_options) { let fileName = constants.CHANGES_NAME + indexFile + '.json'; let filePath = path.join(changesDir, fileName); let writeStream = yield utils.promiseCreateWriteStream(filePath, opt_options); writeStream.on('error', function(err) { //todo integrate error handle in main thread (probable: set flag here and check it in main thread) - logger.error('WriteStreamError (id=%s)\r\n%s', docId, err.stack); + ctx.logger.error('WriteStreamError %s', err.stack); }); return {writeStream: writeStream, filePath: filePath, isNoChangesInFile: true}; } @@ -558,41 +561,41 @@ function* streamEnd(streamObj, text) { streamObj.writeStream.end(text, 'utf8'); yield utils.promiseWaitClose(streamObj.writeStream); } -function* processUploadToStorage(dir, storagePath) { +function* processUploadToStorage(ctx, dir, storagePath) { var list = yield utils.listObjects(dir); if (list.length < MAX_OPEN_FILES) { - yield* processUploadToStorageChunk(list, dir, storagePath); + yield* processUploadToStorageChunk(ctx, list, dir, storagePath); } else { for (var i = 0, j = list.length; i < j; i += MAX_OPEN_FILES) { - yield* processUploadToStorageChunk(list.slice(i, i + MAX_OPEN_FILES), dir, storagePath); + yield* processUploadToStorageChunk(ctx, list.slice(i, i + MAX_OPEN_FILES), dir, storagePath); } } } -function* processUploadToStorageChunk(list, dir, storagePath) { +function* processUploadToStorageChunk(ctx, list, dir, storagePath) { yield Promise.all(list.map(function (curValue) { let localValue = storagePath + '/' + curValue.substring(dir.length + 1); - return storage.uploadObject(localValue, curValue); + return storage.uploadObject(ctx, localValue, curValue); })); } -function writeProcessOutputToLog(docId, childRes, isDebug) { +function writeProcessOutputToLog(ctx, childRes, isDebug) { if (childRes) { if (undefined !== childRes.stdout) { if (isDebug) { - logger.debug('stdout (id=%s):%s', docId, childRes.stdout); + ctx.logger.debug('stdout:%s', childRes.stdout); } else { - logger.error('stdout (id=%s):%s', docId, childRes.stdout); + ctx.logger.error('stdout:%s', childRes.stdout); } } if (undefined !== childRes.stderr) { if (isDebug) { - logger.debug('stderr (id=%s):%s', docId, childRes.stderr); + ctx.logger.debug('stderr:%s', childRes.stderr); } else { - logger.error('stderr (id=%s):%s', docId, childRes.stderr); + ctx.logger.error('stderr:%s', childRes.stderr); } } } } -function* postProcess(cmd, dataConvert, tempDirs, childRes, error, isTimeout) { +function* postProcess(ctx, cmd, dataConvert, tempDirs, childRes, error, isTimeout) { var exitCode = 0; var exitSignal = null; if(childRes) { @@ -608,23 +611,23 @@ function* postProcess(cmd, dataConvert, tempDirs, childRes, error, isTimeout) { error = constants.CONVERT; } if (-1 !== exitCodesMinorError.indexOf(error)) { - writeProcessOutputToLog(dataConvert.key, childRes, true); - logger.debug('ExitCode (code=%d;signal=%s;error:%d;id=%s)', exitCode, exitSignal, error, dataConvert.key); + writeProcessOutputToLog(ctx, childRes, true); + ctx.logger.debug('ExitCode (code=%d;signal=%s;error:%d)', exitCode, exitSignal, error); } else { - writeProcessOutputToLog(dataConvert.key, childRes, false); - logger.error('ExitCode (code=%d;signal=%s;error:%d;id=%s)', exitCode, exitSignal, error, dataConvert.key); + writeProcessOutputToLog(ctx, childRes, false); + ctx.logger.error('ExitCode (code=%d;signal=%s;error:%d)', exitCode, exitSignal, error); if (cfgErrorFiles) { - yield* processUploadToStorage(tempDirs.temp, cfgErrorFiles + '/' + dataConvert.key); - logger.debug('processUploadToStorage error complete(id=%s)', dataConvert.key); + yield* processUploadToStorage(ctx, tempDirs.temp, cfgErrorFiles + '/' + dataConvert.key); + ctx.logger.debug('processUploadToStorage error complete(id=%s)', dataConvert.key); } } } else { - writeProcessOutputToLog(dataConvert.key, childRes, true); - logger.debug('ExitCode (code=%d;signal=%s;error:%d;id=%s)', exitCode, exitSignal, error, dataConvert.key); + writeProcessOutputToLog(ctx, childRes, true); + ctx.logger.debug('ExitCode (code=%d;signal=%s;error:%d)', exitCode, exitSignal, error); } if (-1 !== exitCodesUpload.indexOf(error)) { - yield* processUploadToStorage(tempDirs.result, dataConvert.key); - logger.debug('processUploadToStorage complete(id=%s)', dataConvert.key); + yield* processUploadToStorage(ctx, tempDirs.result, dataConvert.key); + ctx.logger.debug('processUploadToStorage complete'); } cmd.setStatusInfo(error); var existFile = false; @@ -651,13 +654,14 @@ function* postProcess(cmd, dataConvert, tempDirs, childRes, error, isTimeout) { cmd.setTitle(cmd.getOutputPath()); } - var res = new commonDefines.TaskQueueData(); - res.setCmd(cmd); - logger.debug('output (data=%s;id=%s)', JSON.stringify(res), dataConvert.key); - return res; + var queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); + queueData.setCmd(cmd); + ctx.logger.debug('output (data=%j)', queueData); + return queueData; } -function* spawnProcess(isBuilder, tempDirs, dataConvert, authorProps, getTaskTime, task, cmd) { +function* spawnProcess(ctx, isBuilder, tempDirs, dataConvert, authorProps, getTaskTime, task) { let childRes, isTimeout = false; let childArgs; if (cfgArgs.length > 0) { @@ -707,8 +711,11 @@ function* spawnProcess(isBuilder, tempDirs, dataConvert, authorProps, getTaskTim }, waitMS); childRes = yield spawnAsyncPromise; } catch (err) { - let fLog = null === err.status ? logger.error : logger.debug; - fLog.call(logger, 'error spawnAsync(id=%s)\r\n%s', cmd.getDocId(), err.stack); + if (null === err.status) { + ctx.logger.error('error spawnAsync %s', err.stack); + } else { + ctx.logger.debug('error spawnAsync %s', err.stack); + } childRes = err; } if (undefined !== timeoutId) { @@ -717,7 +724,7 @@ function* spawnProcess(isBuilder, tempDirs, dataConvert, authorProps, getTaskTim return {childRes: childRes, isTimeout: isTimeout}; } -function* ExecuteTask(task) { +function* ExecuteTask(ctx, task) { var startDate = null; var curDate = null; if(clientStatsD) { @@ -728,7 +735,7 @@ function* ExecuteTask(task) { var getTaskTime = new Date(); var cmd = task.getCmd(); var dataConvert = new TaskQueueDataConvert(task); - logger.debug('Start Task(id=%s)', dataConvert.key); + ctx.logger.info('Start Task'); var error = constants.NO_ERROR; tempDirs = getTempDir(); let fileTo = task.getToFile(); @@ -738,7 +745,7 @@ function* ExecuteTask(task) { if (cmd.getUrl()) { let format = cmd.getFormat(); dataConvert.fileFrom = path.join(tempDirs.source, dataConvert.key + '.' + format); - if (utils.checkPathTraversal(dataConvert.key, tempDirs.source, dataConvert.fileFrom)) { + if (utils.checkPathTraversal(ctx, dataConvert.key, tempDirs.source, dataConvert.fileFrom)) { let url = cmd.getUrl(); let withAuthorization = cmd.getWithAuthorization(); let filterPrivate = !withAuthorization; @@ -760,13 +767,13 @@ function* ExecuteTask(task) { headers = {'X-WOPI-MaxExpectedSize': cfgDownloadMaxBytes, 'X-WOPI-ItemVersion': fileInfo.Version}; wopiClient.fillStandardHeaders(headers, url, userAuth.access_token); } - logger.debug('wopi url=%s; headers=%j(id=%s)', url, headers, dataConvert.key); + ctx.logger.debug('wopi url=%s; headers=%j', url, headers); } if (undefined === fileSize || fileSize > 0) { - error = yield* downloadFile(dataConvert.key, url, dataConvert.fileFrom, withAuthorization, filterPrivate, headers); + error = yield* downloadFile(ctx, url, dataConvert.fileFrom, withAuthorization, filterPrivate, headers); } if (constants.NO_ERROR === error) { - yield* replaceEmptyFile(dataConvert.key, dataConvert.fileFrom, format, cmd.getLCID()); + yield* replaceEmptyFile(ctx, dataConvert.fileFrom, format, cmd.getLCID()); } if(clientStatsD) { clientStatsD.timing('conv.downloadFile', new Date() - curDate); @@ -776,16 +783,16 @@ function* ExecuteTask(task) { error = constants.CONVERT_PARAMS; } } else if (cmd.getSaveKey()) { - yield* downloadFileFromStorage(cmd.getDocId(), cmd.getDocId(), tempDirs.source); - logger.debug('downloadFileFromStorage complete(id=%s)', dataConvert.key); + yield* downloadFileFromStorage(ctx, cmd.getDocId(), tempDirs.source); + ctx.logger.debug('downloadFileFromStorage complete'); if(clientStatsD) { clientStatsD.timing('conv.downloadFileFromStorage', new Date() - curDate); curDate = new Date(); } - error = yield* processDownloadFromStorage(dataConvert, cmd, task, tempDirs, authorProps); + error = yield* processDownloadFromStorage(ctx, dataConvert, cmd, task, tempDirs, authorProps); } else if (cmd.getForgotten()) { - yield* downloadFileFromStorage(cmd.getDocId(), cmd.getForgotten(), tempDirs.source); - logger.debug('downloadFileFromStorage complete(id=%s)', dataConvert.key); + yield* downloadFileFromStorage(ctx, cmd.getForgotten(), tempDirs.source); + ctx.logger.debug('downloadFileFromStorage complete'); let list = yield utils.listObjects(tempDirs.source, false); if (list.length > 0) { dataConvert.fileFrom = list[0]; @@ -797,8 +804,8 @@ function* ExecuteTask(task) { } } else if (isBuilder) { //in cause script in POST body - yield* downloadFileFromStorage(cmd.getDocId(), cmd.getDocId(), tempDirs.source); - logger.debug('downloadFileFromStorage complete(id=%s)', dataConvert.key); + yield* downloadFileFromStorage(ctx, cmd.getDocId(), tempDirs.source); + ctx.logger.debug('downloadFileFromStorage complete'); let list = yield utils.listObjects(tempDirs.source, false); if (list.length > 0) { dataConvert.fileFrom = list[0]; @@ -813,16 +820,16 @@ function* ExecuteTask(task) { //todo заглушка.вся конвертация на клиенте, но нет простого механизма сохранения на клиенте yield utils.pipeFiles(dataConvert.fileFrom, dataConvert.fileTo); } else { - ({childRes, isTimeout} = yield* spawnProcess(isBuilder, tempDirs, dataConvert, authorProps, getTaskTime, task, cmd)); + ({childRes, isTimeout} = yield* spawnProcess(ctx, isBuilder, tempDirs, dataConvert, authorProps, getTaskTime, task)); if (childRes && 0 !== childRes.status && !isTimeout && task.getFromChanges() && constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML !== dataConvert.formatTo && !formatChecker.isOOXFormat(dataConvert.formatTo) && !cmd.getWopiParams()) { - logger.warn('rollback to save changes to ooxml. See assemblyFormatAsOrigin param. formatTo=%s (id=%s)', formatChecker.getStringFromFormat(dataConvert.formatTo), dataConvert.key); + ctx.logger.warn('rollback to save changes to ooxml. See assemblyFormatAsOrigin param. formatTo=%s', formatChecker.getStringFromFormat(dataConvert.formatTo)); let extOld = path.extname(dataConvert.fileTo); let extNew = '.' + formatChecker.getStringFromFormat(constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML); dataConvert.formatTo = constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML; dataConvert.fileTo = dataConvert.fileTo.slice(0, -extOld.length) + extNew; - ({childRes, isTimeout} = yield* spawnProcess(isBuilder, tempDirs, dataConvert, authorProps, getTaskTime, task, cmd)); + ({childRes, isTimeout} = yield* spawnProcess(ctx, isBuilder, tempDirs, dataConvert, authorProps, getTaskTime, task)); } } if(clientStatsD) { @@ -830,15 +837,15 @@ function* ExecuteTask(task) { curDate = new Date(); } } - resData = yield* postProcess(cmd, dataConvert, tempDirs, childRes, error, isTimeout); - logger.debug('postProcess (id=%s)', dataConvert.key); + resData = yield* postProcess(ctx, cmd, dataConvert, tempDirs, childRes, error, isTimeout); + ctx.logger.debug('postProcess'); if(clientStatsD) { clientStatsD.timing('conv.postProcess', new Date() - curDate); curDate = new Date(); } if (tempDirs) { fs.rmSync(tempDirs.temp, { recursive: true, force: true }); - logger.debug('deleteFolderRecursive (id=%s)', dataConvert.key); + ctx.logger.debug('deleteFolderRecursive'); if(clientStatsD) { clientStatsD.timing('conv.deleteFolderRecursive', new Date() - curDate); curDate = new Date(); @@ -847,6 +854,7 @@ function* ExecuteTask(task) { if(clientStatsD) { clientStatsD.timing('conv.allconvert', new Date() - startDate); } + ctx.logger.info('End Task'); return resData; } @@ -854,13 +862,15 @@ function receiveTask(data, ack) { return co(function* () { var res = null; var task = null; + let ctx = new operationContext.Context(); try { task = new commonDefines.TaskQueueData(JSON.parse(data)); if (task) { - res = yield* ExecuteTask(task); + ctx.initFromTaskQueueData(task); + res = yield* ExecuteTask(ctx, task); } } catch (err) { - logger.error(err); + ctx.logger.error(err); } finally { try { if (!res && task) { @@ -868,13 +878,14 @@ function receiveTask(data, ack) { var cmd = task.getCmd(); cmd.setStatusInfo(constants.CONVERT); res = new commonDefines.TaskQueueData(); + res.setCtx(ctx); res.setCmd(cmd); } if (res) { yield queue.addResponse(res); } } catch (err) { - logger.error(err); + ctx.logger.error(err); } finally { ack(); } @@ -883,10 +894,13 @@ function receiveTask(data, ack) { } function simulateErrorResponse(data){ let task = new commonDefines.TaskQueueData(JSON.parse(data)); + let ctx = new operationContext.Context(); + ctx.initFromTaskQueueData(task); //simulate error response let cmd = task.getCmd(); cmd.setStatusInfo(constants.CONVERT); let res = new commonDefines.TaskQueueData(); + task.setCtx(ctx); res.setCmd(cmd); return res; } @@ -895,7 +909,7 @@ function run() { queue.on('task', receiveTask); queue.init(true, true, true, false, false, false, function(err) { if (null != err) { - logger.error('createTaskQueue error :\r\n%s', err.stack); + operationContext.global.logger.error('createTaskQueue error: %s', err.stack); } }); } diff --git a/FileConverter/sources/convertermaster.js b/FileConverter/sources/convertermaster.js index 2c4526f8..2a0b366a 100644 --- a/FileConverter/sources/convertermaster.js +++ b/FileConverter/sources/convertermaster.js @@ -34,6 +34,7 @@ const cluster = require('cluster'); const logger = require('./../../Common/sources/logger'); +const operationContext = require('./../../Common/sources/operationContext'); if (cluster.isMaster) { const fs = require('fs'); @@ -42,12 +43,18 @@ if (cluster.isMaster) { const configCommon = require('config'); const config = configCommon.get('FileConverter.converter'); const license = require('./../../Common/sources/license'); + const tenantManager = require('./../../Common/sources/tenantManager'); + + const cfgLicenseFile = configCommon.get('license.license_file'); const cfgMaxProcessCount = config.get('maxprocesscount'); - var licenseInfo, workersCount = 0; + var workersCount = 0; const readLicense = function* () { - [licenseInfo] = yield* license.readLicense(); - workersCount = Math.min(licenseInfo.count, Math.ceil(numCPUs * cfgMaxProcessCount)); + workersCount = Math.ceil(numCPUs * cfgMaxProcessCount); + if (!tenantManager.isMultitenantMode()) { + let [licenseInfo] = yield* license.readLicense(cfgLicenseFile); + workersCount = Math.min(licenseInfo.count, workersCount); + } }; const updateWorkers = () => { var i; @@ -55,7 +62,7 @@ if (cluster.isMaster) { if (arrKeyWorkers.length < workersCount) { for (i = arrKeyWorkers.length; i < workersCount; ++i) { const newWorker = cluster.fork(); - logger.warn('worker %s started.', newWorker.process.pid); + operationContext.global.logger.warn('worker %s started.', newWorker.process.pid); } } else { for (i = workersCount; i < arrKeyWorkers.length; ++i) { @@ -70,31 +77,33 @@ if (cluster.isMaster) { return co(function*() { try { yield* readLicense(); - logger.warn('update cluster with %s workers', workersCount); + operationContext.global.logger.warn('update cluster with %s workers', workersCount); updateWorkers(); } catch (err) { - logger.error('updateLicense error:\r\n%s', err.stack); + operationContext.global.logger.error('updateLicense error: %s', err.stack); } }); }; cluster.on('exit', (worker, code, signal) => { - logger.warn('worker %s died (code = %s; signal = %s).', worker.process.pid, code, signal); + operationContext.global.logger.warn('worker %s died (code = %s; signal = %s).', worker.process.pid, code, signal); updateWorkers(); }); updateLicense(); - fs.watchFile(configCommon.get('license').get('license_file'), updateLicense); - setInterval(updateLicense, 86400000); + if (!tenantManager.isMultitenantMode()) { + fs.watchFile(cfgLicenseFile, updateLicense); + setInterval(updateLicense, 86400000); + } } else { const converter = require('./converter'); converter.run(); } process.on('uncaughtException', (err) => { - logger.error((new Date).toUTCString() + ' uncaughtException:', err.message); - logger.error(err.stack); + operationContext.global.logger.error((new Date).toUTCString() + ' uncaughtException:', err.message); + operationContext.global.logger.error(err.stack); logger.shutdown(() => { process.exit(1); }); diff --git a/schema/mysql/createdb.sql b/schema/mysql/createdb.sql index e62ce1a9..e98a8d8f 100644 --- a/schema/mysql/createdb.sql +++ b/schema/mysql/createdb.sql @@ -26,6 +26,7 @@ USE onlyoffice; -- CREATE TABLE IF NOT EXISTS `doc_changes` ( + `tenant` varchar(255) NOT NULL, `id` varchar(255) NOT NULL, `change_id` int(10) unsigned NOT NULL, `user_id` varchar(255) NOT NULL, @@ -33,7 +34,7 @@ CREATE TABLE IF NOT EXISTS `doc_changes` ( `user_name` varchar(255) NOT NULL, `change_data` longtext NOT NULL, `change_date` datetime NOT NULL, - PRIMARY KEY (`id`,`change_id`) + PRIMARY KEY (`tenant`, `id`,`change_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- @@ -48,6 +49,7 @@ CREATE TABLE IF NOT EXISTS `doc_changes` ( -- CREATE TABLE IF NOT EXISTS `task_result` ( + `tenant` varchar(255) NOT NULL, `id` varchar(255) NOT NULL, `status` tinyint(3) NOT NULL, `status_info` int(10) NOT NULL, @@ -59,7 +61,7 @@ CREATE TABLE IF NOT EXISTS `task_result` ( `baseurl` text NOT NULL, `password` longtext NULL, `additional` longtext NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`tenant`, `id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- diff --git a/schema/postgresql/createdb.sql b/schema/postgresql/createdb.sql index b07901c6..b1a0b0d9 100644 --- a/schema/postgresql/createdb.sql +++ b/schema/postgresql/createdb.sql @@ -8,6 +8,7 @@ -- Table structure for doc_changes -- ---------------------------- CREATE TABLE IF NOT EXISTS "public"."doc_changes" ( +"tenant" varchar(255) COLLATE "default" NOT NULL, "id" varchar(255) COLLATE "default" NOT NULL, "change_id" int4 NOT NULL, "user_id" varchar(255) COLLATE "default" NOT NULL, @@ -15,7 +16,7 @@ CREATE TABLE IF NOT EXISTS "public"."doc_changes" ( "user_name" varchar(255) COLLATE "default" NOT NULL, "change_data" text COLLATE "default" NOT NULL, "change_date" timestamp without time zone NOT NULL, -PRIMARY KEY ("id", "change_id") +PRIMARY KEY ("tenant", "id", "change_id") ) WITH (OIDS=FALSE); @@ -23,6 +24,7 @@ WITH (OIDS=FALSE); -- Table structure for task_result -- ---------------------------- CREATE TABLE IF NOT EXISTS "public"."task_result" ( +"tenant" varchar(255) COLLATE "default" NOT NULL, "id" varchar(255) COLLATE "default" NOT NULL, "status" int2 NOT NULL, "status_info" int4 NOT NULL, @@ -34,11 +36,11 @@ CREATE TABLE IF NOT EXISTS "public"."task_result" ( "baseurl" text COLLATE "default" NOT NULL, "password" text COLLATE "default" NULL, "additional" text COLLATE "default" NULL, -PRIMARY KEY ("id") +PRIMARY KEY ("tenant", "id") ) WITH (OIDS=FALSE); -CREATE OR REPLACE FUNCTION merge_db(_id varchar(255), _status int2, _status_info int4, _last_open_date timestamp without time zone, _user_index int4, _change_id int4, _callback text, _baseurl text, OUT isupdate char(5), OUT userindex int4) AS +CREATE OR REPLACE FUNCTION merge_db(_tetant varchar(255), _id varchar(255), _status int2, _status_info int4, _last_open_date timestamp without time zone, _user_index int4, _change_id int4, _callback text, _baseurl text, OUT isupdate char(5), OUT userindex int4) AS $$ DECLARE t_var "public"."task_result"."user_index"%TYPE; @@ -47,9 +49,9 @@ BEGIN -- first try to update the key -- note that "a" must be unique IF ((_callback <> '') IS TRUE) AND ((_baseurl <> '') IS TRUE) THEN - UPDATE "public"."task_result" SET last_open_date=_last_open_date, user_index=user_index+1,callback=_callback,baseurl=_baseurl WHERE id = _id RETURNING user_index into userindex; + UPDATE "public"."task_result" SET last_open_date=_last_open_date, user_index=user_index+1,callback=_callback,baseurl=_baseurl WHERE tenant = _tetant AND id = _id RETURNING user_index into userindex; ELSE - UPDATE "public"."task_result" SET last_open_date=_last_open_date, user_index=user_index+1 WHERE id = _id RETURNING user_index into userindex; + UPDATE "public"."task_result" SET last_open_date=_last_open_date, user_index=user_index+1 WHERE tenant = _tetant AND id = _id RETURNING user_index into userindex; END IF; IF found THEN isupdate := 'true'; @@ -59,7 +61,7 @@ BEGIN -- if someone else inserts the same key concurrently, -- we could get a unique-key failure BEGIN - INSERT INTO "public"."task_result"(id, status, status_info, last_open_date, user_index, change_id, callback, baseurl) VALUES(_id, _status, _status_info, _last_open_date, _user_index, _change_id, _callback, _baseurl) RETURNING user_index into userindex; + INSERT INTO "public"."task_result"(id, status, status_info, last_open_date, user_index, change_id, callback, baseurl) VALUES(_tetant, _id, _status, _status_info, _last_open_date, _user_index, _change_id, _callback, _baseurl) RETURNING user_index into userindex; isupdate := 'false'; RETURN; EXCEPTION WHEN unique_violation THEN