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
This commit is contained in:
Sergey Konovalov
2022-08-02 17:34:06 +03:00
committed by GitHub
parent 0c215cefa9
commit 41399fa3d4
32 changed files with 2159 additions and 1661 deletions

View File

@ -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 ",

View File

@ -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"
}
}
},

View File

@ -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"
}
}
},

View File

@ -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 {

View File

@ -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'];
},

View File

@ -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,

View File

@ -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

View File

@ -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));
};

View File

@ -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();

View File

@ -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);
}
});

View File

@ -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];
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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*/);
};

File diff suppressed because it is too large Load Diff

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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');
}
});
}

View File

@ -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++;
}
}

View File

@ -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 = '<html><head><script type="text/javascript">function load(){ parent.postMessage("';
output += JSON.stringify(outputData).replace(/"/g, '\\"');
output += '", "*"); }</script></head><body onload="load()"></body></html>';
//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 = '<html><head><script type="text/javascript">function load(){ parent.postMessage("';
output += JSON.stringify(outputData).replace(/"/g, '\\"');
output += '", "*"); }</script></head><body onload="load()"></body></html>';
//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);
}
}
});

View File

@ -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);
}

View File

@ -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 {

View File

@ -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);

View File

@ -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');

View File

@ -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);
});

View File

@ -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;
});

View File

@ -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 {

View File

@ -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 += `</net-zone>${proofKey}</wopi-discovery>`;
} 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;
});

View File

@ -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);
}
});
}

View File

@ -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);
});

View File

@ -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;
--

View File

@ -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