Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging

This commit is contained in:
yflory 2020-10-27 10:41:47 +01:00
commit d844538758
10 changed files with 217 additions and 48 deletions

View file

@ -57,9 +57,12 @@ server {
add_header Access-Control-Allow-Origin "*";
# add_header X-Frame-Options "SAMEORIGIN";
set $coop '';
if ($uri ~ ^\/sheet\/.*$) { set $coop 'same-origin'; }
# Enable SharedArrayBuffer in Firefox (for .xlsx export)
add_header Cross-Origin-Resource-Policy cross-origin;
add_header Cross-Origin-Opener-Policy same-origin;
add_header Cross-Origin-Opener-Policy $coop;
add_header Cross-Origin-Embedder-Policy require-corp;
# Insert the path to your CryptPad repository root here

View file

@ -2,7 +2,6 @@
const Data = module.exports;
const Meta = require("../metadata");
const WriteQueue = require("../write-queue");
const Core = require("./core");
const Util = require("../common-util");
const HK = require("../hk-util");
@ -53,7 +52,6 @@ Data.getMetadata = function (Env, channel, cb, Server, netfluxId) {
value: value
}
*/
var queueMetadata = WriteQueue();
Data.setMetadata = function (Env, safeKey, data, cb, Server) {
var unsafeKey = Util.unescapeKeyCharacters(safeKey);
@ -63,7 +61,7 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) {
if (!command || typeof (command) !== 'string') { return void cb('INVALID_COMMAND'); }
if (Meta.commands.indexOf(command) === -1) { return void cb('UNSUPPORTED_COMMAND'); }
queueMetadata(channel, function (next) {
Env.queueMetadata(channel, function (next) {
Data.getMetadataRaw(Env, channel, function (err, metadata) {
if (err) {
cb(err);

View file

@ -48,9 +48,6 @@ Default.httpHeaders = function () {
"X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
"Access-Control-Allow-Origin": "*",
"Cross-Origin-Resource-Policy": 'cross-origin',
"Cross-Origin-Opener-Policy": 'same-origin',
"Cross-Origin-Embedder-Policy": 'require-corp',
};
};

View file

@ -45,6 +45,7 @@ module.exports.create = function (config) {
queueStorage: WriteQueue(),
queueDeletes: WriteQueue(),
queueValidation: WriteQueue(),
queueMetadata: WriteQueue(),
batchIndexReads: BatchRead("HK_GET_INDEX"),
batchMetadata: BatchRead('GET_METADATA'),

View file

@ -80,6 +80,10 @@ module.exports.create = function (Env, cb) {
return void cb();
}
// If the channel is restricted, send the history keeper ID so that they
// can try to authenticate
allowed.unshift(Env.id);
// otherwise they're not allowed.
// respond with a special error that includes the list of keys
// which would be allowed...

View file

@ -60,6 +60,10 @@ var app = Express();
}
}());
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;
@ -96,14 +100,21 @@ var setHeaders = (function () {
}
if (Object.keys(headers).length) {
return function (req, res) {
// apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
"Cross-Origin-Opener-Policy": /^\/sheet\//.test(req.url)? 'same-origin': '',
"Cross-Origin-Embedder-Policy": 'require-corp',
});
// targeted CSP, generic policies, maybe custom headers
const h = [
///^\/pad\/inner\.html.*/,
/^\/common\/onlyoffice\/.*\/index\.html.*/,
/^\/(sheet|ooslide|oodoc)\/inner\.html.*/,
].some((regex) => {
return regex.test(req.url);
}) ? padHeaders : headers;
for (let header in h) { res.setHeader(header, h[header]); }
applyHeaderMap(res, h);
};
}
return function () {};
@ -139,6 +150,7 @@ app.use(function (req, res, next) {
setHeaders(req, res);
if (/[\?\&]ver=[^\/]+$/.test(req.url)) { res.setHeader("Cache-Control", "max-age=31536000"); }
else { res.setHeader("Cache-Control", "no-cache"); }
next();
});

View file

@ -306,11 +306,43 @@
};
Util.throttle = function (f, ms) {
var last = 0;
var to;
var g = function () {
clearTimeout(to);
to = setTimeout(Util.bake(f, Util.slice(arguments)), ms);
var args;
var defer = function (delay) {
// no timeout: run function `f` in `ms` milliseconds
// unless `g` is called again in the meantime
to = setTimeout(function () {
// wipe the current timeout handler
to = undefined;
// take the current time
var now = +new Date();
// compute time passed since `last`
var diff = now - last;
if (diff < ms) {
// don't run `f` if `g` was called since this timeout was set
// instead calculate how much further in the future your next
// timeout should be scheduled
return void defer(ms - diff);
}
// else run `f` with the most recently supplied arguments
f.apply(null, args);
}, delay);
};
var g = function () {
// every time you call this function store the time
last = +new Date();
// remember what arguments were passed
args = Util.slice(arguments);
// if there is a pending timeout then do nothing
if (to) { return; }
defer(ms);
};
g.clear = function () {
clearTimeout(to);
to = undefined;

View file

@ -1,12 +1,15 @@
define([
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/bower_components/netflux-websocket/netflux-client.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
'/common/pinpad.js',
'/bower_components/nthen/index.js',
'/bower_components/chainpad/chainpad.dist.js',
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
], function (Crypto, CPNetflux, Netflux, Util, Hash, Realtime, NetConfig, Pinpad, nThen) {
var finish = function (S, err, doc) {
if (S.done) { return; }
S.cb(err, doc);
@ -28,6 +31,50 @@ define([
}
};
var makeNetwork = function (cb) {
var wsUrl = NetConfig.getWebsocketURL();
Netflux.connect(wsUrl).then(function (network) {
cb(null, network);
}, function (err) {
cb(err);
});
};
var start = function (Session, config) {
// Create a network and authenticate with all our keys if necessary,
// then start chainpad-netflux
nThen(function (waitFor) {
if (Session.hasNetwork) { return; }
makeNetwork(waitFor(function (err, network) {
if (err) { return; }
config.network = network;
}));
}).nThen(function () {
Session.realtime = CPNetflux.start(config);
});
};
var onRejected = function (config, Session, data, cb) {
// Check if we can authenticate
if (!Array.isArray(data) || !data.length || data[0].length !== 16) {
return void cb(true);
}
if (!Array.isArray(Session.accessKeys)) { return void cb(true); }
// Authenticate
config.network.historyKeeper = data[0];
nThen(function (waitFor) {
Session.accessKeys.forEach(function (obj) {
Pinpad.create(config.network, obj, waitFor(function (e) {
console.log('done', obj);
if (e) { console.error(e); }
}));
});
}).nThen(function () {
cb();
});
};
var makeConfig = function (hash, opt) {
var secret;
if (typeof(hash) === 'string') {
@ -67,7 +114,15 @@ define([
progress = progress || function () {};
var config = makeConfig(hash, opt);
var Session = { cb: cb, hasNetwork: Boolean(opt.network) };
var Session = {
cb: cb,
accessKeys: opt.accessKeys,
hasNetwork: Boolean(opt.network)
};
config.onRejected = function (data, cb) {
onRejected(config, Session, data, cb);
};
config.onReady = function (info) {
var rt = Session.session = info.realtime;
@ -95,7 +150,7 @@ define([
overwrite(config, opt);
Session.realtime = CPNetflux.start(config);
start(Session, config);
};
var put = function (hash, doc, cb, opt) {
@ -105,7 +160,15 @@ define([
opt = opt || {};
var config = makeConfig(hash, opt);
var Session = { cb: cb, hasNetwork: Boolean(opt.network) };
var Session = {
cb: cb,
accessKeys: opt.accessKeys,
hasNetwork: Boolean(opt.network)
};
config.onRejected = function (data, cb) {
onRejected(config, Session, data, cb);
};
config.onReady = function (info) {
var realtime = Session.session = info.realtime;
@ -126,7 +189,7 @@ define([
};
overwrite(config, opt);
Session.session = CPNetflux.start(config);
start(Session, config);
};
return {

View file

@ -68,6 +68,38 @@ define([
}, cb);
};
common.getAccessKeys = function (cb) {
var keys = [];
Nthen(function (waitFor) {
// Push account keys
postMessage("GET", {
key: ['edPrivate'],
}, waitFor(function (obj) {
if (obj.error) { return; }
try {
keys.push({
edPrivate: obj,
edPublic: Hash.getSignPublicFromPrivate(obj)
});
} catch (e) { console.error(e); }
}));
// Push teams keys
postMessage("GET", {
key: ['teams'],
}, waitFor(function (obj) {
if (obj.error) { return; }
Object.keys(obj || {}).forEach(function (id) {
var t = obj[id];
var _keys = t.keys.drive || {};
if (!_keys.edPrivate) { return; }
keys.push(t.keys.drive);
});
}));
}).nThen(function () {
cb(keys);
});
};
common.makeNetwork = function (cb) {
require([
'/bower_components/netflux-websocket/netflux-client.js',
@ -629,6 +661,10 @@ define([
optsPut.password = password;
}));
}
common.getAccessKeys(waitFor(function (keys) {
optsGet.accessKeys = keys;
optsPut.accessKeys = keys;
}));
}).nThen(function () {
Crypt.get(parsed.hash, function (err, val) {
if (err) {
@ -666,19 +702,28 @@ define([
password: data.password,
initialState: parsed.type === 'poll' ? '{}' : undefined
};
Crypt.get(parsed.hash, _waitFor(function (err, _val) {
if (err) {
_waitFor.abort();
return void cb(err);
}
try {
val = JSON.parse(_val);
fixPadMetadata(val, true);
} catch (e) {
_waitFor.abort();
return void cb(e.message);
}
}), optsGet);
var next = _waitFor();
Nthen(function (waitFor) {
// Authenticate in case the pad os restricted
common.getAccessKeys(waitFor(function (keys) {
optsGet.accessKeys = keys;
}));
}).nThen(function () {
Crypt.get(parsed.hash, function (err, _val) {
if (err) {
_waitFor.abort();
return void cb(err);
}
try {
val = JSON.parse(_val);
fixPadMetadata(val, true);
next();
} catch (e) {
_waitFor.abort();
return void cb(e.message);
}
}, optsGet);
});
return;
}
@ -741,9 +786,6 @@ define([
}).nThen(function () {
Crypt.put(parsed2.hash, JSON.stringify(val), function () {
cb();
Crypt.get(parsed2.hash, function (err, val) {
console.warn(val);
});
}, optsPut);
});
@ -1006,7 +1048,7 @@ define([
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password);
oldChannel = oldSecret.channel;
common.getPadMetadata({channel: oldChannel}, waitFor(function (metadata) {
oldMetadata = metadata;
oldMetadata = metadata || {};
}));
common.getMetadata(waitFor(function (err, data) {
if (err) {
@ -1058,6 +1100,11 @@ define([
if (expire) {
optsPut.metadata.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds
}
}).nThen(function (waitFor) {
common.getAccessKeys(waitFor(function (keys) {
optsGet.accessKeys = keys;
optsPut.accessKeys = keys;
}));
}).nThen(function (waitFor) {
Crypt.get(parsed.hash, waitFor(function (err, val) {
if (err) {
@ -1074,6 +1121,8 @@ define([
}
}), optsGet);
}).nThen(function (waitFor) {
optsPut.metadata.restricted = oldMetadata.restricted;
optsPut.metadata.allowed = oldMetadata.allowed;
Crypt.put(newHash, cryptgetVal, waitFor(function (err) {
if (err) {
waitFor.abort();
@ -1309,11 +1358,17 @@ define([
validateKey: newSecret.keys.validateKey
},
};
var optsGet = {};
Nthen(function (waitFor) {
common.getPadAttribute('', waitFor(function (err, _data) {
padData = _data;
optsGet.password = padData.password;
}), href);
common.getAccessKeys(waitFor(function (keys) {
optsGet.accessKeys = keys;
optsPut.accessKeys = keys;
}));
}).nThen(function (waitFor) {
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, padData.password);
@ -1392,9 +1447,7 @@ define([
waitFor.abort();
return void cb({ error: 'CANT_PARSE' });
}
}), {
password: padData.password
});
}), optsGet);
}).nThen(function (waitFor) {
// Re-encrypt rtchannel
oldRtChannel = Util.find(cryptgetVal, ['content', 'channel']);

View file

@ -1380,8 +1380,10 @@ define([
};
var i = 0;
sframeChan.on('Q_CRYPTGET', function (data, cb) {
var keys;
var todo = function () {
data.opts.network = cgNetwork;
data.opts.accessKeys = keys;
Cryptget.get(data.hash, function (err, val) {
cb({
error: err,
@ -1400,17 +1402,21 @@ define([
cgNetwork = undefined;
}
i++;
if (!cgNetwork) {
cgNetwork = true;
return void Cryptpad.makeNetwork(function (err, nw) {
console.log(nw);
cgNetwork = nw;
todo();
});
} else if (cgNetwork === true) {
return void whenCGReady(todo);
}
todo();
Cryptpad.getAccessKeys(function (_keys) {
keys = _keys;
if (!cgNetwork) {
cgNetwork = true;
return void Cryptpad.makeNetwork(function (err, nw) {
console.log(nw);
cgNetwork = nw;
todo();
});
} else if (cgNetwork === true) {
return void whenCGReady(todo);
}
todo();
});
});
sframeChan.on('EV_CRYPTGET_DISCONNECT', function () {
if (!cgNetwork) { return; }