Merge remote-tracking branch 'origin/communities-allow-list' into communities-allow
commit
d0eb96815d
|
@ -72,7 +72,7 @@ server {
|
|||
set $styleSrc "'unsafe-inline' 'self' ${main_domain}";
|
||||
|
||||
# connect-src restricts URLs which can be loaded using script interfaces
|
||||
set $connectSrc "'self' https://${main_domain} $main_domain https://${api_domain} blob:";
|
||||
set $connectSrc "'self' https://${main_domain} ${main_domain} https://${api_domain} blob: wss://${api_domain} ${api_domain} ${files_domain}";
|
||||
|
||||
# fonts can be loaded from data-URLs or the main domain
|
||||
set $fontSrc "'self' data: ${main_domain}";
|
||||
|
|
|
@ -23,7 +23,7 @@ Channel.clearOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
|
|||
if (e) { return void cb(e); }
|
||||
cb();
|
||||
|
||||
const channel_cache = Env.historyKeeper.channel_cache;
|
||||
const channel_cache = Env.channel_cache;
|
||||
|
||||
const clear = function () {
|
||||
// delete the channel cache because it will have been invalidated
|
||||
|
@ -117,8 +117,8 @@ Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
|
|||
}
|
||||
cb(void 0, 'OK');
|
||||
|
||||
const channel_cache = Env.historyKeeper.channel_cache;
|
||||
const metadata_cache = Env.historyKeeper.metadata_cache;
|
||||
const channel_cache = Env.channel_cache;
|
||||
const metadata_cache = Env.metadata_cache;
|
||||
|
||||
const clear = function () {
|
||||
delete channel_cache[channelId];
|
||||
|
@ -187,8 +187,8 @@ Channel.trimHistory = function (Env, safeKey, data, cb) {
|
|||
// clear historyKeeper's cache for this channel
|
||||
Env.historyKeeper.channelClose(channelId);
|
||||
cb(void 0, 'OK');
|
||||
delete Env.historyKeeper.channel_cache[channelId];
|
||||
delete Env.historyKeeper.metadata_cache[channelId];
|
||||
delete Env.channel_cache[channelId];
|
||||
delete Env.metadata_cache[channelId];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -42,8 +42,8 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) {
|
|||
var channel = data.channel;
|
||||
var command = data.command;
|
||||
if (!channel || !Core.isValidId(channel)) { return void cb ('INVALID_CHAN'); }
|
||||
if (!command || typeof (command) !== 'string') { return void cb ('INVALID_COMMAND'); }
|
||||
if (Meta.commands.indexOf(command) === -1) { return void('UNSUPPORTED_COMMAND'); }
|
||||
if (!command || typeof (command) !== 'string') { return void cb('INVALID_COMMAND'); }
|
||||
if (Meta.commands.indexOf(command) === -1) { return void cb('UNSUPPORTED_COMMAND'); }
|
||||
|
||||
queueMetadata(channel, function (next) {
|
||||
Data.getMetadata(Env, channel, function (err, metadata) {
|
||||
|
@ -111,8 +111,8 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) {
|
|||
cb(void 0, metadata);
|
||||
next();
|
||||
|
||||
const metadata_cache = Env.historyKeeper.metadata_cache;
|
||||
const channel_cache = Env.historyKeeper.channel_cache;
|
||||
const metadata_cache = Env.metadata_cache;
|
||||
const channel_cache = Env.channel_cache;
|
||||
|
||||
metadata_cache[channel] = metadata;
|
||||
|
||||
|
|
|
@ -6,9 +6,22 @@ const WriteQueue = require("./write-queue");
|
|||
const BatchRead = require("./batch-read");
|
||||
const RPC = require("./rpc");
|
||||
const HK = require("./hk-util.js");
|
||||
const Core = require("./commands/core");
|
||||
|
||||
const Store = require("./storage/file");
|
||||
const BlobStore = require("./storage/blob");
|
||||
|
||||
module.exports.create = function (config, cb) {
|
||||
const Log = config.log;
|
||||
var WARN = function (e, output) {
|
||||
if (e && output) {
|
||||
Log.warn(e, {
|
||||
output: output,
|
||||
message: String(e),
|
||||
stack: new Error(e).stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Log.silly('HK_LOADING', 'LOADING HISTORY_KEEPER MODULE');
|
||||
|
||||
|
@ -25,9 +38,62 @@ module.exports.create = function (config, cb) {
|
|||
channel_cache: {},
|
||||
queueStorage: WriteQueue(),
|
||||
batchIndexReads: BatchRead("HK_GET_INDEX"),
|
||||
|
||||
//historyKeeper: config.historyKeeper,
|
||||
intervals: config.intervals || {},
|
||||
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
|
||||
Sessions: {},
|
||||
paths: {},
|
||||
//msgStore: config.store,
|
||||
|
||||
pinStore: undefined,
|
||||
pinnedPads: {},
|
||||
pinsLoaded: false,
|
||||
pendingPinInquiries: {},
|
||||
pendingUnpins: {},
|
||||
pinWorkers: 5,
|
||||
|
||||
limits: {},
|
||||
admins: [],
|
||||
WARN: WARN,
|
||||
flushCache: config.flushCache,
|
||||
adminEmail: config.adminEmail,
|
||||
allowSubscriptions: config.allowSubscriptions,
|
||||
myDomain: config.myDomain,
|
||||
mySubdomain: config.mySubdomain,
|
||||
customLimits: config.customLimits,
|
||||
// FIXME this attribute isn't in the default conf
|
||||
// but it is referenced in Quota
|
||||
domain: config.domain
|
||||
};
|
||||
|
||||
config.historyKeeper = {
|
||||
var paths = Env.paths;
|
||||
|
||||
var keyOrDefaultString = function (key, def) {
|
||||
return typeof(config[key]) === 'string'? config[key]: def;
|
||||
};
|
||||
|
||||
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
|
||||
paths.block = keyOrDefaultString('blockPath', './block');
|
||||
paths.data = keyOrDefaultString('filePath', './datastore');
|
||||
paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
||||
paths.blob = keyOrDefaultString('blobPath', './blob');
|
||||
|
||||
Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit > 0?
|
||||
config.defaultStorageLimit:
|
||||
Core.DEFAULT_LIMIT;
|
||||
|
||||
try {
|
||||
Env.admins = (config.adminKeys || []).map(function (k) {
|
||||
k = k.replace(/\/+$/, '');
|
||||
var s = k.split('/');
|
||||
return s[s.length-1];
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Can't parse admin keys. Please update or fix your config.js file!");
|
||||
}
|
||||
|
||||
config.historyKeeper = Env.historyKeeper = {
|
||||
metadata_cache: Env.metadata_cache,
|
||||
channel_cache: Env.channel_cache,
|
||||
|
||||
|
@ -45,7 +111,20 @@ module.exports.create = function (config, cb) {
|
|||
HK.dropChannel(Env, channelName);
|
||||
},
|
||||
channelOpen: function (Server, channelName, userId) {
|
||||
Env.channel_cache[channelName] = {};
|
||||
Env.channel_cache[channelName] = Env.channel_cache[channelName] || {};
|
||||
|
||||
//const metadata = Env.metadata_cache[channelName];
|
||||
// chainpad-server@4.0.3 supports a removeFromChannel method
|
||||
// Server.removeFromChannel(channelName, userId);
|
||||
// this lets us kick users from restricted channels
|
||||
|
||||
// XXX RESTRICT
|
||||
// this event is emitted whenever a user joins a channel.
|
||||
// if that channel is restricted then we should forcefully disconnect them.
|
||||
// we won't know that it's restricted until we load its metadata.
|
||||
// as long as metadata is in memory as long as anyone is sending messages to a channel
|
||||
// then we won't broadcast messages to unauthorized users
|
||||
|
||||
Server.send(userId, [
|
||||
0,
|
||||
Env.id,
|
||||
|
@ -63,11 +142,34 @@ module.exports.create = function (config, cb) {
|
|||
Log.verbose('HK_ID', 'History keeper ID: ' + Env.id);
|
||||
|
||||
nThen(function (w) {
|
||||
require('./storage/file').create(config, w(function (_store) {
|
||||
// create a pin store
|
||||
Store.create({
|
||||
filePath: pinPath,
|
||||
}, w(function (s) {
|
||||
Env.pinStore = s;
|
||||
}));
|
||||
|
||||
// create a channel store
|
||||
Store.create(config, w(function (_store) {
|
||||
config.store = _store;
|
||||
Env.store = _store;
|
||||
Env.msgStore = _store; // API used by rpc
|
||||
Env.store = _store; // API used by historyKeeper
|
||||
}));
|
||||
|
||||
// create a blob store
|
||||
BlobStore.create({
|
||||
blobPath: config.blobPath,
|
||||
blobStagingPath: config.blobStagingPath,
|
||||
archivePath: config.archivePath,
|
||||
getSession: function (safeKey) {
|
||||
return Core.getSession(Env.Sessions, safeKey);
|
||||
},
|
||||
}, w(function (err, blob) {
|
||||
if (err) { throw new Error(err); }
|
||||
Env.blobStore = blob;
|
||||
}));
|
||||
}).nThen(function (w) {
|
||||
// create a task store
|
||||
require("./storage/tasks").create(config, w(function (e, tasks) {
|
||||
if (e) {
|
||||
throw e;
|
||||
|
@ -87,7 +189,7 @@ module.exports.create = function (config, cb) {
|
|||
}, 1000 * 60 * 5); // run every five minutes
|
||||
}));
|
||||
}).nThen(function () {
|
||||
RPC.create(config, function (err, _rpc) {
|
||||
RPC.create(Env, function (err, _rpc) {
|
||||
if (err) { throw err; }
|
||||
|
||||
Env.rpc = _rpc;
|
||||
|
|
|
@ -75,6 +75,29 @@ const isMetadataMessage = function (parsed) {
|
|||
return Boolean(parsed && parsed.channel);
|
||||
};
|
||||
|
||||
const isChannelRestricted = function (metadata) { // XXX RESTRICT
|
||||
metadata = metadata;
|
||||
return false;
|
||||
};
|
||||
|
||||
const isUserAllowed = function (metadata, userId) { // XXX RESTRICT
|
||||
/*
|
||||
|
||||
at this point all we have is the user's netflux id.
|
||||
the allow-list is encoded for 'unsafeKeys' (URL-unsafe base64 encoded public signing keys).
|
||||
|
||||
we need a lookup table: netfluxId => public keys with which this netflux session has authenticated.
|
||||
from there we can check whether the user has authenticated for any of the allowed keys this session.
|
||||
|
||||
owners are implicitly allowed to view any file they own.
|
||||
pending_owners too.
|
||||
otherwise check metadata.allowed.
|
||||
|
||||
*/
|
||||
userId = userId;
|
||||
return false;
|
||||
};
|
||||
|
||||
// validateKeyStrings supplied by clients must decode to 32-byte Uint8Arrays
|
||||
const isValidValidateKeyString = function (key) {
|
||||
try {
|
||||
|
@ -646,6 +669,16 @@ 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(); }
|
||||
|
||||
// XXX RESTRICT
|
||||
// once we've loaded the metadata we can check whether the channel is restricted
|
||||
// and notify the user if they're not included in the list
|
||||
if (isChannelRestricted(index.metadata) && isUserAllowed(index.metadata, userId)) {
|
||||
// XXX RESTRICT send a message indicating that they need to authenticate
|
||||
// for a list of private keys...
|
||||
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);
|
||||
}));
|
||||
|
@ -789,9 +822,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
|
||||
|
@ -817,6 +850,10 @@ HK.onDirectMessage = function (Env, Server, seq, userId, json) {
|
|||
// have to abort later (once we know the expiration time)
|
||||
if (checkExpired(Env, Server, parsed[1])) { return; }
|
||||
|
||||
// XXX RESTRICT
|
||||
// metadata might already be in memory.
|
||||
// rejecting unauthorized users here is an optimization
|
||||
|
||||
// look up the appropriate command in the map of commands or fall back to RPC
|
||||
var command = directMessageCommands[parsed[0]] || handleRPC;
|
||||
|
||||
|
|
184
lib/metadata.js
184
lib/metadata.js
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -141,6 +294,14 @@ 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;
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -178,6 +339,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");
|
||||
};
|
||||
|
|
129
lib/rpc.js
129
lib/rpc.js
|
@ -1,6 +1,4 @@
|
|||
/*jshint esversion: 6 */
|
||||
const nThen = require("nthen");
|
||||
|
||||
const Util = require("./common-util");
|
||||
|
||||
const Core = require("./commands/core");
|
||||
|
@ -14,17 +12,14 @@ const Upload = require("./commands/upload");
|
|||
|
||||
var RPC = module.exports;
|
||||
|
||||
const Store = require("./storage/file");
|
||||
const BlobStore = require("./storage/blob");
|
||||
|
||||
const UNAUTHENTICATED_CALLS = {
|
||||
GET_FILE_SIZE: Pinning.getFileSize,
|
||||
GET_MULTIPLE_FILE_SIZE: Pinning.getMultipleFileSize,
|
||||
GET_DELETED_PADS: Pinning.getDeletedPads,
|
||||
IS_CHANNEL_PINNED: Pinning.isChannelPinned,
|
||||
IS_NEW_CHANNEL: Channel.isNewChannel,
|
||||
WRITE_PRIVATE_MESSAGE: Channel.writePrivateMessage,
|
||||
GET_METADATA: Metadata.getMetadata,
|
||||
WRITE_PRIVATE_MESSAGE: Channel.writePrivateMessage, // XXX RESTRICT
|
||||
GET_METADATA: Metadata.getMetadata, // XXX RESTRICT
|
||||
};
|
||||
|
||||
var isUnauthenticateMessage = function (msg) {
|
||||
|
@ -187,86 +182,12 @@ var rpc = function (Env, Server, data, respond) {
|
|||
return void respond("INVALID_RPC_CALL");
|
||||
};
|
||||
|
||||
RPC.create = function (config, cb) {
|
||||
var Log = config.log;
|
||||
|
||||
// load pin-store...
|
||||
Log.silly('LOADING RPC MODULE');
|
||||
|
||||
var keyOrDefaultString = function (key, def) {
|
||||
return typeof(config[key]) === 'string'? config[key]: def;
|
||||
};
|
||||
|
||||
var WARN = function (e, output) {
|
||||
if (e && output) {
|
||||
Log.warn(e, {
|
||||
output: output,
|
||||
message: String(e),
|
||||
stack: new Error(e).stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof(config.domain) !== 'undefined') {
|
||||
throw new Error('fuck');
|
||||
}
|
||||
|
||||
var Env = {
|
||||
historyKeeper: config.historyKeeper,
|
||||
intervals: config.intervals || {},
|
||||
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
|
||||
Sessions: {},
|
||||
paths: {},
|
||||
msgStore: config.store,
|
||||
|
||||
pinStore: undefined,
|
||||
pinnedPads: {},
|
||||
pinsLoaded: false,
|
||||
pendingPinInquiries: {},
|
||||
pendingUnpins: {},
|
||||
pinWorkers: 5,
|
||||
|
||||
limits: {},
|
||||
admins: [],
|
||||
Log: Log,
|
||||
WARN: WARN,
|
||||
flushCache: config.flushCache,
|
||||
adminEmail: config.adminEmail,
|
||||
allowSubscriptions: config.allowSubscriptions,
|
||||
myDomain: config.myDomain,
|
||||
mySubdomain: config.mySubdomain,
|
||||
customLimits: config.customLimits,
|
||||
// FIXME this attribute isn't in the default conf
|
||||
// but it is referenced in Quota
|
||||
domain: config.domain
|
||||
};
|
||||
|
||||
Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit > 0?
|
||||
config.defaultStorageLimit:
|
||||
Core.DEFAULT_LIMIT;
|
||||
|
||||
try {
|
||||
Env.admins = (config.adminKeys || []).map(function (k) {
|
||||
k = k.replace(/\/+$/, '');
|
||||
var s = k.split('/');
|
||||
return s[s.length-1];
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Can't parse admin keys. Please update or fix your config.js file!");
|
||||
}
|
||||
|
||||
RPC.create = function (Env, cb) {
|
||||
var Sessions = Env.Sessions;
|
||||
var paths = Env.paths;
|
||||
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
|
||||
paths.block = keyOrDefaultString('blockPath', './block');
|
||||
paths.data = keyOrDefaultString('filePath', './datastore');
|
||||
paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
||||
paths.blob = keyOrDefaultString('blobPath', './blob');
|
||||
|
||||
var updateLimitDaily = function () {
|
||||
Quota.updateCachedLimits(Env, function (e) {
|
||||
if (e) {
|
||||
WARN('limitUpdate', e);
|
||||
Env.WARN('limitUpdate', e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -276,35 +197,17 @@ RPC.create = function (config, cb) {
|
|||
|
||||
Pinning.loadChannelPins(Env);
|
||||
|
||||
nThen(function (w) {
|
||||
Store.create({
|
||||
filePath: pinPath,
|
||||
}, w(function (s) {
|
||||
Env.pinStore = s;
|
||||
}));
|
||||
BlobStore.create({
|
||||
blobPath: config.blobPath,
|
||||
blobStagingPath: config.blobStagingPath,
|
||||
archivePath: config.archivePath,
|
||||
getSession: function (safeKey) {
|
||||
return Core.getSession(Sessions, safeKey);
|
||||
},
|
||||
}, w(function (err, blob) {
|
||||
if (err) { throw new Error(err); }
|
||||
Env.blobStore = blob;
|
||||
}));
|
||||
}).nThen(function () {
|
||||
cb(void 0, function (Server, data, respond) {
|
||||
try {
|
||||
return rpc(Env, Server, data, respond);
|
||||
} catch (e) {
|
||||
console.log("Error from RPC with data " + JSON.stringify(data));
|
||||
console.log(e.stack);
|
||||
}
|
||||
});
|
||||
// expire old sessions once per minute
|
||||
Env.intervals.sessionExpirationInterval = setInterval(function () {
|
||||
Core.expireSessions(Sessions);
|
||||
}, Core.SESSION_EXPIRATION_TIME);
|
||||
// expire old sessions once per minute
|
||||
Env.intervals.sessionExpirationInterval = setInterval(function () {
|
||||
Core.expireSessions(Sessions);
|
||||
}, Core.SESSION_EXPIRATION_TIME);
|
||||
|
||||
cb(void 0, function (Server, data, respond) {
|
||||
try {
|
||||
return rpc(Env, Server, data, respond);
|
||||
} catch (e) {
|
||||
console.log("Error from RPC with data " + JSON.stringify(data));
|
||||
console.log(e.stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -970,6 +970,7 @@ var trimChannel = function (env, channelName, hash, _cb) {
|
|||
}
|
||||
|
||||
var msg = Util.tryParse(s_msg);
|
||||
if (!msg) { return void readMore(); }
|
||||
var msgHash = Extras.getHash(msg[4]);
|
||||
|
||||
if (msgHash === hash) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
Loading…
Reference in New Issue