add more thorough tests for sandbox configuration on the checkup page

This commit is contained in:
ansuz 2021-05-21 20:35:48 +05:30
parent 506c78f121
commit cc56745858
3 changed files with 202 additions and 181 deletions

View File

@ -12,12 +12,6 @@ html, body {
color: @cryptpad_text_col;
font-family: "IBM Plex Mono";
iframe.sandbox-test {
display: block;
width: 100%;
height: 100%;
}
.report {
font-size: 30px;
max-width: 50%;

View File

@ -20,7 +20,6 @@ define([
], function ($, ApiConfig, Assertions, h, Messages, DomReady,
nThen, SFCommonO, Login, Hash, Util, Pinpad,
NetConfig, Pages) {
var Assert = Assertions();
var trimSlashes = function (s) {
if (typeof(s) !== 'string') { return s; }
@ -51,15 +50,12 @@ define([
]);
};
var cacheBuster = function (url) {
return url + '?test=' + (+new Date());
};
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
/*
// XXX display results from this iframe on this page
document.body.appendChild(h('iframe', {
class: 'sandbox-test',
src: trimmedSafe + '/checkup/sandbox/index.html',
}));
*/
assert(function (cb, msg) {
msg.appendChild(h('span', [
@ -125,7 +121,7 @@ define([
var checkAvailability = function (url, cb) {
$.ajax({
url: url, // XXX bust cache
url: cacheBuster(url),
data: {},
complete: function (xhr) {
cb(xhr.status === 200);
@ -166,7 +162,6 @@ define([
]));
var to;
var obj;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
@ -174,13 +169,8 @@ define([
console.error('TIMEOUT loading iframe on the safe domain');
cb(false);
}, 5000);
obj = SFCommonO.initIframe(waitFor);
SFCommonO.initIframe(waitFor);
}).nThen(function () {
SFCommonO.start({
href: obj.href,
});
}).nThen(function () {
console.error("DONE?");
// Iframe is loaded
clearTimeout(to);
cb(true);
@ -351,14 +341,14 @@ define([
assert(function (cb, msg) {
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
var url = sheetURL;
var url = cacheBuster(sheetURL);
var expect = {
'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
//'cross-origin-opener-policy': 'same-origin', // FIXME this is in our nginx config but not server.js
};
$.ajax(url, { // XXX bust cache
$.ajax(url, {
complete: function (xhr) {
cb(!Object.keys(expect).some(function (k) {
var response = xhr.getResponseHeader(k);
@ -430,7 +420,7 @@ define([
"Your browser console may provide more details as to why this resource could not be loaded. ",
]));
$.ajax('/api/broadcast', { // XXX bust cache
$.ajax(cacheBuster('/api/broadcast'), {
dataType: 'text',
complete: function (xhr) {
cb(xhr.status === 200);
@ -443,7 +433,7 @@ define([
};
var checkAPIHeaders = function (url, msg, cb) {
$.ajax(url, { // XXX bust cache
$.ajax(cacheBuster(url), {
dataType: 'text',
complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders();
@ -495,13 +485,13 @@ define([
var INCORRECT_HEADER_TEXT = ' was served with duplicated or incorrect headers. Compare your reverse-proxy configuration against the provided example.';
assert(function (cb, msg) {
var url = '/api/config'; // XXX bust cache
var url = '/api/config';
msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, msg, cb);
});
assert(function (cb, msg) {
var url = '/api/broadcast'; // XXX bust cache
var url = '/api/broadcast';
msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, msg, cb);
});
@ -559,6 +549,166 @@ define([
cb(false);
});
var response = Util.response(function (err) {
console.error('SANDBOX_ERROR', err);
});
var sandboxIframe = h('iframe', {
class: 'sandbox-test',
src: cacheBuster(trimmedSafe + '/checkup/sandbox/index.html'),
});
document.body.appendChild(sandboxIframe);
var sandboxIframeReady = Util.mkEvent(true);
setTimeout(function () {
sandboxIframeReady.fire("TIMEOUT");
}, 10 * 1000);
var postMessage = function (content, cb) {
try {
var txid = Util.uid();
content.txid = txid;
response.expect(txid, cb, 15000);
sandboxIframe.contentWindow.postMessage(JSON.stringify(content), '*');
} catch (err) {
console.error(err);
}
};
window.addEventListener('message', function (event) {
try {
var msg = JSON.parse(event.data);
if (msg.command === 'READY') { return void sandboxIframeReady.fire(); }
var txid = msg.txid;
if (!txid) { return console.log("no handler for ", txid); }
response.handle(txid, msg.content);
} catch (err) {
console.error(event);
console.error(err);
}
});
var parseCSP = function (CSP) {
//console.error(CSP);
var CSP_headers = {};
CSP.split(";")
.forEach(function (rule) {
rule = (rule || "").trim();
if (!rule) { return; }
var parts = rule.split(/\s/);
var first = parts[0];
var rest = rule.slice(first.length + 1);
CSP_headers[first] = rest;
//console.error(rule.trim());
//console.info("[%s] '%s'", first, rest);
});
return CSP_headers;
};
var hasUnsafeEval = function (CSP_headers) {
return /unsafe\-eval/.test(CSP_headers['script-src']);
};
var hasUnsafeInline = function (CSP_headers) {
return /unsafe\-inline/.test(CSP_headers['script-src']);
};
var hasOnlyOfficeHeaders = function (CSP_headers) {
if (!hasUnsafeEval(CSP_headers)) {
console.error("NO_UNSAFE_EVAL");
console.log(CSP_headers);
return false;
}
if (!hasUnsafeInline(CSP_headers)) {
console.error("NO_UNSAFE_INLINE");
return void false;
}
return true;
};
var CSP_WARNING = function (url) {
return h('span', [
code(url),
' does not have the required ',
code("'content-security-policy'"),
' headers set. This is most often related to incorrectly configured sandbox domains or reverse proxies.',
]);
};
assert(function (_cb, msg) {
var url = '/sheet/inner.html';
var cb = Util.once(Util.mkAsync(_cb));
msg.appendChild(CSP_WARNING(url));
nThen(function (w) {
sandboxIframeReady.reg(w(function (err) {
if (!err) { return; }
w.abort();
cb(err);
}));
}).nThen(function () {
postMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
});
assert(function (cb, msg) {
var url = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
msg.appendChild(CSP_WARNING(url));
nThen(function (w) {
sandboxIframeReady.reg(w(function (err) {
if (!err) { return; }
w.abort();
cb(err);
}));
}).nThen(function () {
postMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
});
assert(function (cb, msg) {
var url = '/sheet/inner.html';
msg.appendChild(h('span', [
code(url),
' does not have the required ',
code("'cross-origin-opener-policy'"),
' headers set.',
]));
nThen(function (w) {
sandboxIframeReady.reg(w(function (err) {
if (!err) { return; }
w.abort();
cb(err);
}));
}).nThen(function () {
postMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'cross-origin-opener-policy',
},
}, function (content) {
cb(content === 'same-origin');
});
});
});
if (false) {
assert(function (cb, msg) {
msg.innerText = 'fake test to simulate failure';

View File

@ -1,34 +1,16 @@
define([
'jquery',
'/api/config',
'/assert/assertions.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/common/dom-ready.js',
'/bower_components/nthen/index.js',
'/common/sframe-common-outer.js',
'/customize/login.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/pinpad.js',
'/common/outer/network-config.js',
'/customize/pages.js',
//'/bower_components/nthen/index.js',
//'/common/common-util.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/checkup/app-checkup.less',
], function ($, ApiConfig, Assertions, h, Messages, DomReady,
nThen, SFCommonO, Login, Hash, Util, Pinpad,
NetConfig, Pages) {
var Assert = Assertions();
var assert = function (f, msg) {
Assert(f, msg || h('span.advisory-text.cp-danger'));
], function ($ /*, nThen, Util */) {
var postMessage = function (content) {
window.parent.postMessage(JSON.stringify(content), '*');
};
var code = function (content) {
return h('code', content);
};
postMessage({ command: "READY", });
var getHeaders = function (url, cb) {
$.ajax(url + "?test=" + (+new Date()), {
dataType: 'text',
@ -38,138 +20,33 @@ define([
},
});
};
var parseCSP = function (CSP) {
//console.error(CSP);
var CSP_headers = {};
CSP.split(";")
.forEach(function (rule) {
rule = (rule || "").trim();
if (!rule) { return; }
var parts = rule.split(/\s/);
var first = parts[0];
var rest = rule.slice(first.length + 1);
CSP_headers[first] = rest;
//console.error(rule.trim());
console.info("[%s] '%s'", first, rest);
});
return CSP_headers;
};
var hasUnsafeEval = function (CSP_headers) {
return /unsafe\-eval/.test(CSP_headers['script-src']);
};
var hasUnsafeInline = function (CSP_headers) {
return /unsafe\-inline/.test(CSP_headers['script-src']);
};
var hasOnlyOfficeHeaders = function (CSP_headers) {
if (!hasUnsafeEval(CSP_headers)) {
console.error("NO_UNSAFE_EVAL");
console.log(CSP_headers);
return false;
}
if (!hasUnsafeInline(CSP_headers)) {
console.error("NO_UNSAFE_INLINE");
return void false;
}
return true;
};
// XXX run these from /checkup/inner.js and report to /checkup/main.js
assert(function (cb, msg) {
var url = '/sheet/inner.html';
msg.appendChild(h('span', [
code(url),
' has the wrong headers.',
]));
var COMMANDS = {};
COMMANDS.GET_HEADER = function (content, cb) {
var url = content.url;
getHeaders(url, function (err, headers, xhr) {
var CSP_headers = parseCSP(xhr.getResponseHeader('content-security-policy'));
cb(hasOnlyOfficeHeaders(CSP_headers));
cb(xhr.getResponseHeader(content.header));
});
});
assert(function (cb, msg) {
var url = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
msg.appendChild(h('span', [
code(url),
' has the wrong headers.',
]));
getHeaders(url, function (err, headers, xhr) {
var CSP_headers = parseCSP(xhr.getResponseHeader('content-security-policy'));
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
var row = function (cells) {
return h('tr', cells.map(function (cell) {
return h('td', cell);
}));
};
var failureReport = function (obj) {
return h('div.error', [
h('h5', obj.message),
h('table', [
row(["Failed test number", obj.test + 1]),
row(["Returned value", obj.output]),
]),
]);
};
var completed = 0;
var $progress = $('#cp-progress');
var versionStatement = function () {
return h('p', [
"This instance is running ",
h('span.cp-app-checkup-version',[
"CryptPad",
' ',
Pages.versionString,
]),
'.',
]);
};
Assert.run(function (state) {
var errors = state.errors;
var failed = errors.length;
Messages.assert_numberOfTestsPassed = "{0} / {1} tests passed.";
var statusClass = failed? 'failure': 'success';
var failedDetails = "Details found below";
var successDetails = "This checkup only tests the most common configuration issues. You may still experience errors or incorrect behaviour.";
var details = h('p', failed? failedDetails: successDetails);
var summary = h('div.summary.' + statusClass, [
versionStatement(),
h('p', Messages._getKey('assert_numberOfTestsPassed', [
state.passed,
state.total
])),
details,
]);
var report = h('div.report', [
summary,
h('div.failures', errors.map(failureReport)),
]);
$progress.remove();
$('body').prepend(report);
}, function (i, total) {
console.log('test '+ i +' completed');
completed++;
Messages.assert_numberOfTestsCompleted = "{0} / {1} tests completed.";
$progress.html('').append(h('div.report.pending.summary', [
versionStatement(),
h('p', [
h('i.fa.fa-spinner.fa-pulse'),
h('span', Messages._getKey('assert_numberOfTestsCompleted', [completed, total]))
])
]));
window.addEventListener("message", function (event) {
if (event && event.data) {
try {
//console.log(JSON.parse(event.data));
var msg = JSON.parse(event.data);
var command = msg.command;
var txid = msg.txid;
COMMANDS[command](msg.content, function (response) {
// postMessage with same txid
postMessage({
txid: txid,
content: response,
});
});
} catch (err) {
console.error(err);
}
} else {
console.error(event);
}
});
});