From a798873230b4e450e328ec15c6946164f0c07af2 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 8 Jul 2020 14:59:23 +0200 Subject: [PATCH] Add basic support for versioned link --- www/common/common-hash.js | 5 +- www/common/inner/share.js | 78 ++++++++++++++------- www/common/outer/async-store.js | 42 ++++++++++- www/common/sframe-chainpad-netflux-outer.js | 4 ++ www/common/sframe-common-outer.js | 2 + 5 files changed, 105 insertions(+), 26 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 0fd85f909..372bd5b39 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -171,7 +171,7 @@ Version 1 return true; } }); - return k; + return k ? Crypto.b64AddSlashes(k) : ''; }; var getOwnerKey = function (hashArr) { var k; @@ -227,6 +227,9 @@ Version 1 if (opts.embed) { hash += 'embed/'; } if (opts.present) { hash += 'present/'; } var versionHash = typeof(opts.versionHash) !== "undefined" ? opts.versionHash : parsed.versionHash; + if (versionHash) { + hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/'; + } return hash; }; diff --git a/www/common/inner/share.js b/www/common/inner/share.js index 457befc22..4a76070ae 100644 --- a/www/common/inner/share.js +++ b/www/common/inner/share.js @@ -329,26 +329,30 @@ define([ } // warning about sharing links - var localStore = window.cryptpadStore; - var dismissButton = h('span.fa.fa-times'); - var shareLinkWarning = h('div.alert.alert-warning.dismissable', - { style: 'display: none;' }, - [ - h('span.cp-inline-alert-text', Messages.share_linkWarning), - dismissButton - ]); - linkContent.push(shareLinkWarning); - - localStore.get('hide-alert-shareLinkWarning', function (val) { - if (val === '1') { return; } - $(shareLinkWarning).show(); + // when sharing a version hash, there is a similar warning and we want + // to avoid alert fatigue + if (!opts.versionHash) { + var localStore = window.cryptpadStore; + var dismissButton = h('span.fa.fa-times'); + var shareLinkWarning = h('div.alert.alert-warning.dismissable', + { style: 'display: none;' }, + [ + h('span.cp-inline-alert-text', Messages.share_linkWarning), + dismissButton + ]); + linkContent.push(shareLinkWarning); + + localStore.get('hide-alert-shareLinkWarning', function (val) { + if (val === '1') { return; } + $(shareLinkWarning).show(); + + $(dismissButton).on('click', function () { + localStore.put('hide-alert-shareLinkWarning', '1'); + $(shareLinkWarning).remove(); + }); - $(dismissButton).on('click', function () { - localStore.put('hide-alert-shareLinkWarning', '1'); - $(shareLinkWarning).remove(); }); - - }); + } // Burn after reading if (opts.barAlert) { linkContent.push(opts.barAlert.cloneNode(true)); } @@ -460,7 +464,8 @@ define([ var pathname = opts.pathname; var parsed = Hash.parsePadUrl(pathname); var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1; - var canBAR = parsed.type !== 'drive'; + var versionHash = hashes.viewHash && opts.versionHash; + var canBAR = parsed.type !== 'drive' && !versionHash; var burnAfterReading = (hashes.viewHash && canBAR) ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, { @@ -519,6 +524,12 @@ define([ var embed = val.embed; var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present')); var burnAfterReading = Util.isChecked($rights.find('#cp-share-bar')); + if (versionHash) { + edit = false; + embed = false; + present = false; + burnAfterReading = false; + } if (burnAfterReading && !opts.burnAfterReadingUrl) { if (cb) { // Called from the contacts tab, "share" button var barHref = origin + pathname + '#' + (hashes.viewHash || hashes.editHash); @@ -533,7 +544,7 @@ define([ var href = burnAfterReading ? opts.burnAfterReadingUrl : (origin + pathname + '#' + hash); var parsed = Hash.parsePadUrl(href); - return origin + parsed.getUrl({embed: embed, present: present}); + return origin + parsed.getUrl({embed: embed, present: present, versionHash: versionHash}); }; opts.getEmbedValue = function () { var url = opts.getLinkValue({ @@ -543,7 +554,11 @@ define([ }; // disable edit share options if you don't have edit rights - if (!hashes.editHash) { + if (versionHash) { + $rights.find('#cp-share-editable-false').attr('checked', true); + $rights.find('#cp-share-present').removeAttr('checked').attr('disabled', true); + $rights.find('#cp-share-editable-true').removeAttr('checked').attr('disabled', true); + } else if (!hashes.editHash) { $rights.find('#cp-share-editable-false').attr('checked', true); $rights.find('#cp-share-editable-true').removeAttr('checked').attr('disabled', true); } else if (!hashes.viewHash) { @@ -582,7 +597,9 @@ define([ // Set default values common.getAttribute(['general', 'share'], function (err, val) { val = val || {}; - if (val.present && canPresent) { + if (versionHash) { + $rights.find('#cp-share-editable-false').prop('checked', true); + } else if (val.present && canPresent) { $rights.find('#cp-share-editable-false').prop('checked', false); $rights.find('#cp-share-editable-true').prop('checked', false); $rights.find('#cp-share-present').prop('checked', true); @@ -671,9 +688,22 @@ define([ onHide: resetTab }]; Modal.getModal(common, opts, tabs, function (err, modal) { - $(modal).find('.cp-bar').hide(); + // Hide the burn-after-reading option by default + var $modal = $(modal); + $modal.find('.cp-bar').hide(); + // Prepend the "rights" radio selection - $(modal).find('.alertify-tabs-titles').after($rights); + $modal.find('.alertify-tabs-titles').after($rights); + + // Add the versionHash warning if needed + if (opts.versionHash) { + Messages.share_versionHash = "You're going to share the selected history version of the document in read-only mode. This will also give view access to the recipients."; // XXX + $rights.after(h('div.alert.alert-warning', [ + h('i.fa.fa-history'), + UI.setHTML(h('span'), Messages.share_versionHash) + ])); + } + // callback cb(err, modal); }); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index bd55a5c9e..7c8793893 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1455,7 +1455,47 @@ define([ var channels = Store.channels = store.channels = {}; + var getVersionHash = function (clientId, data) { + var validateKey; + var fakeNetflux = Hash.createChannelId(); + nThen(function (waitFor) { + Store.getPadMetadata(null, { + channel: data.channel + }, function (md) { + // XXX not needed? we don't need to validate messages coming from history keeper + validateKey = md.validateKey; + }); + }).nThen(function () { + Store.getHistoryRange(clientId, { + cpCount: 1, + channel: data.channel, + lastKnownHash: data.versionHash + }, function (obj) { + if (obj && obj.error) { + postMessage(clientId, "PAD_ERROR", obj.error); + return; + } + postMessage(clientId, "PAD_CONNECT", { + myID: fakeNetflux, + id: data.channel, + members: [fakeNetflux] + }); + (obj.messages || []).forEach(function (data) { + postMessage(clientId, "PAD_MESSAGE", { + msg: data.msg, + user: fakeNetflux.slice(0,16), // fake history keeper to avoid validate + validateKey: validateKey + }); + }); + postMessage(clientId, "PAD_READY"); + }); + }); + }; + Store.joinPad = function (clientId, data) { + if (data.versionHash) { + return void getVersionHash(clientId, data); + } var isNew = typeof channels[data.channel] === "undefined"; var channel = channels[data.channel] = channels[data.channel] || { queue: [], @@ -2046,7 +2086,7 @@ define([ network.on('message', onMsg); network.sendto(hk, JSON.stringify(['GET_HISTORY_RANGE', data.channel, { from: data.lastKnownHash, - cpCount: 2, + cpCount: data.cpCount || 2, txid: txid }])); }; diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index 84b90f24e..e1ade0f88 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -28,10 +28,13 @@ define([], function () { var padRpc = conf.padRpc; var sframeChan = conf.sframeChan; var metadata= conf.metadata || {}; + var versionHash = conf.versionHash; var validateKey = metadata.validateKey; var onConnect = conf.onConnect || function () { }; conf = undefined; + if (versionHash) { readOnly = true; } + padRpc.onReadyEvent.reg(function () { sframeChan.event('EV_RT_READY', null); }); @@ -132,6 +135,7 @@ define([], function () { padRpc.joinPad({ channel: channel || null, readOnly: readOnly, + versionHash: versionHash, metadata: metadata }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 328d79367..bb05df249 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -445,6 +445,7 @@ define([ feedbackAllowed: Utils.Feedback.state, isPresent: parsed.hashData && parsed.hashData.present, isEmbed: parsed.hashData && parsed.hashData.embed, + isHistoryVersion: parsed.hashData && parsed.hashData.versionHash, accounts: { donateURL: Cryptpad.donateURL, upgradeURL: Cryptpad.upgradeURL @@ -1499,6 +1500,7 @@ define([ var cpNfCfg = { sframeChan: sframeChan, channel: secret.channel, + versionHash: parsed.hashData && parsed.hashData.versionHash, padRpc: Cryptpad.padRpc, validateKey: secret.keys.validateKey || undefined, isNewHash: isNewHash,