This commit is contained in:
yflory 2019-11-14 15:49:46 +01:00
parent c9c19b8395
commit 6047a8640e
6 changed files with 338 additions and 6 deletions

View file

@ -551,9 +551,10 @@ define([
$d.append(password);
}
if (!data.noEditPassword && owned && parsed.type !== "sheet") { // FIXME SHEET fix password change for sheets
if (!data.noEditPassword && owned) { // FIXME SHEET fix password change for sheets
var sframeChan = common.getSframeChannel();
var isOO = parsed.type === 'sheet';
var isFile = parsed.hashData.type === 'file';
var isSharedFolder = parsed.type === 'drive';
@ -586,7 +587,8 @@ define([
UI.confirm(changePwConfirm, function (yes) {
if (!yes) { pLocked = false; return; }
$(passwordOk).html('').append(h('span.fa.fa-spinner.fa-spin', {style: 'margin-left: 0'}));
var q = isFile ? 'Q_BLOB_PASSWORD_CHANGE' : 'Q_PAD_PASSWORD_CHANGE';
var q = isFile ? 'Q_BLOB_PASSWORD_CHANGE' :
(isOO ? 'Q_OO_PASSWORD_CHANGE' : 'Q_PAD_PASSWORD_CHANGE');
// If this is a file password change, register to the upload events:
// * if there is a pending upload, ask if we shoudl interrupt

View file

@ -1155,6 +1155,233 @@ define([
});
};
common.changeOOPassword = function (data, cb) {
var href = data.href;
var newPassword = data.password;
var teamId = data.teamId;
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
var parsed = Hash.parsePadUrl(href);
if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); }
if (parsed.type !== 'sheet') { return void cb({ error: 'EINVAL_TYPE' }); }
var warning = false;
var newHash, newRoHref;
var oldChannel;
var oldSecret;
var oldMetadata;
var oldRtChannel;
var privateData;
var padData;
var newSecret;
if (parsed.hashData.version >= 2) {
newSecret = Hash.getSecrets(parsed.type, parsed.hash, newPassword);
if (!(newSecret.keys && newSecret.keys.editKeyStr)) {
return void cb({error: 'EAUTH'});
}
newHash = Hash.getEditHashFromKeys(newSecret);
}
var newHref = '/' + parsed.type + '/#' + newHash;
var newRtChannel = Hash.createChannelId();
var Crypt, Crypto;
var cryptgetVal;
var lastCp;
var optsPut = {
password: newPassword,
metadata: {
validateKey: newSecret.keys.validateKey
},
};
Nthen(function (waitFor) {
common.getPadAttribute('', waitFor(function (err, _data) {
padData = _data;
}), href);
}).nThen(function (waitFor) {
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, padData.password);
require([
'/common/cryptget.js',
'/bower_components/chainpad-crypto/crypto.js',
], waitFor(function (_Crypt, _Crypto) {
Crypt = _Crypt;
Crypto = _Crypto;
}));
common.getPadMetadata({channel: oldSecret.channel}, waitFor(function (metadata) {
oldMetadata = metadata;
}));
common.getMetadata(waitFor(function (err, data) {
if (err) {
waitFor.abort();
return void cb({ error: err });
}
privateData = data.priv;
}));
}).nThen(function (waitFor) {
// Check if we're allowed to change the password
var owners = oldMetadata.owners;
optsPut.metadata.owners = owners;
var edPublic = teamId ? (privateData.teams[teamId] || {}).edPublic : privateData.edPublic;
var isOwner = Array.isArray(owners) && edPublic && owners.indexOf(edPublic) !== -1;
if (!isOwner) {
// We're not an owner, we shouldn't be able to change the password!
waitFor.abort();
return void cb({ error: 'EPERM' });
}
var mailbox = oldMetadata.mailbox;
if (mailbox) {
// Create the encryptors to be able to decrypt and re-encrypt the mailboxes
var oldCrypto = Crypto.createEncryptor(oldSecret.keys);
var newCrypto = Crypto.createEncryptor(newSecret.keys);
var m;
if (typeof(mailbox) === "string") {
try {
m = newCrypto.encrypt(oldCrypto.decrypt(mailbox, true, true));
} catch (e) {}
} else if (mailbox && typeof(mailbox) === "object") {
m = {};
Object.keys(mailbox).forEach(function (ed) {
try {
m[ed] = newCrypto.encrypt(oldCrypto.decrypt(mailbox[ed], true, true));
} catch (e) {
console.error(e);
}
});
}
optsPut.metadata.mailbox = m;
}
var expire = oldMetadata.expire;
if (expire) {
optsPut.metadata.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds
}
// Get last cp (cryptget)
Crypt.get(parsed.hash, waitFor(function (err, val) {
if (err) {
waitFor.abort();
return void cb({ error: err });
}
try {
cryptgetVal = JSON.parse(val);
if (!cryptgetVal.content) {
waitFor.abort();
return void cb({ error: 'INVALID_CONTENT' });
}
} catch (e) {
waitFor.abort();
return void cb({ error: 'CANT_PARSE' });
}
}), {
password: padData.password
});
}).nThen(function (waitFor) {
// Re-encrypt rtchannel
oldRtChannel = Util.find(cryptgetVal, ['content', 'channel']);
var newCrypto = Crypto.createEncryptor(newSecret.keys);
var oldCrypto = Crypto.createEncryptor(oldSecret.keys);
var cps = Util.find(cryptgetVal, ['content', 'hashes']);
var lastCp = cps.length ? cps[cps.length - 1] : {};
common.getHistory({
channel: oldRtChannel,
lastKnownHash: lastCp.hash
}, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();
return void cb(obj);
}
var msgs = obj;
newHistory = msgs.map(function (str) {
try {
var d = oldCrypto.decrypt(msg, true, true);
return newCrypto.encrypt(d);
} catch (e) {
waitFor.abort();
return void cb({error: e});
}
});
// Update last knwon hash in cryptgetVal
if (lastCp) { lastCp.hash = msgs[0].slice(0, 64); }
common.onlyoffice.execCommand({
cmd: 'REENCRYPT',
data: {
channel: newRtChannel,
msgs: newHistory,
metadata: optsPut.metadata
}
}, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();
return void cb(obj);
}
}));
}));
}).nThen(function (waitFor) {
// The new rt channel is ready
// The blob uses its own encryption and doesn't need to be reencrypted
cryptgetVal.content.channel = newRtChannel;
Crypt.put(newHash, cryptgetVal, waitFor(function (err) {
if (err) {
waitFor.abort();
return void cb({ error: err });
}
}), optsPut);
}).nThen(function (waitFor) {
pad.leavePad({
channel: oldSecret.channel
}, waitFor());
pad.onDisconnectEvent.fire(true);
}).nThen(function (waitFor) {
// Set the new password to our pad data
common.setPadAttribute('password', newPassword, waitFor(function (err) {
if (err) { warning = true; }
}), href);
common.setPadAttribute('channel', newSecret.channel, waitFor(function (err) {
if (err) { warning = true; }
}), href);
common.setPadAttribute('rtChannel', newRtChannel, waitFor(function (err) {
if (err) { warning = true; }
}), href);
var viewHash = Hash.getViewHashFromKeys(newSecret);
newRoHref = '/' + parsed.type + '/#' + viewHash;
common.setPadAttribute('roHref', newRoHref, waitFor(function (err) {
if (err) { warning = true; }
}), href);
if (parsed.hashData.password && newPassword) { return; } // same hash
common.setPadAttribute('href', newHref, waitFor(function (err) {
if (err) { warning = true; }
}), href);
}).nThen(function (waitFor) {
// delete the old pad
common.removeOwnedChannel({
channel: oldSecret.channel,
teamId: teamId
}, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();
return void cb(obj);
}
common.removeOwnedChannel({
channel: oldRtChannel,
teamId: teamId
}, waitFor());
}));
}).nThen(function () {
cb({
warning: warning,
hash: newHash,
href: newHref,
roHref: newRoHref
});
});
};
common.changeUserPassword = function (Crypt, edPublic, data, cb) {
if (!edPublic) {
return void cb({
@ -1350,6 +1577,9 @@ define([
common.getFullHistory = function (data, cb) {
postMessage("GET_FULL_HISTORY", data, cb, {timeout: 180000});
};
common.getHistory = function (data, cb) {
postMessage("GET_HISTORY", data, cb, {timeout: 180000});
};
common.getHistoryRange = function (data, cb) {
postMessage("GET_HISTORY_RANGE", data, cb);
};

View file

@ -1692,7 +1692,7 @@ define([
// GET_FULL_HISTORY from sframe-common-outer
Store.getFullHistory = function (clientId, data, cb) {
var network = store.network;
var hkn = network.historyKeeper;
var hk = network.historyKeeper;
//var crypto = Crypto.createEncryptor(data.keys);
// Get the history messages and send them to the iframe
var parse = function (msg) {
@ -1709,6 +1709,7 @@ define([
var parsed = parse(msg);
if (parsed[0] === 'FULL_HISTORY_END') {
cb(msgs);
network.off('message', onMsg);
completed = true;
return;
}
@ -1725,12 +1726,68 @@ define([
}
};
network.on('message', onMsg);
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
network.sendto(hk, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
};
Store.getHistory = function (clientId, data, cb) {
var network = store.network;
var hk = network.historyKeeper;
var parse = function (msg) {
try {
return JSON.parse(msg);
} catch (e) {
return null;
}
};
var msgs = [];
var completed = false;
var onMsg = function (msg, sender) {
if (completed) { return; }
if (sender !== hk) { return; }
var parsed = parse(msg);
// Ignore the metadata message
if (parsed.validateKey && parsed.channel) { return; }
if (parsed.error && parsed.channel) {
if (parsed.channel === data.channel) {
network.off('message', onMsg);
completed = true;
cb({error: parsed.error});
}
return;
}
// End of history: cb
if (parsed.state === 1 && parsed.channel) {
if (parsed.channel !== data.channel) { return; }
cb(msgs);
network.off('message', onMsg);
completed = true;
return;
}
msg = parsed[4];
// Keep only the history for our channel
if (parsed[3] !== data.channel) { return; }
if (msg) {
msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
msgs.push(msg);
}
};
network.on('message', onMsg);
var cfg = {
lastKnownHash: data.lastKnownHash
};
var msg = ['GET_HISTORY', data.channel, cfg];
network.sendto(hk, JSON.stringify(msg));
};
Store.getHistoryRange = function (clientId, data, cb) {
var network = store.network;
var hkn = network.historyKeeper;
var hk = network.historyKeeper;
var parse = function (msg) {
try {
return JSON.parse(msg);
@ -1778,7 +1835,7 @@ define([
};
network.on('message', onMsg);
network.sendto(hkn, JSON.stringify(['GET_HISTORY_RANGE', data.channel, {
network.sendto(hk, JSON.stringify(['GET_HISTORY_RANGE', data.channel, {
from: data.lastKnownHash,
cpCount: 2,
txid: txid

View file

@ -200,6 +200,40 @@ define([
}));
};
var reencrypt = function (ctx, data, cId, cb) {
var channel = data.channel;
var network = ctx.store.network;
var onOpen = function (wc) {
var hk = network.historyKeeper;
var cfg = {
metadata: data.metadata
};
var msg = ['GET_HISTORY', wc.id, cfg];
network.sendto(hk, JSON.stringify(msg));
data.msgs.forEach(function (msg) {
wc.bcast(msg);
});
wc.leave();
cb();
};
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", channel, function (e, response) {
if (e) { return void cb({error: e}); }
if (response && response.length && typeof(response[0]) === 'boolean') {
var isNew = response[0];
} else {
cb({error: 'INVALID_RESPONSE'});
}
if (!isNew) { return void cb({error: 'EEXISTS'}); }
// Channel is new: we can push our reencrypted history
network.join(channel).then(onOpen, function (err) {
return void cb({error: err});
});
});
};
var leaveChannel = function (ctx, padChan) {
// Leave channel and prevent reconnect when we leave a pad
Object.keys(ctx.channels).some(function (ooChan) {
@ -267,6 +301,9 @@ define([
if (cmd === 'OPEN_CHANNEL') {
return void openChannel(ctx, data, clientId, cb);
}
if (cmd === 'REENCRYPT') {
return void reencrypt(ctx, data, clientId, cb);
}
};
return oo;

View file

@ -73,6 +73,7 @@ define([
JOIN_PAD: Store.joinPad,
LEAVE_PAD: Store.leavePad,
GET_FULL_HISTORY: Store.getFullHistory,
GET_HISTORY: Store.getHistory,
GET_HISTORY_RANGE: Store.getHistoryRange,
IS_NEW_CHANNEL: Store.isNewChannel,
REQUEST_PAD_ACCESS: Store.requestPadAccess,

View file

@ -1007,6 +1007,11 @@ define([
}, cb);
});
sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href || window.location.href;
Cryptpad.changeOOPassword(data, cb);
});
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href || window.location.href;
Cryptpad.changePadPassword(Cryptget, Crypto, data, cb);