diff --git a/lib/metadata.js b/lib/metadata.js index aa5b49517..a8ed86fd3 100644 --- a/lib/metadata.js +++ b/lib/metadata.js @@ -2,23 +2,48 @@ 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 * validateKey * owners * ADD_OWNERS * RM_OWNERS + * RESET_OWNERS + * pending_owners + * ADD_PENDING_OWNERS + * RM_PENDING_OWNERS * expire + * UPDATE_EXPIRATION (NOT_IMPLEMENTED) + * restricted + * RESTRICT_ACCESS + * allowed + * ADD_ALLOWED + * RM_ALLOWED + * RESET_ALLOWED + * ADD_OWNERS + * RESET_OWNERS + * mailbox + * 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) { @@ -49,20 +74,95 @@ commands.RESTRICT_ACCESS = function (meta, args) { // ["ADD_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989] commands.ADD_ALLOWED = function (meta, args) { - args = args; - throw new Error('NOT_IMPLEMENTED'); + 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) { - args = args; - throw new Error('NOT_IMPLEMENTED'); + 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) { - args = args; - throw new Error('NOT_IMPLEMENTED'); + 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] @@ -86,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; }; @@ -187,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; }; @@ -224,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"); };