From 2d5b6a53ff7247e0d1ba863801962f2b1c4f5414 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 29 Aug 2019 11:28:51 +0200 Subject: [PATCH 1/8] Fix UI issues with ownership management --- www/common/common-ui-elements.js | 34 +++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 650d0bf1f..5aa390120 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -118,6 +118,8 @@ define([ var priv = common.getMetadataMgr().getPrivateData(); var edPublic = priv.edPublic; + var $div1, $div2; + // Remove owner column var drawRemove = function () { var _owners = {}; @@ -142,7 +144,7 @@ define([ }, function () { console.log(arguments); }); - var $div1 = $(removeCol.div); + $div1 = $(removeCol.div); var others1 = removeCol.others; $div1.append(h('div.cp-share-grid', others1)); $div1.find('.cp-share-friend').click(function () { @@ -179,8 +181,12 @@ define([ err = err || (res && res.error); if (err) { return void UI.warn('ERROR' + err); } // XXX owners = res.owners; - drawRemove().insertBefore($div1); - $div1.remove(); + var $d1 = $div1; + var $d2 = $div2; + drawRemove().insertBefore($d1); + $d1.remove(); + drawAdd().insertBefore($d2); + $d2.remove(); UI.log('DONE'); // XXX }); }; @@ -198,14 +204,20 @@ define([ // Add owners column var drawAdd = function () { + var _friends = JSON.parse(JSON.stringify(friends)); + Object.keys(_friends).forEach(function (curve) { + if (owners.indexOf(_friends[curve].edPublic) !== -1) { + delete _friends[curve]; + } + }); var addCol = UIElements.getFriendsList('Ask a friend to be an owner.', { common: common, - friends: friends + friends: _friends }, function () { // XXX onSelect... console.log(arguments); }); - var $div2 = $(addCol.div); + $div2 = $(addCol.div); var others2 = addCol.others; $div2.append(h('div.cp-share-grid', others2)); $div2.find('.cp-share-friend').click(function () { @@ -226,7 +238,7 @@ define([ var $sel = $div2.find('.cp-share-friend.cp-selected'); var sel = $sel.toArray(); var toAdd = sel.map(function (el) { - return $(el).attr('data-curve'); + return friends[$(el).attr('data-curve')].edPublic; }).filter(function (x) { return x; }); // Send the command var send = function () { @@ -239,8 +251,12 @@ define([ err = err || (res && res.error); if (err) { return void UI.warn('ERROR' + err); } // XXX owners = res.owners; - drawRemove().insertBefore($div2); - $div2.remove(); + var $d1 = $div1; + var $d2 = $div2; + drawRemove().insertBefore($d1); + $d1.remove(); + drawAdd().insertBefore($d2); + $d2.remove(); UI.log('DONE'); // XXX }); }; @@ -250,7 +266,7 @@ define([ send(); }); }); - //$div2.append(h('p', addButton)); + $div2.append(h('p', addButton)); return $div2; }; From e12e5e1f2517d130c8a698bc97d742af47afe538 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Aug 2019 11:49:15 +0200 Subject: [PATCH 2/8] Allow pending_owners to claim ownership --- lib/metadata.js | 4 ++-- rpc.js | 32 ++++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/metadata.js b/lib/metadata.js index 11214931b..2a33bb21c 100644 --- a/lib/metadata.js +++ b/lib/metadata.js @@ -73,7 +73,7 @@ commands.UPDATE_EXPIRATION = function () { throw new Error("E_NOT_IMPLEMENTED"); }; -var handleCommand = function (meta, line) { +var handleCommand = Meta.handleCommand = function (meta, line) { var command = line[0]; var args = line[1]; //var time = line[2]; @@ -84,6 +84,7 @@ var handleCommand = function (meta, line) { commands[command](meta, args); }; +Meta.commands = Object.keys(commands); Meta.createLineHandler = function (ref, errorHandler) { ref.meta = {}; @@ -125,4 +126,3 @@ Meta.createLineHandler = function (ref, errorHandler) { }; }; -Meta.commands = Object.keys(commands); diff --git a/rpc.js b/rpc.js index c6600125e..32afa4f30 100644 --- a/rpc.js +++ b/rpc.js @@ -347,27 +347,39 @@ var setMetadata = function (Env, data, unsafeKey, cb) { if (!command || typeof (command) !== 'string') { return void cb ('INVALID_COMMAND'); } if (Meta.commands.indexOf(command) === -1) { return void('UNSUPPORTED_COMMAND'); } - // XXX should we add checks to "metadata.js" to make sure data.value is - // valid for the selected command? - getMetadata(Env, channel, function (err, metadata) { if (err) { return void cb(err); } if (!(metadata && Array.isArray(metadata.owners))) { return void cb('E_NO_OWNERS'); } + // Confirm that the channel is owned by the user in question - if (metadata.owners.indexOf(unsafeKey) === -1) { + // or the user is accepting a pending ownerhsip offer + if (metadata.pending_owners && Array.isArray(metadata.pending_owners) && + metadata.pending_owners.indexOf(unsafeKey) !== -1 && + metadata.owners.indexOf(unsafeKey) === -1) { + + // If you are a pending owner, make sure you can only add yourelf as an owner + if (command !== 'ADD_OWNERS' || !Array.isArray(data.value) || data.value.length !== 1 + || data.value[0] !== unsafeKey) { + return void cb('INSUFFICIENT_PERMISSIONS'); + } + + } else if (metadata.owners.indexOf(unsafeKey) === -1) { return void cb('INSUFFICIENT_PERMISSIONS'); } // Add the new metadata line - var line = JSON.stringify([command, data.value]); - return void Env.msgStore.writeMetadata(channel, line, function (e) { + var line = [command, data.value, +new Date()]; + try { + Meta.handleCommand(metadata, line); + } catch (e) { + return void cb(e); + } + + return void Env.msgStore.writeMetadata(channel, JSON.stringify(line), function (e) { if (e) { return void cb(e); } - getMetadata(Env, channel, function (err, metadata) { - // XXX handle error here? - cb(void 0, metadata); - }); + cb(void 0, metadata); }); }); }; From 5f37aae990e3c9be4d285d3ed79e0a7ae1a721ac Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Aug 2019 11:49:38 +0200 Subject: [PATCH 3/8] Add a queue to setMetadata to aovid race conditions --- rpc.js | 67 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/rpc.js b/rpc.js index 32afa4f30..7f0e7732b 100644 --- a/rpc.js +++ b/rpc.js @@ -340,6 +340,7 @@ var getMetadata = function (Env, channel, cb) { value: value } */ +var metadataSem = Saferphore.create(1); var setMetadata = function (Env, data, unsafeKey, cb) { var channel = data.channel; var command = data.command; @@ -347,39 +348,53 @@ var setMetadata = function (Env, data, unsafeKey, cb) { if (!command || typeof (command) !== 'string') { return void cb ('INVALID_COMMAND'); } if (Meta.commands.indexOf(command) === -1) { return void('UNSUPPORTED_COMMAND'); } - getMetadata(Env, channel, function (err, metadata) { - if (err) { return void cb(err); } - if (!(metadata && Array.isArray(metadata.owners))) { return void cb('E_NO_OWNERS'); } + metadataSem.take(function (give) { + var g = give(); + getMetadata(Env, channel, function (err, metadata) { + if (err) { + g(); + return void cb(err); + } + if (!(metadata && Array.isArray(metadata.owners))) { + g(); + return void cb('E_NO_OWNERS'); + } - // Confirm that the channel is owned by the user in question - // or the user is accepting a pending ownerhsip offer - if (metadata.pending_owners && Array.isArray(metadata.pending_owners) && - metadata.pending_owners.indexOf(unsafeKey) !== -1 && - metadata.owners.indexOf(unsafeKey) === -1) { + // Confirm that the channel is owned by the user in question + // or the user is accepting a pending ownerhsip offer + if (metadata.pending_owners && Array.isArray(metadata.pending_owners) && + metadata.pending_owners.indexOf(unsafeKey) !== -1 && + metadata.owners.indexOf(unsafeKey) === -1) { - // If you are a pending owner, make sure you can only add yourelf as an owner - if (command !== 'ADD_OWNERS' || !Array.isArray(data.value) || data.value.length !== 1 - || data.value[0] !== unsafeKey) { + // If you are a pending owner, make sure you can only add yourelf as an owner + if (command !== 'ADD_OWNERS' || !Array.isArray(data.value) + || data.value.length !== 1 + || data.value[0] !== unsafeKey) { + g(); + return void cb('INSUFFICIENT_PERMISSIONS'); + } + + } else if (metadata.owners.indexOf(unsafeKey) === -1) { + g(); return void cb('INSUFFICIENT_PERMISSIONS'); } - } else if (metadata.owners.indexOf(unsafeKey) === -1) { - return void cb('INSUFFICIENT_PERMISSIONS'); - } - - // Add the new metadata line - var line = [command, data.value, +new Date()]; - try { - Meta.handleCommand(metadata, line); - } catch (e) { - return void cb(e); - } - - return void Env.msgStore.writeMetadata(channel, JSON.stringify(line), function (e) { - if (e) { + // Add the new metadata line + var line = [command, data.value, +new Date()]; + try { + Meta.handleCommand(metadata, line); + } catch (e) { + g(); return void cb(e); } - cb(void 0, metadata); + + Env.msgStore.writeMetadata(channel, JSON.stringify(line), function (e) { + g(); + if (e) { + return void cb(e); + } + cb(void 0, metadata); + }); }); }); }; From ea37166dc2bdc248f6013a94018f89ea8784f871 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Aug 2019 12:00:49 +0200 Subject: [PATCH 4/8] Update the index metadata cache --- historyKeeper.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/historyKeeper.js b/historyKeeper.js index 6027fc3fe..10137dd06 100644 --- a/historyKeeper.js +++ b/historyKeeper.js @@ -717,6 +717,9 @@ module.exports.create = function (cfg) { if (channel && metadata_cache[channel] && typeof (metadata) === "object") { Log.silly('SET_METADATA_CACHE', 'Channel '+ channel +', metadata: '+ JSON.stringify(metadata)); metadata_cache[channel] = metadata; + if (ctx.channels[channel] && ctx.channels[channel].index) { + ctx.channels[channel].index.metadata = metadata; + } historyKeeperBroadcast(ctx, channel, metadata); } }; From 738030e8c004ad488a6c2916b15da46935865b45 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Aug 2019 12:07:03 +0200 Subject: [PATCH 5/8] Add pending_owners commands --- lib/metadata.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/lib/metadata.js b/lib/metadata.js index 2a33bb21c..037024b57 100644 --- a/lib/metadata.js +++ b/lib/metadata.js @@ -54,6 +54,51 @@ commands.RM_OWNERS = function (meta, args) { }); }; +// ["ADD_PENDING_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623438989] +commands.ADD_PENDING_OWNERS = function (meta, args) { + // bail out if args isn't an array + if (!Array.isArray(args)) { + throw new Error('METADATA_INVALID_PENDING_OWNERS'); + } + + // you shouldn't be able to get here if there are no owners + // because only an owner should be able to change the owners + if (meta.pending_owners && !Array.isArray(meta.pending_owners)) { + throw new Error("METADATA_NONSENSE_PENDING_OWNERS"); + } + + // Add pending_owners array if it doesn't exist + if (!meta.pending_owners) { + meta.pending_owners = deduplicate(args); + return; + } + // or fill it + args.forEach(function (owner) { + if (meta.pending_owners.indexOf(owner) >= 0) { return; } + meta.pending_owners.push(owner); + }); +}; + +// ["RM_PENDING_OWNERS", ["CrufexqXcY-z+eKJlEbNELVy5Sb7E-EAAEFI8GnEtZ0="], 1561623439989] +commands.RM_PENDING_OWNERS = function (meta, args) { + // what are you doing if you don't have owners to remove? + if (!Array.isArray(args)) { + throw new Error('METADATA_INVALID_PENDING_OWNERS'); + } + // if there aren't any owners to start, this is also pointless + if (!Array.isArray(meta.pending_owners)) { + throw new Error("METADATA_NONSENSE_PENDING_OWNERS"); + } + + // remove owners one by one + // we assume there are no duplicates + args.forEach(function (owner) { + var index = meta.pending_owners.indexOf(owner); + if (index < 0) { return; } + meta.pending_owners.splice(index, 1); + }); +}; + // ["RESET_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623439989] commands.RESET_OWNERS = function (meta, args) { // expect a new array, even if it's empty From 9822c28f3b968b564025258522377a161ac9db9d Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Aug 2019 16:41:48 +0200 Subject: [PATCH 6/8] Implement ADD_OWNER notification with pending_owner metadata --- www/common/common-ui-elements.js | 173 ++++++++++++++++++++++----- www/common/cryptpad-common.js | 9 +- www/common/notifications.js | 21 ++++ www/common/outer/async-store.js | 1 + www/common/outer/mailbox-handlers.js | 26 ++++ www/common/sframe-common-mailbox.js | 8 +- www/common/sframe-common-outer.js | 11 +- www/drive/inner.js | 14 +++ 8 files changed, 231 insertions(+), 32 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 5aa390120..21e157394 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -76,6 +76,8 @@ define([ })); }).nThen(function (waitFor) { var base = common.getMetadataMgr().getPrivateData().origin; + // XXX getFileData? + // XXX getPadMetadata common.getPadAttribute('href', waitFor(function (err, val) { if (!val) { return; } data.href = base + val; @@ -99,26 +101,40 @@ define([ common.getPadAttribute('ctime', waitFor(function (err, val) { data.ctime = val; })); + common.getPadAttribute('title', waitFor(function (err, val) { + data.title = val; + })); common.getPadAttribute('tags', waitFor(function (err, val) { data.tags = val; })); + common.getSframeChannel().query('Q_GET_PAD_METADATA', null, waitFor(function (err, val) { + if (err) { return; } + data.owners = val.owners; + data.expire = val.expire; + data.pending_owners = val.pending_owners; + })); + /* common.getPadAttribute('owners', waitFor(function (err, val) { data.owners = val; })); common.getPadAttribute('expire', waitFor(function (err, val) { data.expire = val; - })); + }));*/ }).nThen(function () { cb(void 0, data); }); }; - var createOwnerModal = function (common, channel, owners) { + var createOwnerModal = function (common, data) { var friends = common.getFriends(true); var sframeChan = common.getSframeChannel(); var priv = common.getMetadataMgr().getPrivateData(); + var user = common.getMetadataMgr().getUserData(); var edPublic = priv.edPublic; + var channel = data.channel; + var owners = data.owners; var $div1, $div2; + var redrawAll = function () {}; // Remove owner column var drawRemove = function () { @@ -181,12 +197,7 @@ define([ err = err || (res && res.error); if (err) { return void UI.warn('ERROR' + err); } // XXX owners = res.owners; - var $d1 = $div1; - var $d2 = $div2; - drawRemove().insertBefore($d1); - $d1.remove(); - drawAdd().insertBefore($d2); - $d2.remove(); + redrawAll(); UI.log('DONE'); // XXX }); }; @@ -240,36 +251,70 @@ define([ var toAdd = sel.map(function (el) { return friends[$(el).attr('data-curve')].edPublic; }).filter(function (x) { return x; }); - // Send the command - var send = function () { - // XXX Pinning problem.... + + nThen(function (waitFor) { + var msg = "Are you sure?"; // XXX + UI.confirm(msg, waitFor(function (yes) { + if (!yes) { + waitFor.abort(); + return; + } + })); + }).nThen(function (waitFor) { + // Send the command sframeChan.query('Q_SET_PAD_METADATA', { channel: channel, - command: 'ADD_OWNERS', + command: 'ADD_PENDING_OWNERS', value: toAdd - }, function (err, res) { + }, waitFor(function (err, res) { err = err || (res && res.error); - if (err) { return void UI.warn('ERROR' + err); } // XXX + if (err) { + waitFor.abort(); + return void UI.warn('ERROR' + err); + } // XXX owners = res.owners; - var $d1 = $div1; - var $d2 = $div2; - drawRemove().insertBefore($d1); - $d1.remove(); - drawAdd().insertBefore($d2); - $d2.remove(); - UI.log('DONE'); // XXX + })); + }).nThen(function (waitFor) { + // TODO send notifications + sel.forEach(function () { + var friend = friends[$(el).attr('data-curve')]; + if (!friend) { return; } + common.mailbox.sendTo("ADD_OWNER", { + channel: channel, + href: data.href, + password: data.password, + title: data.title, + user: { + displayName: user.name, + avatar: user.avatar, + profile: user.profile, + notifications: user.notifications, + curvePublic: user.curvePublic, + edPublic: priv.edPublic + } + }, { + channel: friend.notifications, + curvePublic: friend.curvePublic + }, waitFor()); }); - }; - var msg = "Are you sure?"; // XXX - UI.confirm(msg, function (yes) { - if (!yes) { return; } - send(); + }).nThen(function () { + redrawAll(); + UI.log('DONE'); // XXX }); }); $div2.append(h('p', addButton)); return $div2; }; + redrawAll = function () { + var $d1 = $div1; + var $d2 = $div2; + drawRemove().insertBefore($d1); + $d1.remove(); + drawAdd().insertBefore($d2); + $d2.remove(); + }; + // Create modal var link = h('div.cp-share-columns', [ drawRemove()[0], @@ -326,7 +371,7 @@ define([ if (owned) { var manageOwners = h('button.no-margin', 'Manage owners'); // XXX $(manageOwners).click(function () { - var modal = createOwnerModal(common, data.channel, data.owners); + var modal = createOwnerModal(common, data); UI.openCustomModal(modal, { wide: true, }); @@ -3168,7 +3213,7 @@ define([ UIElements.displayFriendRequestModal = function (common, data) { var msg = data.content.msg; - var text = Messages._getKey('contacts_request', [msg.content.displayName]); + var text = Messages._getKey('contacts_request', [Util.fixHTML(msg.content.displayName)]); var todo = function (yes) { common.getSframeChannel().query("Q_ANSWER_FRIEND_REQUEST", { @@ -3213,5 +3258,77 @@ define([ UI.openCustomModal(modal); }; + UIElements.displayAddOwnerModal = function (common, data) { + var priv = common.getMetadataMgr().getPrivateData(); + var sframeChan = common.getSframeChannel(); + var msg = data.content.msg; + var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; + var title = Util.fixHTML(msg.content.title); + + Messages.owner_add = '{0} wants you to be an owner of the pad {1}. Do you accept?'; //XXX + var text = Messages._getKey('owner_add', [name, title]); + + var link = h('a', { + href: '#' + }, Messages.requestEdit_viewPad); + $(link).click(function (e) { + e.preventDefault(); + e.stopPropagation(); + if (data.content.password) { + common.sessionStorage.put('newPadPassword', data.content.password, function () { + common.openURL(msg.content.href); + }); + return; + } + common.openURL(msg.content.href); + }); + + var div = h('div', [ + UI.setHTML(h('p'), text), + link + ]); + + var todo = function (yes) { + if (yes) { + sframeChan.query('Q_SET_PAD_METADATA', { + channel: channel, + command: 'ADD_OWNERS', + value: [priv.edPublic] + }, function (err, res) { + err = err || (res && res.error); + if (err) { + return void UI.warn('ERROR' + err); + } // XXX + UI.log('DONE'); // XXX + // TODO send notification to the sender? + }); + return; + } + // XXX implement decline? + }; + + var buttons = [{ + name: Messages.friendRequest_later, + onClick: function () {}, + keys: [27] + }, { + className: 'primary', + name: Messages.friendRequest_accept, + onClick: function () { + todo(true); + }, + keys: [13] + }, { + className: 'primary', + name: Messages.friendRequest_decline, + onClick: function () { + todo(false); + }, + keys: [[13, 'ctrl']] + }]; + var modal = UI.dialog.customModal(div, {buttons: buttons}); + UI.openCustomModal(modal); + }; + return UIElements; }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 4ed0976ff..56e5edcae 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -758,6 +758,10 @@ define([ pad.onConnectEvent = Util.mkEvent(); pad.onErrorEvent = Util.mkEvent(); + pad.getPadMetadata = function (data, cb) { + postMessage('GET_PAD_METADATA', data, cb); + }; + pad.requestAccess = function (data, cb) { postMessage("REQUEST_PAD_ACCESS", data, cb); }; @@ -769,7 +773,10 @@ define([ postMessage('SET_PAD_METADATA', data, cb); }; common.getPadMetadata = function (data, cb) { - postMessage('GET_PAD_METADATA', data, cb); + common.anonRpcMsg('GET_METADATA', data.channel, function (err, obj) { + if (err) { return void cb({error: err}); } + cb(obj && obj[0]); + }); }; common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) { diff --git a/www/common/notifications.js b/www/common/notifications.js index 72b62b501..010d1f46e 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -210,6 +210,27 @@ define([ }; }; + + handlers['ADD_OWNER'] = function (common, data) { + var content = data.content; + var msg = content.msg; + + // Display the notification + content.getFormatText = function () { + return Messages._getKey('friendRequest_notification', [name]); + }; + + // Check authenticity + if (msg.author !== msg.content.curvePublic) { return; } + + // if not archived, add handlers + if (!content.archived) { + content.handler = function () { + UIElements.displayAddOwnerModal(common, data); + }; + } + }; + // NOTE: don't forget to fixHTML everything returned by "getFormatText" return { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 9a2ce0f7c..9c5c23bf8 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1374,6 +1374,7 @@ define([ }; Store.getPadMetadata = function (clientId, data, cb) { + console.log(data); if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } var channel = channels[data.channel]; if (!channel) { return void cb({ error: 'ENOTFOUND' }); } diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 9d383caa1..cca192d04 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -259,6 +259,32 @@ define([ cb(false); }; + // Hide duplicates when receiving an ADD_OWNER notification: + var addOwners = {}; + handlers['ADD_OWNER'] = function (ctx, box, data, cb) { + var msg = data.msg; + var content = msg.content; + + if (msg.author !== content.user.curvePublic) { return void cb(true); } + if (!content.href || !content.title || !content.channel) { + console.log('Remove invalid notification'); + return void cb(true); + } + + var channel = content.channel; + + if (addOwners[channel]) { return void cb(true); } + addOwners[channel] = true; + + cb(false); + }; + removeHandlers['ADD_OWNER'] = function (ctx, box, data) { + var channel = data.content.channel; + if (addOwners[channel]) { + delete addOwners[channel]; + } + }; + return { add: function (ctx, box, data, cb) { /** diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index 5aaaa5340..e468b29f1 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -32,13 +32,17 @@ define([ }); }; - mailbox.sendTo = function (type, content, user) { + mailbox.sendTo = function (type, content, user, cb) { + cb = cb || function () {}; execCommand('SENDTO', { type: type, msg: content, user: user }, function (err, obj) { - if (err || (obj && obj.error)) { return void console.error(err || obj.error); } + cb(err || (obj && obj.error), obj); + if (err || (obj && obj.error)) { + return void console.error(err || obj.error); + } }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 38214752f..cc568553a 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -981,7 +981,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.getPadMetadata({ + Cryptpad.pad.getPadMetadata({ channel: secret.channel }, waitFor(function (obj) { obj = obj || {}; @@ -1004,6 +1004,15 @@ define([ }); }); + sframeChan.on('Q_GET_PAD_METADATA', function (data, cb) { + if (!data || !data.channel) { + data = { + channel: secret.channel + }; + } + console.log(data); + Cryptpad.getPadMetadata(data, cb); + }); sframeChan.on('Q_SET_PAD_METADATA', function (data, cb) { Cryptpad.setPadMetadata(data, cb); }); diff --git a/www/drive/inner.js b/www/drive/inner.js index fbdaaa7a0..138d01f3d 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -3715,6 +3715,20 @@ define([ data.sharedFolder = true; } + if (manager.isFile(el) && data.roHref) { // Only for pads! + sframeChan.query('Q_GET_PAD_METADATA', { + channel: data.channel + }, function (err, val) { + console.log(arguments); + if (!err && !(val && val.error)) { + data.owners = val.owners; + data.expire = val.expire; + data.pending_owners = val.pending_owners; + } + UIElements.getProperties(common, data, cb); + }); + return; + } UIElements.getProperties(common, data, cb); }; From c76363e76525fbf131722a6704c19b0e0c3e0320 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Aug 2019 16:43:18 +0200 Subject: [PATCH 7/8] lint compliance --- www/common/common-ui-elements.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 21e157394..d3afa3012 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -252,7 +252,7 @@ define([ return friends[$(el).attr('data-curve')].edPublic; }).filter(function (x) { return x; }); - nThen(function (waitFor) { + NThen(function (waitFor) { var msg = "Are you sure?"; // XXX UI.confirm(msg, waitFor(function (yes) { if (!yes) { @@ -276,7 +276,7 @@ define([ })); }).nThen(function (waitFor) { // TODO send notifications - sel.forEach(function () { + sel.forEach(function (el) { var friend = friends[$(el).attr('data-curve')]; if (!friend) { return; } common.mailbox.sendTo("ADD_OWNER", { @@ -3291,7 +3291,7 @@ define([ var todo = function (yes) { if (yes) { sframeChan.query('Q_SET_PAD_METADATA', { - channel: channel, + channel: data.content.channel, command: 'ADD_OWNERS', value: [priv.edPublic] }, function (err, res) { From e131661673b04af4a620f4cc2026728932e4cde5 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Aug 2019 17:36:27 +0200 Subject: [PATCH 8/8] Accept and decline ownership offers --- .../src/less2/include/alertify.less | 2 +- rpc.js | 3 +- www/common/common-ui-elements.js | 140 +++++++++++++----- www/common/notifications.js | 25 +++- www/common/outer/async-store.js | 1 + www/common/outer/mailbox-handlers.js | 1 + www/common/sframe-common.js | 7 + 7 files changed, 142 insertions(+), 37 deletions(-) diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 3274f1c63..c73c27c78 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -427,7 +427,7 @@ display: flex; flex-flow: row; - .cp-share-column { + & > .cp-share-column { width: 50%; padding: 0 10px; } diff --git a/rpc.js b/rpc.js index 7f0e7732b..058215200 100644 --- a/rpc.js +++ b/rpc.js @@ -367,7 +367,8 @@ var setMetadata = function (Env, data, unsafeKey, cb) { metadata.owners.indexOf(unsafeKey) === -1) { // If you are a pending owner, make sure you can only add yourelf as an owner - if (command !== 'ADD_OWNERS' || !Array.isArray(data.value) + if ((command !== 'ADD_OWNERS' && command !== 'RM_PENDING_OWNERS') + || !Array.isArray(data.value) || data.value.length !== 1 || data.value[0] !== unsafeKey) { g(); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index d3afa3012..b37d71355 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -107,11 +107,12 @@ define([ common.getPadAttribute('tags', waitFor(function (err, val) { data.tags = val; })); - common.getSframeChannel().query('Q_GET_PAD_METADATA', null, waitFor(function (err, val) { - if (err) { return; } - data.owners = val.owners; - data.expire = val.expire; - data.pending_owners = val.pending_owners; + common.getPadMetadata(null, waitFor(function (obj) { + console.log(obj); + if (obj && obj.error) { return; } + data.owners = obj.owners; + data.expire = obj.expire; + data.pending_owners = obj.pending_owners; })); /* common.getPadAttribute('owners', waitFor(function (err, val) { @@ -132,14 +133,20 @@ define([ var edPublic = priv.edPublic; var channel = data.channel; var owners = data.owners; + var pending_owners = data.pending_owners; - var $div1, $div2; var redrawAll = function () {}; + var div1 = h('div.cp-share-friends.cp-share-column'); + var div2 = h('div.cp-share-friends.cp-share-column'); + var $div1 = $(div1); + var $div2 = $(div2); + // Remove owner column - var drawRemove = function () { + var drawRemove = function (pending) { var _owners = {}; - owners.forEach(function (ed) { + var o = pending ? pending_owners : owners; + o.forEach(function (ed) { var f; Object.keys(friends).some(function (c) { if (friends[c].edPublic === ed) { @@ -153,17 +160,19 @@ define([ edPublic: ed, }; }); - var removeCol = UIElements.getFriendsList('Remove an existing owner instantly', { + var msg = pending ? 'Remove a pending owner:' + : 'Remove an existing owner:'; // XXX + var removeCol = UIElements.getFriendsList(msg, { common: common, friends: _owners, noFilter: true }, function () { console.log(arguments); }); - $div1 = $(removeCol.div); + var $div = $(removeCol.div); var others1 = removeCol.others; - $div1.append(h('div.cp-share-grid', others1)); - $div1.find('.cp-share-friend').click(function () { + $div.append(h('div.cp-share-grid', others1)); + $div.find('.cp-share-friend').click(function () { var sel = $(this).hasClass('cp-selected'); if (!sel) { $(this).addClass('cp-selected'); @@ -175,10 +184,11 @@ define([ }); // When clicking on the remove button, we check the selected users. // If you try to remove yourself, we'll display an additional warning message - var removeButton = h('button.no-margin', 'Remove owners'); // XXX + var btnMsg = pending ? 'Remove pending owners' : 'Remove owners'; // XXX + var removeButton = h('button.no-margin', btnMsg); $(removeButton).click(function () { // Check selection - var $sel = $div1.find('.cp-share-friend.cp-selected'); + var $sel = $div.find('.cp-share-friend.cp-selected'); var sel = $sel.toArray(); var me = false; var toRemove = sel.map(function (el) { @@ -191,12 +201,11 @@ define([ var send = function () { sframeChan.query('Q_SET_PAD_METADATA', { channel: channel, - command: 'RM_OWNERS', + command: pending ? 'RM_PENDING_OWNERS' : 'RM_OWNERS', value: toRemove }, function (err, res) { err = err || (res && res.error); if (err) { return void UI.warn('ERROR' + err); } // XXX - owners = res.owners; redrawAll(); UI.log('DONE'); // XXX }); @@ -209,8 +218,8 @@ define([ send(); }); }); - $div1.append(h('p', removeButton)); - return $div1; + $div.append(h('p', removeButton)); + return $div; }; // Add owners column @@ -261,20 +270,22 @@ define([ } })); }).nThen(function (waitFor) { + console.log('koko'); // Send the command sframeChan.query('Q_SET_PAD_METADATA', { channel: channel, command: 'ADD_PENDING_OWNERS', value: toAdd }, waitFor(function (err, res) { + console.error(arguments); err = err || (res && res.error); if (err) { waitFor.abort(); return void UI.warn('ERROR' + err); } // XXX - owners = res.owners; })); }).nThen(function (waitFor) { + console.log('okok'); // TODO send notifications sel.forEach(function (el) { var friend = friends[$(el).attr('data-curve')]; @@ -307,18 +318,26 @@ define([ }; redrawAll = function () { - var $d1 = $div1; - var $d2 = $div2; - drawRemove().insertBefore($d1); - $d1.remove(); - drawAdd().insertBefore($d2); - $d2.remove(); + $div1.empty(); + $div2.empty(); + common.getPadMetadata(null, function (obj) { + if (obj && obj.error) { return; } + owners = obj.owners; + pending_owners = obj.pending_owners; + $div1.append(drawRemove(false)).append(drawRemove(true)); + $div2.append(drawAdd()); + }); }; + $div1.append(drawRemove(false)).append(drawRemove(true)); + $div2.append(drawAdd()); + // Create modal var link = h('div.cp-share-columns', [ - drawRemove()[0], - drawAdd()[0] + div1, + div2 + /*drawRemove()[0], + drawAdd()[0]*/ ]); var linkButtons = [{ className: 'cancel', @@ -3260,8 +3279,10 @@ define([ UIElements.displayAddOwnerModal = function (common, data) { var priv = common.getMetadataMgr().getPrivateData(); + var user = common.getMetadataMgr().getUserData(); var sframeChan = common.getSframeChannel(); var msg = data.content.msg; + var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; var title = Util.fixHTML(msg.content.title); @@ -3274,8 +3295,8 @@ define([ $(link).click(function (e) { e.preventDefault(); e.stopPropagation(); - if (data.content.password) { - common.sessionStorage.put('newPadPassword', data.content.password, function () { + if (msg.content.password) { + common.sessionStorage.put('newPadPassword', msg.content.password, function () { common.openURL(msg.content.href); }); return; @@ -3288,23 +3309,76 @@ define([ link ]); + var answer = function (yes) { + common.mailbox.sendTo("ADD_OWNER_ANSWER", { + channel: msg.content.channel, + href: msg.content.href, + password: msg.content.password, + title: msg.content.title, + answer: yes, + user: { + displayName: user.name, + avatar: user.avatar, + profile: user.profile, + notifications: user.notifications, + curvePublic: user.curvePublic, + edPublic: priv.edPublic + } + }, { + channel: msg.content.user.notifications, + curvePublic: msg.content.user.curvePublic + }); + common.mailbox.dismiss(data, function (err) { + console.log(err); + }); + }; + var todo = function (yes) { if (yes) { + // ACCEPT sframeChan.query('Q_SET_PAD_METADATA', { - channel: data.content.channel, + channel: msg.content.channel, command: 'ADD_OWNERS', value: [priv.edPublic] }, function (err, res) { err = err || (res && res.error); if (err) { - return void UI.warn('ERROR' + err); + return void UI.warn('ERROR ' + err); } // XXX UI.log('DONE'); // XXX - // TODO send notification to the sender? + + // Send notification to the sender + answer(true); + + // Remove yourself from the pending owners + sframeChan.query('Q_SET_PAD_METADATA', { + channel: msg.content.channel, + command: 'RM_PENDING_OWNERS', + value: [priv.edPublic] + }, function (err, res) { + err = err || (res && res.error); + if (err) { + console.error(err); + } + }); }); return; } - // XXX implement decline? + + // DECLINE + // Remove yourself from the pending owners + sframeChan.query('Q_SET_PAD_METADATA', { + channel: msg.content.channel, + command: 'RM_PENDING_OWNERS', + value: [priv.edPublic] + }, function (err, res) { + err = err || (res && res.error); + if (err) { + console.error(err); + } + // Send notification to the sender + answer(false); + }); }; var buttons = [{ diff --git a/www/common/notifications.js b/www/common/notifications.js index 010d1f46e..b06e8e3fc 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -216,12 +216,15 @@ define([ var msg = content.msg; // Display the notification + var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; + var title = Util.fixHTML(msg.content.title); + Messages.owner_request = '{0} wants you to be an owner of {1}'; // XXX content.getFormatText = function () { - return Messages._getKey('friendRequest_notification', [name]); + return Messages._getKey('owner_request', [name, title]); }; // Check authenticity - if (msg.author !== msg.content.curvePublic) { return; } + if (msg.author !== msg.content.user.curvePublic) { return; } // if not archived, add handlers if (!content.archived) { @@ -231,6 +234,24 @@ define([ } }; + handlers['ADD_OWNER_ANSWER'] = function (common, data) { + var content = data.content; + var msg = content.msg; + + // Display the notification + var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; + var title = Util.fixHTML(msg.content.title); + Messages.owner_request_accepted = '{0} has accepted your offer to be an owner of {1}'; // XXX + Messages.owner_request_declined = '{0} has declined your offer to be an owner of {1}'; // XXX + var key = 'owner_request_' + (msg.content.answer ? 'accepted' : 'declined'); + content.getFormatText = function () { + return Messages._getKey(key, [name, title]); + }; + if (!content.archived) { + content.dismissHandler = defaultDismiss(common, data); + } + }; + // NOTE: don't forget to fixHTML everything returned by "getFormatText" return { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 9c5c23bf8..a293de8e4 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1397,6 +1397,7 @@ define([ cb(channel.data || {}); }; Store.setPadMetadata = function (clientId, data, cb) { + console.log(data); if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } if (!data.command) { return void cb({ error: 'EINVAL' }); } store.rpc.setMetadata(data, function (err, res) { diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index cca192d04..ed1d954b1 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -264,6 +264,7 @@ define([ handlers['ADD_OWNER'] = function (ctx, box, data, cb) { var msg = data.msg; var content = msg.content; +console.log(msg); if (msg.author !== content.user.curvePublic) { return void cb(true); } if (!content.href || !content.title || !content.channel) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 58e1ffcb0..6b4fc96aa 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -467,6 +467,13 @@ define([ }, { timeout: 60000 }); }; + funcs.getPadMetadata = function (data, cb) { + ctx.sframeChan.query('Q_GET_PAD_METADATA', data, function (err, val) { + if (err || (val && val.error)) { return void cb({error: err || val.error}); } + cb(val); + }); + }; + funcs.gotoURL = function (url) { ctx.sframeChan.event('EV_GOTO_URL', url); }; funcs.openURL = function (url) { ctx.sframeChan.event('EV_OPEN_URL', url); }; funcs.openUnsafeURL = function (url) {