merge communities-allow-list and lint compliance

This commit is contained in:
ansuz 2020-02-25 09:48:41 -05:00
commit f951951077
28 changed files with 2059 additions and 191 deletions

View File

@ -72,6 +72,10 @@
z-index: 100000; // alertify container
font: @colortheme_app-font;
.cp-checkmark {
color: @cryptpad_text_col;
}
.cp-inline-alert-text {
flex: 1;
}
@ -182,6 +186,7 @@
margin-bottom: 10px;
box-sizing: content-box;
span {
.tools_unselectable();
font-size: 20px;
height: 40px;
line-height: 40px;
@ -190,12 +195,16 @@
border-left: 1px solid lighten(@alertify-base, 10%);
border-right: 1px solid lighten(@alertify-base, 10%);
cursor: pointer;
&:hover {
&:not(.disabled):hover {
background-color: @alertify-light-bg;
}
&.disabled {
color: #949494;
cursor: not-allowed;
}
}
span.alertify-tabs-active {
background-color: @alertify-fore;
background-color: @alertify-fore !important;
border-left: 1px solid @alertify-fore;
border-right: 1px solid @alertify-fore;
color: @alertify-base;
@ -386,18 +395,13 @@
}
}
div.wide {
div.alertify-tabs {
p.msg:not(:last-child) {
border-bottom: 1px solid @alertify-fore;
}
}
.cp-share-columns {
display: flex;
flex-flow: row;
& > .cp-share-column {
width: 50%;
padding: 0 10px;
//padding: 0 10px;
position: relative;
&.contains-nav {
nav {
@ -414,7 +418,20 @@
}
}
&:first-child {
border-right: 1px solid @alertify-fore;
margin-right: @alertify_padding-base;
}
&:last-child {
margin-left: @alertify_padding-base;
}
}
& > .cp-share-column-mid {
display: flex;
align-items: center;
button {
width: 50px;
margin: 0;
min-width: 0;
font-size: 18px;
}
}
}

View File

@ -68,6 +68,23 @@
color: @cryptpad_text_col;
}
// Access modal
.cp-overlay-container {
position: relative;
.cp-overlay {
position: absolute;
background-color: rgba(255,255,255,0.5);
top: 0;
bottom: 0;
left: 0;
right: 0;
}
}
.cp-access-margin-right {
margin-right: 5px !important;
}
// teams invite modal
.cp-teams-invite-block {
display: flex;

View File

@ -109,6 +109,27 @@
color: @colortheme_alertify-primary-text;
}
}
.fa-times {
padding-left: 5px;
cursor: pointer;
height: 100%;
line-height: 25px;
color: @cryptpad_text_col;
&:hover {
color: lighten(@cryptpad_text_col, 10%);
}
}
}
&.list {
.cp-usergrid-user {
width: auto;
max-width: calc(100% - 6px);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: inline-flex;
flex: unset;
}
}
}
}

View File

@ -14,21 +14,7 @@ module.exports.create = function (config) {
.on('channelClose', historyKeeper.channelClose)
.on('channelMessage', historyKeeper.channelMessage)
.on('channelOpen', historyKeeper.channelOpen)
.on('sessionClose', function (userId, reason) {
if (['BAD_MESSAGE', 'SOCKET_ERROR', 'SEND_MESSAGE_FAIL_2'].indexOf(reason) !== -1) {
if (reason && reason.code === 'ECONNRESET') { return; }
return void log.error('SESSION_CLOSE_WITH_ERROR', {
userId: userId,
reason: reason,
});
}
if (['SOCKET_CLOSED', 'SOCKET_ERROR'].indexOf(reason)) { return; }
log.verbose('SESSION_CLOSE_ROUTINE', {
userId: userId,
reason: reason,
});
})
.on('sessionClose', historyKeeper.sessionClose)
.on('error', function (error, label, info) {
if (!error) { return; }
/* labels:

View File

@ -261,6 +261,8 @@ Channel.writePrivateMessage = function (Env, args, cb, Server) {
msg // the actual message content. Generally a string
];
// XXX RESTRICT respect allow lists
// historyKeeper already knows how to handle metadata and message validation, so we just pass it off here
// if the message isn't valid it won't be stored.
Env.historyKeeper.channelMessage(Server, channelStruct, fullMessage);

View File

@ -2,21 +2,24 @@
const Data = module.exports;
const Meta = require("../metadata");
const BatchRead = require("../batch-read");
const WriteQueue = require("../write-queue");
const Core = require("./core");
const Util = require("../common-util");
const HK = require("../hk-util");
const batchMetadata = BatchRead("GET_METADATA");
Data.getMetadata = function (Env, channel, cb/* , Server */) {
Data.getMetadataRaw = function (Env, channel /* channelName */, _cb) {
const cb = Util.once(Util.mkAsync(_cb));
if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length !== 32) { return cb("INVALID_CHAN_LENGTH"); }
if (channel.length !== HK.STANDARD_CHANNEL_LENGTH) { return cb("INVALID_CHAN_LENGTH"); }
// FIXME get metadata from the server cache if it is available
batchMetadata(channel, cb, function (done) {
var cached = Env.metadata_cache[channel];
if (HK.isMetadataMessage(cached)) {
return void cb(void 0, cached);
}
Env.batchMetadata(channel, cb, function (done) {
var ref = {};
var lineHandler = Meta.createLineHandler(ref, Env.Log.error);
return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) {
if (err) {
// stream errors?
@ -27,6 +30,28 @@ Data.getMetadata = function (Env, channel, cb/* , Server */) {
});
};
Data.getMetadata = function (Env, channel, cb, Server, netfluxId) {
Data.getMetadataRaw(Env, channel, function (err, metadata) {
if (err) { return void cb(err); }
if (!(metadata && metadata.restricted)) {
// if it's not restricted then just call back
return void cb(void 0, metadata);
}
const session = HK.getNetfluxSession(Env, netfluxId);
const allowed = HK.listAllowedUsers(metadata);
if (!HK.isUserSessionAllowed(allowed, session)) {
return void cb(void 0, {
restricted: metadata.restricted,
allowed: allowed,
});
}
cb(void 0, metadata);
});
};
/* setMetadata
- write a new line to the metadata log if a valid command is provided
- data is an object: {
@ -46,7 +71,7 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) {
if (Meta.commands.indexOf(command) === -1) { return void cb('UNSUPPORTED_COMMAND'); }
queueMetadata(channel, function (next) {
Data.getMetadata(Env, channel, function (err, metadata) {
Data.getMetadataRaw(Env, channel, function (err, metadata) {
if (err) {
cb(err);
return void next();
@ -108,21 +133,70 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) {
return void next();
}
// send the message back to the person who changed it
// since we know they're allowed to see it
cb(void 0, metadata);
next();
const metadata_cache = Env.metadata_cache;
const channel_cache = Env.channel_cache;
// update the cached metadata
metadata_cache[channel] = metadata;
// as well as the metadata that's attached to the index...
// XXX determine if we actually need this...
var index = Util.find(channel_cache, [channel, 'index']);
if (index && typeof(index) === 'object') { index.metadata = metadata; }
Server.channelBroadcast(channel, JSON.stringify(metadata), Env.historyKeeper.id);
// it's easy to check if the channel is restricted
const isRestricted = metadata.restricted;
// and these values will be used in any case
const s_metadata = JSON.stringify(metadata);
const hk_id = Env.historyKeeper.id;
if (!isRestricted) {
// pre-allow-list behaviour
// if it's not restricted, broadcast the new metadata to everyone
return void Server.channelBroadcast(channel, s_metadata, hk_id);
}
// otherwise derive the list of users (unsafeKeys) that are allowed to stay
const allowed = HK.listAllowedUsers(metadata);
// anyone who is not allowed will get the same error message
const s_error = JSON.stringify({
error: 'ERESTRICTED',
channel: channel,
});
// iterate over the channel's userlist
const toRemove = [];
Server.getChannelUserList(channel).forEach(function (userId) {
const session = HK.getNetfluxSession(Env, userId);
// if the user is allowed to remain, send them the metadata
if (HK.isUserSessionAllowed(allowed, session)) {
return void Server.send(userId, [
0,
hk_id,
'MSG',
userId,
s_metadata
], function () {});
}
// otherwise they are not in the list.
// send them an error and kick them out!
Server.send(userId, [
0,
hk_id,
'MSG',
userId,
s_error
], function () {});
});
Server.removeFromChannel(channel, toRemove);
});
});
});
};

View File

@ -147,7 +147,6 @@ module.exports.create = function (config, cb) {
error: err,
});
}
if (!metadata || (metadata && !metadata.restricted)) {
// the channel doesn't have metadata, or it does and it's not restricted
// either way, let them join.
@ -168,14 +167,12 @@ module.exports.create = function (config, cb) {
// otherwise they're not allowed.
// respond with a special error that includes the list of keys
// which would be allowed...
// FIXME bonus points if you hash the keys to limit data exposure
// FIXME RESTRICT bonus points if you hash the keys to limit data exposure
cb("ERESTRICTED", allowed);
});
},
sessionClose: function (userId, reason) {
HK.closeNetfluxSession(Env, userId);
// TODO RESTRICT drop user session data
if (['BAD_MESSAGE', 'SOCKET_ERROR', 'SEND_MESSAGE_FAIL_2'].indexOf(reason) !== -1) {
if (reason && reason.code === 'ECONNRESET') { return; }
return void Log.error('SESSION_CLOSE_WITH_ERROR', {
@ -184,7 +181,7 @@ module.exports.create = function (config, cb) {
});
}
if (reason && reason === 'SOCKET_CLOSED') { return; }
if (['SOCKET_CLOSED', 'SOCKET_ERROR'].indexOf(reason)) { return; }
Log.verbose('SESSION_CLOSE_ROUTINE', {
userId: userId,
reason: reason,

View File

@ -4,7 +4,7 @@ var HK = module.exports;
const nThen = require('nthen');
const Util = require("./common-util");
const Meta = require("./metadata");
const MetaRPC = require("./commands/metadata");
const Nacl = require('tweetnacl/nacl-fast');
const now = function () { return (new Date()).getTime(); };
@ -71,10 +71,37 @@ const sliceCpIndex = function (cpIndex, line) {
return start.concat(end);
};
const isMetadataMessage = function (parsed) {
const isMetadataMessage = HK.isMetadataMessage = function (parsed) {
return Boolean(parsed && parsed.channel);
};
HK.listAllowedUsers = function (metadata) {
return (metadata.owners || []).concat((metadata.allowed || []));
};
HK.getNetfluxSession = function (Env, netfluxId) {
return Env.netfluxUsers[netfluxId];
};
HK.isUserSessionAllowed = function (allowed, session) {
if (!session) { return false; }
for (var unsafeKey in session) {
if (allowed.indexOf(unsafeKey) !== -1) {
return true;
}
}
return false;
};
HK.authenticateNetfluxSession = function (Env, netfluxId, unsafeKey) {
var user = Env.netfluxUsers[netfluxId] = Env.netfluxUsers[netfluxId] || {};
user[unsafeKey] = +new Date();
};
HK.closeNetfluxSession = function (Env, netfluxId) {
delete Env.netfluxUsers[netfluxId];
};
// validateKeyStrings supplied by clients must decode to 32-byte Uint8Arrays
const isValidValidateKeyString = function (key) {
try {
@ -151,6 +178,29 @@ const checkExpired = function (Env, Server, channel) {
return true;
};
const getMetadata = HK.getMetadata = function (Env, channelName, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var metadata = Env.metadata_cache[channelName];
if (metadata && typeof(metadata) === 'object') {
return void cb(undefined, metadata);
}
MetaRPC.getMetadataRaw(Env, channelName, function (err, metadata) {
if (err) {
console.error(err);
return void cb(err);
}
if (!(metadata && typeof(metadata.channel) === 'string' && metadata.channel.length === STANDARD_CHANNEL_LENGTH)) {
return cb();
}
// cache it
Env.metadata_cache[channelName] = metadata;
cb(undefined, metadata);
});
};
/* computeIndex
can call back with an error or a computed index which includes:
* cpIndex:
@ -180,13 +230,19 @@ const computeIndex = function (Env, channelName, cb) {
let metadata;
let i = 0;
const ref = {};
const CB = Util.once(cb);
const offsetByHash = {};
let size = 0;
nThen(function (w) {
getMetadata(Env, channelName, w(function (err, _metadata) {
if (err) {
console.log(err);
throw new Error(err); // XXX
}
metadata = _metadata;
}));
}).nThen(function (w) {
// iterate over all messages in the channel log
// old channels can contain metadata as the first message of the log
// remember metadata the first time you encounter it
@ -195,14 +251,15 @@ const computeIndex = function (Env, channelName, cb) {
let msg;
// keep an eye out for the metadata line if you haven't already seen it
// but only check for metadata on the first line
if (!i && !metadata && msgObj.buff.indexOf('{') === 0) {
if (!i && msgObj.buff.indexOf('{') === 0) { // XXX RESTRICT metadata...
i++; // always increment the message counter
msg = tryParse(Env, msgObj.buff.toString('utf8'));
if (typeof msg === "undefined") { return readMore(); }
// validate that the current line really is metadata before storing it as such
if (isMetadataMessage(msg)) {
metadata = msg;
if (isMetadataMessage(msg)) { // XXX RESTRICT
//metadata = msg; // XXX RESTRICT
// skip this, as you already have metadata...
return readMore();
}
}
@ -245,26 +302,8 @@ const computeIndex = function (Env, channelName, cb) {
size = msgObj.offset + msgObj.buff.length + 1;
});
}));
}).nThen(function (w) {
// create a function which will iterate over amendments to the metadata
const handler = Meta.createLineHandler(ref, Log.error);
// initialize the accumulator in case there was a foundational metadata line in the log content
if (metadata) { handler(void 0, metadata); }
// iterate over the dedicated metadata log (if it exists)
// proceed even in the event of a stream error on the metadata log
store.readDedicatedMetadata(channelName, handler, w(function (err) {
if (err) {
return void Log.error("DEDICATED_METADATA_ERROR", err);
}
}));
}).nThen(function () {
// when all is done, cache the metadata in memory
if (ref.index) { // but don't bother if no metadata was found...
metadata = Env.metadata_cache[channelName] = ref.meta;
}
// and return the computed index
// return the computed index
CB(null, {
// Only keep the checkpoints included in the last 100 messages
cpIndex: sliceCpIndex(cpIndex, i),
@ -293,9 +332,7 @@ const getIndex = (Env, channelName, cb) => {
// if there is a channel in memory and it has an index cached, return it
if (chan && chan.index) {
// enforce async behaviour
return void setTimeout(function () {
cb(undefined, chan.index);
});
return void Util.mkAsync(cb)(undefined, chan.index);
}
Env.batchIndexReads(channelName, cb, function (done) {
@ -569,7 +606,7 @@ const handleRPC = function (Env, Server, seq, userId, parsed) {
Server.send(userId, [seq, 'ACK']);
try {
// slice off the sequence number and pass in the rest of the message
Env.rpc(Server, rpc_call, function (err, output) {
Env.rpc(Server, userId, rpc_call, function (err, output) {
if (err) {
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify([parsed[0], 'ERROR', err])]);
return;
@ -646,6 +683,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
// And then check if the channel is expired. If it is, send the error and abort
// FIXME this is hard to read because 'checkExpired' has side effects
if (checkExpired(Env, Server, channelName)) { return void waitFor.abort(); }
// always send metadata with GET_HISTORY requests
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(index.metadata)], w);
}));
@ -662,7 +700,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
}, (err) => {
if (err && err.code !== 'ENOENT') {
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", err); }
const parsedMsg = {error:err.message, channel: channelName, txid: txid};
const parsedMsg = {error:err.message, channel: channelName, txid: txid}; // XXX history retrieval error format
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
return;
}
@ -789,9 +827,9 @@ const handleGetFullHistory = function (Env, Server, seq, userId, parsed) {
};
const directMessageCommands = {
GET_HISTORY: handleGetHistory,
GET_HISTORY_RANGE: handleGetHistoryRange,
GET_FULL_HISTORY: handleGetFullHistory,
GET_HISTORY: handleGetHistory, // XXX RESTRICT
GET_HISTORY_RANGE: handleGetHistoryRange, // XXX RESTRICT
GET_FULL_HISTORY: handleGetFullHistory, // XXX RESTRICT
};
/* onDirectMessage
@ -812,16 +850,63 @@ HK.onDirectMessage = function (Env, Server, seq, userId, json) {
return;
}
// If the requested history is for an expired channel, abort
// Note the if we don't have the keys for that channel in metadata_cache, we'll
// have to abort later (once we know the expiration time)
if (checkExpired(Env, Server, parsed[1])) { return; }
var first = parsed[0];
// look up the appropriate command in the map of commands or fall back to RPC
var command = directMessageCommands[parsed[0]] || handleRPC;
if (typeof(directMessageCommands[first]) !== 'function') {
// it's either an unsupported command or an RPC call
// either way, RPC has it covered
return void handleRPC(Env, Server, seq, userId, parsed);
}
// run the command with the standard function signature
command(Env, Server, seq, userId, parsed);
// otherwise it's some kind of history retrieval command...
// go grab its metadata, because unfortunately people can ask for history
// whether or not they have joined the channel, so we can't rely on JOIN restriction
// to stop people from loading history they shouldn't see.
var channelName = parsed[1];
nThen(function (w) {
HK.getMetadata(Env, channelName, w(function (err, metadata) {
if (err) {
// stream errors?
// we should log these, but if we can't load metadata
// then it's probably not restricted or expired
// it's not like anything else will recover from this anyway
return;
}
// likewise, we can't do anything more here if there's no metadata
// jump to the next block
if (!metadata) { return; }
// If the requested history is for an expired channel, abort
// checkExpired has side effects and will disconnect users for you...
if (checkExpired(Env, Server, parsed[1])) {
// if the channel is expired just abort.
w.abort();
// XXX what do we tell the person who asked?
return;
}
// jump to handling the command if there's no restriction...
if (!metadata.restricted) { return; }
// check if the user is in the allow list...
const allowed = HK.listAllowedUsers(metadata);
const session = HK.getNetfluxSession(Env, userId);
if (HK.isUserSessionAllowed(allowed, session)) {
return;
}
// XXX NOT ALLOWED
// respond to txid with error as in handleGetHistory
// send the allow list anyway, it might not get used currently
// but will in the future
}));
}).nThen(function () {
// run the appropriate command from the map
directMessageCommands[first](Env, Server, seq, userId, parsed);
});
};
/* onChannelMessage

View File

@ -2,23 +2,169 @@ var Meta = module.exports;
var deduplicate = require("./common-util").deduplicateString;
/* Metadata fields:
/* Metadata fields and the commands that can modify them
we assume that these commands can only be performed
by owners or in some cases pending owners. Thus
the owners field is guaranteed to exist.
* channel <STRING>
* validateKey <STRING>
* owners <ARRAY>
* ADD_OWNERS
* RM_OWNERS
* RESET_OWNERS
* pending_owners <ARRAY>
* ADD_PENDING_OWNERS
* RM_PENDING_OWNERS
* expire <NUMBER>
* UPDATE_EXPIRATION (NOT_IMPLEMENTED)
* restricted <BOOLEAN>
* RESTRICT_ACCESS
* allowed <ARRAY>
* ADD_ALLOWED
* RM_ALLOWED
* RESET_ALLOWED
* ADD_OWNERS
* RESET_OWNERS
* mailbox <STRING|MAP>
* ADD_MAILBOX
* RM_MAILBOX
*/
var commands = {};
var isValidOwner = function (owner) {
var isValidPublicKey = function (owner) {
return typeof(owner) === 'string' && owner.length === 44;
};
// isValidPublicKey is a better indication of what the above function does
// I'm preserving this function name in case we ever want to expand its
// criteria at a later time...
var isValidOwner = isValidPublicKey;
// ["RESTRICT_ACCESS", [true], 1561623438989]
// ["RESTRICT_ACCESS", [false], 1561623438989]
commands.RESTRICT_ACCESS = function (meta, args) {
if (!Array.isArray(args) || typeof(args[0]) !== 'boolean') {
throw new Error('INVALID_STATE');
}
var bool = args[0];
// reject the proposed command if there is no change in state
if (meta.restricted === bool) { return false; }
// apply the new state
meta.restricted = args[0];
// if you're disabling access restrictions then you can assume
// then there is nothing more to do. Leave the existing list as-is
if (!bool) { return true; }
// you're all set if an allow list already exists
if (Array.isArray(meta.allowed)) { return true; }
// otherwise define it
meta.allowed = [];
return true;
};
// ["ADD_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
commands.ADD_ALLOWED = function (meta, args) {
if (!Array.isArray(args)) {
throw new Error("INVALID_ARGS");
}
var allowed = meta.allowed || [];
var changed = false;
args.forEach(function (arg) {
// don't add invalid public keys
if (!isValidPublicKey(arg)) { return; }
// don't add owners to the allow list
if (meta.owners.indexOf(arg) >= 0) { return; }
// don't duplicate entries in the allow list
if (allowed.indexOf(arg) >= 0) { return; }
allowed.push(arg);
changed = true;
});
if (changed) {
meta.allowed = meta.allowed || allowed;
}
return changed;
};
// ["RM_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
commands.RM_ALLOWED = function (meta, args) {
if (!Array.isArray(args)) {
throw new Error("INVALID_ARGS");
}
// there may not be anything to remove
if (!meta.allowed) { return false; }
var changed = false;
args.forEach(function (arg) {
var index = meta.allowed.indexOf(arg);
if (index < 0) { return; }
meta.allowed.splice(index, 1);
changed = true;
});
return changed;
};
var arrayHasChanged = function (A, B) {
var changed;
A.some(function (a) {
if (B.indexOf(a) < 0) { return (changed = true); }
});
if (changed) { return true; }
B.some(function (b) {
if (A.indexOf(b) < 0) { return (changed = true); }
});
return changed;
};
var filterInPlace = function (A, f) {
for (var i = A.length - 1; i >= 0; i--) {
if (f(A[i], i, A)) { A.splice(i, 1); }
}
};
// ["RESET_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
commands.RESET_ALLOWED = function (meta, args) {
if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
var updated = args.filter(function (arg) {
// don't allow invalid public keys
if (!isValidPublicKey(arg)) { return false; }
// don't ever add owners to the allow list
if (meta.owners.indexOf(arg)) { return false; }
return true;
});
// this is strictly an optimization...
// a change in length is a clear indicator of a functional change
if (meta.allowed && meta.allowed.length !== updated.length) {
meta.allowed = updated;
return true;
}
// otherwise we must check that the arrays contain distinct elements
// if there is no functional change, then return false
if (!arrayHasChanged(meta.allowed, updated)) { return false; }
// otherwise overwrite the in-memory data and indicate that there was a change
meta.allowed = updated;
return true;
};
// ["ADD_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623438989]
commands.ADD_OWNERS = function (meta, args) {
// bail out if args isn't an array
@ -40,6 +186,13 @@ commands.ADD_OWNERS = function (meta, args) {
changed = true;
});
if (changed && Array.isArray(meta.allowed)) {
// make sure owners are not included in the allow list
filterInPlace(meta.allowed, function (member) {
return meta.owners.indexOf(member) !== -1;
});
}
return changed;
};
@ -71,6 +224,10 @@ commands.RM_OWNERS = function (meta, args) {
changed = true;
});
if (meta.owners.length === 0 && meta.restricted) {
meta.restricted = false;
}
return changed;
};
@ -141,6 +298,18 @@ commands.RESET_OWNERS = function (meta, args) {
// overwrite the existing owners with the new one
meta.owners = deduplicate(args.filter(isValidOwner));
if (Array.isArray(meta.allowed)) {
// make sure owners are not included in the allow list
filterInPlace(meta.allowed, function (member) {
return meta.owners.indexOf(member) !== -1;
});
}
if (meta.owners.length === 0 && meta.restricted) {
meta.restricted = false;
}
return true;
};
@ -178,6 +347,25 @@ commands.ADD_MAILBOX = function (meta, args) {
return changed;
};
commands.RM_MAILBOX = function (meta, args) {
if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
if (!meta.mailbox || typeof(meta.mailbox) === 'undefined') {
return false;
}
if (typeof(meta.mailbox) === 'string' && args.length === 0) {
delete meta.mailbox;
return true;
}
var changed = false;
args.forEach(function (arg) {
if (meta.mailbox[arg] === 'undefined') { return; }
delete meta.mailbox[arg];
changed = true;
});
return changed;
};
commands.UPDATE_EXPIRATION = function () {
throw new Error("E_NOT_IMPLEMENTED");
};

View File

@ -9,6 +9,7 @@ const Block = require("./commands/block");
const Metadata = require("./commands/metadata");
const Channel = require("./commands/channel");
const Upload = require("./commands/upload");
const HK = require("./hk-util");
var RPC = module.exports;
@ -26,7 +27,7 @@ var isUnauthenticateMessage = function (msg) {
return msg && msg.length === 2 && typeof(UNAUTHENTICATED_CALLS[msg[0]]) === 'function';
};
var handleUnauthenticatedMessage = function (Env, msg, respond, Server) {
var handleUnauthenticatedMessage = function (Env, msg, respond, Server, netfluxId) {
Env.Log.silly('LOG_RPC', msg[0]);
var method = UNAUTHENTICATED_CALLS[msg[0]];
@ -36,7 +37,7 @@ var handleUnauthenticatedMessage = function (Env, msg, respond, Server) {
return void respond(err);
}
respond(err, [null, value, null]);
}, Server);
}, Server, netfluxId);
};
const AUTHENTICATED_USER_TARGETED = {
@ -117,7 +118,7 @@ var handleAuthenticatedMessage = function (Env, unsafeKey, msg, respond, Server)
return void Respond('UNSUPPORTED_RPC_CALL', msg);
};
var rpc = function (Env, Server, data, respond) {
var rpc = function (Env, Server, userId, data, respond) {
if (!Array.isArray(data)) {
Env.Log.debug('INVALID_ARG_FORMET', data);
return void respond('INVALID_ARG_FORMAT');
@ -136,15 +137,16 @@ var rpc = function (Env, Server, data, respond) {
}
if (isUnauthenticateMessage(msg)) {
return handleUnauthenticatedMessage(Env, msg, respond, Server);
return handleUnauthenticatedMessage(Env, msg, respond, Server, userId);
}
var signature = msg.shift();
var publicKey = msg.shift();
// make sure a user object is initialized in the cookie jar
var session;
if (publicKey) {
Core.getSession(Env.Sessions, publicKey);
session = Core.getSession(Env.Sessions, publicKey);
} else {
Env.Log.debug("NO_PUBLIC_KEY_PROVIDED", publicKey);
}
@ -174,6 +176,7 @@ var rpc = function (Env, Server, data, respond) {
// check the signature on the message
// refuse the command if it doesn't validate
if (Core.checkSignature(Env, serialized, signature, publicKey) === true) {
HK.authenticateNetfluxSession(Env, userId, publicKey);
return void handleAuthenticatedMessage(Env, publicKey, msg, respond, Server);
}
return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY");
@ -202,9 +205,9 @@ RPC.create = function (Env, cb) {
Core.expireSessions(Sessions);
}, Core.SESSION_EXPIRATION_TIME);
cb(void 0, function (Server, data, respond) {
cb(void 0, function (Server, userId, data, respond) {
try {
return rpc(Env, Server, data, respond);
return rpc(Env, Server, userId, data, respond);
} catch (e) {
console.log("Error from RPC with data " + JSON.stringify(data));
console.log(e.stack);

4
package-lock.json generated
View File

@ -113,9 +113,7 @@
}
},
"chainpad-server": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-4.0.2.tgz",
"integrity": "sha512-9NrFsATd70uAdksxsCZBIJ/SiREmJ6QLYTNaeFLH/nJpeZ2b7wblVGABCj3JYWvngdEZ7Umc+afbWH8sUmtgeQ==",
"version": "4.0.3",
"requires": {
"nthen": "0.1.8",
"pull-stream": "^3.6.9",

View File

@ -13,7 +13,7 @@
},
"dependencies": {
"chainpad-crypto": "^0.2.2",
"chainpad-server": "^4.0.0",
"chainpad-server": "^4.0.3",
"express": "~4.16.0",
"fs-extra": "^7.0.0",
"get-folder-size": "^2.0.1",

View File

@ -357,9 +357,142 @@ nThen(function (w) {
bob.name = 'bob';
//console.log("Initialized Bob");
}));
}).nThen(function (w) {
// restrict access to oscar's mailbox channel
oscar.rpc.send('SET_METADATA', {
command: 'RESTRICT_ACCESS',
channel: oscar.mailboxChannel,
value: [ true ]
}, w(function (err, response) {
if (err) {
return void console.log(err);
}
var metadata = response[0];
if (!(metadata && metadata.restricted)) {
throw new Error("EXPECTED MAILBOX TO BE RESTRICTED");
}
}));
}).nThen(function (w) {
// XXX RESTRICT GET_METADATA should fail because alice is not on the allow list
// expect INSUFFICIENT_PERMISSIONS
alice.anonRpc.send('GET_METADATA', oscar.mailboxChannel, w(function (err) {
if (!err) {
// XXX RESTRICT alice should not be permitted to read oscar's mailbox's metadata
}
}));
}).nThen(function (w) {
// add alice to oscar's mailbox's allow list for some reason
oscar.rpc.send('SET_METADATA', {
command: 'ADD_ALLOWED',
channel: oscar.mailboxChannel,
value: [
alice.edKeys.edPublic
]
}, w(function (err /*, metadata */) {
if (err) {
return void console.error(err);
}
//console.log('XXX', metadata);
}));
}).nThen(function (w) {
oscar.anonRpc.send('GET_METADATA', oscar.mailboxChannel, w(function (err, response) {
if (err) {
throw new Error("OSCAR SHOULD BE ABLE TO READ HIS OWN METADATA");
}
var metadata = response && response[0];
if (!metadata) {
throw new Error("EXPECTED METADATA");
}
if (metadata.allowed[0] !== alice.edKeys.edPublic) {
throw new Error("EXPECTED ALICE TO BE ON ALLOW LIST");
}
}));
}).nThen(function () {
// XXX RESTRICT alice should now be able to read oscar's mailbox metadata
/*
alice.anonRpc.send('GET_METADATA', oscar.mailboxChannel, function (err, response) {
if (err) {
PROBLEM
}
});
*/
}).nThen(function (w) {
//throw new Error("boop");
// add alice as an owner of oscar's mailbox for some reason
oscar.rpc.send('SET_METADATA', {
command: 'ADD_OWNERS',
channel: oscar.mailboxChannel,
value: [
alice.edKeys.edPublic
]
}, Util.mkTimeout(w(function (err) {
if (err === 'TIMEOUT') {
throw new Error(err);
}
if (err) {
throw new Error("ADD_OWNERS_FAILURE");
}
}), 2000));
}).nThen(function (w) {
// alice should now be able to read oscar's mailbox metadata
alice.anonRpc.send('GET_METADATA', oscar.mailboxChannel, w(function (err, response) {
if (err) {
throw new Error("EXPECTED ALICE TO BE ALLOWED TO READ OSCAR'S METADATA");
}
var metadata = response && response[0];
if (!metadata) { throw new Error("EXPECTED METADATA"); }
if (metadata.allowed.length !== 0) {
throw new Error("EXPECTED AN EMPTY ALLOW LIST");
}
}));
}).nThen(function (w) {
// disable the access restrictionallow list
oscar.rpc.send('SET_METADATA', {
command: 'RESTRICT_ACCESS',
channel: oscar.mailboxChannel,
value: [
false
]
}, w(function (err) {
if (err) {
throw new Error("COULD_NOT_DISABLE_RESTRICTED_ACCESS");
}
}));
// add alice to oscar's mailbox's allow list for some reason
oscar.rpc.send('SET_METADATA', {
command: 'ADD_ALLOWED',
channel: oscar.mailboxChannel,
value: [
bob.edKeys.edPublic
]
}, w(function (err) {
if (err) {
return void console.error(err);
}
}));
}).nThen(function (w) {
oscar.anonRpc.send('GET_METADATA', oscar.mailboxChannel, w(function (err, response) {
if (err) {
throw new Error("OSCAR SHOULD BE ABLE TO READ HIS OWN METADATA");
}
var metadata = response && response[0];
if (!metadata) {
throw new Error("EXPECTED METADATA");
}
if (metadata.allowed[0] !== bob.edKeys.edPublic) {
throw new Error("EXPECTED ALICE TO BE ON ALLOW LIST");
}
if (metadata.restricted) {
throw new Error("RESTRICTED_ACCESS_NOT_DISABLED");
}
}));
}).nThen(function () {
//setTimeout(w(), 500);
}).nThen(function (w) {
// Alice loads the roster...
var rosterKeys = Crypto.Team.deriveMemberKeys(sharedConfig.rosterSeed, alice.curveKeys);

View File

@ -218,14 +218,15 @@ define([
var titles = [];
var active = 0;
tabs.forEach(function (tab, i) {
if (!tab.content || !tab.title) { return; }
if (!(tab.content || tab.disabled) || !tab.title) { return; }
var content = h('div.alertify-tabs-content', tab.content);
var title = h('span.alertify-tabs-title', tab.title);
var title = h('span.alertify-tabs-title'+ (tab.disabled ? '.disabled' : ''), tab.title);
if (tab.icon) {
var icon = h('i', {class: tab.icon});
$(title).prepend(' ').prepend(icon);
}
$(title).click(function () {
if (tab.disabled) { return; }
var old = tabs[active];
if (old.onHide) { old.onHide(); }
titles.forEach(function (t) { $(t).removeClass('alertify-tabs-active'); });
@ -239,7 +240,7 @@ define([
});
titles.push(title);
contents.push(content);
if (tab.active) { active = i; }
if (tab.active && !tab.disabled) { active = i; }
});
if (contents.length) {
$(contents[active]).addClass('alertify-tabs-content-active');
@ -1192,15 +1193,20 @@ define([
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide();
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide();
var state = false;
var spin = function () {
state = true;
$ok.hide();
$spinner.show();
};
var hide = function () {
state = false;
$ok.hide();
$spinner.hide();
};
var done = function () {
state = false;
$ok.show();
$spinner.hide();
};
@ -1211,6 +1217,7 @@ define([
}
return {
getState: function () { return state; },
ok: $ok[0],
spinner: $spinner[0],
spin: spin,

View File

@ -14,7 +14,7 @@ define([
'/customize/application_config.js',
'/customize/pages.js',
'/bower_components/nthen/index.js',
'/common/invitation.js',
'/common/inner/invitation.js',
'css!/customize/fonts/cptools/style.css',
'/bower_components/croppie/croppie.min.js',
@ -99,7 +99,7 @@ define([
});
};
};
/*
var getPropertiesData = function (common, cb) {
var data = {};
NThen(function (waitFor) {
@ -127,6 +127,32 @@ define([
cb(void 0, data);
});
};
*/
var getPropertiesData = function (common, opts, cb) {
opts = opts || {};
var data = {};
NThen(function (waitFor) {
var base = common.getMetadataMgr().getPrivateData().origin;
common.getPadAttribute('', waitFor(function (err, val) {
if (err || !val) {
waitFor.abort();
return void cb(err || 'EEMPTY');
}
if (!val.fileType) {
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; }
}), opts.href);
}).nThen(function () {
cb(void 0, data);
});
};
/*
var createOwnerModal = function (common, data) {
var friends = common.getFriends(true);
var sframeChan = common.getSframeChannel();
@ -425,8 +451,8 @@ define([
var link = h('div.cp-share-columns', [
div1,
div2
/*drawRemove()[0],
drawAdd()[0]*/
// drawRemove()[0],
//drawAdd()[0]
]);
var linkButtons = [{
className: 'cancel',
@ -436,6 +462,8 @@ define([
}];
return UI.dialog.customModal(link, {buttons: linkButtons});
};
*/
/*
var getRightsProperties = function (common, data, cb) {
var $div = $('<div>');
if (!data) { return void cb(void 0, $div); }
@ -707,7 +735,10 @@ define([
cb(void 0, $div);
};
var getPadProperties = function (common, data, cb) {
*/
var getPadProperties = function (common, data, opts, cb) {
opts = opts || {};
var $d = $('<div>');
if (!data) { return void cb(void 0, $d); }
@ -721,7 +752,7 @@ define([
}));
}
if (data.roHref) {
if (data.roHref && !opts.noReadOnly) {
$('<label>', {'for': 'cp-app-prop-rolink'}).text(Messages.viewShare).appendTo($d);
$d.append(UI.dialog.selectable(data.roHref, {
id: 'cp-app-prop-rolink',
@ -859,35 +890,42 @@ define([
};
UIElements.getProperties = function (common, data, cb) {
var c1;
var c2;
UIElements.getProperties = function (common, opts, cb) {
var data;
var content;
var button = [{
className: 'primary',
name: Messages.okButton,
className: 'cancel',
name: Messages.filePicker_close,
onClick: function () {},
keys: [13]
keys: [13,27]
}];
NThen(function (waitFor) {
getPadProperties(common, data, waitFor(function (e, c) {
c1 = UI.dialog.customModal(c[0], {
buttons: button
});
getPropertiesData(common, opts, waitFor(function (e, _data) {
if (e) {
waitFor.abort();
return void cb(e);
}
data = _data;
}));
getRightsProperties(common, data, waitFor(function (e, c) {
c2 = UI.dialog.customModal(c[0], {
}).nThen(function (waitFor) {
getPadProperties(common, data, opts, waitFor(function (e, c) {
if (e) {
waitFor.abort();
return void cb(e);
}
content = UI.dialog.customModal(c[0], {
buttons: button
});
}));
}).nThen(function () {
var tabs = UI.dialog.tabs([{
title: Messages.fc_prop,
content: c1
}, {
title: Messages.creation_propertiesTitle,
content: c2
icon: "fa fa-info-circle",
content: content
}]);
cb (void 0, $(tabs));
var modal = UI.openCustomModal(tabs);
cb (void 0, modal);
});
};
@ -901,7 +939,15 @@ define([
var name = data.displayName || data.name || Messages.anonymous;
var avatar = h('span.cp-usergrid-avatar.cp-avatar');
UIElements.displayAvatar(common, $(avatar), data.avatar, name);
return h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), {
var removeBtn, el;
if (config.remove) {
removeBtn = h('span.fa.fa-times');
$(removeBtn).click(function () {
config.remove(el);
});
}
el = h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), {
'data-ed': data.edPublic,
'data-teamid': data.teamId,
'data-curve': data.curvePublic || '',
@ -911,17 +957,20 @@ define([
style: 'order:'+i+';'
},[
avatar,
h('span.cp-usergrid-user-name', name)
h('span.cp-usergrid-user-name', name),
data.notRemovable ? undefined : removeBtn
]);
return el;
}).filter(function (x) { return x; });
var noOthers = icons.length === 0 ? '.cp-usergrid-empty' : '';
var classes = noOthers + (config.large?'.large':'') + (config.list?'.list':'');
var inputFilter = h('input', {
placeholder: Messages.share_filterFriend
});
var div = h('div.cp-usergrid-container' + noOthers + (config.large?'.large':''), [
var div = h('div.cp-usergrid-container' + classes, [
label ? h('label', label) : undefined,
h('div.cp-usergrid-filter', (config.noFilter || config.noSelect) ? undefined : [
inputFilter
@ -2374,6 +2423,26 @@ define([
});
updateIcon(data.element.is(':visible'));
break;
case 'access':
button = $('<button>', {
'class': 'fa fa-unlock-alt cp-toolbar-icon-access',
title: "ACCESS", // XXX
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'})
.text("ACCESS")) // XXX
.click(common.prepareFeedback(type))
.click(function () {
common.isPadStored(function (err, data) {
if (!data) {
return void UI.alert(Messages.autostore_notAvailable);
}
require(['/common/inner/access.js'], function (Access) {
Access.getAccessModal(common, {}, function (e) {
if (e) { console.error(e); }
});
});
});
});
break;
case 'properties':
button = $('<button>', {
'class': 'fa fa-info-circle cp-toolbar-icon-properties',
@ -2386,12 +2455,8 @@ define([
if (!data) {
return void UI.alert(Messages.autostore_notAvailable);
}
getPropertiesData(common, function (e, data) {
UIElements.getProperties(common, {}, function (e) {
if (e) { return void console.error(e); }
UIElements.getProperties(common, data, function (e, $prop) {
if (e) { return void console.error(e); }
UI.openCustomModal($prop[0]);
});
});
});
});
@ -4144,7 +4209,7 @@ define([
};
UIElements.onServerError = function (common, err, toolbar, cb) {
if (["EDELETED", "EEXPIRED"].indexOf(err.type) === -1) { return; }
if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
var priv = common.getMetadataMgr().getPrivateData();
var msg = err.type;
if (err.type === 'EEXPIRED') {
@ -4158,11 +4223,13 @@ define([
if (err.loaded) {
msg += Messages.errorCopy;
}
} else if (err.type === 'ERESTRICTED') {
msg = Messages.restrictedError || "RESTRICTED"; // XXX
}
var sframeChan = common.getSframeChannel();
sframeChan.event('EV_SHARE_OPEN', {hidden: true});
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
UI.errorLoadingScreen(msg, true, true);
UI.errorLoadingScreen(msg, Boolean(err.loaded), Boolean(err.loaded));
(cb || function () {})();
};

View File

@ -68,6 +68,19 @@
};
};
Util.mkTimeout = function (_f, ms) {
ms = ms || 0;
var f = Util.once(_f);
var timeout = setTimeout(function () {
f('TIMEOUT');
}, ms);
return Util.both(f, function () {
clearTimeout(timeout);
});
};
Util.response = function () {
var pending = {};
var timeouts = {};

View File

@ -8,6 +8,9 @@ define([
'/common/common-interface.js',
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/inner/access.js',
'/bower_components/nthen/index.js',
'/common/hyperscript.js',
'/common/proxy-manager.js',
@ -23,6 +26,7 @@ define([
UI,
Constants,
Feedback,
Access,
nThen,
h,
ProxyManager,
@ -80,6 +84,7 @@ define([
var faTrash = 'fa-trash';
var faCopy = 'fa-clone';
var faDelete = 'fa-eraser';
var faAccess = 'fa-unlock-alt';
var faProperties = 'fa-info-circle';
var faTags = 'fa-hashtag';
var faUploadFiles = 'cptools-file-upload';
@ -117,9 +122,9 @@ define([
var $addIcon = $('<span>', {"class": "fa fa-plus"});
var $renamedIcon = $('<span>', {"class": "fa fa-flag"});
var $readonlyIcon = $('<span>', {"class": "fa " + faReadOnly});
var $ownedIcon = $('<span>', {"class": "fa fa-id-card-o"});
var $ownedIcon = $('<span>', {"class": "fa fa-id-badge"});
var $sharedIcon = $('<span>', {"class": "fa " + faShared});
var $ownerIcon = $('<span>', {"class": "fa fa-id-card"});
//var $ownerIcon = $('<span>', {"class": "fa fa-id-card"});
var $tagsIcon = $('<span>', {"class": "fa " + faTags});
var $passwordIcon = $('<span>', {"class": "fa fa-lock"});
var $expirableIcon = $('<span>', {"class": "fa fa-clock-o"});
@ -453,6 +458,10 @@ define([
'data-icon': faDelete,
}, Messages.fc_remove_sharedfolder)),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-access.dropdown-item', {
'tabindex': '-1',
'data-icon': faAccess,
}, "ACCESS")), // XXX
h('li', h('a.cp-app-drive-context-properties.dropdown-item', {
'tabindex': '-1',
'data-icon': faProperties,
@ -1212,7 +1221,7 @@ define([
hide.push('savelocal');
hide.push('openro');
hide.push('openincode');
hide.push('properties');
hide.push('properties', 'access');
hide.push('hashtag');
hide.push('makeacopy');
}
@ -1243,7 +1252,7 @@ define([
});
if (paths.length > 1) {
hide.push('restore');
hide.push('properties');
hide.push('properties', 'access');
hide.push('rename');
hide.push('openparent');
hide.push('hashtag');
@ -1273,7 +1282,7 @@ define([
'deleteowned', 'removesf', 'properties', 'hashtag'];
break;
case 'default':
show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'hashtag', 'makeacopy'];
show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy'];
break;
case 'trashtree': {
show = ['empty'];
@ -1811,10 +1820,10 @@ define([
var $owned = $ownedIcon.clone().appendTo($state);
$owned.attr('title', Messages.fm_padIsOwned);
$span.addClass('cp-app-drive-element-owned');
} else if (data.owners && data.owners.length) {
} /* else if (data.owners && data.owners.length) {
var $owner = $ownerIcon.clone().appendTo($state);
$owner.attr('title', Messages.fm_padIsOwnedOther);
}
} */
};
var thumbsUrls = {};
var addFileData = function (element, $element) {
@ -3086,9 +3095,8 @@ define([
}).appendTo($openDir);
}
$('<a>').text(Messages.fc_prop).click(function () {
APP.getProperties(r.id, function (e, $prop) {
APP.getProperties(r.id, function (e) {
if (e) { return void logError(e); }
UI.alert($prop[0], undefined, true);
});
}).appendTo($openDir);
}
@ -3836,12 +3844,11 @@ define([
}
};
var getProperties = APP.getProperties = function (el, cb) {
APP.getProperties = function (el, cb) {
if (!manager.isFile(el) && !manager.isSharedFolder(el)) {
return void cb('NOT_FILE');
}
//var ro = manager.isReadOnlyFile(el);
var base = APP.origin;
var data;
if (manager.isSharedFolder(el)) {
data = JSON.parse(JSON.stringify(manager.getSharedFolderData(el)));
@ -3850,42 +3857,42 @@ define([
}
if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); }
if (data.href) {
data.href = base + data.href;
}
if (data.roHref) {
data.roHref = base + data.roHref;
}
if (currentPath[0] === TEMPLATE) {
data.isTemplate = true;
}
var opts = {};
opts.href = Hash.getRelativeHref(data.href || data.roHref);
if (manager.isSharedFolder(el)) {
var ro = folders[el] && folders[el].version >= 2;
if (!ro) { delete data.roHref; }
//data.noPassword = true;
//data.noEditPassword = true;
data.noExpiration = true;
// this is here to allow users to check the channel id of a shared folder
// we should remove it at some point
data.sharedFolder = true;
if (!ro) { opts.noReadOnly = true; }
}
UIElements.getProperties(common, opts, cb);
};
APP.getAccess = function (el, cb) {
if (!manager.isFile(el) && !manager.isSharedFolder(el)) {
return void cb('NOT_FILE');
}
var data;
if (manager.isSharedFolder(el)) {
data = JSON.parse(JSON.stringify(manager.getSharedFolderData(el)));
} else {
data = JSON.parse(JSON.stringify(manager.getFileData(el)));
}
if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); }
var opts = {};
opts.href = Hash.getRelativeHref(data.href || data.roHref);
opts.channel = data.channel;
// Transfer ownership: templates are stored as templates for other users/teams
if (currentPath[0] === TEMPLATE) {
opts.isTemplate = true;
}
if ((manager.isFile(el) && data.roHref) || manager.isSharedFolder(el)) { // Only for pads!
sframeChan.query('Q_GET_PAD_METADATA', {
channel: data.channel
}, function (err, val) {
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;
// Shared folders: no expiration date
if (manager.isSharedFolder(el)) {
opts.noExpiration = true;
}
UIElements.getProperties(common, data, cb);
Access.getAccessModal(common, opts, cb);
};
if (!APP.loggedIn) {
@ -4259,9 +4266,19 @@ define([
// ANON_SHARED_FOLDER
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
}
getProperties(el, function (e, $prop) {
APP.getProperties(el, function (e) {
if (e) { return void logError(e); }
});
}
else if ($this.hasClass("cp-app-drive-context-access")) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
if (paths[0].path[0] === SHARED_FOLDER && APP.newSharedFolder) {
// ANON_SHARED_FOLDER
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
}
APP.getAccess(el, function (e) {
if (e) { return void logError(e); }
UI.openCustomModal($prop[0]);
});
}
else if ($this.hasClass("cp-app-drive-context-hashtag")) {

1144
www/common/inner/access.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1479,6 +1479,8 @@ define([
var $properties = common.createButton('properties', true);
toolbar.$drawer.append($properties);
var $access = common.createButton('access', true);
toolbar.$drawer.append($access);
};
config.onReady = function (info) {

View File

@ -1628,10 +1628,11 @@ define([
// data.send === true ==> send the request
Store.requestPadAccess = function (clientId, data, cb) {
var owner = data.owner;
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
/* // XXX check mailbox in our contacts is not compatible with the new "mute pad" feature
var owners = data.owners;
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)
@ -1648,6 +1649,7 @@ define([
});
}
}
*/
// If send is true, send the request to the owner.
if (owner) {

View File

@ -900,10 +900,11 @@ define([
cb = cb || function () {};
var sfId = Env.user.userObject.getSFIdFromHref(data.href);
if (sfId) {
var sfData = Env.user.proxy[UserObject.SHARED_FOLDERS][sfId];
var sfData = getSharedFolderData(Env, sfId);
var sfValue = data.attr ? sfData[data.attr] : JSON.parse(JSON.stringify(sfData));
setTimeout(function () {
cb(null, {
value: sfData[data.attr],
value: sfValue,
atime: 1
});
});

View File

@ -702,6 +702,8 @@ define([
var $properties = common.createButton('properties', true);
toolbar.$drawer.append($properties);
var $access = common.createButton('access', true);
toolbar.$drawer.append($access);
createFilePicker();

View File

@ -1250,22 +1250,24 @@ define([
});
// 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) {
sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) {
if (readOnly && hashes.editHash) {
return void cb({error: 'ALREADYKNOWN'});
}
var send = data.send;
var metadata = data.metadata;
var owner, owners;
var crypto = Crypto.createEncryptor(secret.keys);
var _secret = secret;
if (metadata && metadata.roHref) {
var _parsed = Utils.Hash.parsePadUrl(metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
}
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.getPadMetadata({
channel: secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) { return; }
var todo = function (obj) {
owners = obj.owners;
var mailbox;
@ -1284,22 +1286,107 @@ define([
owner = data;
} catch (e) { console.error(e); }
}
};
// If we already have metadata, use it, otherwise, try to get it
if (metadata) { return void todo(metadata); }
Cryptpad.getPadMetadata({
channel: _secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) { return; }
todo(obj);
}));
}).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
/* // XXX
if (owner && !send) {
return void cb({state: true});
}
*/
if (!send) { return void cb({state: Boolean(owner)}); }
Cryptpad.padRpc.requestAccess({
send: send,
channel: secret.channel,
channel: _secret.channel,
owner: owner,
owners: owners
}, cb);
});
});
// Add or remove our mailbox from the list if we're an owner
sframeChan.on('Q_UPDATE_MAILBOX', function (data, cb) {
var metadata = data.metadata;
var add = data.add;
var _secret = secret;
if (metadata && (metadata.href || metadata.roHref)) {
var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
}
var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) {
// If we already have metadata, use it, otherwise, try to get it
if (metadata) { return; }
Cryptpad.getPadMetadata({
channel: secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) {
waitFor.abort();
return void cb(obj);
}
metadata = obj;
}));
}).nThen(function () {
// Get and maybe migrate the existing mailbox object
var owners = metadata.owners;
if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) {
return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
}
// Remove a mailbox
if (!add) {
// Old format: this is the mailbox of the first owner
if (typeof (metadata.mailbox) === "string" && metadata.mailbox) {
// Not our mailbox? abort
if (owners[0] !== edPublic) {
return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
}
// Remove it
return void Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'RM_MAILBOX',
value: []
}, cb);
} else if (metadata.mailbox) { // New format
return void Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'RM_MAILBOX',
value: [edPublic]
}, cb);
}
return void cb({
error: 'NO_MAILBOX'
});
}
// Add a mailbox
var toAdd = {};
toAdd[edPublic] = crypto.encrypt(JSON.stringify({
notifications: notifications,
curvePublic: curvePublic
}));
Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'ADD_MAILBOX',
value: toAdd
}, cb);
});
});
sframeChan.on('EV_BURN_PAD', function (channel) {
if (!burnAfterReading) { return; }
Cryptpad.burnPad({

View File

@ -574,6 +574,7 @@ MessengerUI, Messages) {
return $shareBlock;
};
/*
var createRequest = function (toolbar, config) {
if (!config.metadataMgr) {
throw new Error("You must provide a `metadataMgr` to display the request access button");
@ -590,13 +591,13 @@ MessengerUI, Messages) {
// If we have access to the owner's mailbox, display the button and enable it
// false => check if we can contact the owner
// true ==> send the request
Common.getSframeChannel().query('Q_REQUEST_ACCESS', false, function (err, obj) {
Common.getSframeChannel().query('Q_REQUEST_ACCESS', {send:false}, function (err, obj) {
if (obj && obj.state) {
var locked = false;
$requestBlock.show().click(function () {
if (locked) { return; }
locked = true;
Common.getSframeChannel().query('Q_REQUEST_ACCESS', true, function (err, obj) {
Common.getSframeChannel().query('Q_REQUEST_ACCESS', {send:true}, function (err, obj) {
if (obj && obj.state) {
UI.log(Messages.requestEdit_sent);
$requestBlock.hide();
@ -614,6 +615,7 @@ MessengerUI, Messages) {
return $requestBlock;
};
*/
var createTitle = function (toolbar, config) {
var $titleContainer = $('<span>', {
@ -1226,7 +1228,7 @@ MessengerUI, Messages) {
tb['fileshare'] = createFileShare;
tb['title'] = createTitle;
tb['pageTitle'] = createPageTitle;
tb['request'] = createRequest;
//tb['request'] = createRequest;
tb['lag'] = $.noop;
tb['spinner'] = createSpinner;
tb['state'] = $.noop;

View File

@ -132,6 +132,7 @@ define([
title: Title.getTitleConfig(),
});
toolbar.$rightside.append(common.createButton('forget', true));
toolbar.$rightside.append(common.createButton('access', true));
toolbar.$rightside.append(common.createButton('properties', true));
if (common.isLoggedIn()) {
toolbar.$rightside.append(common.createButton('hashtag', true));

View File

@ -1171,6 +1171,8 @@ define([
var $forgetPad = common.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
var $access = common.createButton('access', true);
$drawer.append($access);
var $properties = common.createButton('properties', true);
$drawer.append($properties);

View File

@ -15,7 +15,7 @@ define([
'/common/hyperscript.js',
'/customize/application_config.js',
'/common/messenger-ui.js',
'/common/invitation.js',
'/common/inner/invitation.js',
'/customize/messages.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',