add more thorough tests for sandbox configuration on the checkup page

pull/1/head
ansuz 4 years ago
parent 506c78f121
commit cc56745858

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

@ -20,7 +20,6 @@ define([
], function ($, ApiConfig, Assertions, h, Messages, DomReady, ], function ($, ApiConfig, Assertions, h, Messages, DomReady,
nThen, SFCommonO, Login, Hash, Util, Pinpad, nThen, SFCommonO, Login, Hash, Util, Pinpad,
NetConfig, Pages) { NetConfig, Pages) {
var Assert = Assertions(); var Assert = Assertions();
var trimSlashes = function (s) { var trimSlashes = function (s) {
if (typeof(s) !== 'string') { return 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 trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin); 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) { assert(function (cb, msg) {
msg.appendChild(h('span', [ msg.appendChild(h('span', [
@ -125,7 +121,7 @@ define([
var checkAvailability = function (url, cb) { var checkAvailability = function (url, cb) {
$.ajax({ $.ajax({
url: url, // XXX bust cache url: cacheBuster(url),
data: {}, data: {},
complete: function (xhr) { complete: function (xhr) {
cb(xhr.status === 200); cb(xhr.status === 200);
@ -166,7 +162,6 @@ define([
])); ]));
var to; var to;
var obj;
nThen(function (waitFor) { nThen(function (waitFor) {
DomReady.onReady(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -174,13 +169,8 @@ define([
console.error('TIMEOUT loading iframe on the safe domain'); console.error('TIMEOUT loading iframe on the safe domain');
cb(false); cb(false);
}, 5000); }, 5000);
obj = SFCommonO.initIframe(waitFor); SFCommonO.initIframe(waitFor);
}).nThen(function () { }).nThen(function () {
SFCommonO.start({
href: obj.href,
});
}).nThen(function () {
console.error("DONE?");
// Iframe is loaded // Iframe is loaded
clearTimeout(to); clearTimeout(to);
cb(true); cb(true);
@ -351,14 +341,14 @@ define([
assert(function (cb, msg) { assert(function (cb, msg) {
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. "; msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
var url = sheetURL; var url = cacheBuster(sheetURL);
var expect = { var expect = {
'cross-origin-resource-policy': 'cross-origin', 'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp', 'cross-origin-embedder-policy': 'require-corp',
//'cross-origin-opener-policy': 'same-origin', // FIXME this is in our nginx config but not server.js //'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) { complete: function (xhr) {
cb(!Object.keys(expect).some(function (k) { cb(!Object.keys(expect).some(function (k) {
var response = xhr.getResponseHeader(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. ", "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', dataType: 'text',
complete: function (xhr) { complete: function (xhr) {
cb(xhr.status === 200); cb(xhr.status === 200);
@ -443,7 +433,7 @@ define([
}; };
var checkAPIHeaders = function (url, msg, cb) { var checkAPIHeaders = function (url, msg, cb) {
$.ajax(url, { // XXX bust cache $.ajax(cacheBuster(url), {
dataType: 'text', dataType: 'text',
complete: function (xhr) { complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders(); 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.'; var INCORRECT_HEADER_TEXT = ' was served with duplicated or incorrect headers. Compare your reverse-proxy configuration against the provided example.';
assert(function (cb, msg) { assert(function (cb, msg) {
var url = '/api/config'; // XXX bust cache var url = '/api/config';
msg.innerText = url + INCORRECT_HEADER_TEXT; msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, msg, cb); checkAPIHeaders(url, msg, cb);
}); });
assert(function (cb, msg) { assert(function (cb, msg) {
var url = '/api/broadcast'; // XXX bust cache var url = '/api/broadcast';
msg.innerText = url + INCORRECT_HEADER_TEXT; msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, msg, cb); checkAPIHeaders(url, msg, cb);
}); });
@ -559,6 +549,166 @@ define([
cb(false); 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) { if (false) {
assert(function (cb, msg) { assert(function (cb, msg) {
msg.innerText = 'fake test to simulate failure'; msg.innerText = 'fake test to simulate failure';

@ -1,34 +1,16 @@
define([ define([
'jquery', 'jquery',
'/api/config', //'/bower_components/nthen/index.js',
'/assert/assertions.js', //'/common/common-util.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/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/checkup/app-checkup.less', 'less!/checkup/app-checkup.less',
], function ($, ApiConfig, Assertions, h, Messages, DomReady, ], function ($ /*, nThen, Util */) {
nThen, SFCommonO, Login, Hash, Util, Pinpad, var postMessage = function (content) {
NetConfig, Pages) { window.parent.postMessage(JSON.stringify(content), '*');
var Assert = Assertions();
var assert = function (f, msg) {
Assert(f, msg || h('span.advisory-text.cp-danger'));
}; };
postMessage({ command: "READY", });
var code = function (content) {
return h('code', content);
};
var getHeaders = function (url, cb) { var getHeaders = function (url, cb) {
$.ajax(url + "?test=" + (+new Date()), { $.ajax(url + "?test=" + (+new Date()), {
dataType: 'text', dataType: 'text',
@ -38,138 +20,33 @@ define([
}, },
}); });
}; };
var parseCSP = function (CSP) { var COMMANDS = {};
//console.error(CSP); COMMANDS.GET_HEADER = function (content, cb) {
var CSP_headers = {}; var url = content.url;
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.',
]));
getHeaders(url, function (err, headers, xhr) { getHeaders(url, function (err, headers, xhr) {
var CSP_headers = parseCSP(xhr.getResponseHeader('content-security-policy')); cb(xhr.getResponseHeader(content.header));
cb(hasOnlyOfficeHeaders(CSP_headers));
}); });
});
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) { window.addEventListener("message", function (event) {
var errors = state.errors; if (event && event.data) {
var failed = errors.length; try {
//console.log(JSON.parse(event.data));
Messages.assert_numberOfTestsPassed = "{0} / {1} tests passed."; var msg = JSON.parse(event.data);
var command = msg.command;
var statusClass = failed? 'failure': 'success'; var txid = msg.txid;
COMMANDS[command](msg.content, function (response) {
var failedDetails = "Details found below"; // postMessage with same txid
var successDetails = "This checkup only tests the most common configuration issues. You may still experience errors or incorrect behaviour."; postMessage({
var details = h('p', failed? failedDetails: successDetails); txid: txid,
content: response,
var summary = h('div.summary.' + statusClass, [ });
versionStatement(), });
h('p', Messages._getKey('assert_numberOfTestsPassed', [ } catch (err) {
state.passed, console.error(err);
state.total }
])), } else {
details, console.error(event);
]); }
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]))
])
]));
}); });
}); });

Loading…
Cancel
Save