[jwt] Validate required parameters of browser token

This commit is contained in:
Sergey Konovalov
2022-02-03 23:58:46 +03:00
committed by Sergey Konovalov
parent 733a941d59
commit b4d72bc7f8
5 changed files with 216 additions and 78 deletions

View File

@ -28,6 +28,17 @@
}
}
},
"ajv": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"apicache": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/apicache/-/apicache-1.6.2.tgz",
@ -433,6 +444,11 @@
}
}
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"faye-websocket": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
@ -512,6 +528,11 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"json5": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
@ -846,6 +867,11 @@
"ipaddr.js": "1.9.1"
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@ -920,6 +946,11 @@
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-1.3.0.tgz",
"integrity": "sha1-gG6+e7+3005NfB6e8oLvz60EEmo="
},
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
"retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
@ -1095,6 +1126,14 @@
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"requires": {
"punycode": "^2.1.0"
}
},
"utf7": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz",

View File

@ -9,6 +9,7 @@
"prepare4shutdown": "sources/shutdown.js"
},
"dependencies": {
"ajv": "^8.9.0",
"apicache": "^1.6.2",
"base64-stream": "^1.0.0",
"body-parser": "^1.18.3",

View File

@ -105,6 +105,8 @@ const wopiClient = require('./wopiClient');
const queueService = require('./../../Common/sources/taskqueueRabbitMQ');
const rabbitMQCore = require('./../../Common/sources/rabbitMQCore');
const activeMQCore = require('./../../Common/sources/activeMQCore');
const Ajv = require("ajv");
const openingFileSchema = require("../../schema/json-api/opening-file.json");
const editorDataStorage = require('./' + configCommon.get('services.CoAuthoring.server.editorDataStorage'));
let cfgEditor = JSON.parse(JSON.stringify(config.get('editor')));
@ -181,6 +183,9 @@ const PRECISION = [{name: 'hour', val: ms('1h')}, {name: 'day', val: ms('1d')},
{name: 'month', val: ms('31d')},
];
const ajv = new Ajv();
const ajvValidate = ajv.compile(openingFileSchema);
function getIsShutdown() {
return shutdownFlag;
}
@ -1940,93 +1945,81 @@ exports.install = function(server, callbackFunction) {
//not '=' because if it jwt from previous version, we must use values from data
Object.assign(data.permissions, permissions);
}
//issuer for secret
if (decoded.iss) {
data.iss = decoded.iss;
}
return res;
}
function fillDataFromJwt(decoded, data) {
let res = true;
var openCmd = data.openCmd;
if (decoded.document) {
var doc = decoded.document;
if(null != doc.key){
data.docid = doc.key;
if(openCmd){
openCmd.id = doc.key;
var doc = decoded.document;
data.docid = doc.key;
if (openCmd) {
openCmd.id = doc.key;
}
if (doc.permissions) {
res = deepEqual(data.permissions, doc.permissions, {strict: true});
if (!data.permissions) {
data.permissions = {};
}
//not '=' because if it jwt from previous version, we must use values from data
Object.assign(data.permissions, doc.permissions);
}
if (openCmd) {
if (null != doc.fileType) {
openCmd.format = doc.fileType;
}
if (null != doc.title) {
openCmd.title = doc.title;
}
openCmd.url = doc.url;
}
if (null != doc.ds_encrypted) {
data.encrypted = doc.ds_encrypted;
}
var edit = decoded.editorConfig;
data.documentCallbackUrl = edit.callbackUrl;
if (null != edit.lang) {
data.lang = edit.lang;
}
if (null != edit.mode) {
data.mode = edit.mode;
}
if (null != edit.ds_view) {
data.view = edit.ds_view;
}
if (null != edit.ds_isCloseCoAuthoring) {
data.isCloseCoAuthoring = edit.ds_isCloseCoAuthoring;
}
data.isEnterCorrectPassword = edit.ds_isEnterCorrectPassword;
data.denyChangeName = edit.ds_denyChangeName;
if (edit.user) {
var dataUser = data.user;
var user = edit.user;
if (null != user.id) {
dataUser.id = user.id;
if (openCmd) {
openCmd.userid = user.id;
}
}
if(doc.permissions) {
res = deepEqual(data.permissions, doc.permissions, {strict: true});
if(!data.permissions){
data.permissions = {};
}
//not '=' because if it jwt from previous version, we must use values from data
Object.assign(data.permissions, doc.permissions);
if (null != user.index) {
dataUser.indexUser = user.index;
}
if(openCmd){
if(null != doc.fileType) {
openCmd.format = doc.fileType;
}
if(null != doc.title) {
openCmd.title = doc.title;
}
if(null != doc.url) {
openCmd.url = doc.url;
}
if (null != user.firstname) {
dataUser.firstname = user.firstname;
}
if (null != doc.ds_encrypted) {
data.encrypted = doc.ds_encrypted;
if (null != user.lastname) {
dataUser.lastname = user.lastname;
}
if (user.name) {
dataUser.username = user.name;
}
}
if (decoded.editorConfig) {
var edit = decoded.editorConfig;
if (null != edit.callbackUrl) {
data.documentCallbackUrl = edit.callbackUrl;
}
if (null != edit.lang) {
data.lang = edit.lang;
}
if (null != edit.mode) {
data.mode = edit.mode;
}
if (null != edit.ds_view) {
data.view = edit.ds_view;
}
if (null != edit.ds_isCloseCoAuthoring) {
data.isCloseCoAuthoring = edit.ds_isCloseCoAuthoring;
}
data.isEnterCorrectPassword = edit.ds_isEnterCorrectPassword;
data.denyChangeName = edit.ds_denyChangeName;
if (edit.user) {
var dataUser = data.user;
var user = edit.user;
if (null != user.id) {
dataUser.id = user.id;
if (openCmd) {
openCmd.userid = user.id;
}
}
if (null != user.index) {
dataUser.indexUser = user.index;
}
if (null != user.firstname) {
dataUser.firstname = user.firstname;
}
if (null != user.lastname) {
dataUser.lastname = user.lastname;
}
if (user.name) {
dataUser.username = user.name;
}
}
if (edit.user && edit.user.name) {
data.denyChangeName = true;
}
}
res = res && fillDataFromWopiJwt(decoded, data);
//todo make required fields
if (decoded.url || decoded.payload|| (decoded.key && !decoded.fileInfo)) {
res = false;
if (edit.user && edit.user.name) {
data.denyChangeName = true;
}
//issuer for secret
@ -2073,7 +2066,26 @@ exports.install = function(server, callbackFunction) {
commonDefines.c_oAscSecretType.Browser;
const checkJwtRes = checkJwt(docId, data.jwtSession || data.jwtOpen, secretType);
if (checkJwtRes.decoded) {
if (!fillDataFromJwt(checkJwtRes.decoded, data)) {
let decoded = checkJwtRes.decoded;
let fillDataFromJwtRes = false;
if (decoded.fileInfo) {
//wopi
fillDataFromJwtRes = fillDataFromWopiJwt(decoded, data);
} else if (decoded.editorConfig && undefined !== decoded.editorConfig.ds_view) {
//reconnection
fillDataFromJwtRes = fillDataFromJwt(decoded, data);
} else {
//opening
let valid = ajvValidate(decoded);
if (valid) {
fillDataFromJwtRes = fillDataFromJwt(decoded, data);
} else {
logger.error("auth missing required parameter (since 7.1 version): docId = %s %j", docId, ajvValidate.errors);
conn.close(constants.JWT_ERROR_CODE, constants.JWT_ERROR_REASON);
return;
}
}
if(!fillDataFromJwtRes) {
logger.warn("fillDataFromJwt return false: docId = %s", docId);
conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
return;

View File

@ -1081,7 +1081,7 @@ function* commandSfcCallback(cmd, isSfcm, isEncrypted) {
if (!isSfcm) {
//cleanupRes can be false in case of simultaneous opening. it is OK
let cleanupRes = yield cleanupCacheIf(updateMask);
logger.debug('storeForgotten cleanupRes=%s', cleanupRes);
logger.debug('storeForgotten cleanupRes=%s: docId = %s', cleanupRes, docId);
}
}
if (forceSave) {

View File

@ -0,0 +1,86 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://example.com/example.json",
"type": "object",
"required": [
"document",
"editorConfig"
],
"properties": {
"document": {
"$id": "#/properties/document",
"type": "object",
"required": [
"key",
"permissions",
"url"
],
"properties": {
"key": {
"$id": "#/properties/document/properties/key",
"type": "string"
},
"permissions": {
"$id": "#/properties/document/properties/permissions",
"type": "object",
"required": [],
"additionalProperties": true
},
"url": {
"$id": "#/properties/document/properties/url",
"type": "string"
}
},
"additionalProperties": true
},
"editorConfig": {
"$id": "#/properties/editorConfig",
"type": "object",
"required": [
"callbackUrl",
"mode"
],
"properties": {
"callbackUrl": {
"$id": "#/properties/editorConfig/properties/callbackUrl",
"type": "string"
},
"mode": {
"$id": "#/properties/editorConfig/properties/mode",
"type": "string"
},
"user": {
"$id": "#/properties/editorConfig/properties/user",
"type": "object",
"required": [],
"properties": {
"group": {
"$id": "#/properties/editorConfig/properties/user/properties/group",
"type": "string"
},
"id": {
"$id": "#/properties/editorConfig/properties/user/properties/id",
"anyOf": [
{
"$id": "#/properties/editorConfig/properties/user/properties/id/anyOf/0",
"type": "string"
},
{
"$id": "#/properties/editorConfig/properties/user/properties/id/anyOf/1",
"type": "integer"
}
]
},
"name": {
"$id": "#/properties/editorConfig/properties/user/properties/name",
"type": "string"
}
},
"additionalProperties": true
}
},
"additionalProperties": true
}
},
"additionalProperties": true
}