merge communities-allow-list and lint compliance

pull/1/head
ansuz 5 years ago
commit f951951077

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

@ -68,6 +68,23 @@
color: @cryptpad_text_col; 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 // teams invite modal
.cp-teams-invite-block { .cp-teams-invite-block {
display: flex; display: flex;

@ -109,6 +109,27 @@
color: @colortheme_alertify-primary-text; 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;
}
} }
} }
} }

@ -14,21 +14,7 @@ module.exports.create = function (config) {
.on('channelClose', historyKeeper.channelClose) .on('channelClose', historyKeeper.channelClose)
.on('channelMessage', historyKeeper.channelMessage) .on('channelMessage', historyKeeper.channelMessage)
.on('channelOpen', historyKeeper.channelOpen) .on('channelOpen', historyKeeper.channelOpen)
.on('sessionClose', function (userId, reason) { .on('sessionClose', historyKeeper.sessionClose)
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('error', function (error, label, info) { .on('error', function (error, label, info) {
if (!error) { return; } if (!error) { return; }
/* labels: /* labels:

@ -261,6 +261,8 @@ Channel.writePrivateMessage = function (Env, args, cb, Server) {
msg // the actual message content. Generally a string 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 // 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. // if the message isn't valid it won't be stored.
Env.historyKeeper.channelMessage(Server, channelStruct, fullMessage); Env.historyKeeper.channelMessage(Server, channelStruct, fullMessage);

@ -2,21 +2,24 @@
const Data = module.exports; const Data = module.exports;
const Meta = require("../metadata"); const Meta = require("../metadata");
const BatchRead = require("../batch-read");
const WriteQueue = require("../write-queue"); const WriteQueue = require("../write-queue");
const Core = require("./core"); const Core = require("./core");
const Util = require("../common-util"); const Util = require("../common-util");
const HK = require("../hk-util");
const batchMetadata = BatchRead("GET_METADATA"); Data.getMetadataRaw = function (Env, channel /* channelName */, _cb) {
Data.getMetadata = function (Env, channel, cb/* , Server */) { const cb = Util.once(Util.mkAsync(_cb));
if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); } 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 var cached = Env.metadata_cache[channel];
batchMetadata(channel, cb, function (done) { if (HK.isMetadataMessage(cached)) {
return void cb(void 0, cached);
}
Env.batchMetadata(channel, cb, function (done) {
var ref = {}; var ref = {};
var lineHandler = Meta.createLineHandler(ref, Env.Log.error); var lineHandler = Meta.createLineHandler(ref, Env.Log.error);
return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) { return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) {
if (err) { if (err) {
// stream errors? // 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 /* setMetadata
- write a new line to the metadata log if a valid command is provided - write a new line to the metadata log if a valid command is provided
- data is an object: { - 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'); } if (Meta.commands.indexOf(command) === -1) { return void cb('UNSUPPORTED_COMMAND'); }
queueMetadata(channel, function (next) { queueMetadata(channel, function (next) {
Data.getMetadata(Env, channel, function (err, metadata) { Data.getMetadataRaw(Env, channel, function (err, metadata) {
if (err) { if (err) {
cb(err); cb(err);
return void next(); return void next();
@ -108,21 +133,70 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) {
return void next(); 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); cb(void 0, metadata);
next(); next();
const metadata_cache = Env.metadata_cache; const metadata_cache = Env.metadata_cache;
const channel_cache = Env.channel_cache; const channel_cache = Env.channel_cache;
// update the cached metadata
metadata_cache[channel] = 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']); var index = Util.find(channel_cache, [channel, 'index']);
if (index && typeof(index) === 'object') { index.metadata = metadata; } 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);
}); });
}); });
}); });
}; };

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

@ -4,7 +4,7 @@ var HK = module.exports;
const nThen = require('nthen'); const nThen = require('nthen');
const Util = require("./common-util"); const Util = require("./common-util");
const Meta = require("./metadata"); const MetaRPC = require("./commands/metadata");
const Nacl = require('tweetnacl/nacl-fast'); const Nacl = require('tweetnacl/nacl-fast');
const now = function () { return (new Date()).getTime(); }; const now = function () { return (new Date()).getTime(); };
@ -71,10 +71,37 @@ const sliceCpIndex = function (cpIndex, line) {
return start.concat(end); return start.concat(end);
}; };
const isMetadataMessage = function (parsed) { const isMetadataMessage = HK.isMetadataMessage = function (parsed) {
return Boolean(parsed && parsed.channel); 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 // validateKeyStrings supplied by clients must decode to 32-byte Uint8Arrays
const isValidValidateKeyString = function (key) { const isValidValidateKeyString = function (key) {
try { try {
@ -151,6 +178,29 @@ const checkExpired = function (Env, Server, channel) {
return true; 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 /* computeIndex
can call back with an error or a computed index which includes: can call back with an error or a computed index which includes:
* cpIndex: * cpIndex:
@ -180,13 +230,19 @@ const computeIndex = function (Env, channelName, cb) {
let metadata; let metadata;
let i = 0; let i = 0;
const ref = {};
const CB = Util.once(cb); const CB = Util.once(cb);
const offsetByHash = {}; const offsetByHash = {};
let size = 0; let size = 0;
nThen(function (w) { 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 // iterate over all messages in the channel log
// old channels can contain metadata as the first message of the log // old channels can contain metadata as the first message of the log
// remember metadata the first time you encounter it // remember metadata the first time you encounter it
@ -195,14 +251,15 @@ const computeIndex = function (Env, channelName, cb) {
let msg; let msg;
// keep an eye out for the metadata line if you haven't already seen it // keep an eye out for the metadata line if you haven't already seen it
// but only check for metadata on the first line // 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 i++; // always increment the message counter
msg = tryParse(Env, msgObj.buff.toString('utf8')); msg = tryParse(Env, msgObj.buff.toString('utf8'));
if (typeof msg === "undefined") { return readMore(); } if (typeof msg === "undefined") { return readMore(); }
// validate that the current line really is metadata before storing it as such // validate that the current line really is metadata before storing it as such
if (isMetadataMessage(msg)) { if (isMetadataMessage(msg)) { // XXX RESTRICT
metadata = msg; //metadata = msg; // XXX RESTRICT
// skip this, as you already have metadata...
return readMore(); return readMore();
} }
} }
@ -245,26 +302,8 @@ const computeIndex = function (Env, channelName, cb) {
size = msgObj.offset + msgObj.buff.length + 1; 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 () { }).nThen(function () {
// when all is done, cache the metadata in memory // return the computed index
if (ref.index) { // but don't bother if no metadata was found...
metadata = Env.metadata_cache[channelName] = ref.meta;
}
// and return the computed index
CB(null, { CB(null, {
// Only keep the checkpoints included in the last 100 messages // Only keep the checkpoints included in the last 100 messages
cpIndex: sliceCpIndex(cpIndex, i), 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 there is a channel in memory and it has an index cached, return it
if (chan && chan.index) { if (chan && chan.index) {
// enforce async behaviour // enforce async behaviour
return void setTimeout(function () { return void Util.mkAsync(cb)(undefined, chan.index);
cb(undefined, chan.index);
});
} }
Env.batchIndexReads(channelName, cb, function (done) { Env.batchIndexReads(channelName, cb, function (done) {
@ -569,7 +606,7 @@ const handleRPC = function (Env, Server, seq, userId, parsed) {
Server.send(userId, [seq, 'ACK']); Server.send(userId, [seq, 'ACK']);
try { try {
// slice off the sequence number and pass in the rest of the message // 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) { if (err) {
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify([parsed[0], 'ERROR', err])]); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify([parsed[0], 'ERROR', err])]);
return; 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 // 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 // FIXME this is hard to read because 'checkExpired' has side effects
if (checkExpired(Env, Server, channelName)) { return void waitFor.abort(); } if (checkExpired(Env, Server, channelName)) { return void waitFor.abort(); }
// always send metadata with GET_HISTORY requests // always send metadata with GET_HISTORY requests
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(index.metadata)], w); 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) => { }, (err) => {
if (err && err.code !== 'ENOENT') { if (err && err.code !== 'ENOENT') {
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", err); } 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)]); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
return; return;
} }
@ -789,9 +827,9 @@ const handleGetFullHistory = function (Env, Server, seq, userId, parsed) {
}; };
const directMessageCommands = { const directMessageCommands = {
GET_HISTORY: handleGetHistory, GET_HISTORY: handleGetHistory, // XXX RESTRICT
GET_HISTORY_RANGE: handleGetHistoryRange, GET_HISTORY_RANGE: handleGetHistoryRange, // XXX RESTRICT
GET_FULL_HISTORY: handleGetFullHistory, GET_FULL_HISTORY: handleGetFullHistory, // XXX RESTRICT
}; };
/* onDirectMessage /* onDirectMessage
@ -812,16 +850,63 @@ HK.onDirectMessage = function (Env, Server, seq, userId, json) {
return; return;
} }
// If the requested history is for an expired channel, abort var first = parsed[0];
// 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; }
// look up the appropriate command in the map of commands or fall back to RPC if (typeof(directMessageCommands[first]) !== 'function') {
var command = directMessageCommands[parsed[0]] || handleRPC; // it's either an unsupported command or an RPC call
// either way, RPC has it covered
return void handleRPC(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;
}
// run the command with the standard function signature
command(Env, Server, seq, userId, parsed); // 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 /* onChannelMessage

@ -2,23 +2,169 @@ var Meta = module.exports;
var deduplicate = require("./common-util").deduplicateString; 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> * channel <STRING>
* validateKey <STRING> * validateKey <STRING>
* owners <ARRAY> * owners <ARRAY>
* ADD_OWNERS * ADD_OWNERS
* RM_OWNERS * RM_OWNERS
* RESET_OWNERS
* pending_owners <ARRAY>
* ADD_PENDING_OWNERS
* RM_PENDING_OWNERS
* expire <NUMBER> * 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 commands = {};
var isValidOwner = function (owner) { var isValidPublicKey = function (owner) {
return typeof(owner) === 'string' && owner.length === 44; 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] // ["ADD_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623438989]
commands.ADD_OWNERS = function (meta, args) { commands.ADD_OWNERS = function (meta, args) {
// bail out if args isn't an array // bail out if args isn't an array
@ -40,6 +186,13 @@ commands.ADD_OWNERS = function (meta, args) {
changed = true; 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; return changed;
}; };
@ -71,6 +224,10 @@ commands.RM_OWNERS = function (meta, args) {
changed = true; changed = true;
}); });
if (meta.owners.length === 0 && meta.restricted) {
meta.restricted = false;
}
return changed; return changed;
}; };
@ -141,6 +298,18 @@ commands.RESET_OWNERS = function (meta, args) {
// overwrite the existing owners with the new one // overwrite the existing owners with the new one
meta.owners = deduplicate(args.filter(isValidOwner)); 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; return true;
}; };
@ -178,6 +347,25 @@ commands.ADD_MAILBOX = function (meta, args) {
return changed; 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 () { commands.UPDATE_EXPIRATION = function () {
throw new Error("E_NOT_IMPLEMENTED"); throw new Error("E_NOT_IMPLEMENTED");
}; };

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

4
package-lock.json generated

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

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

@ -357,9 +357,142 @@ nThen(function (w) {
bob.name = 'bob'; bob.name = 'bob';
//console.log("Initialized 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 () { }).nThen(function () {
//setTimeout(w(), 500); // 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) { }).nThen(function (w) {
// Alice loads the roster... // Alice loads the roster...
var rosterKeys = Crypto.Team.deriveMemberKeys(sharedConfig.rosterSeed, alice.curveKeys); var rosterKeys = Crypto.Team.deriveMemberKeys(sharedConfig.rosterSeed, alice.curveKeys);

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

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

@ -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 () { Util.response = function () {
var pending = {}; var pending = {};
var timeouts = {}; var timeouts = {};

@ -8,6 +8,9 @@ define([
'/common/common-interface.js', '/common/common-interface.js',
'/common/common-constants.js', '/common/common-constants.js',
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/inner/access.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/common/hyperscript.js', '/common/hyperscript.js',
'/common/proxy-manager.js', '/common/proxy-manager.js',
@ -23,6 +26,7 @@ define([
UI, UI,
Constants, Constants,
Feedback, Feedback,
Access,
nThen, nThen,
h, h,
ProxyManager, ProxyManager,
@ -80,6 +84,7 @@ define([
var faTrash = 'fa-trash'; var faTrash = 'fa-trash';
var faCopy = 'fa-clone'; var faCopy = 'fa-clone';
var faDelete = 'fa-eraser'; var faDelete = 'fa-eraser';
var faAccess = 'fa-unlock-alt';
var faProperties = 'fa-info-circle'; var faProperties = 'fa-info-circle';
var faTags = 'fa-hashtag'; var faTags = 'fa-hashtag';
var faUploadFiles = 'cptools-file-upload'; var faUploadFiles = 'cptools-file-upload';
@ -117,9 +122,9 @@ define([
var $addIcon = $('<span>', {"class": "fa fa-plus"}); var $addIcon = $('<span>', {"class": "fa fa-plus"});
var $renamedIcon = $('<span>', {"class": "fa fa-flag"}); var $renamedIcon = $('<span>', {"class": "fa fa-flag"});
var $readonlyIcon = $('<span>', {"class": "fa " + faReadOnly}); 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 $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 $tagsIcon = $('<span>', {"class": "fa " + faTags});
var $passwordIcon = $('<span>', {"class": "fa fa-lock"}); var $passwordIcon = $('<span>', {"class": "fa fa-lock"});
var $expirableIcon = $('<span>', {"class": "fa fa-clock-o"}); var $expirableIcon = $('<span>', {"class": "fa fa-clock-o"});
@ -453,6 +458,10 @@ define([
'data-icon': faDelete, 'data-icon': faDelete,
}, Messages.fc_remove_sharedfolder)), }, Messages.fc_remove_sharedfolder)),
$separator.clone()[0], $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', { h('li', h('a.cp-app-drive-context-properties.dropdown-item', {
'tabindex': '-1', 'tabindex': '-1',
'data-icon': faProperties, 'data-icon': faProperties,
@ -1212,7 +1221,7 @@ define([
hide.push('savelocal'); hide.push('savelocal');
hide.push('openro'); hide.push('openro');
hide.push('openincode'); hide.push('openincode');
hide.push('properties'); hide.push('properties', 'access');
hide.push('hashtag'); hide.push('hashtag');
hide.push('makeacopy'); hide.push('makeacopy');
} }
@ -1243,7 +1252,7 @@ define([
}); });
if (paths.length > 1) { if (paths.length > 1) {
hide.push('restore'); hide.push('restore');
hide.push('properties'); hide.push('properties', 'access');
hide.push('rename'); hide.push('rename');
hide.push('openparent'); hide.push('openparent');
hide.push('hashtag'); hide.push('hashtag');
@ -1273,7 +1282,7 @@ define([
'deleteowned', 'removesf', 'properties', 'hashtag']; 'deleteowned', 'removesf', 'properties', 'hashtag'];
break; break;
case 'default': case 'default':
show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'hashtag', 'makeacopy']; show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy'];
break; break;
case 'trashtree': { case 'trashtree': {
show = ['empty']; show = ['empty'];
@ -1811,10 +1820,10 @@ define([
var $owned = $ownedIcon.clone().appendTo($state); var $owned = $ownedIcon.clone().appendTo($state);
$owned.attr('title', Messages.fm_padIsOwned); $owned.attr('title', Messages.fm_padIsOwned);
$span.addClass('cp-app-drive-element-owned'); $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); var $owner = $ownerIcon.clone().appendTo($state);
$owner.attr('title', Messages.fm_padIsOwnedOther); $owner.attr('title', Messages.fm_padIsOwnedOther);
} } */
}; };
var thumbsUrls = {}; var thumbsUrls = {};
var addFileData = function (element, $element) { var addFileData = function (element, $element) {
@ -3086,9 +3095,8 @@ define([
}).appendTo($openDir); }).appendTo($openDir);
} }
$('<a>').text(Messages.fc_prop).click(function () { $('<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); } if (e) { return void logError(e); }
UI.alert($prop[0], undefined, true);
}); });
}).appendTo($openDir); }).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)) { if (!manager.isFile(el) && !manager.isSharedFolder(el)) {
return void cb('NOT_FILE'); return void cb('NOT_FILE');
} }
//var ro = manager.isReadOnlyFile(el); //var ro = manager.isReadOnlyFile(el);
var base = APP.origin;
var data; var data;
if (manager.isSharedFolder(el)) { if (manager.isSharedFolder(el)) {
data = JSON.parse(JSON.stringify(manager.getSharedFolderData(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 || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); }
if (data.href) { var opts = {};
data.href = base + data.href; opts.href = Hash.getRelativeHref(data.href || data.roHref);
if (manager.isSharedFolder(el)) {
var ro = folders[el] && folders[el].version >= 2;
if (!ro) { opts.noReadOnly = true; }
} }
if (data.roHref) { UIElements.getProperties(common, opts, cb);
data.roHref = base + data.roHref; };
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) { if (currentPath[0] === TEMPLATE) {
data.isTemplate = true; opts.isTemplate = true;
} }
// Shared folders: no expiration date
if (manager.isSharedFolder(el)) { if (manager.isSharedFolder(el)) {
var ro = folders[el] && folders[el].version >= 2; opts.noExpiration = true;
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 ((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;
} }
UIElements.getProperties(common, data, cb);
Access.getAccessModal(common, opts, cb);
}; };
if (!APP.loggedIn) { if (!APP.loggedIn) {
@ -4259,9 +4266,19 @@ define([
// ANON_SHARED_FOLDER // ANON_SHARED_FOLDER
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder); 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); } if (e) { return void logError(e); }
UI.openCustomModal($prop[0]);
}); });
} }
else if ($this.hasClass("cp-app-drive-context-hashtag")) { else if ($this.hasClass("cp-app-drive-context-hashtag")) {

File diff suppressed because it is too large Load Diff

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

@ -1628,10 +1628,11 @@ define([
// data.send === true ==> send the request // data.send === true ==> send the request
Store.requestPadAccess = function (clientId, data, cb) { Store.requestPadAccess = function (clientId, data, cb) {
var owner = data.owner; var owner = data.owner;
var owners = data.owners;
// If the owner was not is the pad metadata, check if it is a friend. // 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 // 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)) { if (!owner && Array.isArray(owners)) {
var friends = store.proxy.friends || {}; var friends = store.proxy.friends || {};
// If we have friends, check if an owner is one of them (with a mailbox) // 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 send is true, send the request to the owner.
if (owner) { if (owner) {

@ -900,10 +900,11 @@ define([
cb = cb || function () {}; cb = cb || function () {};
var sfId = Env.user.userObject.getSFIdFromHref(data.href); var sfId = Env.user.userObject.getSFIdFromHref(data.href);
if (sfId) { 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 () { setTimeout(function () {
cb(null, { cb(null, {
value: sfData[data.attr], value: sfValue,
atime: 1 atime: 1
}); });
}); });

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

@ -1250,22 +1250,24 @@ define([
}); });
// REQUEST_ACCESS is used both to check IF we can contact an owner (send === false) // 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) // 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) { if (readOnly && hashes.editHash) {
return void cb({error: 'ALREADYKNOWN'}); return void cb({error: 'ALREADYKNOWN'});
} }
var send = data.send;
var metadata = data.metadata;
var owner, owners; 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) { nThen(function (waitFor) {
// Try to get the owner's mailbox from the pad metadata first. // 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 // If it's is an older owned pad, check if the owner is a friend
// or an acquaintance (from async-store directly in requestAccess) // or an acquaintance (from async-store directly in requestAccess)
Cryptpad.getPadMetadata({ var todo = function (obj) {
channel: secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) { return; }
owners = obj.owners; owners = obj.owners;
var mailbox; var mailbox;
@ -1284,22 +1286,107 @@ define([
owner = data; owner = data;
} catch (e) { console.error(e); } } 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 () { }).nThen(function () {
// If we are just checking (send === false) and there is a mailbox field, cb state true // If we are just checking (send === false) and there is a mailbox field, cb state true
// If there is no mailbox, we'll have to check if an owner is a friend in the worker // If there is no mailbox, we'll have to check if an owner is a friend in the worker
/* // XXX
if (owner && !send) { if (owner && !send) {
return void cb({state: true}); return void cb({state: true});
} }
*/
if (!send) { return void cb({state: Boolean(owner)}); }
Cryptpad.padRpc.requestAccess({ Cryptpad.padRpc.requestAccess({
send: send, send: send,
channel: secret.channel, channel: _secret.channel,
owner: owner, owner: owner,
owners: owners owners: owners
}, cb); }, 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) { sframeChan.on('EV_BURN_PAD', function (channel) {
if (!burnAfterReading) { return; } if (!burnAfterReading) { return; }
Cryptpad.burnPad({ Cryptpad.burnPad({

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

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

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

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

Loading…
Cancel
Save