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 { diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 3ab4e406f..a64b32aa6 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 b3fd03d33..3072925b3 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -769,10 +769,6 @@ define([ pad.onErrorEvent = Util.mkEvent(); pad.onMetadataEvent = Util.mkEvent(); - pad.getPadMetadata = function (data, cb) { - postMessage('GET_PAD_METADATA', data, cb); - }; - pad.requestAccess = function (data, cb) { postMessage("REQUEST_PAD_ACCESS", data, cb); }; @@ -784,14 +780,11 @@ 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); }; // XXX Teams: change the password of a pad owned by the team - 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' }); } @@ -799,6 +792,8 @@ define([ var warning = false; var newHash, newRoHref; var oldChannel; + var oldSecret; + var oldMetadata; var newSecret; if (parsed.hashData.version >= 2) { @@ -815,27 +810,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) { @@ -850,8 +879,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/async-store.js b/www/common/outer/async-store.js index 626719214..e214915b0 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -952,6 +952,9 @@ define([ if (channelData && channelData.wc && channel === channelData.wc.id) { expire = +channelData.data.expire || undefined; } + if (data.expire) { + expire = data.expire; + } // Check if the pad is stored in any managers. // If it is stored, update its data, otherwise ask the user where to store it @@ -1380,10 +1383,15 @@ define([ var allData = s.manager.findChannel(data.channel); allData.forEach(function (obj) { obj.data.owners = metadata.owners; + obj.data.atime = +new Date(); if (metadata.expire) { obj.data.expire = +metadata.expire; } }); + var send = s.sendEvent || sendDriveEvent; + send('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }); }); channel.bcast("PAD_METADATA", metadata); }, @@ -1451,32 +1459,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 && @@ -1545,27 +1536,31 @@ 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'}); } - 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}); } + var metadata = (obj && obj[0]) || {}; + cb(metadata); + + // Update owners and expire time in the drive + getAllStores().forEach(function (s) { + var allData = s.manager.findChannel(data.channel); + allData.forEach(function (obj) { + obj.data.owners = metadata.owners; + obj.data.atime = +new Date(); + if (metadata.expire) { + obj.data.expire = +metadata.expire; + } + }); + var send = s.sendEvent || sendDriveEvent; + send('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }); + }); + }); }; Store.setPadMetadata = function (clientId, data, cb) { if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } 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..e39e6880f 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) { @@ -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.pad.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); }); }); 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 ?",