diff --git a/lib/defaults.js b/lib/defaults.js index 4dd906515..85d3be55e 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -1,7 +1,8 @@ var Default = module.exports; -Default.commonCSP = function (domain, sandbox) { - domain = ' ' + domain; +Default.commonCSP = function (Env) { + domain = ' ' + Env.httpUnsafeOrigin; + sandbox = Env.httpSafeOrigin; sandbox = (sandbox && sandbox !== domain? ' ' + sandbox: ''); // Content-Security-Policy @@ -31,25 +32,25 @@ Default.commonCSP = function (domain, sandbox) { "media-src blob:", // for accounts.cryptpad.fr authentication and cross-domain iframe sandbox - "frame-ancestors *", + Env.disableEmbedding? `frame-ancestors ${domain}${sandbox}`: "frame-ancestors *", "worker-src 'self'", "" ]; }; -Default.contentSecurity = function (domain, sandbox) { - return (Default.commonCSP(domain, sandbox).join('; ') + "script-src 'self' resource: " + domain).replace(/\s+/g, ' '); +Default.contentSecurity = function (Env) { + return (Default.commonCSP(Env).join('; ') + "script-src 'self' resource: " + domain).replace(/\s+/g, ' '); }; -Default.padContentSecurity = function (domain, sandbox) { - return (Default.commonCSP(domain, sandbox).join('; ') + "script-src 'self' 'unsafe-eval' 'unsafe-inline' resource: " + domain).replace(/\s+/g, ' '); +Default.padContentSecurity = function (Env) { + return (Default.commonCSP(Env).join('; ') + "script-src 'self' 'unsafe-eval' 'unsafe-inline' resource: " + domain).replace(/\s+/g, ' '); }; -Default.httpHeaders = function () { +Default.httpHeaders = function (Env) { return { "X-XSS-Protection": "1; mode=block", "X-Content-Type-Options": "nosniff", - "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Origin": Env.disableEmbedding? '': "*", "Permissions-policy":"interest-cohort=()" }; }; diff --git a/lib/env.js b/lib/env.js index 9970bc4f9..fe2fa4828 100644 --- a/lib/env.js +++ b/lib/env.js @@ -39,9 +39,19 @@ module.exports.create = function (config) { DEV_MODE: false, configCache: {}, broadcastCache: {}, + + officeHeadersCache: undefined, + standardHeadersCache: undefined, + apiHeadersCache: undefined, + flushCache: function () { Env.configCache = {}; Env.broadcastCache = {}; + + Env.officeHeadersCache = undefined; + Env.standardHeadersCache = undefined; + Env.apiHeadersCache = undefined; + Env.FRESH_KEY = +new Date(); if (!(Env.DEV_MODE || Env.FRESH_MODE)) { Env.FRESH_MODE = true; } if (!Env.Log) { return; } diff --git a/server.js b/server.js index 7d08c3656..41b0b016d 100644 --- a/server.js +++ b/server.js @@ -48,9 +48,17 @@ var applyHeaderMap = function (res, map) { for (let header in map) { res.setHeader(header, map[header]); } }; -var setHeaders = (function () { - // load the default http headers unless the admin has provided their own via the config file - var headers; +var EXEMPT = [ + /^\/common\/onlyoffice\/.*\.html.*/, + /^\/(sheet|presentation|doc)\/inner\.html.*/, + /^\/unsafeiframe\/inner\.html.*$/, +]; + +var getHeaders = function (Env, type) { + var key = type + 'HeadersCache'; + if (Env[key]) { return Env[key]; } + + var headers = {}; var custom = config.httpHeaders; // if the admin provided valid http headers then use them @@ -58,11 +66,11 @@ var setHeaders = (function () { headers = Util.clone(custom); } else { // otherwise use the default - headers = Default.httpHeaders(); + headers = Default.httpHeaders(Env); } // next define the base Content Security Policy (CSP) headers - if (typeof(config.contentSecurity) === 'string') { + if (typeof(config.contentSecurity) === 'string') { // XXX deprecate this??? headers['Content-Security-Policy'] = config.contentSecurity; if (!/;$/.test(headers['Content-Security-Policy'])) { headers['Content-Security-Policy'] += ';' } if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) { @@ -73,47 +81,54 @@ var setHeaders = (function () { } } else { // use the default CSP headers constructed with your domain - headers['Content-Security-Policy'] = Default.contentSecurity(Env.httpUnsafeOrigin, Env.httpSafeOrigin); + headers['Content-Security-Policy'] = Default.contentSecurity(Env); } - const padHeaders = Util.clone(headers); - if (typeof(config.padContentSecurity) === 'string') { - padHeaders['Content-Security-Policy'] = config.padContentSecurity; - } else { - padHeaders['Content-Security-Policy'] = Default.padContentSecurity(Env.httpUnsafeOrigin, Env.httpSafeOrigin); + //const padHeaders = Util.clone(headers); + if (type === 'office') { + if (typeof(config.padContentSecurity) === 'string') { + headers['Content-Security-Policy'] = config.padContentSecurity; // XXX drop support for this + } else { + headers['Content-Security-Policy'] = Default.padContentSecurity(Env); + } + } +/* + headers['Content-Security-Policy'] = type === 'office'? + Default.padContentSecurity(Env): + Default.contentSecurity(Env);*/ + + if (Env.NO_SANDBOX) { // handles correct configuration for local development + // https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs + headers["Cross-Origin-Resource-Policy"] = 'cross-origin'; + headers["Cross-Origin-Embedder-Policy"] = 'require-corp'; } - if (Object.keys(headers).length) { - return function (req, res) { - if (Env.NO_SANDBOX) { // handles correct configuration for local development - // https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs - applyHeaderMap(res, { - "Cross-Origin-Resource-Policy": 'cross-origin', - "Cross-Origin-Embedder-Policy": 'require-corp', - }); - } - // Don't set CSP headers on /api/ endpoints - // because they aren't necessary and they cause problems - // when duplicated by NGINX in production environments - if (/^\/api\/(broadcast|config)/.test(req.url)) { return; } - - applyHeaderMap(res, { - "Cross-Origin-Resource-Policy": 'cross-origin', - }); - - // targeted CSP, generic policies, maybe custom headers - const h = [ - /^\/common\/onlyoffice\/.*\.html.*/, - /^\/(sheet|presentation|doc)\/inner\.html.*/, - /^\/unsafeiframe\/inner\.html.*$/, - ].some((regex) => { - return regex.test(req.url); - }) ? padHeaders : headers; - applyHeaderMap(res, h); - }; + // Don't set CSP headers on /api/ endpoints + // because they aren't necessary and they cause problems + // when duplicated by NGINX in production environments + if (type === 'api') { + Env[key] = headers; + return headers; } - return function () {}; -}()); + + headers["Cross-Origin-Resource-Policy"] = 'cross-origin'; + Env[key] = headers; + return headers; +}; + +var setHeaders = function (req, res) { + var type; + if (EXEMPT.some(regex => regex.test(req.url))) { + type = 'office'; + } else if (/^\/api\/(broadcast|config)/.test(req.url)) { + type = 'api'; + } else { + type = 'standard'; + } + + var h = getHeaders(Env, type); + applyHeaderMap(res, h); +}; (function () { if (!config.logFeedback) { return; } diff --git a/www/checkup/main.js b/www/checkup/main.js index e7d952dbb..f465283cc 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -978,8 +978,8 @@ define([ 'img-src': ["'self'", 'data:', 'blob:', $outer], 'media-src': ['blob:'], - //'frame-ancestors': ['*'], // XXX IFF you want to support remote embedding - 'worker-src': ["'self'"], // , $outer, $sandbox], + 'frame-ancestors': ApiConfig.disableEmbedding? [$outer, $sandbox]: ['*'], + 'worker-src': ["'self'"], }); cb(result); }); @@ -1016,7 +1016,7 @@ define([ ], 'img-src': ["'self'", 'data:', 'blob:', $outer], 'media-src': ['blob:'], - //'frame-ancestors': ['*'], // XXX IFF you want to support remote embedding + 'frame-ancestors': ApiConfig.disableEmbedding? [$outer, $sandbox]: ['*'], 'worker-src': ["'self'"],//, $outer, $sandbox], }); @@ -1026,7 +1026,7 @@ define([ assert(function (cb, msg) { var header = 'Access-Control-Allow-Origin'; - msg.appendChild(h('span', [ + msg.appendChild(h('span', [ // XXX update text to indicate that the value doesn't match their preference 'Assets must be served with an ', code(header), ' header with a value of ', @@ -1035,6 +1035,14 @@ define([ ])); Tools.common_xhr('/', function (xhr) { var raw = xhr.getResponseHeader(header); + + if (ApiConfig.disableEmbedding) { + if ([null, ''].includes(raw)) { return void cb(true); } + else { + return void cb(raw === '*' || raw); + } + } + cb(raw === "*" || raw); }); });