Merge branch 'ownership' of github.com:xwiki-labs/cryptpad into origin/ownership

pull/1/head
ansuz 5 years ago
commit 0abb4f222d

@ -427,7 +427,7 @@
display: flex;
flex-flow: row;
.cp-share-column {
& > .cp-share-column {
width: 50%;
padding: 0 10px;
}

@ -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);
}
};

@ -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
@ -73,7 +118,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 +129,7 @@ var handleCommand = function (meta, line) {
commands[command](meta, args);
};
Meta.commands = Object.keys(commands);
Meta.createLineHandler = function (ref, errorHandler) {
ref.meta = {};
@ -125,4 +171,3 @@ Meta.createLineHandler = function (ref, errorHandler) {
};
};
Meta.commands = Object.keys(commands);

@ -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,25 +348,52 @@ 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?
metadataSem.take(function (give) {
var g = give();
getMetadata(Env, channel, function (err, metadata) {
if (err) { return void cb(err); }
if (!(metadata && Array.isArray(metadata.owners))) { return void cb('E_NO_OWNERS'); }
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
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' && command !== 'RM_PENDING_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');
}
// 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) {
g();
return void cb(e);
}
Env.msgStore.writeMetadata(channel, JSON.stringify(line), function (e) {
g();
if (e) {
return void cb(e);
}
getMetadata(Env, channel, function (err, metadata) {
// XXX handle error here?
cb(void 0, metadata);
});
});

@ -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,29 +101,52 @@ 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.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) {
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 pending_owners = data.pending_owners;
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) {
@ -135,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);
});
var $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');
@ -157,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) {
@ -173,14 +201,12 @@ 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;
drawRemove().insertBefore($div1);
$div1.remove();
redrawAll();
UI.log('DONE'); // XXX
});
};
@ -192,20 +218,26 @@ define([
send();
});
});
$div1.append(h('p', removeButton));
return $div1;
$div.append(h('p', removeButton));
return $div;
};
// 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,38 +258,86 @@ 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; });
NThen(function (waitFor) {
var msg = "Are you sure?"; // XXX
UI.confirm(msg, waitFor(function (yes) {
if (!yes) {
waitFor.abort();
return;
}
}));
}).nThen(function (waitFor) {
console.log('koko');
// Send the command
var send = function () {
// XXX Pinning problem....
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
command: 'ADD_OWNERS',
command: 'ADD_PENDING_OWNERS',
value: toAdd
}, function (err, res) {
}, waitFor(function (err, res) {
console.error(arguments);
err = err || (res && res.error);
if (err) { return void UI.warn('ERROR' + err); } // XXX
owners = res.owners;
drawRemove().insertBefore($div2);
$div2.remove();
UI.log('DONE'); // XXX
if (err) {
waitFor.abort();
return void UI.warn('ERROR' + err);
} // XXX
}));
}).nThen(function (waitFor) {
console.log('okok');
// TODO send notifications
sel.forEach(function (el) {
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));
$div2.append(h('p', addButton));
return $div2;
};
redrawAll = function () {
$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',
@ -310,7 +390,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,
});
@ -3152,7 +3232,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", {
@ -3197,5 +3277,132 @@ define([
UI.openCustomModal(modal);
};
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);
Messages.owner_add = '{0} wants you to be an owner of the pad <b>{1}</b>. 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 (msg.content.password) {
common.sessionStorage.put('newPadPassword', msg.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 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: 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);
} // XXX
UI.log('DONE'); // XXX
// 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;
}
// 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 = [{
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;
});

@ -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) {

@ -210,6 +210,48 @@ define([
};
};
handlers['ADD_OWNER'] = 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 = '{0} wants you to be an owner of <b>{1}</b>'; // XXX
content.getFormatText = function () {
return Messages._getKey('owner_request', [name, title]);
};
// Check authenticity
if (msg.author !== msg.content.user.curvePublic) { return; }
// if not archived, add handlers
if (!content.archived) {
content.handler = function () {
UIElements.displayAddOwnerModal(common, data);
};
}
};
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 <b>{1}</b>'; // XXX
Messages.owner_request_declined = '{0} has declined your offer to be an owner of <b>{1}</b>'; // 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 {

@ -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' }); }
@ -1396,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) {

@ -259,6 +259,33 @@ 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;
console.log(msg);
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) {
/**

@ -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);
}
});
};

@ -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);
});

@ -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) {

@ -3716,6 +3716,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);
};

Loading…
Cancel
Save