Merge remote-tracking branch 'remotes/origin/release/v7.2.0' into develop

This commit is contained in:
Sergey Konovalov
2022-09-11 17:28:25 +03:00
11 changed files with 226 additions and 173 deletions

View File

@ -27,6 +27,7 @@
"endpoint": "http://localhost/s3",
"bucketName": "cache",
"storageFolderName": "files",
"cacheFolderName": "data",
"urlExpires": 604800,
"accessKeyId": "AKID",
"secretAccessKey": "SECRET",
@ -103,7 +104,12 @@
"baseDomain": "",
"filenameSecret": "secret.key",
"filenameLicense": "license.lic",
"defaultTetant": "tetant"
"defaultTenant": "localhost",
"cache" : {
"stdTTL": 300,
"checkperiod": 60,
"useClones": false
}
},
"services": {
"CoAuthoring": {
@ -258,6 +264,7 @@
},
"sockjs": {
"sockjs_url": "",
"disable_cors": true,
"websocket": true
},
"callbackBackoffOptions": {

View File

@ -904,7 +904,7 @@ function OutputMailMerge(mailMergeSendData) {
this['title'] = mailMergeSendData.getFileName();
const mailFormat = mailMergeSendData.getMailFormat();
switch (mailFormat) {
case constants.AVS_OFFICESTUDIO_FILE_OTHER_HTMLZIP :
case constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_HTML :
this['type'] = 0;
break;
case constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX :

View File

@ -37,42 +37,45 @@ var utils = require('./utils');
var storage = require('./' + config.get('storage.name'));
var tenantManager = require('./tenantManager');
function getStoragePath(ctx, strPath) {
return tenantManager.getTenantPathPrefix(ctx) + strPath.replace(/\\/g, '/')
const cfgCacheFolderName = config.get('storage.cacheFolderName');
function getStoragePath(ctx, strPath, opt_specialDir) {
opt_specialDir = opt_specialDir || cfgCacheFolderName;
return opt_specialDir + '/' + tenantManager.getTenantPathPrefix(ctx) + strPath.replace(/\\/g, '/')
}
exports.headObject = function(ctx, strPath) {
return storage.headObject(getStoragePath(ctx, strPath));
exports.headObject = function(ctx, strPath, opt_specialDir) {
return storage.headObject(getStoragePath(ctx, strPath, opt_specialDir));
};
exports.getObject = function(ctx, strPath) {
return storage.getObject(getStoragePath(ctx, strPath));
exports.getObject = function(ctx, strPath, opt_specialDir) {
return storage.getObject(getStoragePath(ctx, strPath, opt_specialDir));
};
exports.createReadStream = function(ctx, strPath) {
return storage.createReadStream(getStoragePath(ctx, strPath));
exports.createReadStream = function(ctx, strPath, opt_specialDir) {
return storage.createReadStream(getStoragePath(ctx, strPath, opt_specialDir));
};
exports.putObject = function(ctx, strPath, buffer, contentLength) {
return storage.putObject(getStoragePath(ctx, strPath), buffer, contentLength);
exports.putObject = function(ctx, strPath, buffer, contentLength, opt_specialDir) {
return storage.putObject(getStoragePath(ctx, strPath, opt_specialDir), buffer, contentLength);
};
exports.uploadObject = function(ctx, strPath, filePath) {
return storage.uploadObject(getStoragePath(ctx, strPath), filePath);
exports.uploadObject = function(ctx, strPath, filePath, opt_specialDir) {
return storage.uploadObject(getStoragePath(ctx, strPath, opt_specialDir), filePath);
};
exports.copyObject = function(ctx, sourceKey, destinationKey) {
let storageSrc = getStoragePath(ctx, sourceKey);
let storageDst = getStoragePath(ctx, destinationKey);
exports.copyObject = function(ctx, sourceKey, destinationKey, opt_specialDirSrc, opt_specialDirDst) {
let storageSrc = getStoragePath(ctx, sourceKey, opt_specialDirSrc);
let storageDst = getStoragePath(ctx, destinationKey, opt_specialDirDst);
return storage.copyObject(storageSrc, storageDst);
};
exports.copyPath = function(ctx, sourcePath, destinationPath) {
let storageSrc = getStoragePath(ctx, sourcePath);
let storageDst = getStoragePath(ctx, destinationPath);
exports.copyPath = function(ctx, sourcePath, destinationPath, opt_specialDirSrc, opt_specialDirDst) {
let storageSrc = getStoragePath(ctx, sourcePath, opt_specialDirSrc);
let storageDst = getStoragePath(ctx, destinationPath, opt_specialDirDst);
return storage.listObjects(storageSrc).then(function(list) {
return Promise.all(list.map(function(curValue) {
return storage.copyObject(curValue, storageDst + '/' + exports.getRelativePath(storageSrc, curValue));
}));
});
};
exports.listObjects = function(ctx, strPath) {
let prefix = getStoragePath(ctx, "");
return storage.listObjects(getStoragePath(ctx, strPath)).then(function(list) {
exports.listObjects = function(ctx, strPath, opt_specialDir) {
let prefix = getStoragePath(ctx, "", opt_specialDir);
return storage.listObjects(getStoragePath(ctx, strPath, opt_specialDir)).then(function(list) {
return list.map((currentValue) => {
return currentValue.substring(prefix.length);
});
@ -81,26 +84,26 @@ exports.listObjects = function(ctx, strPath) {
return [];
});
};
exports.deleteObject = function(ctx, strPath) {
return storage.deleteObject(getStoragePath(ctx, strPath));
exports.deleteObject = function(ctx, strPath, opt_specialDir) {
return storage.deleteObject(getStoragePath(ctx, strPath, opt_specialDir));
};
exports.deleteObjects = function(ctx, strPaths) {
exports.deleteObjects = function(ctx, strPaths, opt_specialDir) {
var StoragePaths = strPaths.map(function(curValue) {
return getStoragePath(ctx, curValue);
return getStoragePath(ctx, curValue, opt_specialDir);
});
return storage.deleteObjects(StoragePaths);
};
exports.deletePath = function(ctx, strPath) {
let storageSrc = getStoragePath(ctx, strPath);
exports.deletePath = function(ctx, strPath, opt_specialDir) {
let storageSrc = getStoragePath(ctx, strPath, opt_specialDir);
return storage.listObjects(storageSrc).then(function(list) {
return storage.deleteObjects(list);
});
};
exports.getSignedUrl = function(ctx, baseUrl, strPath, urlType, optFilename, opt_creationDate) {
return storage.getSignedUrl(baseUrl, getStoragePath(ctx, strPath), urlType, optFilename, opt_creationDate);
exports.getSignedUrl = function(ctx, baseUrl, strPath, urlType, optFilename, opt_creationDate, opt_specialDir) {
return storage.getSignedUrl(baseUrl, getStoragePath(ctx, strPath, opt_specialDir), urlType, optFilename, opt_creationDate);
};
exports.getSignedUrls = function(ctx, baseUrl, strPath, urlType, opt_creationDate) {
let storageSrc = getStoragePath(ctx, strPath);
exports.getSignedUrls = function(ctx, baseUrl, strPath, urlType, opt_creationDate, opt_specialDir) {
let storageSrc = getStoragePath(ctx, strPath, opt_specialDir);
return storage.listObjects(storageSrc).then(function(list) {
return Promise.all(list.map(function(curValue) {
return storage.getSignedUrl(baseUrl, curValue, urlType, undefined, opt_creationDate);
@ -113,18 +116,18 @@ exports.getSignedUrls = function(ctx, baseUrl, strPath, urlType, opt_creationDat
});
});
};
exports.getSignedUrlsArrayByArray = function(ctx, baseUrl, list, urlType) {
exports.getSignedUrlsArrayByArray = function(ctx, baseUrl, list, urlType, opt_specialDir) {
return Promise.all(list.map(function(curValue) {
let storageSrc = getStoragePath(ctx, curValue);
let storageSrc = getStoragePath(ctx, curValue, opt_specialDir);
return storage.getSignedUrl(baseUrl, storageSrc, urlType, undefined);
}));
};
exports.getSignedUrlsByArray = function(ctx, baseUrl, list, optPath, urlType) {
return exports.getSignedUrlsArrayByArray(ctx, baseUrl, list, urlType).then(function(urls) {
exports.getSignedUrlsByArray = function(ctx, baseUrl, list, optPath, urlType, opt_specialDir) {
return exports.getSignedUrlsArrayByArray(ctx, baseUrl, list, urlType, opt_specialDir).then(function(urls) {
var outputMap = {};
for (var i = 0; i < list.length && i < urls.length; ++i) {
if (optPath) {
let storageSrc = getStoragePath(ctx, optPath);
let storageSrc = getStoragePath(ctx, optPath, opt_specialDir);
outputMap[exports.getRelativePath(storageSrc, list[i])] = urls[i];
} else {
outputMap[list[i]] = urls[i];

View File

@ -34,6 +34,7 @@
const config = require('config');
const co = require('co');
const NodeCache = require( "node-cache" );
const license = require('./../../Common/sources/license');
const constants = require('./../../Common/sources/constants');
const commonDefines = require('./../../Common/sources/commondefines');
@ -46,7 +47,8 @@ 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 cfgTenantsDefaultTenant = config.get('tenants.defaultTenant');
const cfgTenantsCache = config.get('tenants.cache');
const cfgSecretInbox = config.get('services.CoAuthoring.secret.inbox');
const cfgSecretOutbox = config.get('services.CoAuthoring.secret.outbox');
const cfgSecretSession = config.get('services.CoAuthoring.secret.session');
@ -54,19 +56,22 @@ const cfgSecretSession = config.get('services.CoAuthoring.secret.session');
let licenseInfo;
let licenseOriginal;
const nodeCache = new NodeCache(cfgTenantsCache);
function getDefautTenant() {
return cfgTenantsDefaultTetant;
return cfgTenantsDefaultTenant;
}
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);
domain = domain.replace(/\:.*$/, '');
tenant = domain;
if (cfgTenantsBaseDomain) {
let index = domain.indexOf('.' + cfgTenantsBaseDomain);
if (-1 !== index) {
tenant = domain.substring(0, index);
}
}
}
return tenant;
@ -86,8 +91,14 @@ function getTenantSecret(ctx, type) {
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'});
res = nodeCache.get(secretPath);
if (res) {
ctx.logger.debug('getTenantSecret from cache');
} else {
res = yield readFile(secretPath, {encoding: 'utf8'});
nodeCache.set(secretPath, res);
ctx.logger.debug('getTenantSecret from %s', secretPath);
}
} else {
switch (type) {
case commonDefines.c_oAscSecretType.Browser:
@ -116,8 +127,14 @@ function getTenantLicense(ctx) {
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);
res = nodeCache.get(licensePath);
if (res) {
ctx.logger.debug('getTenantLicense from cache');
} else {
[res] = yield* license.readLicense(licensePath);
nodeCache.set(licensePath, res);
ctx.logger.debug('getTenantLicense from %s', licensePath);
}
} else {
res = licenseInfo;
}
@ -125,7 +142,7 @@ function getTenantLicense(ctx) {
});
}
function isMultitenantMode() {
return !!cfgTenantsBaseDir && !!cfgTenantsBaseDomain;
return !!cfgTenantsBaseDir;
}
exports.getDefautTenant = getDefautTenant;

View File

@ -945,6 +945,7 @@ exports.convertLicenseInfoToFileParams = function(licenseInfo) {
license.light = licenseInfo.light;
license.branding = licenseInfo.branding;
license.customization = licenseInfo.customization;
license.advanced_api = licenseInfo.advancedApi;
license.plugins = licenseInfo.plugins;
license.connections = licenseInfo.connections;
license.connections_view = licenseInfo.connectionsView;

View File

@ -137,7 +137,6 @@ const cfgTokenSessionExpires = ms(config.get('token.session.expires'));
const cfgTokenInboxHeader = config.get('token.inbox.header');
const cfgTokenInboxPrefix = config.get('token.inbox.prefix');
const cfgTokenVerifyOptions = config.get('token.verifyOptions');
const cfgSecretSession = config.get('secret.session');
const cfgForceSaveEnable = config.get('autoAssembly.enable');
const cfgForceSaveInterval = ms(config.get('autoAssembly.interval'));
const cfgForceSaveStep = ms(config.get('autoAssembly.step'));
@ -465,41 +464,45 @@ let changeConnectionInfo = co.wrap(function*(ctx, conn, cmd) {
}
return false;
});
function signToken(payload, algorithm, expiresIn, secretElem) {
var options = {algorithm: algorithm, expiresIn: expiresIn};
var secret = utils.getSecretByElem(secretElem);
return jwt.sign(payload, secret, options);
function signToken(ctx, payload, algorithm, expiresIn, secretElem) {
return co(function*() {
var options = {algorithm: algorithm, expiresIn: expiresIn};
let secret = yield tenantManager.getTenantSecret(ctx, secretElem);
return jwt.sign(payload, secret, options);
});
}
function needSendChanges (conn){
return !conn.user?.view || utils.isLiveViewer(conn);
}
function fillJwtByConnection(conn) {
var payload = {document: {}, editorConfig: {user: {}}};
var doc = payload.document;
doc.key = conn.docId;
doc.permissions = conn.permissions;
doc.ds_encrypted = conn.encrypted;
var edit = payload.editorConfig;
//todo
//edit.callbackUrl = callbackUrl;
//edit.lang = conn.lang;
//edit.mode = conn.mode;
var user = edit.user;
user.id = conn.user.idOriginal;
user.name = conn.user.username;
user.index = conn.user.indexUser;
if (conn.coEditingMode) {
edit.coEditing = {mode: conn.coEditingMode};
}
//no standart
edit.ds_view = conn.user.view;
edit.ds_isCloseCoAuthoring = conn.isCloseCoAuthoring;
edit.ds_isEnterCorrectPassword = conn.isEnterCorrectPassword;
// presenter viewer opens with same session jwt. do not put sessionId to jwt
// edit.ds_sessionId = conn.sessionId;
edit.ds_sessionTimeConnect = conn.sessionTimeConnect;
function fillJwtByConnection(ctx, conn) {
return co(function*() {
var payload = {document: {}, editorConfig: {user: {}}};
var doc = payload.document;
doc.key = conn.docId;
doc.permissions = conn.permissions;
doc.ds_encrypted = conn.encrypted;
var edit = payload.editorConfig;
//todo
//edit.callbackUrl = callbackUrl;
//edit.lang = conn.lang;
//edit.mode = conn.mode;
var user = edit.user;
user.id = conn.user.idOriginal;
user.name = conn.user.username;
user.index = conn.user.indexUser;
if (conn.coEditingMode) {
edit.coEditing = {mode: conn.coEditingMode};
}
//no standart
edit.ds_view = conn.user.view;
edit.ds_isCloseCoAuthoring = conn.isCloseCoAuthoring;
edit.ds_isEnterCorrectPassword = conn.isEnterCorrectPassword;
// presenter viewer opens with same session jwt. do not put sessionId to jwt
// edit.ds_sessionId = conn.sessionId;
edit.ds_sessionTimeConnect = conn.sessionTimeConnect;
return signToken(payload, cfgTokenSessionAlgorithm, cfgTokenSessionExpires / 1000, cfgSecretSession);
return yield signToken(ctx, payload, cfgTokenSessionAlgorithm, cfgTokenSessionExpires / 1000, commonDefines.c_oAscSecretType.Session);
});
}
function sendData(ctx, conn, data) {
@ -526,8 +529,8 @@ function sendDataMeta(ctx, conn, msg) {
function sendDataSession(ctx, conn, msg) {
sendData(ctx, conn, {type: "session", messages: msg});
}
function sendDataRefreshToken(ctx, conn) {
sendData(ctx, conn, {type: "refreshToken", messages: fillJwtByConnection(conn)});
function sendDataRefreshToken(ctx, conn, msg) {
sendData(ctx, conn, {type: "refreshToken", messages: msg});
}
function sendDataRpc(ctx, conn, responseKey, data) {
sendData(ctx, conn, {type: "rpc", responseKey: responseKey, data: data});
@ -543,12 +546,15 @@ function sendReleaseLock(ctx, conn, userLocks) {
})});
}
function modifyConnectionForPassword(ctx, conn, isEnterCorrectPassword) {
if (isEnterCorrectPassword) {
conn.isEnterCorrectPassword = true;
if (cfgTokenEnableBrowser) {
sendDataRefreshToken(ctx, conn);
return co(function*() {
if (isEnterCorrectPassword) {
conn.isEnterCorrectPassword = true;
if (cfgTokenEnableBrowser) {
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
}
}
}
});
}
function getParticipants(docId, excludeClosed, excludeUserId, excludeViewer) {
return _.filter(connections, function(el) {
@ -1098,7 +1104,7 @@ function* cleanDocumentOnExit(ctx, docId, deleteChanges, opt_userIndex) {
yield taskResult.restoreInitialPassword(ctx.tenant, docId);
sqlBase.deleteChanges(ctx, docId, null);
//delete forgotten after successful send on callbackUrl
yield storage.deletePath(ctx, cfgForgottenFiles + '/' + docId);
yield storage.deletePath(ctx, docId, cfgForgottenFiles);
}
yield unlockWopiDoc(ctx, docId, opt_userIndex);
}
@ -1300,12 +1306,12 @@ exports.install = function(server, callbackFunction) {
var sockjs_echo = sockjs.createServer(cfgSockjs);
sockjs_echo.on('connection', function(conn) {
let ctx = new operationContext.Context();
ctx.initFromConnection(conn);
if (!conn) {
operationContext.global.logger.error("null == conn");
return;
}
let ctx = new operationContext.Context();
ctx.initFromConnection(conn);
if (getIsShutdown()) {
sendFileError(ctx, conn, 'Server shutdow');
return;
@ -1391,8 +1397,8 @@ exports.install = function(server, callbackFunction) {
case 'changesError':
ctx.logger.error("changesError: %s", data.stack);
if (cfgErrorFiles && docId) {
let destDir = cfgErrorFiles + '/browser/' + docId;
yield storage.copyPath(ctx, docId, destDir);
let destDir = 'browser/' + docId;
yield storage.copyPath(ctx, docId, destDir, undefined, cfgErrorFiles);
yield* saveErrorChanges(ctx, docId, destDir);
}
break;
@ -1485,7 +1491,8 @@ exports.install = function(server, callbackFunction) {
conn.isCloseCoAuthoring = true;
yield addPresence(ctx, conn, true);
if (cfgTokenEnableBrowser) {
sendDataRefreshToken(ctx, conn);
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
}
}
}
@ -1546,7 +1553,7 @@ exports.install = function(server, callbackFunction) {
if (!needSaveChanges) {
//start save changes if forgotten file exists.
//more effective to send file without sfc, but this method is simpler by code
let forgotten = yield storage.listObjects(ctx, cfgForgottenFiles + '/' + docId);
let forgotten = yield storage.listObjects(ctx, docId, cfgForgottenFiles);
needSaveChanges = forgotten.length > 0;
ctx.logger.debug('closeDocument hasForgotten %s', needSaveChanges);
}
@ -1592,7 +1599,8 @@ exports.install = function(server, callbackFunction) {
conn.docId = docIdNew;
yield addPresence(ctx, conn, true);
if (cfgTokenEnableBrowser) {
sendDataRefreshToken(ctx, conn);
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
}
}
//open
@ -2124,6 +2132,7 @@ exports.install = function(server, callbackFunction) {
function* auth(ctx, conn, data) {
//TODO: Do authorization etc. check md5 or query db
if (data.token && data.user) {
ctx.setUserId(data.user.id);
let licenseInfo = yield tenantManager.getTenantLicense(ctx);
//check jwt
if (cfgTokenEnableBrowser) {
@ -2162,6 +2171,7 @@ exports.install = function(server, callbackFunction) {
return;
}
}
ctx.setUserId(data.user.id);
let docId = data.docid;
const user = data.user;
@ -2433,7 +2443,7 @@ exports.install = function(server, callbackFunction) {
let callback = yield* sendStatusDocument(ctx, docId, c_oAscChangeBase.No, userAction, userIndex, documentCallback, conn.baseUrl);
if (!callback && !bIsRestore) {
//check forgotten file
let forgotten = yield storage.listObjects(ctx, cfgForgottenFiles + '/' + docId);
let forgotten = yield storage.listObjects(ctx, docId, cfgForgottenFiles);
hasForgotten = forgotten.length > 0;
ctx.logger.debug('endAuth hasForgotten %s', hasForgotten);
}
@ -2507,7 +2517,7 @@ exports.install = function(server, callbackFunction) {
}
changesJSON += ']\r\n';
let buffer = Buffer.from(changesJSON, 'utf8');
yield storage.putObject(ctx, changesPrefix + (indexChunk++).toString().padStart(3, '0'), buffer, buffer.length);
yield storage.putObject(ctx, changesPrefix + (indexChunk++).toString().padStart(3, '0'), buffer, buffer.length, cfgErrorFiles);
}
index += cfgMaxRequestChanges;
} while (changes && cfgMaxRequestChanges === changes.length);
@ -2560,6 +2570,10 @@ exports.install = function(server, callbackFunction) {
}
let allMessages = yield editorData.getMessages(ctx, docId);
allMessages = allMessages.length > 0 ? allMessages : undefined;//todo client side
let sessionToken;
if (cfgTokenEnableBrowser && !bIsRestore) {
sessionToken = yield fillJwtByConnection(ctx, conn);
}
const sendObject = {
type: 'auth',
result: 1,
@ -2570,7 +2584,7 @@ exports.install = function(server, callbackFunction) {
locks: docLock,
indexUser: conn.user.indexUser,
hasForgotten: opt_hasForgotten,
jwt: (!bIsRestore && cfgTokenEnableBrowser) ? fillJwtByConnection(conn) : undefined,
jwt: sessionToken,
g_cAscSpellCheckUrl: cfgEditor["spellcheckerUrl"],
buildVersion: commonDefines.buildVersion,
buildNumber: commonDefines.buildNumber,
@ -2975,6 +2989,7 @@ exports.install = function(server, callbackFunction) {
function _checkLicense(ctx, conn) {
return co(function* () {
try {
ctx.logger.info('_checkLicense start');
let rights = constants.RIGHTS.Edit;
if (config.get('server.edit_singleton')) {
// ToDo docId from url ?
@ -3005,9 +3020,11 @@ exports.install = function(server, callbackFunction) {
liveViewerSupport: (licenseInfo.connectionsView > 0 || licenseInfo.usersViewCount > 0 ),
branding: licenseInfo.branding,
customization: licenseInfo.customization,
advancedApi: licenseInfo.advancedApi,
plugins: licenseInfo.plugins
}
});
ctx.logger.info('_checkLicense end');
} catch (err) {
ctx.logger.error('_checkLicense error: %s', err.stack);
}
@ -3213,7 +3230,7 @@ exports.install = function(server, callbackFunction) {
} else {
let url;
if (cmd.getInline()) {
url = canvasService.getPrintFileUrl(data.needUrlKey, participant.baseUrl, cmd.getTitle());
url = yield canvasService.getPrintFileUrl(ctx, data.needUrlKey, participant.baseUrl, cmd.getTitle());
outputData.setExtName('.pdf');
} else {
url = yield storage.getSignedUrl(ctx, participant.baseUrl, data.needUrlKey, data.needUrlType, cmd.getTitle(), data.creationDate);
@ -3224,7 +3241,7 @@ exports.install = function(server, callbackFunction) {
if (undefined !== data.openedAt) {
outputData.setOpenedAt(data.openedAt);
}
modifyConnectionForPassword(ctx, participant, data.needUrlIsCorrectPassword);
yield modifyConnectionForPassword(ctx, participant, data.needUrlIsCorrectPassword);
}
sendData(ctx, participant, output);
}
@ -3283,7 +3300,8 @@ exports.install = function(server, callbackFunction) {
participant.user.username = cmd.getUserName();
yield addPresence(ctx, participant, false);
if (cfgTokenEnableBrowser) {
sendDataRefreshToken(ctx, participant);
let sessionToken = yield fillJwtByConnection(ctx, participant);
sendDataRefreshToken(ctx, participant, sessionToken);
}
}
}
@ -3316,6 +3334,7 @@ exports.install = function(server, callbackFunction) {
return co(function* () {
let ctx = new operationContext.Context();
try {
let tenants = {};
let countEditByShard = 0;
let countLiveViewByShard = 0;
let countViewByShard = 0;
@ -3325,6 +3344,10 @@ exports.install = function(server, callbackFunction) {
for (var i = 0; i < connections.length; ++i) {
var conn = connections[i];
ctx.initFromConnection(conn);
let tenant = tenants[ctx.tenant];
if (!tenant) {
tenant = tenants[ctx.tenant] = {countEditByShard: 0, countLiveViewByShard: 0, countViewByShard: 0};
}
//wopi access_token_ttl;
if (cfgExpSessionAbsolute > 0 || conn.access_token_ttl) {
if ((cfgExpSessionAbsolute > 0 && maxMs - conn.sessionTimeConnect > cfgExpSessionAbsolute ||
@ -3339,7 +3362,7 @@ exports.install = function(server, callbackFunction) {
continue;
}
}
if (cfgExpSessionIdle > 0) {
if (cfgExpSessionIdle > 0 && !conn.user?.view) {
if (maxMs - conn.sessionTimeLastAction > cfgExpSessionIdle && !conn.sessionIsSendWarning) {
conn.sessionIsSendWarning = true;
sendDataSession(ctx, conn, {
@ -3357,26 +3380,33 @@ exports.install = function(server, callbackFunction) {
}
yield addPresence(ctx, conn, false);
if (utils.isLiveViewer(conn)) {
countLiveViewByShard++;
tenant.countLiveViewByShard++;
} else if(conn.isCloseCoAuthoring || (conn.user && conn.user.view)) {
countViewByShard++;
tenant.countViewByShard++;
} else {
countEditByShard++;
tenant.countEditByShard++;
}
}
for (let tenantId in tenants) {
if(tenants.hasOwnProperty(tenantId)) {
ctx.setTenant(tenantId);
let tenant = tenants[tenantId];
yield* collectStats(ctx, tenant.countEditByShard, tenant.countLiveViewByShard, tenant.countViewByShard);
yield editorData.setEditorConnectionsCountByShard(ctx, SHARD_ID, tenant.countEditByShard);
yield editorData.setLiveViewerConnectionsCountByShard(ctx, SHARD_ID, tenant.countLiveViewByShard);
yield editorData.setViewerConnectionsCountByShard(ctx, SHARD_ID, tenant.countViewByShard);
if (clientStatsD) {
//todo with multitenant
let countEdit = yield editorData.getEditorConnectionsCount(ctx, connections);
clientStatsD.gauge('expireDoc.connections.edit', countEdit);
let countLiveView = yield editorData.getLiveViewerConnectionsCount(ctx, connections);
clientStatsD.gauge('expireDoc.connections.liveview', countLiveView);
let countView = yield editorData.getViewerConnectionsCount(ctx, connections);
clientStatsD.gauge('expireDoc.connections.view', countView);
}
}
}
ctx.initDefault();
yield* collectStats(ctx, countEditByShard, countLiveViewByShard, countViewByShard);
yield editorData.setEditorConnectionsCountByShard(ctx, SHARD_ID, countEditByShard);
yield editorData.setLiveViewerConnectionsCountByShard(ctx, SHARD_ID, countLiveViewByShard);
yield editorData.setViewerConnectionsCountByShard(ctx, SHARD_ID, countViewByShard);
if (clientStatsD) {
let countEdit = yield editorData.getEditorConnectionsCount(ctx, connections);
clientStatsD.gauge('expireDoc.connections.edit', countEdit);
let countLiveView = yield editorData.getLiveViewerConnectionsCount(ctx, connections);
clientStatsD.gauge('expireDoc.connections.liveview', countLiveView);
let countView = yield editorData.getViewerConnectionsCount(ctx, connections);
clientStatsD.gauge('expireDoc.connections.view', countView);
}
} catch (err) {
ctx.logger.error('expireDoc error: %s', err.stack);
} finally {

View File

@ -63,7 +63,6 @@ var cfgRedisPrefix = config.get('services.CoAuthoring.redis.prefix');
var cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');
const cfgTokenSessionAlgorithm = config.get('services.CoAuthoring.token.session.algorithm');
const cfgTokenSessionExpires = ms(config.get('services.CoAuthoring.token.session.expires'));
const cfgSecretSession = config.get('services.CoAuthoring.secret.session');
const cfgForgottenFiles = config_server.get('forgottenfiles');
const cfgForgottenFilesName = config_server.get('forgottenfilesname');
const cfgOpenProtectedFile = config_server.get('openProtectedFile');
@ -221,7 +220,7 @@ var getOutputData = co.wrap(function* (ctx, cmd, outputData, key, optConn, optAd
if (optConn) {
let url;
if(cmd.getInline()) {
url = getPrintFileUrl(key, optConn.baseUrl, cmd.getTitle());
url = yield getPrintFileUrl(ctx, key, optConn.baseUrl, cmd.getTitle());
} else {
url = yield storage.getSignedUrl(ctx, optConn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Temporary,
cmd.getTitle());
@ -487,13 +486,12 @@ function* commandOpen(ctx, conn, cmd, outputData, opt_upsertRes, opt_bIsRestore)
let updateIfRes = yield taskResult.updateIf(ctx, task, updateMask);
if (updateIfRes.affectedRows > 0) {
let forgottenId = cfgForgottenFiles + '/' + cmd.getDocId();
let forgotten = yield storage.listObjects(ctx, forgottenId);
let forgotten = yield storage.listObjects(ctx, cmd.getDocId(), cfgForgottenFiles);
//replace url with forgotten file because it absorbed all lost changes
if (forgotten.length > 0) {
ctx.logger.debug("commandOpen from forgotten");
cmd.setUrl(undefined);
cmd.setForgotten(forgottenId);
cmd.setForgotten(cmd.getDocId());
}
//add task
cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_CANVAS);
@ -530,7 +528,7 @@ function* commandReopen(ctx, conn, cmd, outputData) {
if (sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password)) {
ctx.logger.debug('commandReopen has password');
yield* commandOpenFillOutput(ctx, conn, cmd, outputData, false);
docsCoServer.modifyConnectionForPassword(ctx, conn, constants.FILE_STATUS_OK === outputData.getStatus());
yield docsCoServer.modifyConnectionForPassword(ctx, conn, constants.FILE_STATUS_OK === outputData.getStatus());
return res;
}
}
@ -848,7 +846,7 @@ function* commandSetPassword(ctx, conn, cmd, outputData) {
if (upsertRes.affectedRows > 0) {
outputData.setStatus('ok');
if (!conn.isEnterCorrectPassword) {
docsCoServer.modifyConnectionForPassword(ctx, conn, true);
yield docsCoServer.modifyConnectionForPassword(ctx, conn, true);
}
yield docsCoServer.resetForceSaveAfterChanges(ctx, cmd.getDocId(), newChangesLastDate.getTime(), 0, utils.getBaseUrlByConnection(conn), changeInfo);
} else {
@ -979,8 +977,7 @@ function* commandSfcCallback(ctx, cmd, isSfcm, isEncrypted) {
outputSfc.setUserData(cmd.getUserData());
if (!isError || isErrorCorrupted) {
try {
let forgottenId = cfgForgottenFiles + '/' + docId;
let forgotten = yield storage.listObjects(ctx, forgottenId);
let forgotten = yield storage.listObjects(ctx, docId, cfgForgottenFiles);
let isSendHistory = 0 === forgotten.length;
if (!isSendHistory) {
//check indicator file to determine if opening was from the forgotten file
@ -1120,7 +1117,7 @@ function* commandSfcCallback(ctx, cmd, isSfcm, isEncrypted) {
try {
ctx.logger.warn("storeForgotten");
let forgottenName = cfgForgottenFilesName + pathModule.extname(cmd.getOutputPath());
yield storage.copyObject(ctx, savePathDoc, cfgForgottenFiles + '/' + docId + '/' + forgottenName);
yield storage.copyObject(ctx, savePathDoc, docId + '/' + forgottenName, undefined, cfgForgottenFiles);
} catch (err) {
ctx.logger.error('Error storeForgotten: %s', err.stack);
}
@ -1453,17 +1450,19 @@ exports.saveFile = function(req, res) {
}
});
};
function getPrintFileUrl(docId, baseUrl, filename) {
baseUrl = utils.checkBaseUrl(baseUrl);
let token = '';
if (cfgTokenEnableBrowser) {
let payload = {document: {key: docId}};
token = docsCoServer.signToken(payload, cfgTokenSessionAlgorithm, cfgTokenSessionExpires / 1000, cfgSecretSession);
}
//while save printed file Chrome's extension seems to rely on the resource name set in the URI https://stackoverflow.com/a/53593453
//replace '/' with %2f before encodeURIComponent becase nginx determine %2f as '/' and get wrong system path
var userFriendlyName = encodeURIComponent(filename.replace(/\//g, "%2f"));
return `${baseUrl}/printfile/${encodeURIComponent(docId)}/${userFriendlyName}?token=${encodeURIComponent(token)}&filename=${userFriendlyName}`;
function getPrintFileUrl(ctx, docId, baseUrl, filename) {
return co(function*() {
baseUrl = utils.checkBaseUrl(baseUrl);
let token = '';
if (cfgTokenEnableBrowser) {
let payload = {document: {key: docId}};
token = yield docsCoServer.signToken(ctx, payload, cfgTokenSessionAlgorithm, cfgTokenSessionExpires / 1000, commonDefines.c_oAscSecretType.Session);
}
//while save printed file Chrome's extension seems to rely on the resource name set in the URI https://stackoverflow.com/a/53593453
//replace '/' with %2f before encodeURIComponent becase nginx determine %2f as '/' and get wrong system path
var userFriendlyName = encodeURIComponent(filename.replace(/\//g, "%2f"));
return `${baseUrl}/printfile/${encodeURIComponent(docId)}/${userFriendlyName}?token=${encodeURIComponent(token)}&filename=${userFriendlyName}`;
});
}
exports.getPrintFileUrl = getPrintFileUrl;
exports.printFile = function(req, res) {

View File

@ -122,22 +122,20 @@ exports.uploadImageFileOld = function(req, res) {
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.setDocId(docId);
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;
ctx.setDocId(docId);
ctx.setUserId(checkJwtRes.userid);
} else {
res.sendStatus(403);
return;
}
}
ctx.init(ctx.tenant, docId, userid);
var listImages = [];
//todo userid
if (docId) {
var isError = false;
var form = new multiparty.Form();
@ -176,7 +174,7 @@ exports.uploadImageFileOld = function(req, res) {
ctx.logger.error('Error parsing form part:%s', err.toString());
});
});
form.on('close', function() {
form.once('close', function() {
if (isError) {
res.sendStatus(400);
} else {
@ -193,8 +191,8 @@ exports.uploadImageFileOld = function(req, res) {
ctx.logger.debug('End uploadImageFileOld:%s', output);
}
).catch(function(err) {
ctx.logger.error('error getSignedUrlsByArray:%s', err.stack);
res.sendStatus(400);
ctx.logger.error('upload getSignedUrlsByArray:%s', err.stack);
});
}
});
@ -229,11 +227,12 @@ exports.uploadImageFile = function(req, res) {
if (!transformedRes.err) {
docId = transformedRes.docId || docId;
encrypted = transformedRes.encrypted;
ctx.setDocId(docId);
ctx.setUserId(transformedRes.userid);
} else {
isValidJwt = false;
}
}
ctx.setDocId(docId);
if (isValidJwt && docId && req.body && Buffer.isBuffer(req.body)) {
let buffer = req.body;

View File

@ -218,8 +218,9 @@ docsCoServer.install(server, () => {
res.sendStatus(403);
}
});
app.post('/uploadold/:docid/:userid/:index', fileUploaderService.uploadImageFileOld);
app.post('/upload/:docid/:userid/:index', rawFileParser, fileUploaderService.uploadImageFile);
//'*' for backward compatible
app.post('/uploadold/:docid*', fileUploaderService.uploadImageFileOld);
app.post('/upload/:docid*', rawFileParser, fileUploaderService.uploadImageFile);
app.post('/downloadas/:docid', rawFileParser, canvasService.downloadAs);
app.post('/savefile/:docid', rawFileParser, canvasService.saveFile);

View File

@ -74,6 +74,7 @@ var cfgInputLimits = configConverter.get('inputLimits');
const cfgStreamWriterBufferSize = configConverter.get('streamWriterBufferSize');
//cfgMaxRequestChanges was obtained as a result of the test: 84408 changes - 5,16 MB
const cfgMaxRequestChanges = config.get('services.CoAuthoring.server.maxRequestChanges');
const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles');
const cfgForgottenFilesName = config.get('services.CoAuthoring.server.forgottenfilesname');
const cfgNewFileTemplate = config.get('services.CoAuthoring.server.newFileTemplate');
@ -346,8 +347,8 @@ function* downloadFile(ctx, uri, fileFrom, withAuthorization, filterPrivate, opt
}
return res;
}
function* downloadFileFromStorage(ctx, strPath, dir) {
var list = yield storage.listObjects(ctx, strPath);
function* downloadFileFromStorage(ctx, strPath, dir, opt_specialDir) {
var list = yield storage.listObjects(ctx, strPath, opt_specialDir);
ctx.logger.debug('downloadFileFromStorage list %s', list.toString());
//create dirs
var dirsToCreate = [];
@ -374,7 +375,7 @@ function* downloadFileFromStorage(ctx, 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(ctx, file);
var data = yield storage.getObject(ctx, file, opt_specialDir);
fs.writeFileSync(path.join(dir, fileRel), data);
}
}
@ -617,7 +618,7 @@ function* postProcess(ctx, cmd, dataConvert, tempDirs, childRes, error, isTimeou
writeProcessOutputToLog(ctx, childRes, false);
ctx.logger.error('ExitCode (code=%d;signal=%s;error:%d)', exitCode, exitSignal, error);
if (cfgErrorFiles) {
yield* processUploadToStorage(ctx, tempDirs.temp, cfgErrorFiles + '/' + dataConvert.key);
yield* processUploadToStorage(ctx, tempDirs.temp, dataConvert.key, cfgErrorFiles);
ctx.logger.debug('processUploadToStorage error complete(id=%s)', dataConvert.key);
}
}
@ -791,7 +792,7 @@ function* ExecuteTask(ctx, task) {
}
error = yield* processDownloadFromStorage(ctx, dataConvert, cmd, task, tempDirs, authorProps);
} else if (cmd.getForgotten()) {
yield* downloadFileFromStorage(ctx, cmd.getForgotten(), tempDirs.source);
yield* downloadFileFromStorage(ctx, cmd.getForgotten(), tempDirs.source, cfgForgottenFiles);
ctx.logger.debug('downloadFileFromStorage complete');
let list = yield utils.listObjects(tempDirs.source, false);
if (list.length > 0) {
@ -816,21 +817,16 @@ function* ExecuteTask(ctx, task) {
let childRes = null;
let isTimeout = false;
if (constants.NO_ERROR === error) {
if(constants.AVS_OFFICESTUDIO_FILE_OTHER_HTMLZIP === dataConvert.formatTo && cmd.getSaveKey() && !dataConvert.mailMergeSend) {
//todo заглушка.вся конвертация на клиенте, но нет простого механизма сохранения на клиенте
yield utils.pipeFiles(dataConvert.fileFrom, dataConvert.fileTo);
} else {
({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()) {
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(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()) {
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(ctx, isBuilder, tempDirs, dataConvert, authorProps, getTaskTime, task));
}
}
if(clientStatsD) {
clientStatsD.timing('conv.spawnSync', new Date() - curDate);

View File

@ -40,7 +40,7 @@ PRIMARY KEY ("tenant", "id")
)
WITH (OIDS=FALSE);
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
CREATE OR REPLACE FUNCTION merge_db(_tenant 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;
@ -49,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 tenant = _tetant AND 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 = _tenant 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 tenant = _tetant AND 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 = _tenant AND id = _id RETURNING user_index into userindex;
END IF;
IF found THEN
isupdate := 'true';
@ -61,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(_tetant, _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(_tenant, _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