[feature] Add operationContext.initTenantCache to init tenant specific settings

This commit is contained in:
Sergey Konovalov
2023-07-06 14:35:38 +03:00
parent c9a9ecf437
commit 38ea39ced7
14 changed files with 132 additions and 18 deletions

View File

@ -105,6 +105,7 @@
"tenants": {
"baseDir": "",
"baseDomain": "",
"filenameConfig": "config.json",
"filenameSecret": "secret.key",
"filenameLicense": "license.lic",
"defaultTenant": "localhost",

View File

@ -45,6 +45,10 @@ Context.prototype.init = function(tenant, docId, userId) {
this.setTenant(tenant);
this.setDocId(docId);
this.setUserId(userId);
this.config = null;
this.secret = null;
this.license = null;
};
Context.prototype.initDefault = function() {
this.init(tenantManager.getDefautTenant(), constants.DEFAULT_DOC_ID, constants.DEFAULT_USER_ID);
@ -74,6 +78,10 @@ Context.prototype.initFromPubSub = function(data) {
let ctx = data.ctx;
this.init(ctx.tenant, ctx.docId, ctx.userId);
};
Context.prototype.initTenantCache = async function() {
this.config = await tenantManager.getTenantConfig(this);
//todo license and secret
};
Context.prototype.setTenant = function(tenant) {
this.tenant = tenant;
@ -88,5 +96,37 @@ Context.prototype.setUserId = function(userId) {
this.logger.addContext('USERID', userId);
};
Context.prototype.getCfg = function(property, defaultValue) {
if (this.config){
return getImpl(this.config, property) ?? defaultValue;
}
return defaultValue;
};
/**
* Underlying get mechanism
*
* @private
* @method getImpl
* @param object {object} - Object to get the property for
* @param property {string | array[string]} - The property name to get (as an array or '.' delimited string)
* @return value {*} - Property value, including undefined if not defined.
*/
function getImpl(object, property) {
//from https://github.com/node-config/node-config/blob/a8b91ac86b499d11b90974a2c9915ce31266044a/lib/config.js#L137
var t = this,
elems = Array.isArray(property) ? property : property.split('.'),
name = elems[0],
value = object[name];
if (elems.length <= 1) {
return value;
}
// Note that typeof null === 'object'
if (value === null || typeof value !== 'object') {
return undefined;
}
return getImpl(value, elems.slice(1));
};
exports.Context = Context;
exports.global = new Context();

View File

@ -68,8 +68,8 @@ var configS3 = {
region: cfgRegion,
endpoint: cfgEndpoint,
credentials : {
accessKeyId: cfgAccessKeyId,
secretAccessKey: cfgSecretAccessKey
accessKeyId: cfgAccessKeyId,
secretAccessKey: cfgSecretAccessKey
}
};
@ -97,23 +97,23 @@ function joinListObjects(inputArray, outputArray) {
}
async function listObjectsExec(output, params) {
const data = await client.send(new ListObjectsCommand(params));
joinListObjects(data.Contents, output);
joinListObjects(data.Contents, output);
if (data.IsTruncated && (data.NextMarker || (data.Contents && data.Contents.length > 0))) {
params.Marker = data.NextMarker || data.Contents[data.Contents.length - 1].Key;
params.Marker = data.NextMarker || data.Contents[data.Contents.length - 1].Key;
return await listObjectsExec(output, params);
} else {
} else {
return output;
}
}
}
async function deleteObjectsHelp(aKeys) {
//By default, the operation uses verbose mode in which the response includes the result of deletion of each key in your request.
//In quiet mode the response includes only keys where the delete operation encountered an error.
//By default, the operation uses verbose mode in which the response includes the result of deletion of each key in your request.
//In quiet mode the response includes only keys where the delete operation encountered an error.
const input = {
Bucket: cfgBucketName,
Delete: {
Objects: aKeys,
Quiet: true
}
}
};
const command = new DeleteObjectsCommand(input);
return await client.send(command);
@ -141,7 +141,7 @@ exports.createReadStream = async function(strPath) {
const input = {
Bucket: cfgBucketName,
Key: getFilePath(strPath)
};
};
const command = new GetObjectCommand(input);
const output = await client.send(command);
return {
@ -150,7 +150,7 @@ exports.createReadStream = async function(strPath) {
};
};
exports.putObject = async function(strPath, buffer, contentLength) {
//todo рассмотреть Expires
//todo consider Expires
const input = {
Bucket: cfgBucketName,
Key: getFilePath(strPath),
@ -184,8 +184,8 @@ exports.copyObject = function(sourceKey, destinationKey) {
return client.send(command);
};
exports.listObjects = async function(strPath) {
var params = {Bucket: cfgBucketName, Prefix: getFilePath(strPath)};
var output = [];
var params = {Bucket: cfgBucketName, Prefix: getFilePath(strPath)};
var output = [];
return await listObjectsExec(output, params);
};
exports.deleteObject = function(strPath) {
@ -207,11 +207,11 @@ exports.deleteObjects = function(strPaths) {
return Promise.all(deletePromises);
};
exports.getSignedUrl = async function (baseUrl, strPath, urlType, optFilename, opt_creationDate) {
var expires = (commonDefines.c_oAscUrlTypes.Session === urlType ? cfgExpSessionAbsolute / 1000 : cfgStorageUrlExpires) || 31536000;
var expires = (commonDefines.c_oAscUrlTypes.Session === urlType ? cfgExpSessionAbsolute / 1000 : cfgStorageUrlExpires) || 31536000;
// Signature version 4 presigned URLs must have an expiration date less than one week in the future
expires = Math.min(expires, 604800);
var userFriendlyName = optFilename ? optFilename.replace(/\//g, "%2f") : path.basename(strPath);
var contentDisposition = utils.getContentDisposition(userFriendlyName, null, null);
var userFriendlyName = optFilename ? optFilename.replace(/\//g, "%2f") : path.basename(strPath);
var contentDisposition = utils.getContentDisposition(userFriendlyName, null, null);
const input = {
Bucket: cfgBucketName,
@ -219,10 +219,10 @@ exports.getSignedUrl = async function (baseUrl, strPath, urlType, optFilename, o
ResponseContentDisposition: contentDisposition
};
const command = new GetObjectCommand(input);
//default Expires 900 seconds
//default Expires 900 seconds
var options = {
expiresIn: expires
};
};
return await getSignedUrl(client, command, options);
//extra query params cause SignatureDoesNotMatch
//https://stackoverflow.com/questions/55503009/amazon-s3-signature-does-not-match-when-extra-query-params-ga-added-in-url

View File

@ -47,6 +47,7 @@ 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 cfgTenantsFilenameConfig = config.get('tenants.filenameConfig');
const cfgTenantsDefaultTenant = config.get('tenants.defaultTenant');
const cfgTenantsCache = config.get('tenants.cache');
const cfgSecretInbox = config.get('services.CoAuthoring.secret.inbox');
@ -82,6 +83,28 @@ function getTenantByRequest(ctx, req) {
function getTenantPathPrefix(ctx) {
return isMultitenantMode(ctx) ? utils.removeIllegalCharacters(ctx.tenant) + '/' : '';
}
async function getTenantConfig(ctx) {
let res = null;
if (isMultitenantMode(ctx)) {
let tenantPath = utils.removeIllegalCharacters(ctx.tenant);
let configPath = path.join(cfgTenantsBaseDir, tenantPath, cfgTenantsFilenameConfig);
res = nodeCache.get(configPath);
if (res) {
ctx.logger.debug('getTenantConfig from cache');
} else {
try {
let cfgString = await readFile(configPath, {encoding: 'utf8'});
res = config.util.parseString(cfgString, path.extname(configPath).substring(1));
ctx.logger.debug('getTenantConfig from %s', configPath);
} catch (e) {
ctx.logger.error('getTenantConfig error: %s', e.stack);
} finally {
nodeCache.set(configPath, res);
}
}
}
return res;
}
function getTenantSecret(ctx, type) {
return co(function*() {
let res = undefined;
@ -159,6 +182,7 @@ exports.getDefautTenant = getDefautTenant;
exports.getTenantByConnection = getTenantByConnection;
exports.getTenantByRequest = getTenantByRequest;
exports.getTenantPathPrefix = getTenantPathPrefix;
exports.getTenantConfig = getTenantConfig;
exports.getTenantSecret = getTenantSecret;
exports.getTenantLicense = getTenantLicense;
exports.getServerLicense = getServerLicense;

View File

@ -428,6 +428,7 @@ function updatePresenceCounters(ctx, conn, val) {
//aggregated server stats
aggregationCtx = new operationContext.Context();
aggregationCtx.init(cfgTenantsAggregationTenant, ctx.docId, ctx.userId);
//yield ctx.initTenantCache(); //no need.only global config
}
if (utils.isLiveViewer(conn)) {
yield editorData.incrLiveViewerConnectionsCountByShard(ctx, SHARD_ID, val);
@ -1015,6 +1016,7 @@ function handleDeadLetter(data, ack) {
let task = new commonDefines.TaskQueueData(JSON.parse(data));
if (task) {
ctx.initFromTaskQueueData(task);
yield ctx.initTenantCache();
let cmd = task.getCmd();
ctx.logger.warn('handleDeadLetter start: %s', data);
let forceSave = cmd.getForceSave();
@ -1451,6 +1453,7 @@ exports.install = function(server, callbackFunction) {
let checkJwtRes;
try {
ctx.initFromConnection(socket);
yield ctx.initTenantCache();
ctx.logger.info('io.use start');
let handshake = socket.handshake;
if (cfgTokenEnableBrowser) {
@ -1478,6 +1481,8 @@ exports.install = function(server, callbackFunction) {
}
let ctx = new operationContext.Context();
ctx.initFromConnection(conn);
//todo
//yield ctx.initTenantCache();
if (getIsShutdown()) {
sendFileError(ctx, conn, 'Server shutdow');
return;
@ -1492,6 +1497,7 @@ exports.install = function(server, callbackFunction) {
let ctx = new operationContext.Context();
try {
ctx.initFromConnection(conn);
yield ctx.initTenantCache();
var startDate = null;
if(clientStatsD) {
startDate = new Date();
@ -1618,6 +1624,7 @@ exports.install = function(server, callbackFunction) {
let ctx = new operationContext.Context();
try {
ctx.initFromConnection(conn);
yield ctx.initTenantCache();
yield* closeDocument(ctx, conn, reason);
} catch (err) {
ctx.logger.error('Error conn close: %s', err.stack);
@ -2461,6 +2468,7 @@ exports.install = function(server, callbackFunction) {
//check server aggregation license
aggregationCtx = new operationContext.Context();
aggregationCtx.init(cfgTenantsAggregationTenant, ctx.docId, ctx.userId);
//yield ctx.initTenantCache(); //no need
licenseInfoAggregation = tenantManager.getServerLicense();
licenseType = yield* _checkLicenseAuth(aggregationCtx, licenseInfoAggregation, conn.user.idOriginal, isLiveViewer, logPrefixServer);
}
@ -3398,6 +3406,7 @@ exports.install = function(server, callbackFunction) {
try {
var data = JSON.parse(msg);
ctx.initFromPubSub(data);
yield ctx.initTenantCache();
ctx.logger.debug('pubsub message start:%s', msg);
var participants;
var participant;
@ -3623,6 +3632,8 @@ exports.install = function(server, callbackFunction) {
for (var i = 0; i < connections.length; ++i) {
var conn = connections[i];
ctx.initFromConnection(conn);
//todo group by tenant
yield ctx.initTenantCache();
let tenant = tenants[ctx.tenant];
if (!tenant) {
tenant = tenants[ctx.tenant] = {countEditByShard: 0, countLiveViewByShard: 0, countViewByShard: 0};
@ -3696,6 +3707,7 @@ exports.install = function(server, callbackFunction) {
//aggregated tenant stats
let aggregationCtx = new operationContext.Context();
aggregationCtx.init(cfgTenantsAggregationTenant, ctx.docId, ctx.userId);
//yield ctx.initTenantCache();//no need
yield editorData.setEditorConnectionsCountByShard(ctx, SHARD_ID, countEditByShard);
yield editorData.setLiveViewerConnectionsCountByShard(ctx, SHARD_ID, countLiveViewByShard);
yield editorData.setViewerConnectionsCountByShard(ctx, SHARD_ID, countViewByShard);
@ -3718,6 +3730,8 @@ exports.install = function(server, callbackFunction) {
for (let i = 0; i < connections.length; ++i) {
let conn = connections[i];
ctx.initFromConnection(conn);
//todo group by tenant
yield ctx.initTenantCache();
let docId = conn.docId;
if ((conn.user && conn.user.view) || docIds.has(docId)) {
continue;
@ -3798,6 +3812,7 @@ exports.healthCheck = function(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('healthCheck start');
//database
yield sqlBase.healthCheck(ctx);
@ -3875,6 +3890,7 @@ exports.licenseInfo = function(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.debug('licenseInfo start');
let licenseInfo = yield tenantManager.getTenantLicense(ctx);
@ -4178,6 +4194,7 @@ exports.commandFromServer = function (req, res) {
const ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('commandFromServer start');
const authRes = yield getRequestParams(ctx, req);
const params = authRes.params;
@ -4207,6 +4224,7 @@ exports.shutdown = function(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('shutdown start');
output = yield shutdown.shutdown(ctx, editorData, req.method === 'PUT');
} catch (err) {

View File

@ -1325,6 +1325,7 @@ exports.downloadAs = function(req, res) {
startDate = new Date();
}
ctx.initFromRequest(req);
yield ctx.initTenantCache();
var strCmd = req.query['cmd'];
var cmd = new commonDefines.InputCommand(JSON.parse(strCmd));
docId = cmd.getDocId();
@ -1416,6 +1417,7 @@ exports.saveFile = function(req, res) {
startDate = new Date();
}
ctx.initFromRequest(req);
yield ctx.initTenantCache();
let strCmd = req.query['cmd'];
let cmd = new commonDefines.InputCommand(JSON.parse(strCmd));
docId = cmd.getDocId();
@ -1490,6 +1492,7 @@ exports.printFile = function(req, res) {
startDate = new Date();
}
ctx.initFromRequest(req);
yield ctx.initTenantCache();
let filename = req.query['filename'];
let token = req.query['token'];
docId = req.params.docid;
@ -1540,6 +1543,7 @@ exports.downloadFile = function(req, res) {
startDate = new Date();
}
ctx.initFromRequest(req);
yield ctx.initTenantCache();
let url = decodeURI(req.get('x-url'));
ctx.setDocId(req.params.docid);
ctx.logger.info('Start downloadFile');
@ -1659,6 +1663,7 @@ exports.receiveTask = function(data, ack) {
if (task) {
var cmd = task.getCmd();
ctx.initFromTaskQueueData(task);
yield ctx.initTenantCache();
ctx.logger.info('receiveTask start: %s', data);
var updateTask = yield* getUpdateResponse(ctx, cmd);
var updateRes = yield taskResult.update(ctx, updateTask);

View File

@ -137,7 +137,10 @@ function shutdown() {
for (let i = 0; i < docsToConvert.length; ++i) {
let tenant = docsToConvert[i][0];
let docId = docsToConvert[i][1];
//todo refactor. group tenants?
ctx.setTenant(tenant);
yield ctx.initTenantCache();
yield updateDoc(ctx, docId, commonDefines.FileStatus.Ok, "");
yield editorData.addShutdown(redisKeyShutdown, docId);
ctx.logger.debug('shutdown createSaveTimerPromise %s', docId);

View File

@ -234,6 +234,7 @@ function convertRequest(req, res, isJson) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('convertRequest start');
let params;
let authRes = yield docsCoServer.getRequestParams(ctx, req);
@ -388,6 +389,7 @@ function builderRequest(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('builderRequest start');
let authRes;
if (!utils.isEmptyObject(req.query)) {
@ -457,6 +459,7 @@ function convertTo(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('convert-to start');
let format = req.body['format'];
if (req.params.format) {
@ -603,6 +606,7 @@ function getConverterHtmlHandler(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('convert-and-edit-handler start');
let wopiSrc = req.query['wopisrc'];

View File

@ -60,6 +60,7 @@ exports.uploadTempFile = function(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('uploadTempFile start');
let params;
let authRes = yield docsCoServer.getRequestParams(ctx, req, true);
@ -121,6 +122,7 @@ exports.uploadImageFileOld = function(req, res) {
return co(function* () {
let ctx = new operationContext.Context();
ctx.initFromRequest(req);
yield ctx.initTenantCache();
var docId = req.params.docid;
ctx.setDocId(docId);
ctx.logger.debug('Start uploadImageFileOld');
@ -211,6 +213,7 @@ exports.uploadImageFile = function(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
docId = req.params.docid;
ctx.setDocId(docId);
let encrypted = false;

View File

@ -79,6 +79,7 @@ var checkFileExpire = function() {
let tenant = expired[i].tenant;
let docId = expired[i].id;
ctx.init(tenant, docId, ctx.userId);
yield ctx.initTenantCache();
//todo tenant
//check that no one is in the document
let editorsCount = yield docsCoServer.getEditorsCountPromise(ctx, docId);
@ -120,6 +121,7 @@ var checkDocumentExpire = function() {
let docId = expiredKeys[i][1];
if (docId) {
ctx.init(tenant, docId, ctx.userId);
yield ctx.initTenantCache();
var hasChanges = yield docsCoServer.hasChanges(ctx, docId);
if (hasChanges) {
yield docsCoServer.createSaveTimer(ctx, docId, null, null, queue, true);
@ -169,6 +171,7 @@ let forceSaveTimeout = function() {
let docId = expiredKeys[i][1];
if (docId) {
ctx.init(tenant, docId, ctx.userId);
yield ctx.initTenantCache();
actions.push(docsCoServer.startForceSave(ctx, docId, commondefines.c_oAscForceSaveTypes.Timeout,
undefined, undefined, undefined, undefined, undefined, undefined, undefined, queue, pubsub));
}

View File

@ -172,6 +172,7 @@ docsCoServer.install(server, () => {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
let licenseInfo = yield tenantManager.getTenantLicense(ctx);
let buildVersion = commonDefines.buildVersion;
let buildNumber = commonDefines.buildNumber;
@ -236,6 +237,8 @@ docsCoServer.install(server, () => {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
//todo
// yield ctx.initTenantCache();
res.send(utils.getBaseUrlByRequest(ctx, req));
} catch (err) {
ctx.logger.error('baseurl error: %s', err.stack);
@ -269,6 +272,7 @@ docsCoServer.install(server, () => {
app.post('/dummyCallback', utils.checkClientIp, rawFileParser, function(req, res){
let ctx = new operationContext.Context();
ctx.initFromRequest(req);
//yield ctx.initTenantCache();//no need
ctx.logger.debug(`dummyCallback req.body:%s`, req.body);
utils.fillResponseSimple(res, JSON.stringify({error: 0}, "application/json"));
});
@ -327,6 +331,7 @@ docsCoServer.install(server, () => {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('themes.json start');
if (!config.has('server.static_content') || !config.has('themes.uri')) {
return;

View File

@ -106,6 +106,7 @@ function discovery(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('wopiDiscovery start');
let baseUrl = cfgWopiHost || utils.getBaseUrlByRequest(ctx, req);
let names = ['Word','Excel','PowerPoint'];
@ -226,6 +227,7 @@ function collaboraCapabilities(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.logger.info('collaboraCapabilities start');
} catch (err) {
ctx.logger.error('collaboraCapabilities error:%s', err.stack);
@ -347,6 +349,7 @@ function getEditorHtml(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
let wopiSrc = req.query['wopisrc'];
let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1);
ctx.setDocId(fileId);
@ -451,6 +454,7 @@ function getConverterHtml(req, res) {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
let wopiSrc = req.query['wopisrc'];
let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1);
ctx.setDocId(fileId);

View File

@ -1052,6 +1052,7 @@ function receiveTask(data, ack) {
task = new commonDefines.TaskQueueData(JSON.parse(data));
if (task) {
ctx.initFromTaskQueueData(task);
yield ctx.initTenantCache();
timeoutId = receiveTaskSetTimeout(ctx, task, ack, outParams);
res = yield* ExecuteTask(ctx, task);
}
@ -1082,6 +1083,8 @@ function simulateErrorResponse(data){
let task = new commonDefines.TaskQueueData(JSON.parse(data));
let ctx = new operationContext.Context();
ctx.initFromTaskQueueData(task);
//todo
//yield ctx.initTenantCache();
return createErrorResponse(ctx, task);
}
function run() {

View File

@ -18,6 +18,7 @@ const cfgStorageName = config.get('storage.name');
const cfgEndpoint = config.get('storage.endpoint');
const cfgBucketName = config.get('storage.bucketName');
const ctx = new operationContext.Context();
//yield ctx.initTenantCache();//no need
const testFilesNames = {
get: 'DocService-DocsCoServer-forgottenFilesCommands-getForgotten-integration-test',
delete1: 'DocService-DocsCoServer-forgottenFilesCommands-deleteForgotten-integration-test',