|
|
@ -19,38 +19,100 @@ 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) {
|
|
|
|
NetConfig) {
|
|
|
|
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; }
|
|
|
|
return s.replace(/\/+$/, '');
|
|
|
|
return s.replace(/\/+$/, '');
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var _alert = function (content) {
|
|
|
|
var assert = function (f, msg) {
|
|
|
|
return h('span.advisory-text', content);
|
|
|
|
Assert(f, msg || h('span.advisory-text'));
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var CONFIG_PATH = function () {
|
|
|
|
|
|
|
|
return h('code', 'cryptpad/config/config.js');
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
var API_CONFIG_LINK = function () {
|
|
|
|
|
|
|
|
return h('a', {
|
|
|
|
|
|
|
|
href: '/api/config',
|
|
|
|
|
|
|
|
target: '_blank',
|
|
|
|
|
|
|
|
}, '/api/config');
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var RESTART_WARNING = function () {
|
|
|
|
|
|
|
|
return h('span', [
|
|
|
|
|
|
|
|
'Changes to ',
|
|
|
|
|
|
|
|
CONFIG_PATH(),
|
|
|
|
|
|
|
|
' will require a server restart in order for ',
|
|
|
|
|
|
|
|
API_CONFIG_LINK(),
|
|
|
|
|
|
|
|
' to be updated.',
|
|
|
|
|
|
|
|
]);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
|
|
|
|
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
|
|
|
|
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
|
|
|
|
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb) {
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
"CryptPad's sandbox requires that both ",
|
|
|
|
|
|
|
|
h('code', 'httpUnsafeOrigin'),
|
|
|
|
|
|
|
|
' and ',
|
|
|
|
|
|
|
|
h('code', 'httpSafeOrigin'),
|
|
|
|
|
|
|
|
" be configured in ",
|
|
|
|
|
|
|
|
CONFIG_PATH(),
|
|
|
|
|
|
|
|
'. ',
|
|
|
|
|
|
|
|
RESTART_WARNING(),
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
//console.error(trimmedSafe, trimmedUnsafe);
|
|
|
|
//console.error(trimmedSafe, trimmedUnsafe);
|
|
|
|
cb(Boolean(trimmedSafe && trimmedUnsafe));
|
|
|
|
cb(Boolean(trimmedSafe && trimmedUnsafe));
|
|
|
|
}, _alert("Sandbox configuration: ensure that both httpUnsafeOrigin and httpSafeOrigin are defined"));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
h('code', 'httpUnsafeOrigin'),
|
|
|
|
|
|
|
|
' and ',
|
|
|
|
|
|
|
|
h('code', 'httpSafeOrigin'),
|
|
|
|
|
|
|
|
' are equivalent. ',
|
|
|
|
|
|
|
|
"In order for CryptPad's security features to be as effective as intended they must be different. ",
|
|
|
|
|
|
|
|
"See ",
|
|
|
|
|
|
|
|
CONFIG_PATH(),
|
|
|
|
|
|
|
|
'. ',
|
|
|
|
|
|
|
|
RESTART_WARNING(),
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb) {
|
|
|
|
|
|
|
|
return void cb(trimmedSafe !== trimmedUnsafe);
|
|
|
|
return void cb(trimmedSafe !== trimmedUnsafe);
|
|
|
|
}, _alert('Sandbox configuration: httpUnsafeOrigin !== httpSafeOrigin'));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
h('code', 'httpUnsafeOrigin'),
|
|
|
|
|
|
|
|
' and ',
|
|
|
|
|
|
|
|
h('code', 'httpSafeOrigin'),
|
|
|
|
|
|
|
|
' must not contain trailing slashes. This can be configured in ',
|
|
|
|
|
|
|
|
CONFIG_PATH(),
|
|
|
|
|
|
|
|
'. ',
|
|
|
|
|
|
|
|
RESTART_WARNING(),
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
cb(trimmedSafe === ApiConfig.httpSafeOrigin && trimmedUnsafe === ApiConfig.httpUnsafeOrigin);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb) {
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
cb(trimmedSafe === ApiConfig.httpSafeOrigin);
|
|
|
|
msg.appendChild(h("span", [
|
|
|
|
}, "httpSafeOrigin must not have a trailing slash");
|
|
|
|
"It appears that you are trying to load this page via an origin other than its main domain (",
|
|
|
|
|
|
|
|
h('code', ApiConfig.httpUnsafeOrigin),
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb) {
|
|
|
|
"). See the ",
|
|
|
|
|
|
|
|
h('code', 'httpUnsafeOrigin'),
|
|
|
|
|
|
|
|
" option in ",
|
|
|
|
|
|
|
|
CONFIG_PATH(),
|
|
|
|
|
|
|
|
" which is exposed via ",
|
|
|
|
|
|
|
|
API_CONFIG_LINK(),
|
|
|
|
|
|
|
|
'.',
|
|
|
|
|
|
|
|
]));
|
|
|
|
var origin = window.location.origin;
|
|
|
|
var origin = window.location.origin;
|
|
|
|
return void cb(ApiConfig.httpUnsafeOrigin === origin);
|
|
|
|
return void cb(ApiConfig.httpUnsafeOrigin === origin);
|
|
|
|
}, _alert('Sandbox configuration: loading via httpUnsafeOrigin'));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var checkAvailability = function (url, cb) {
|
|
|
|
var checkAvailability = function (url, cb) {
|
|
|
|
$.ajax({
|
|
|
|
$.ajax({
|
|
|
@ -62,12 +124,38 @@ define([
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb) {
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
"The main domain (configured via ",
|
|
|
|
|
|
|
|
h('code', 'httpUnsafeOrigin'),
|
|
|
|
|
|
|
|
' as ',
|
|
|
|
|
|
|
|
ApiConfig.httpUnsafeOrigin,
|
|
|
|
|
|
|
|
' in ',
|
|
|
|
|
|
|
|
CONFIG_PATH(),
|
|
|
|
|
|
|
|
' and exposed via ',
|
|
|
|
|
|
|
|
API_CONFIG_LINK(),
|
|
|
|
|
|
|
|
') could not be reached.',
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
checkAvailability(trimmedUnsafe, cb);
|
|
|
|
checkAvailability(trimmedUnsafe, cb);
|
|
|
|
}, _alert("Main domain is not available"));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Try loading an iframe on the safe domain
|
|
|
|
// Try loading an iframe on the safe domain
|
|
|
|
assert(function (cb) {
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
"Your browser was not able to load an iframe using the origin specified as ",
|
|
|
|
|
|
|
|
h('code', "httpSafeOrigin"),
|
|
|
|
|
|
|
|
" (",
|
|
|
|
|
|
|
|
ApiConfig.httpSafeOrigin,
|
|
|
|
|
|
|
|
") in ",
|
|
|
|
|
|
|
|
CONFIG_PATH(),
|
|
|
|
|
|
|
|
". This can be caused by an invalid ",
|
|
|
|
|
|
|
|
h('code', 'httpUnsafeDomain'),
|
|
|
|
|
|
|
|
', invalid CSP configuration in your reverse proxy, invalid SSL certificates, and many other factors. ',
|
|
|
|
|
|
|
|
'More information about your particular error may be found in your browser console. ',
|
|
|
|
|
|
|
|
RESTART_WARNING(),
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
var to;
|
|
|
|
var to;
|
|
|
|
nThen(function (waitFor) {
|
|
|
|
nThen(function (waitFor) {
|
|
|
|
DomReady.onReady(waitFor());
|
|
|
|
DomReady.onReady(waitFor());
|
|
|
@ -82,16 +170,22 @@ define([
|
|
|
|
clearTimeout(to);
|
|
|
|
clearTimeout(to);
|
|
|
|
cb(true);
|
|
|
|
cb(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, _alert("Sandbox domain is not available"));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Test Websocket
|
|
|
|
// Test Websocket
|
|
|
|
var evWSError = Util.mkEvent(true);
|
|
|
|
var evWSError = Util.mkEvent(true);
|
|
|
|
assert(function (cb) {
|
|
|
|
assert(function (_cb, msg) {
|
|
|
|
|
|
|
|
var cb = Util.once(Util.both(_cb, function (err) {
|
|
|
|
|
|
|
|
if (typeof(err) === 'string') {
|
|
|
|
|
|
|
|
msg.innerText = err;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
var ws = new WebSocket(NetConfig.getWebsocketURL());
|
|
|
|
var ws = new WebSocket(NetConfig.getWebsocketURL());
|
|
|
|
var to = setTimeout(function () {
|
|
|
|
var to = setTimeout(function () {
|
|
|
|
console.error('Websocket TIMEOUT');
|
|
|
|
console.error('Websocket TIMEOUT');
|
|
|
|
evWSError.fire();
|
|
|
|
evWSError.fire();
|
|
|
|
cb('TIMEOUT (5 seconds)');
|
|
|
|
cb('Could not connect to the websocket server within 5 seconds.');
|
|
|
|
}, 5000);
|
|
|
|
}, 5000);
|
|
|
|
ws.onopen = function () {
|
|
|
|
ws.onopen = function () {
|
|
|
|
clearTimeout(to);
|
|
|
|
clearTimeout(to);
|
|
|
@ -99,14 +193,25 @@ define([
|
|
|
|
};
|
|
|
|
};
|
|
|
|
ws.onerror = function (err) {
|
|
|
|
ws.onerror = function (err) {
|
|
|
|
clearTimeout(to);
|
|
|
|
clearTimeout(to);
|
|
|
|
console.error('Websocket error', err);
|
|
|
|
console.error('[Websocket error]', err);
|
|
|
|
evWSError.fire();
|
|
|
|
evWSError.fire();
|
|
|
|
cb('WebSocket error: check your console');
|
|
|
|
cb('Unable to connect to the websocket server. More information may be available in your browser console ([Websocket error]).');
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}, _alert("Websocket is not available"));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Test login block
|
|
|
|
// Test login block
|
|
|
|
assert(function (cb) {
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
"Unable to create, retrieve, or remove encrypted credentials from the server. ",
|
|
|
|
|
|
|
|
"This is most commonly caused by a mismatch between the value of the ",
|
|
|
|
|
|
|
|
h('code', 'blockPath'),
|
|
|
|
|
|
|
|
' value configured in ',
|
|
|
|
|
|
|
|
CONFIG_PATH(),
|
|
|
|
|
|
|
|
" and the corresponding settings in your reverse proxy's configuration file,",
|
|
|
|
|
|
|
|
" but it can also be explained by a websocket error. ",
|
|
|
|
|
|
|
|
RESTART_WARNING(),
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
var bytes = new Uint8Array(Login.requiredBytes);
|
|
|
|
var bytes = new Uint8Array(Login.requiredBytes);
|
|
|
|
|
|
|
|
|
|
|
|
var opt = Login.allocateBytes(bytes);
|
|
|
|
var opt = Login.allocateBytes(bytes);
|
|
|
@ -132,7 +237,7 @@ define([
|
|
|
|
// If WebSockets aren't working, don't wait forever here
|
|
|
|
// If WebSockets aren't working, don't wait forever here
|
|
|
|
evWSError.reg(function () {
|
|
|
|
evWSError.reg(function () {
|
|
|
|
waitFor.abort();
|
|
|
|
waitFor.abort();
|
|
|
|
cb("No WebSocket (test number 6)");
|
|
|
|
cb("No WebSocket available");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
// Create proxy
|
|
|
|
// Create proxy
|
|
|
|
Login.loadUserObject(opt, waitFor(function (err, rt) {
|
|
|
|
Login.loadUserObject(opt, waitFor(function (err, rt) {
|
|
|
@ -200,28 +305,78 @@ define([
|
|
|
|
cb(true);
|
|
|
|
cb(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
}, _alert("Login block is not working (write/read/remove)"));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var sheetURL = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb) {
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
var url = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
|
|
|
|
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
|
|
|
|
|
|
|
|
var url = 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
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
$.ajax(url, {
|
|
|
|
$.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);
|
|
|
|
console.log(k, response);
|
|
|
|
if (response !== expect[k]) {
|
|
|
|
return response !== expect[k];
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
'A value of ',
|
|
|
|
|
|
|
|
h('code', expect[k]),
|
|
|
|
|
|
|
|
' was expected for the ',
|
|
|
|
|
|
|
|
h('code', k),
|
|
|
|
|
|
|
|
' HTTP header, but instead a value of "',
|
|
|
|
|
|
|
|
h('code', response),
|
|
|
|
|
|
|
|
'" was received.',
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
return true; // returning true indicates that a value is incorrect
|
|
|
|
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, _alert("Missing HTTP headers required for XLSX export"));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
"The spreadsheet editor's code was not served with the required Content-Security Policy headers. ",
|
|
|
|
|
|
|
|
"This is most often caused by incorrectly configured sandbox parameters (",
|
|
|
|
|
|
|
|
h('code', 'httpUnsafeOrigin'),
|
|
|
|
|
|
|
|
' and ',
|
|
|
|
|
|
|
|
h('code', 'httpSafeOrigin'),
|
|
|
|
|
|
|
|
' in ',
|
|
|
|
|
|
|
|
CONFIG_PATH,
|
|
|
|
|
|
|
|
"), or settings in your reverse proxy's configuration which don't match your application server's config. ",
|
|
|
|
|
|
|
|
RESTART_WARNING(),
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$.ajax(sheetURL, {
|
|
|
|
|
|
|
|
complete: function (xhr) {
|
|
|
|
|
|
|
|
var csp = xhr.getResponseHeader('Content-Security-Policy');
|
|
|
|
|
|
|
|
if (!/unsafe\-eval/.test(csp)) {
|
|
|
|
|
|
|
|
// OnlyOffice requires unsafe-eval
|
|
|
|
|
|
|
|
return cb(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!/unsafe\-inline/.test(csp)) {
|
|
|
|
|
|
|
|
// OnlyOffice also requires unsafe-inline
|
|
|
|
|
|
|
|
return cb(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//console.error('CSP', csp);
|
|
|
|
|
|
|
|
cb(true);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb, msg) {
|
|
|
|
|
|
|
|
msg.appendChild(h('span', [
|
|
|
|
|
|
|
|
h('code', '/api/broadcast'),
|
|
|
|
|
|
|
|
" could not be loaded. This can be caused by an outdated application server or an incorrectly configured reverse proxy. ",
|
|
|
|
|
|
|
|
"Even if the most recent code has been downloaded it's possible the application server has not been restarted. ",
|
|
|
|
|
|
|
|
"Your browser console may provide more details as to why this resource could not be loaded. ",
|
|
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
assert(function (cb) {
|
|
|
|
|
|
|
|
cb(true);
|
|
|
|
|
|
|
|
$.ajax('/api/broadcast', {
|
|
|
|
$.ajax('/api/broadcast', {
|
|
|
|
dataType: 'text',
|
|
|
|
dataType: 'text',
|
|
|
|
complete: function (xhr) {
|
|
|
|
complete: function (xhr) {
|
|
|
@ -229,7 +384,7 @@ define([
|
|
|
|
cb(xhr.status === 200);
|
|
|
|
cb(xhr.status === 200);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, _alert("/api/broadcast is not available"));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
var row = function (cells) {
|
|
|
|
var row = function (cells) {
|
|
|
|
return h('tr', cells.map(function (cell) {
|
|
|
|
return h('tr', cells.map(function (cell) {
|
|
|
@ -249,7 +404,7 @@ define([
|
|
|
|
|
|
|
|
|
|
|
|
var completed = 0;
|
|
|
|
var completed = 0;
|
|
|
|
var $progress = $('#cp-progress');
|
|
|
|
var $progress = $('#cp-progress');
|
|
|
|
assert.run(function (state) {
|
|
|
|
Assert.run(function (state) {
|
|
|
|
var errors = state.errors;
|
|
|
|
var errors = state.errors;
|
|
|
|
var failed = errors.length;
|
|
|
|
var failed = errors.length;
|
|
|
|
|
|
|
|
|
|
|
|