mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-02-10 18:05:07 +08:00
[wopi] Log userSessionId; Allow usid in discovery query param; Send WOPI X-WOPI-CorrelationId/X-WOPI-SessionId headers;
This commit is contained in:
@ -3,8 +3,8 @@
|
||||
"default": {
|
||||
"type": "console",
|
||||
"layout": {
|
||||
"type": "pattern",
|
||||
"pattern": "%[[%d] [%p] [%X{TENANT}] [%X{DOCID}] [%X{USERID}] %c -%] %.10000m"
|
||||
"type": "patternWithTokens",
|
||||
"pattern": "%[[%d] [%p] [%X{TENANT}] [%X{DOCID}] [%X{USERID}]%x{usid} %c -%] %.10000m"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
"default": {
|
||||
"type": "console",
|
||||
"layout": {
|
||||
"type": "pattern",
|
||||
"pattern": "[%d] [%p] [%X{TENANT}] [%X{DOCID}] [%X{USERID}] %c - %.10000m"
|
||||
"type": "patternWithTokens",
|
||||
"pattern": "[%d] [%p] [%X{TENANT}] [%X{DOCID}] [%X{USERID}]%x{usid} %c - %.10000m"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -52,6 +52,7 @@ exports.DEFAULT_USER_ID = 'userId';
|
||||
exports.ALLOWED_PROTO = /^https?$/i;
|
||||
exports.SHARD_KEY_WOPI_NAME = 'WOPISrc';
|
||||
exports.SHARD_KEY_API_NAME = 'shardkey';
|
||||
exports.USER_SESSION_ID_NAME = 'usid';
|
||||
|
||||
exports.RIGHTS = {
|
||||
None : 0,
|
||||
|
||||
@ -36,6 +36,7 @@ var config = require('config');
|
||||
var util = require('util');
|
||||
|
||||
var log4js = require('log4js');
|
||||
const layouts = require('log4js/lib/layouts');
|
||||
|
||||
// https://stackoverflow.com/a/36643588
|
||||
var dateToJSONWithTZ = function (d) {
|
||||
@ -62,6 +63,23 @@ log4js.addLayout('json', function(config) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Custom pattern layout that supports %x{usid} using USERSESSIONID from context.
|
||||
* @param {object} cfg
|
||||
* @returns {function}
|
||||
*/
|
||||
log4js.addLayout('patternWithTokens', function(cfg) {
|
||||
const pattern = (cfg && cfg.pattern) ? cfg.pattern : '%m';
|
||||
const baseTokens = (cfg && cfg.tokens) ? cfg.tokens : {};
|
||||
const tokens = Object.assign({}, baseTokens, {
|
||||
usid: function(ev) {
|
||||
const id = ev && ev.context && ev.context.USERSESSIONID;
|
||||
return id ? ` [${id}]` : '';
|
||||
}
|
||||
});
|
||||
return layouts.patternLayout(pattern, tokens);
|
||||
});
|
||||
|
||||
log4js.configure(config.get('log.filePath'));
|
||||
|
||||
var logger = log4js.getLogger('nodeJS');
|
||||
|
||||
@ -43,12 +43,13 @@ function Context(){
|
||||
this.logger = logger.getLogger('nodeJS');
|
||||
this.initDefault();
|
||||
}
|
||||
Context.prototype.init = function(tenant, docId, userId, opt_shardKey, opt_WopiSrc) {
|
||||
Context.prototype.init = function(tenant, docId, userId, opt_shardKey, opt_WopiSrc, opt_userSessionId) {
|
||||
this.setTenant(tenant);
|
||||
this.setDocId(docId);
|
||||
this.setUserId(userId);
|
||||
this.setShardKey(opt_shardKey);
|
||||
this.setWopiSrc(opt_WopiSrc);
|
||||
this.setUserSessionId(opt_userSessionId);
|
||||
|
||||
this.config = null;
|
||||
this.secret = null;
|
||||
@ -72,21 +73,23 @@ Context.prototype.initFromConnection = function(conn) {
|
||||
let userId = conn.user?.id;
|
||||
let shardKey = utils.getShardKeyByConnection(this, conn);
|
||||
let wopiSrc = utils.getWopiSrcByConnection(this, conn);
|
||||
this.init(tenant, docId || this.docId, userId || this.userId, shardKey, wopiSrc);
|
||||
let userSessionId = utils.getSessionIdByConnection(this, conn);
|
||||
this.init(tenant, docId || this.docId, userId || this.userId, shardKey, wopiSrc, userSessionId);
|
||||
};
|
||||
Context.prototype.initFromRequest = function(req) {
|
||||
let tenant = tenantManager.getTenantByRequest(this, req);
|
||||
let shardKey = utils.getShardKeyByRequest(this, req);
|
||||
let wopiSrc = utils.getWopiSrcByRequest(this, req);
|
||||
this.init(tenant, this.docId, this.userId, shardKey, wopiSrc);
|
||||
let userSessionId = utils.getSessionIdByRequest(this, req);
|
||||
this.init(tenant, this.docId, this.userId, shardKey, wopiSrc, userSessionId);
|
||||
};
|
||||
Context.prototype.initFromTaskQueueData = function(task) {
|
||||
let ctx = task.getCtx();
|
||||
this.init(ctx.tenant, ctx.docId, ctx.userId, ctx.shardKey, ctx.wopiSrc);
|
||||
this.init(ctx.tenant, ctx.docId, ctx.userId, ctx.shardKey, ctx.wopiSrc, ctx.userSessionId);
|
||||
};
|
||||
Context.prototype.initFromPubSub = function(data) {
|
||||
let ctx = data.ctx;
|
||||
this.init(ctx.tenant, ctx.docId, ctx.userId, ctx.shardKey, ctx.wopiSrc);
|
||||
this.init(ctx.tenant, ctx.docId, ctx.userId, ctx.shardKey, ctx.wopiSrc, ctx.userSessionId);
|
||||
};
|
||||
Context.prototype.initTenantCache = async function() {
|
||||
const runtimeConfig = await runtimeConfigManager.getConfig(this);
|
||||
@ -114,11 +117,18 @@ Context.prototype.setShardKey = function(shardKey) {
|
||||
Context.prototype.setWopiSrc = function(wopiSrc) {
|
||||
this.wopiSrc = wopiSrc;
|
||||
};
|
||||
Context.prototype.setUserSessionId = function(userSessionId) {
|
||||
if (userSessionId) {
|
||||
this.userSessionId = userSessionId;
|
||||
this.logger.addContext('USERSESSIONID', userSessionId);
|
||||
}
|
||||
};
|
||||
Context.prototype.toJSON = function() {
|
||||
return {
|
||||
tenant: this.tenant,
|
||||
docId: this.docId,
|
||||
userId: this.userId,
|
||||
userSessionId: this.userSessionId,
|
||||
shardKey: this.shardKey,
|
||||
wopiSrc: this.wopiSrc
|
||||
}
|
||||
|
||||
@ -877,16 +877,24 @@ function getShardKeyByConnection(ctx, conn) {
|
||||
function getWopiSrcByConnection(ctx, conn) {
|
||||
return conn?.handshake?.query?.[constants.SHARD_KEY_WOPI_NAME];
|
||||
}
|
||||
function getSessionIdByConnection(ctx, conn) {
|
||||
return conn?.handshake?.query?.[constants.USER_SESSION_ID_NAME];
|
||||
}
|
||||
function getShardKeyByRequest(ctx, req) {
|
||||
return req.query?.[constants.SHARD_KEY_API_NAME];
|
||||
}
|
||||
function getWopiSrcByRequest(ctx, req) {
|
||||
return req.query?.[constants.SHARD_KEY_WOPI_NAME];
|
||||
}
|
||||
function getSessionIdByRequest(ctx, req) {
|
||||
return req.query?.[constants.USER_SESSION_ID_NAME];
|
||||
}
|
||||
exports.getShardKeyByConnection = getShardKeyByConnection;
|
||||
exports.getWopiSrcByConnection = getWopiSrcByConnection;
|
||||
exports.getSessionIdByConnection = getSessionIdByConnection;
|
||||
exports.getShardKeyByRequest = getShardKeyByRequest;
|
||||
exports.getWopiSrcByRequest = getWopiSrcByRequest;
|
||||
exports.getSessionIdByRequest = getSessionIdByRequest;
|
||||
function stream2Buffer(stream) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (!stream.readable) {
|
||||
|
||||
@ -1602,6 +1602,9 @@ function getPrintFileUrl(ctx, docId, baseUrl, filename) {
|
||||
if (ctx.wopiSrc) {
|
||||
res += `&${constants.SHARD_KEY_WOPI_NAME}=${encodeURIComponent(ctx.wopiSrc)}`;
|
||||
}
|
||||
if (ctx.userSessionId) {
|
||||
res += `&${constants.USER_SESSION_ID_NAME}=${encodeURIComponent(ctx.userSessionId)}`;
|
||||
}
|
||||
res += `&filename=${userFriendlyName}`;
|
||||
return res;
|
||||
});
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const { pipeline } = require('node:stream/promises');
|
||||
const {URL} = require('url');
|
||||
@ -576,7 +577,7 @@ async function prepareDocumentForEditing(ctx, wopiSrc, fileInfo, userAuth, fileT
|
||||
if (!shutdownFlag) {
|
||||
const preOpenRes = await preOpen(ctx, checkRes.lockId, docId, fileInfo, userAuth, baseUrl, fileType);
|
||||
if (!preOpenRes && userAuth.mode !== 'view') {
|
||||
ctx.logger.error('prepareDocumentForEditing error: lock failed, fallback to view mode');
|
||||
ctx.logger.warn('prepareDocumentForEditing error: lock failed, fallback to view mode');
|
||||
userAuth.mode = 'view';
|
||||
userAuth.forcedViewMode = true;
|
||||
return await prepareDocumentForEditing(ctx, wopiSrc, fileInfo, userAuth, fileType, baseUrl, params);
|
||||
@ -601,6 +602,8 @@ function getEditorHtml(req, res) {
|
||||
let wopiSrc = req.query['wopisrc'];
|
||||
let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1);
|
||||
ctx.setDocId(fileId);
|
||||
let usid = req.query['usid'] || crypto.randomUUID();
|
||||
ctx.setUserSessionId(usid);
|
||||
|
||||
ctx.logger.info('wopiEditor start');
|
||||
ctx.logger.debug(`wopiEditor req.url:%s`, req.url);
|
||||
@ -610,7 +613,6 @@ function getEditorHtml(req, res) {
|
||||
params.documentType = req.params.documentType;
|
||||
let mode = req.params.mode;
|
||||
let sc = req.query['sc'];
|
||||
let hostSessionId = req.query['hid'];
|
||||
let lang = req.query['lang'];
|
||||
let ui = req.query['ui'];
|
||||
let access_token = req.body['access_token'] || "";
|
||||
@ -619,7 +621,15 @@ function getEditorHtml(req, res) {
|
||||
if (docs_api_config) {
|
||||
params.docs_api_config = JSON.parse(docs_api_config);
|
||||
}
|
||||
|
||||
// Create user authentication object
|
||||
const userAuth = params.userAuth = {
|
||||
wopiSrc: wopiSrc,
|
||||
access_token: access_token,
|
||||
access_token_ttl: access_token_ttl,
|
||||
userSessionId: usid,
|
||||
mode: mode,
|
||||
forcedViewMode: false
|
||||
};
|
||||
|
||||
let fileInfo = params.fileInfo = yield checkFileInfo(ctx, wopiSrc, access_token, sc);
|
||||
if (!fileInfo) {
|
||||
@ -633,19 +643,10 @@ function getEditorHtml(req, res) {
|
||||
|
||||
const canEdit = (fileInfo.UserCanOnlyComment || fileInfo.UserCanWrite || fileInfo.UserCanReview);
|
||||
if (!canEdit) {
|
||||
ctx.logger.error('wopiEditor error: edit mode is not allowed');
|
||||
mode = 'view';
|
||||
ctx.logger.warn('wopiEditor: edit mode is not allowed, fallback to view mode');
|
||||
userAuth.mode = 'view';
|
||||
userAuth.forcedViewMode = true;
|
||||
}
|
||||
// Create user authentication object
|
||||
const userAuth = params.userAuth = {
|
||||
wopiSrc: wopiSrc,
|
||||
access_token: access_token,
|
||||
access_token_ttl: access_token_ttl,
|
||||
hostSessionId: hostSessionId,
|
||||
userSessionId: undefined, // Will be set after prepareDocumentForEditing
|
||||
mode: mode,
|
||||
forcedViewMode: false
|
||||
};
|
||||
|
||||
// Prepare document for editing (docId, cache validation)
|
||||
const prepareResult = yield prepareDocumentForEditing(ctx, wopiSrc, fileInfo, userAuth, fileType, utils.getBaseUrlByRequest(ctx, req), params);
|
||||
@ -654,8 +655,6 @@ function getEditorHtml(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update userSessionId with the document ID
|
||||
userAuth.userSessionId = params.key;
|
||||
mode = userAuth.mode;
|
||||
ctx.setDocId(params.key);
|
||||
|
||||
@ -1054,7 +1053,7 @@ function getWopiParams(lockId, fileInfo, wopiSrc, access_token, access_token_ttl
|
||||
let commonInfo = {lockId: lockId, fileInfo: fileInfo};
|
||||
let userAuth = {
|
||||
wopiSrc: wopiSrc, access_token: access_token, access_token_ttl: access_token_ttl,
|
||||
hostSessionId: null, userSessionId: null, mode: null
|
||||
userSessionId: null, mode: null
|
||||
};
|
||||
return {commonInfo: commonInfo, userAuth: userAuth, LastModifiedTime: null};
|
||||
}
|
||||
|
||||
@ -106,9 +106,8 @@ async function fillStandardHeaders(ctx, headers, url, access_token) {
|
||||
}
|
||||
headers['X-WOPI-TimeStamp'] = timeStamp;
|
||||
headers['X-WOPI-ClientVersion'] = commonDefines.buildVersion + '.' + commonDefines.buildNumber;
|
||||
// todo
|
||||
// headers['X-WOPI-CorrelationId '] = "";
|
||||
// headers['X-WOPI-SessionId'] = "";
|
||||
headers['X-WOPI-CorrelationId'] = crypto.randomUUID();
|
||||
headers['X-WOPI-SessionId'] = ctx.userSessionId;
|
||||
//remove redundant header https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/common-headers#request-headers
|
||||
// headers['Authorization'] = `Bearer ${access_token}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user