[wopi] Always sign and verify session token; Verify browser token if present; Always sign browser token for wopi; For bug 75893

This commit is contained in:
Sergey Konovalov
2025-08-21 11:11:42 +03:00
parent 6d672be874
commit 076f1b5b76
6 changed files with 87 additions and 129 deletions

View File

@ -600,13 +600,10 @@ function sendReleaseLock(ctx, conn, userLocks) {
}
function modifyConnectionForPassword(ctx, conn, isEnterCorrectPassword) {
return co(function*() {
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
if (isEnterCorrectPassword) {
conn.isEnterCorrectPassword = true;
if (tenTokenEnableBrowser) {
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
}
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
}
});
}
@ -1100,7 +1097,7 @@ async function saveRelativeFromChanges(ctx, conn, responseKey, data) {
let docId = data.docId;
let token = data.token;
let forceSaveRes;
if (tenTokenEnableBrowser) {
if (tenTokenEnableBrowser || token) {
docId = null;
let checkJwtRes = await checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
if (checkJwtRes.decoded) {
@ -1685,9 +1682,9 @@ exports.install = function(server, callbackFunction) {
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
let handshake = socket.handshake;
if (tenTokenEnableBrowser) {
const token = handshake?.auth?.session || handshake?.auth?.token;
if (tenTokenEnableBrowser || token) {
let secretType = !!(handshake?.auth?.session) ? commonDefines.c_oAscSecretType.Session : commonDefines.c_oAscSecretType.Browser;
let token = handshake?.auth?.session || handshake?.auth?.token;
checkJwtRes = yield checkJwt(ctx, token, secretType);
if (!checkJwtRes.decoded) {
res = new Error("not authorized");
@ -1879,7 +1876,6 @@ exports.install = function(server, callbackFunction) {
* @param reason - the reason of the disconnection (either client or server-side)
*/
function* closeDocument(ctx, conn, reason) {
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles);
ctx.logger.info("Connection closed or timed out: reason = %s", reason);
@ -1916,10 +1912,8 @@ exports.install = function(server, callbackFunction) {
modifyConnectionEditorToView(ctx, conn);
conn.isCloseCoAuthoring = true;
yield addPresence(ctx, conn, true);
if (tenTokenEnableBrowser) {
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
}
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
}
}
@ -2135,8 +2129,6 @@ exports.install = function(server, callbackFunction) {
}
function* sendFileErrorAuth(ctx, conn, sessionId, errorId, code, opt_notWarn) {
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
conn.sessionId = sessionId;//restore old
//Kill previous connections
connections = _.reject(connections, function(el) {
@ -2150,10 +2142,8 @@ exports.install = function(server, callbackFunction) {
// We put it in an array, because we need to send data to open/save the document
connections.push(conn);
yield addPresence(ctx, conn, true);
if (tenTokenEnableBrowser) {
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
}
let sessionToken = yield fillJwtByConnection(ctx, conn);
sendDataRefreshToken(ctx, conn, sessionToken);
sendFileError(ctx, conn, errorId, code, opt_notWarn);
}
}
@ -2554,9 +2544,10 @@ exports.install = function(server, callbackFunction) {
let [licenseInfo] = yield tenantManager.getTenantLicense(ctx);
let isDecoded = false;
//check jwt
if (tenTokenEnableBrowser) {
const token = data.jwtSession || data.jwtOpen;
if (tenTokenEnableBrowser || token) {
let secretType = !!data.jwtSession ? commonDefines.c_oAscSecretType.Session : commonDefines.c_oAscSecretType.Browser;
const checkJwtRes = yield checkJwt(ctx, data.jwtSession || data.jwtOpen, secretType);
const checkJwtRes = yield checkJwt(ctx, token, secretType);
if (checkJwtRes.decoded) {
isDecoded = true;
let decoded = checkJwtRes.decoded;
@ -3086,7 +3077,6 @@ exports.install = function(server, callbackFunction) {
} while (connections.length > 0 && changes && tenMaxRequestChanges === changes.length);
}
function* sendAuthInfo(ctx, conn, bIsRestore, participantsMap, opt_hasForgotten, opt_openedAt) {
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenImageSize = ctx.getCfg('services.CoAuthoring.server.limits_image_size', cfgImageSize);
const tenTypesUpload = ctx.getCfg('services.CoAuthoring.utils.limits_image_types_upload', cfgTypesUpload);
@ -3102,7 +3092,7 @@ exports.install = function(server, callbackFunction) {
let allMessages = yield editorData.getMessages(ctx, docId);
allMessages = allMessages.length > 0 ? allMessages : undefined;//todo client side
let sessionToken;
if (tenTokenEnableBrowser && !bIsRestore) {
if (!bIsRestore) {
sessionToken = yield fillJwtByConnection(ctx, conn);
}
let tenEditor = getEditorConfig(ctx);
@ -3621,7 +3611,6 @@ exports.install = function(server, callbackFunction) {
ctx.initFromPubSub(data);
yield ctx.initTenantCache();
ctx.logger.debug('pubsub message start:%s', msg);
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
var participants;
var participant;
@ -3795,10 +3784,8 @@ exports.install = function(server, callbackFunction) {
ctx.logger.debug('changeConnectionInfo: userId = %s', data.useridoriginal);
participant.user.username = cmd.getUserName();
yield addPresence(ctx, participant, false);
if (tenTokenEnableBrowser) {
let sessionToken = yield fillJwtByConnection(ctx, participant);
sendDataRefreshToken(ctx, participant, sessionToken);
}
let sessionToken = yield fillJwtByConnection(ctx, participant);
sendDataRefreshToken(ctx, participant, sessionToken);
}
}
if (hasChanges) {

View File

@ -162,7 +162,6 @@ async function proxyRequest(req, res) {
try {
ctx.logger.info('Start proxyRequest');
await ctx.initTenantCache();
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenAiApiTimeout = ctx.getCfg('aiSettings.timeout', cfgAiApiTimeout);
const tenAiApi = ctx.getCfg('aiSettings', cfgAiSettings);
const tenAiApiProxy = ctx.getCfg('aiSettings.proxy', cfgAiApiProxy);
@ -176,26 +175,24 @@ async function proxyRequest(req, res) {
let userId = '';
let userName = '';
let userCustomerId = '';
if (tenTokenEnableBrowser) {
let checkJwtRes = await docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
if (!checkJwtRes || checkJwtRes.err) {
ctx.logger.error('proxyRequest: checkJwtHeader error: %s', checkJwtRes?.err);
res.status(403).json({
"error": {
"message": "proxyRequest: checkJwtHeader error",
"code": "403"
}
});
return;
} else {
docId = checkJwtRes?.decoded?.document?.key;
userId = checkJwtRes?.decoded?.editorConfig?.user?.id;
userName = checkJwtRes?.decoded?.editorConfig?.user?.name;
userCustomerId = checkJwtRes?.decoded?.editorConfig?.user?.customerId;
ctx.setDocId(docId);
ctx.setUserId(userId);
}
let checkJwtRes = await docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
if (!checkJwtRes || checkJwtRes.err) {
ctx.logger.error('proxyRequest: checkJwtHeader error: %s', checkJwtRes?.err);
res.status(403).json({
"error": {
"message": "proxyRequest: checkJwtHeader error",
"code": "403"
}
});
return;
} else {
docId = checkJwtRes?.decoded?.document?.key;
userId = checkJwtRes?.decoded?.editorConfig?.user?.id;
userName = checkJwtRes?.decoded?.editorConfig?.user?.name;
userCustomerId = checkJwtRes?.decoded?.editorConfig?.user?.customerId;
ctx.setDocId(docId);
ctx.setUserId(userId);
}
if (!tenAiApi?.providers) {

View File

@ -695,7 +695,7 @@ function* commandImgurls(ctx, conn, cmd, outputData) {
let authorizations = [];
let isInJwtToken = false;
let token = cmd.getTokenDownload();
if (tenTokenEnableBrowser && token) {
if (tenTokenEnableBrowser && token) {// allow requests without token
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
if (checkJwtRes.decoded) {
//todo multiple url case
@ -1447,7 +1447,7 @@ exports.downloadAs = function(req, res) {
ctx.logger.debug('Start downloadAs: %s', strCmd);
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
if (tenTokenEnableBrowser) {
if (tenTokenEnableBrowser || cmd.getTokenDownload() || cmd.getTokenSession()) {
var isValidJwt = false;
if (cmd.getTokenDownload()) {
let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenDownload(), commonDefines.c_oAscSecretType.Browser);
@ -1535,28 +1535,25 @@ exports.saveFile = function(req, res) {
docId = cmd.getDocId();
ctx.setDocId(docId);
ctx.logger.debug('Start saveFile');
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
if (tenTokenEnableBrowser) {
let isValidJwt = false;
let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session);
if (checkJwtRes.decoded) {
let doc = checkJwtRes.decoded.document;
var edit = checkJwtRes.decoded.editorConfig;
if (doc.ds_encrypted && !edit.ds_view && !edit.ds_isCloseCoAuthoring) {
isValidJwt = true;
docId = doc.key;
cmd.setDocId(doc.key);
} else {
ctx.logger.warn('Error saveFile jwt: %s', 'access deny');
}
let isValidJwt = false;
let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session);
if (checkJwtRes.decoded) {
let doc = checkJwtRes.decoded.document;
var edit = checkJwtRes.decoded.editorConfig;
if (doc.ds_encrypted && !edit.ds_view && !edit.ds_isCloseCoAuthoring) {
isValidJwt = true;
docId = doc.key;
cmd.setDocId(doc.key);
} else {
ctx.logger.warn('Error saveFile jwt: %s', checkJwtRes.description);
}
if (!isValidJwt) {
res.sendStatus(403);
return;
ctx.logger.warn('Error saveFile jwt: %s', 'access deny');
}
} else {
ctx.logger.warn('Error saveFile jwt: %s', checkJwtRes.description);
}
if (!isValidJwt) {
res.sendStatus(403);
return;
}
ctx.setDocId(docId);
cmd.setStatusInfo(constants.NO_ERROR);
@ -1582,16 +1579,12 @@ exports.saveFile = function(req, res) {
};
function getPrintFileUrl(ctx, docId, baseUrl, filename) {
return co(function*() {
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenTokenSessionAlgorithm = ctx.getCfg('services.CoAuthoring.token.session.algorithm', cfgTokenSessionAlgorithm);
const tenTokenSessionExpires = ms(ctx.getCfg('services.CoAuthoring.token.session.expires', cfgTokenSessionExpires));
baseUrl = utils.checkBaseUrl(ctx, baseUrl);
let token = '';
if (tenTokenEnableBrowser) {
let payload = {document: {key: docId}};
token = yield docsCoServer.signToken(ctx, payload, tenTokenSessionAlgorithm, tenTokenSessionExpires / 1000, commonDefines.c_oAscSecretType.Session);
}
let payload = {document: {key: docId}};
let token = yield docsCoServer.signToken(ctx, payload, tenTokenSessionAlgorithm, tenTokenSessionExpires / 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
let userFriendlyName = encodeURIComponent(filename.replace(/\//g, "%2f"));
@ -1626,22 +1619,19 @@ exports.printFile = function(req, res) {
docId = req.params.docid;
ctx.setDocId(docId);
ctx.logger.info('Start printFile');
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
if (tenTokenEnableBrowser) {
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session);
if (checkJwtRes.decoded) {
let docIdBase = checkJwtRes.decoded.document.key;
if (!docId.startsWith(docIdBase)) {
ctx.logger.warn('Error printFile jwt: description = %s', 'access deny');
res.sendStatus(403);
return;
}
} else {
ctx.logger.warn('Error printFile jwt: description = %s', checkJwtRes.description);
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session);
if (checkJwtRes.decoded) {
let docIdBase = checkJwtRes.decoded.document.key;
if (!docId.startsWith(docIdBase)) {
ctx.logger.warn('Error printFile jwt: description = %s', 'access deny');
res.sendStatus(403);
return;
}
} else {
ctx.logger.warn('Error printFile jwt: description = %s', checkJwtRes.description);
res.sendStatus(403);
return;
}
ctx.setDocId(docId);
let streamObj = yield storage.createReadStream(ctx, `${docId}/${constants.OUTPUT_NAME}.pdf`);

View File

@ -685,7 +685,7 @@ function getConverterHtmlHandler(req, res) {
return;
}
let token = req.query['token'];
if (tenTokenEnableBrowser) {
if (tenTokenEnableBrowser || token) {
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
if (checkJwtRes.decoded) {
docId = checkJwtRes.decoded.docId;

View File

@ -50,10 +50,6 @@ const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.brow
const PATTERN_ENCRYPTED = 'ENCRYPTED;';
function* checkJwtUpload(ctx, errorName, token){
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session);
return checkJwtUploadTransformRes(ctx, errorName, checkJwtRes);
}
function checkJwtUploadTransformRes(ctx, errorName, checkJwtRes){
var res = {err: true, docId: null, userid: null, encrypted: null};
if (checkJwtRes.decoded) {
@ -90,23 +86,20 @@ exports.uploadImageFile = function(req, res) {
ctx.logger.debug('Start uploadImageFile');
const tenImageSize = ctx.getCfg('services.CoAuthoring.server.limits_image_size', cfgImageSize);
const tenTypesUpload = ctx.getCfg('services.CoAuthoring.utils.limits_image_types_upload', cfgTypesUpload);
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
if (tenTokenEnableBrowser) {
let checkJwtRes = yield docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
if (!checkJwtRes) {
//todo remove compatibility with previous versions
checkJwtRes = yield docsCoServer.checkJwt(ctx, req.query['token'], commonDefines.c_oAscSecretType.Session);
}
let transformedRes = checkJwtUploadTransformRes(ctx, 'uploadImageFile', checkJwtRes);
if (!transformedRes.err) {
docId = transformedRes.docId || docId;
encrypted = transformedRes.encrypted;
ctx.setDocId(docId);
ctx.setUserId(transformedRes.userid);
} else {
httpStatus = 403;
}
let checkJwtRes = yield docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
if (!checkJwtRes) {
//todo remove compatibility with previous versions
checkJwtRes = yield docsCoServer.checkJwt(ctx, req.query['token'], commonDefines.c_oAscSecretType.Session);
}
let transformedRes = checkJwtUploadTransformRes(ctx, 'uploadImageFile', checkJwtRes);
if (!transformedRes.err) {
docId = transformedRes.docId || docId;
encrypted = transformedRes.encrypted;
ctx.setDocId(docId);
ctx.setUserId(transformedRes.userid);
} else {
httpStatus = 403;
}
if (200 === httpStatus && docId && req.body && Buffer.isBuffer(req.body)) {

View File

@ -594,7 +594,6 @@ function getEditorHtml(req, res) {
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', cfgTokenOutboxAlgorithm);
const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', cfgTokenOutboxExpires);
const tenWopiFileInfoBlockList = ctx.getCfg('wopi.fileInfoBlockList', cfgWopiFileInfoBlockList);
@ -662,11 +661,9 @@ function getEditorHtml(req, res) {
delete params.fileInfo[item];
});
if (tenTokenEnableBrowser) {
let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires};
let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
params.token = jwt.sign(params, secret, options);
}
let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires};
let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
params.token = jwt.sign(params, secret, options);
} catch (err) {
ctx.logger.error('wopiEditor error: %s', err.stack);
params.fileInfo = {};
@ -689,7 +686,6 @@ function getConverterHtml(req, res) {
try {
ctx.initFromRequest(req);
yield ctx.initTenantCache();
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', cfgTokenOutboxAlgorithm);
const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', cfgTokenOutboxExpires);
const tenWopiHost = ctx.getCfg('wopi.host', cfgWopiHost);
@ -723,14 +719,12 @@ function getConverterHtml(req, res) {
params.statusHandler = `${baseUrl}/hosting/wopi/convert-and-edit-handler`;
params.statusHandler += `?${constants.SHARD_KEY_WOPI_NAME}=${encodeURIComponent(wopiSrc)}&access_token=${encodeURIComponent(access_token)}`;
params.statusHandler += `&targetext=${encodeURIComponent(targetext)}&docId=${encodeURIComponent(docId)}`;
if (tenTokenEnableBrowser) {
let tokenData = {docId: docId};
let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires};
let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
let token = jwt.sign(tokenData, secret, options);
let tokenData = {docId: docId};
let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires};
let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
let token = jwt.sign(tokenData, secret, options);
params.statusHandler += `&token=${encodeURIComponent(token)}`;
}
params.statusHandler += `&token=${encodeURIComponent(token)}`;
}
} catch (err) {
ctx.logger.error('convert-and-edit error:%s', err.stack);
@ -894,7 +888,6 @@ async function refreshFile(ctx, wopiParams, baseUrl) {
if (!userAuth) {
return;
}
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', cfgTokenOutboxAlgorithm);
const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', cfgTokenOutboxExpires);
@ -906,11 +899,9 @@ async function refreshFile(ctx, wopiParams, baseUrl) {
if (!prepareResult) {
return;
}
if (tenTokenEnableBrowser) {
let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires};
let secret = await tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
res.token = jwt.sign(res, secret, options);
}
let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires};
let secret = await tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
res.token = jwt.sign(res, secret, options);
} catch (err) {
res = undefined;
ctx.logger.error('wopi error RefreshFile:%s', err.stack);