From f60e7ffd191cca24636848f60cc4f281c7cb850c Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Sep 2019 11:02:08 +0200 Subject: [PATCH 1/8] Transfer ownership UI --- customize.dist/src/less2/include/alertify.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 683584461..3f36efd5e 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -442,7 +442,7 @@ margin-bottom: 15px; } .cp-share-grid { - max-height: 228px; + max-height: 225px; overflow-x: auto; } .cp-recent-only { @@ -478,6 +478,7 @@ margin-bottom: 6px; cursor: default; transition: order 0.5s, background-color 0.5s; + margin-top: 1px; .tools_unselectable(); &.cp-selected { From f8bc5343d5e86743ac9325ff88c14c865533d412 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Sep 2019 12:18:57 +0200 Subject: [PATCH 2/8] Improve french translation --- www/common/translations/messages.fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index c55977f6f..9ae1e0a2a 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1149,7 +1149,7 @@ "owner_unknownUser": "Utilisateur inconnu", "owner_removeButton": "Supprimer les propriétaires sélectionnés", "owner_removePendingButton": "Annuler les offres sélectionnées", - "owner_addButton": "Proposer la co-propriété", + "owner_addButton": "Proposer d'être propriétaire", "owner_removeConfirm": "Êtes-vous sûr de vouloir suppprimer les droits de propriétaire pour les utilisateurs sélectionnés ? Ils seront notifiés de cette action.", "owner_removeMeConfirm": "Vous êtes sur le point de renoncer à vos droits de propriétaire. Vous ne serez pas en mesure d'annuler cette action. Continuer ?", "owner_addConfirm": "Les co-propriétaires seront en mesure de changer le contenu du document et pourront supprimer vos droits de propriétaire. Continuer ?", From 0a5bf7a5c24983ea2133ee9b688bc21901d2de4e Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Sep 2019 12:19:29 +0200 Subject: [PATCH 3/8] Update atime when updating the owners of a pad --- www/common/outer/async-store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 928978574..1148df343 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1212,6 +1212,7 @@ define([ allData.forEach(function (obj) { obj.data.owners = metadata.owners; if (metadata.expire) { + obj.data.atime = +new Date(); obj.data.expire = +metadata.expire; } }); From bb0365622b9c776974098b33308b1ef24e47d051 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Sep 2019 14:13:27 +0200 Subject: [PATCH 4/8] re-encrypt the mailbox field when changing a pad password --- www/common/cryptpad-common.js | 66 +++++++++++++++++++++++-------- www/common/outer/mailbox.js | 7 +++- www/common/sframe-common-outer.js | 4 +- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 0c4ccb3a3..7941163ab 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -779,7 +779,7 @@ define([ }); }; - common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) { + common.changePadPassword = function (Crypt, Crypto, href, newPassword, edPublic, cb) { if (!href) { return void cb({ error: 'EINVAL_HREF' }); } var parsed = Hash.parsePadUrl(href); if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); } @@ -787,6 +787,8 @@ define([ var warning = false; var newHash, newRoHref; var oldChannel; + var oldSecret; + var oldMetadata; var newSecret; if (parsed.hashData.version >= 2) { @@ -803,27 +805,61 @@ define([ var optsGet = {}; var optsPut = { - password: newPassword + password: newPassword, + metadata: {} }; + + Nthen(function (waitFor) { if (parsed.hashData && parsed.hashData.password) { common.getPadAttribute('password', waitFor(function (err, password) { optsGet.password = password; }), href); } - common.getPadAttribute('owners', waitFor(function (err, owners) { - if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) { - // We're not an owner, we shouldn't be able to change the password! - waitFor.abort(); - return void cb({ error: 'EPERM' }); + }).nThen(function (waitFor) { + oldSecret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password); + oldChannel = oldSecret.channel; + common.getPadMetadata({channel: oldChannel}, waitFor(function (metadata) { + oldMetadata = metadata; + })); + }).nThen(function (waitFor) { + // Get owners, mailbox and expiration time + + var owners = oldMetadata.owners; + if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) { + // We're not an owner, we shouldn't be able to change the password! + waitFor.abort(); + return void cb({ error: 'EPERM' }); + } + optsPut.metadata.owners = owners; + + 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) { + console.log(mailbox[ed]); + try { + m[ed] = newCrypto.encrypt(oldCrypto.decrypt(mailbox[ed], true, true)); + } catch (e) { + console.error(e); + } + }); } - optsPut.owners = owners; - }), href); - // XXX get the mailbox fields, decrypt all the mailboxes, reencrypt them, and put them in the metadata - // XXX also use optsPut.metadata (and fix it in cryptget) instead of optsPut.owners - common.getPadAttribute('expire', waitFor(function (err, expire) { - optsPut.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds - }), href); + optsPut.metadata.mailbox = m; + } + + var expire = oldMetadata.expire; + optsPut.metadata.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds }).nThen(function (waitFor) { Crypt.get(parsed.hash, waitFor(function (err, val) { if (err) { @@ -838,8 +874,6 @@ define([ }), optsPut); }), optsGet); }).nThen(function (waitFor) { - var secret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password); - oldChannel = secret.channel; pad.leavePad({ channel: oldChannel }, waitFor()); diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 30c03c1b9..ac88b616e 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -77,7 +77,12 @@ proxy.mailboxes = { }; // Send a message to someone else - var sendTo = Mailbox.sendTo = function (ctx, type, msg, user, cb) { + var sendTo = Mailbox.sendTo = function (ctx, type, msg, user, _cb) { + var cb = _cb || function (obj) { + if (obj && obj.error) { + console.error(obj.error); + } + }; if (!Crypto.Mailbox) { return void cb({error: "chainpad-crypto is outdated and doesn't support mailboxes."}); } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 71282114b..a8bc62bd0 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -911,7 +911,7 @@ define([ sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) { var href = data.href || window.location.href; - Cryptpad.changePadPassword(Cryptget, href, data.password, edPublic, cb); + Cryptpad.changePadPassword(Cryptget, Crypto, href, data.password, edPublic, cb); }); sframeChan.on('Q_CHANGE_USER_PASSWORD', function (data, cb) { @@ -1020,7 +1020,7 @@ define([ // Try to get the owner's mailbox from the pad metadata first. // If it's is an older owned pad, check if the owner is a friend // or an acquaintance (from async-store directly in requestAccess) - Cryptpad.pad.getPadMetadata({ + Cryptpad.padRpc.getPadMetadata({ channel: secret.channel }, waitFor(function (obj) { obj = obj || {}; From 84249a92b5fd4deede35cdf86f466efe17282800 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Sep 2019 14:46:50 +0200 Subject: [PATCH 5/8] Keep only one getPadMetadata in outer --- www/common/common-ui-elements.js | 2 ++ www/common/cryptpad-common.js | 6 +--- www/common/outer/async-store.js | 51 ++++++------------------------- www/common/sframe-common-outer.js | 21 ++++++++++--- 4 files changed, 29 insertions(+), 51 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index c0dfbe49b..fccef7ce2 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -77,6 +77,8 @@ define([ waitFor.abort(); return void cb(err || 'EEMPTY'); } + delete val.owners; + delete val.expire; Util.extend(data, val); if (data.href) { data.href = base + data.href; } if (data.roHref) { data.roHref = base + data.roHref; } diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 7941163ab..aafab7755 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -759,7 +759,6 @@ define([ pad.onMetadataEvent = Util.mkEvent(); pad.getPadMetadata = function (data, cb) { - postMessage('GET_PAD_METADATA', data, cb); }; pad.requestAccess = function (data, cb) { @@ -773,10 +772,7 @@ define([ postMessage('SET_PAD_METADATA', data, cb); }; common.getPadMetadata = function (data, cb) { - common.anonRpcMsg('GET_METADATA', data.channel, function (err, obj) { - if (err) { return void cb({error: err}); } - cb(obj && obj[0]); - }); + postMessage('GET_PAD_METADATA', data, cb); }; common.changePadPassword = function (Crypt, Crypto, href, newPassword, edPublic, cb) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 1148df343..0ed9971e3 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1282,32 +1282,15 @@ define([ // data.send === true ==> send the request Store.requestPadAccess = function (clientId, data, cb) { var owner = data.owner; - var channel = channels[data.channel]; - if (!channel) { return void cb({error: 'ENOTFOUND'}); } - if (!data.send && channel && (!channel.data || !channel.data.channel)) { - var i = 0; - var it = setInterval(function () { - if (channel.data && channel.data.channel) { - clearInterval(it); - Store.requestPadAccess(clientId, data, cb); - return; - } - if (i >= 300) { // One minute timeout - clearInterval(it); - return void cb({error: 'ETIMEOUT'}); - } - i++; - }, 200); - return; - } + var owners = data.owners; // If the owner was not is the pad metadata, check if it is a friend. // We'll contact the first owner for whom we know the mailbox - var fData = channel.data || {}; - if (!owner && fData.owners) { + if (!owner && Array.isArray(owners)) { var friends = store.proxy.friends || {}; - if (Object.keys(friends).length > 1) { - fData.owners.some(function (edPublic) { + // If we have friends, check if an owner is one of them (with a mailbox) + if (Object.keys(friends).filter(function (curve) { return curve !== 'me' }).length) { + owners.some(function (edPublic) { return Object.keys(friends).some(function (curve) { if (curve === "me") { return; } if (edPublic === friends[curve].edPublic && @@ -1377,25 +1360,11 @@ define([ Store.getPadMetadata = function (clientId, data, cb) { if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } - var channel = channels[data.channel]; - if (!channel) { return void cb({ error: 'ENOTFOUND' }); } - if (!channel.data || !channel.data.channel) { - var i = 0; - var it = setInterval(function () { - if (channel.data && channel.data.channel) { - clearInterval(it); - Store.getPadMetadata(clientId, data, cb); - return; - } - if (i >= 300) { // One minute timeout - clearInterval(it); - return void cb({error: 'ETIMEOUT'}); - } - i++; - }, 200); - return; - } - cb(channel.data || {}); + store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) { + if (err) { return void cb({error: err}); } + // XXX update local owner and expire here + cb((obj && obj[0]) || {}); + }); }; Store.setPadMetadata = function (clientId, data, cb) { if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index a8bc62bd0..e39e6880f 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1010,21 +1010,26 @@ define([ sframeChan.on('EV_GIVE_ACCESS', function (data, cb) { Cryptpad.padRpc.giveAccess(data, cb); }); - sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) { + // REQUEST_ACCESS is used both to check IF we can contact an owner (send === false) + // AND also to send the request if we want (send === true) + sframeChan.on('Q_REQUEST_ACCESS', function (send, cb) { if (readOnly && hashes.editHash) { return void cb({error: 'ALREADYKNOWN'}); } - var owner; + var owner, owners; var crypto = Crypto.createEncryptor(secret.keys); nThen(function (waitFor) { // Try to get the owner's mailbox from the pad metadata first. // If it's is an older owned pad, check if the owner is a friend // or an acquaintance (from async-store directly in requestAccess) - Cryptpad.padRpc.getPadMetadata({ + Cryptpad.getPadMetadata({ channel: secret.channel }, waitFor(function (obj) { obj = obj || {}; if (obj.error) { return; } + + owners = obj.owners; + var mailbox; // Get the first available mailbox (the field can be an string or an object) // TODO maybe we should send the request to all the owners? @@ -1043,10 +1048,16 @@ define([ } })); }).nThen(function () { + // If we are just checking (send === false) and there is a mailbox field, cb state true + // If there is no mailbox, we'll have to check if an owner is a friend in the worker + if (owner && !send) { + return void cb({state: true}); + } Cryptpad.padRpc.requestAccess({ - send: data, + send: send, channel: secret.channel, - owner: owner + owner: owner, + owners: owners }, cb); }); }); From ed2ae72c115c56d5a4a79396a55180a11bf4e5e3 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Sep 2019 14:53:37 +0200 Subject: [PATCH 6/8] lint compliance --- www/common/cryptpad-common.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index aafab7755..1cdfda3e9 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -758,9 +758,6 @@ define([ pad.onErrorEvent = Util.mkEvent(); pad.onMetadataEvent = Util.mkEvent(); - pad.getPadMetadata = function (data, cb) { - }; - pad.requestAccess = function (data, cb) { postMessage("REQUEST_PAD_ACCESS", data, cb); }; @@ -840,7 +837,7 @@ define([ try { m = newCrypto.encrypt(oldCrypto.decrypt(mailbox, true, true)); } catch (e) {} - } else if (mailbox && typeof(mailbox) == "object") { + } else if (mailbox && typeof(mailbox) === "object") { m = {}; Object.keys(mailbox).forEach(function (ed) { console.log(mailbox[ed]); From 5e5d1ecddb0469fa2d7d9a86bfbd9ff18d939960 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Sep 2019 14:55:16 +0200 Subject: [PATCH 7/8] Update local metadata when fetching latest values from the server --- www/common/outer/async-store.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 0ed9971e3..492b3ed2a 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1211,12 +1211,15 @@ define([ var allData = store.manager.findChannel(data.channel); allData.forEach(function (obj) { obj.data.owners = metadata.owners; + obj.data.atime = +new Date(); if (metadata.expire) { - obj.data.atime = +new Date(); obj.data.expire = +metadata.expire; } }); channel.bcast("PAD_METADATA", metadata); + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }); }, crypto: { // The encryption and decryption is done in the outer window. @@ -1289,7 +1292,7 @@ define([ if (!owner && Array.isArray(owners)) { var friends = store.proxy.friends || {}; // If we have friends, check if an owner is one of them (with a mailbox) - if (Object.keys(friends).filter(function (curve) { return curve !== 'me' }).length) { + if (Object.keys(friends).filter(function (curve) { return curve !== 'me'; }).length) { owners.some(function (edPublic) { return Object.keys(friends).some(function (curve) { if (curve === "me") { return; } @@ -1358,12 +1361,27 @@ define([ cb(); }; + // Fetch the latest version of the metadata on the server and return it. + // If the pad is stored in our drive, update the local values of "owners" and "expire" Store.getPadMetadata = function (clientId, data, cb) { if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) { if (err) { return void cb({error: err}); } - // XXX update local owner and expire here - cb((obj && obj[0]) || {}); + var metadata = (obj && obj[0]) || {}; + cb(metadata); + + // Update owners and expire time in the drive + var allData = store.manager.findChannel(data.channel); + allData.forEach(function (obj) { + obj.data.atime = +new Date(); + obj.data.owners = metadata.owners; + if (metadata.expire) { + obj.data.expire = +metadata.expire; + } + }); + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }); }); }; Store.setPadMetadata = function (clientId, data, cb) { From d993827c7e4ba500f8248f0c8a14132906718dc1 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 9 Sep 2019 15:31:59 +0200 Subject: [PATCH 8/8] Fix expiration time not updated properly on accept ownership --- www/common/outer/async-store.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 492b3ed2a..1dfe35716 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -824,6 +824,9 @@ define([ if (channelData && channelData.wc && channel === channelData.wc.id) { expire = +channelData.data.expire || undefined; } + if (data.expire) { + expire = data.expire; + } var datas = store.manager.findChannel(channel); var contains = datas.length !== 0;