@geexbox/accesscontrol
Version:
Subject and Attribute based Access Control for Node.js
823 lines (822 loc) • 33.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
// dep modules
var Notation = require("notation");
var core_1 = require("./core");
/**
* List of reserved keywords.
* i.e. Subjects, resources with these names are not allowed.
*/
var RESERVED_KEYWORDS = ['*', '!', '$', '_extend_'];
exports.RESERVED_KEYWORDS = RESERVED_KEYWORDS;
/**
* Error message to be thrown after AccessControl instance is locked.
*/
var ERR_LOCK = 'Cannot alter the underlying grants model. AccessControl instance is locked.';
exports.ERR_LOCK = ERR_LOCK;
var utils = {
// ----------------------
// GENERIC UTILS
// ----------------------
/**
* Gets the type of the given object.
* @param {Any} o
* @returns {String}
*/
type: function (o) {
return Object.prototype.toString.call(o).match(/\s(\w+)/i)[1].toLowerCase();
},
// for later use
// isPlainObject(o:any) {
// return o && (o.constructor === Object || o.constructor === undefined);
// },
/**
* Specifies whether the given value is set (other that `null` or
* `undefined`).
* @param {Any} o - Value to be checked.
* @returns {Boolean}
*/
// isset(o:any):boolean {
// return o === null || o === undefined;
// },
/**
* Specifies whether the property/key is defined on the given object.
* @param {Object} o
* @param {string} propName
* @returns {Boolean}
*/
hasDefined: function (o, propName) {
return o.hasOwnProperty(propName) && o[propName] !== undefined;
},
/**
* Converts the given (string) value into an array of string. Note that
* this does not throw if the value is not a string or array. It will
* silently return `[]` (empty array). So where ever it's used, the host
* function should consider throwing.
* @param {Any} value
* @returns {string[]}
*/
toStringArray: function (value) {
if (Array.isArray(value))
return value;
if (typeof value === 'string')
return value.trim().split(/\s*[;,]\s*/);
// throw new Error('Expected a string or array of strings, got ' + utils.type(value));
return [];
},
/**
* Checks whether the given array consists of non-empty string items.
* (Array can be empty but no item should be an empty string.)
* @param {Array} arr - Array to be checked.
* @returns {Boolean}
*/
isFilledStringArray: function (arr) {
if (!arr || !Array.isArray(arr))
return false;
for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) {
var s = arr_1[_i];
if (typeof s !== 'string' || s.trim() === '')
return false;
}
return true;
},
/**
* Checks whether the given value is an empty array.
* @param {Any} value - Value to be checked.
* @returns {Boolean}
*/
isEmptyArray: function (value) {
return Array.isArray(value) && value.length === 0;
},
/**
* Ensures that the pushed item is unique in the target array.
* @param {Array} arr - Target array.
* @param {Any} item - Item to be pushed to array.
* @returns {Array}
*/
pushUniq: function (arr, item) {
if (arr.indexOf(item) < 0)
arr.push(item);
return arr;
},
/**
* Concats the given two arrays and ensures all items are unique.
* @param {Array} arrA
* @param {Array} arrB
* @returns {Array} - Concat'ed array.
*/
uniqConcat: function (arrA, arrB) {
var arr = arrA.concat();
arrB.forEach(function (b) {
utils.pushUniq(arr, b);
});
return arr;
},
/**
* Subtracts the second array from the first.
* @param {Array} arrA
* @param {Array} arrB
* @return {Array} - Resulting array.
*/
subtractArray: function (arrA, arrB) {
return arrA.concat().filter(function (a) { return arrB.indexOf(a) === -1; });
},
/**
* Deep freezes the given object.
* @param {Object} o - Object to be frozen.
* @returns {Object} - Frozen object.
*/
deepFreeze: function (o) {
// Object.freeze accepts also an array. But here, we only use this for
// objects.
if (utils.type(o) !== 'object')
return;
var props = Object.getOwnPropertyNames(o);
// freeze deeper before self
props.forEach(function (key) {
var sub = o[key];
if (Array.isArray(sub))
Object.freeze(sub);
if (utils.type(sub) === 'object') {
utils.deepFreeze(sub);
}
});
// finally freeze self
return Object.freeze(o);
},
/**
* Similar to JS .forEach, except this allows for breaking out early,
* (before all iterations are executed) by returning `false`.
* @param array
* @param callback
* @param thisArg
*/
each: function (array, callback, thisArg) {
if (thisArg === void 0) { thisArg = null; }
var length = array.length;
var index = -1;
while (++index < length) {
if (callback.call(thisArg, array[index], index, array) === false)
break;
}
},
/**
* Iterates through the keys of the given object. Breaking out early is
* possible by returning `false`.
* @param object
* @param callback
* @param thisArg
*/
eachKey: function (object, callback, thisArg) {
if (thisArg === void 0) { thisArg = null; }
// return Object.keys(o).forEach(callback);
// forEach has no way to interrupt execution, short-circuit unless an
// error is thrown. so we use this:
utils.each(Object.keys(object), callback, thisArg);
},
// ----------------------
// AC ITERATION UTILS
// ----------------------
eachRole: function (grants, callback) {
utils.eachKey(grants, function (name) { return callback(grants[name], name); });
},
/**
*
*/
eachRoleResource: function (grants, callback) {
var resources, resourceDefinition;
utils.eachKey(grants, function (subject) {
resources = grants[subject];
utils.eachKey(resources, function (resource) {
resourceDefinition = subject[resource];
callback(subject, resource, resourceDefinition);
});
});
},
// ----------------------
// AC VALIDATION UTILS
// ----------------------
/**
* Checks whether the given access info can be commited to grants model.
* @param {IAccessInfo|IQueryInfo} info
* @returns {Boolean}
*/
isInfoFulfilled: function (info) {
return utils.hasDefined(info, 'subject')
&& utils.hasDefined(info, 'action')
&& utils.hasDefined(info, 'resource');
},
/**
* Checks whether the given name can be used and is not a reserved keyword.
*
* @param {string} name - Name to be checked.
* @param {boolean} [throwOnInvalid=true] - Specifies whether to throw if
* name is not valid.
*
* @returns {Boolean}
*
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and name
* is invalid.
*/
validName: function (name, throwOnInvalid) {
if (throwOnInvalid === void 0) { throwOnInvalid = true; }
if (typeof name !== 'string' || name.trim() === '') {
if (!throwOnInvalid)
return false;
throw new core_1.AccessControlError('Invalid name, expected a valid string.');
}
if (RESERVED_KEYWORDS.indexOf(name) >= 0) {
if (!throwOnInvalid)
return false;
throw new core_1.AccessControlError("Cannot use reserved name: \"" + name + "\"");
}
return true;
},
/**
* Checks whether the given array does not contain a reserved keyword.
*
* @param {string|string[]} list - Name(s) to be checked.
* @param {boolean} [throwOnInvalid=true] - Specifies whether to throw if
* name is not valid.
*
* @returns {Boolean}
*
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and name
* is invalid.
*/
hasValidNames: function (list, throwOnInvalid) {
if (throwOnInvalid === void 0) { throwOnInvalid = true; }
var allValid = true;
utils.each(utils.toStringArray(list), function (name) {
if (!utils.validName(name, throwOnInvalid)) {
allValid = false;
return false; // break out of loop
}
// suppress tslint warning
return true; // continue
});
return allValid;
},
/**
* Checks whether the given object is a valid resource definition object.
*
* @param {Object} o - Resource definition to be checked.
*
* @returns {Boolean}
*
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and object
* is invalid.
*/
validResourceObject: function (o) {
if (utils.type(o) !== 'object') {
throw new core_1.AccessControlError("Invalid resource definition.");
}
utils.eachKey(o, function (action) {
var s = action.split(':');
var perms = o[action];
if (!utils.isEmptyArray(perms) && !utils.isFilledStringArray(perms)) {
throw new core_1.AccessControlError("Invalid resource attributes for action \"" + action + "\".");
}
});
return true;
},
/**
* Checks whether the given object is a valid subject definition object.
*
* @param {Object} grants - Original grants object being inspected.
* @param {string} subjectName - Name of the subject.
*
* @returns {Boolean}
*
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and object
* is invalid.
*/
validRoleObject: function (grants, subjectName) {
var subject = grants[subjectName];
if (!subject || utils.type(subject) !== 'object') {
throw new core_1.AccessControlError("Invalid subject definition.");
}
utils.eachKey(subject, function (resourceName) {
if (!utils.validName(resourceName, false)) {
if (resourceName === '_extend_') {
var extRoles = subject[resourceName]; // semantics
if (!utils.isFilledStringArray(extRoles)) {
throw new core_1.AccessControlError("Invalid extend value for subject \"" + subjectName + "\": " + JSON.stringify(extRoles));
}
else {
// attempt to actually extend the subjects. this will throw
// on failure.
utils.extendRole(grants, subjectName, extRoles, false);
}
}
else {
throw new core_1.AccessControlError("Cannot use reserved name \"" + resourceName + "\" for a resource.");
}
}
else {
utils.validResourceObject(subject[resourceName]); // throws on failure
}
});
return true;
},
/**
* Inspects whether the given grants object has a valid structure and
* configuration; and returns a restructured grants object that can be used
* internally by AccessControl.
*
* @param {Object|Array} o - Original grants object to be inspected.
*
* @returns {Object} - Inspected, restructured grants object.
*
* @throws {AccessControlError} - If given grants object has an invalid
* structure or configuration.
*/
getInspectedGrants: function (o) {
var grants = {};
var strErr = 'Invalid grants object.';
var type = utils.type(o);
if (type === 'object') {
utils.eachKey(o, function (subjectName) {
if (utils.validName(subjectName)) { // throws on failure
return utils.validRoleObject(o, subjectName); // throws on failure
}
/* istanbul ignore next */
return false;
// above is redundant, previous checks will already throw on
// failure so we'll never need to break early from this.
});
grants = o;
}
else if (type === 'array') {
o.forEach(function (item) { return utils.commitToGrants(grants, item, true); });
}
else {
throw new core_1.AccessControlError(strErr + " Expected an array or object.");
}
return grants;
},
// ----------------------
// AC COMMON UTILS
// ----------------------
/**
* Gets all the unique resources that are granted access for at
* least one subject.
*
* @returns {string[]}
*/
getResources: function (grants) {
// using an object for unique list
var resources = {};
utils.eachRoleResource(grants, function (subject, resource, permissions) {
resources[resource] = null;
});
return Object.keys(resources);
},
/**
* Normalizes the actions and possessions in the given `IQueryInfo` or
* `IAccessInfo`.
*
* @param {IQueryInfo|IAccessInfo} info
* @param {boolean} [asString=false]
*
* @return {IQueryInfo|IAccessInfo|string}
*
* @throws {AccessControlError} - If invalid action/possession found.
*/
normalizeActionPossession: function (info, asString) {
if (asString === void 0) { asString = false; }
// validate and normalize action
if (typeof info.action !== 'string') {
// throw new AccessControlError(`Invalid action: ${info.action}`);
throw new core_1.AccessControlError("Invalid action: " + JSON.stringify(info));
}
var s = info.action.split(':');
info.action = s[0].trim().toLowerCase();
// validate and normalize possession
var poss = info.possession || s[1];
if (poss) {
if (poss !== 'any' && poss !== 'own') {
throw new core_1.AccessControlError("Invalid action possession: " + poss);
}
else {
info.possession = poss;
}
}
else {
// if no possession is set, we'll default to "any".
info.possession = 'any';
}
return asString
? info.action + ':' + info.possession
: info;
},
/**
* Normalizes the subjects and resources in the given `IQueryInfo`.
*
* @param {IQueryInfo} info
*
* @return {IQueryInfo}
*
* @throws {AccessControlError} - If invalid subject/resource found.
*/
normalizeQueryInfo: function (query) {
if (utils.type(query) !== 'object') {
throw new core_1.AccessControlError("Invalid IQueryInfo: " + typeof query);
}
// clone the object
query = Object.assign({}, query);
// validate and normalize subject(s)
query.subject = utils.toStringArray(query.subject);
if (!utils.isFilledStringArray(query.subject)) {
throw new core_1.AccessControlError("Invalid subject(s): " + JSON.stringify(query.subject));
}
// validate resource
if (typeof query.resource !== 'string' || query.resource.trim() === '') {
throw new core_1.AccessControlError("Invalid resource: \"" + query.resource + "\"");
}
query.resource = query.resource.trim();
query = utils.normalizeActionPossession(query);
return query;
},
/**
* Normalizes the subjects and resources in the given `IAccessInfo`.
*
* @param {IAccessInfo} info
* @param {boolean} [all=false] - Whether to validate all properties such
* as `action` and `possession`.
*
* @return {IQueryInfo}
*
* @throws {AccessControlError} - If invalid subject/resource found.
*/
normalizeAccessInfo: function (access, all) {
if (all === void 0) { all = false; }
if (utils.type(access) !== 'object') {
throw new core_1.AccessControlError("Invalid IAccessInfo: " + typeof access);
}
// clone the object
access = Object.assign({}, access);
// validate and normalize subject(s)
access.subject = utils.toStringArray(access.subject);
if (access.subject.length === 0 || !utils.isFilledStringArray(access.subject)) {
throw new core_1.AccessControlError("Invalid subject(s): " + JSON.stringify(access.subject));
}
// validate and normalize resource
access.resource = utils.toStringArray(access.resource);
if (access.resource.length === 0 || !utils.isFilledStringArray(access.resource)) {
throw new core_1.AccessControlError("Invalid resource(s): " + JSON.stringify(access.resource));
}
// normalize attributes
if (access.denied || (Array.isArray(access.attributes) && access.attributes.length === 0)) {
access.attributes = [];
}
else {
// if omitted and not denied, all attributes are allowed
access.attributes = !access.attributes ? ['*'] : utils.toStringArray(access.attributes);
}
// this part is not necessary if this is invoked from a comitter method
// such as `createAny()`. So we'll check if we need to validate all
// properties such as `action` and `possession`.
if (all)
access = utils.normalizeActionPossession(access);
return access;
},
/**
* Used to re-set (prepare) the `attributes` of an `IAccessInfo` object
* when it's first initialized with e.g. `.grant()` or `.deny()` chain
* methods.
* @param {IAccessInfo} access
* @returns {IAccessInfo}
*/
resetAttributes: function (access) {
if (access.denied) {
access.attributes = [];
return access;
}
if (!access.attributes || utils.isEmptyArray(access.attributes)) {
access.attributes = ['*'];
}
return access;
},
/**
* Gets a flat, ordered list of inherited subjects for the given subject.
* @param {Object} grants - Main grants object to be processed.
* @param {string} subjectName - Subject name to be inspected.
* @returns {string[]}
*/
getRoleHierarchyOf: function (grants, subjectName, rootRole) {
// `rootRole` is for memory storage. Do NOT set it when using;
// and do NOT document this paramter.
// rootRole = rootRole || subjectName;
var subject = grants[subjectName];
if (!subject)
throw new core_1.AccessControlError("Subject not found: \"" + subjectName + "\"");
var arr = [subjectName];
if (!Array.isArray(subject._extend_) || subject._extend_.length === 0)
return arr;
subject._extend_.forEach(function (exRoleName) {
if (!grants[exRoleName]) {
throw new core_1.AccessControlError("Subject not found: \"" + grants[exRoleName] + "\"");
}
if (exRoleName === subjectName) {
throw new core_1.AccessControlError("Cannot extend subject \"" + subjectName + "\" by itself.");
}
// throw if cross-inheritance and also avoid memory leak with
// maximum call stack error
if (rootRole && (rootRole === exRoleName)) {
throw new core_1.AccessControlError("Cross inheritance is not allowed. Subject \"" + exRoleName + "\" already extends \"" + rootRole + "\".");
}
var ext = utils.getRoleHierarchyOf(grants, exRoleName, rootRole || subjectName);
arr = utils.uniqConcat(arr, ext);
});
return arr;
},
/**
* Gets subjects and extended subjects in a flat array.
*/
getFlatRoles: function (grants, subjects) {
var arrRoles = utils.toStringArray(subjects);
if (arrRoles.length === 0) {
throw new core_1.AccessControlError("Invalid subject(s): " + JSON.stringify(subjects));
}
var arr = utils.uniqConcat([], arrRoles); // subjects.concat();
arrRoles.forEach(function (subjectName) {
arr = utils.uniqConcat(arr, utils.getRoleHierarchyOf(grants, subjectName));
});
// console.log(`flat subjects for ${subjects}`, arr);
return arr;
},
/**
* Checks the given grants model and gets an array of non-existent subjects
* from the given subjects.
* @param {Any} grants - Grants model to be checked.
* @param {string[]} subjects - Subjects to be checked.
* @returns {string[]} - Array of non-existent subjects. Empty array if
* all exist.
*/
getNonExistentRoles: function (grants, subjects) {
var non = [];
if (utils.isEmptyArray(subjects))
return non;
for (var _i = 0, subjects_1 = subjects; _i < subjects_1.length; _i++) {
var subject = subjects_1[_i];
if (!grants.hasOwnProperty(subject))
non.push(subject);
}
return non;
},
/**
* Checks whether the given extender subject(s) is already (cross) inherited
* by the given subject and returns the first cross-inherited subject. Otherwise,
* returns `false`.
*
* Note that cross-inheritance is not allowed.
*
* @param {Any} grants - Grants model to be checked.
* @param {string} subjects - Target subject to be checked.
* @param {string|string[]} extenderRoles - Extender subject(s) to be checked.
*
* @returns {string|null} - Returns the first cross extending subject. `null`
* if none.
*/
getCrossExtendingRole: function (grants, subjectName, extenderRoles) {
var extenders = utils.toStringArray(extenderRoles);
var crossInherited = null;
utils.each(extenders, function (e) {
if (crossInherited || subjectName === e) {
return false; // break out of loop
}
var inheritedByExtender = utils.getRoleHierarchyOf(grants, e);
utils.each(inheritedByExtender, function (r) {
if (r === subjectName) {
// get/report the parent subject
crossInherited = e;
return false; // break out of loop
}
// suppress tslint warning
return true; // continue
});
// suppress tslint warning
return true; // continue
});
return crossInherited;
},
/**
* Extends the given subject(s) with privileges of one or more other subjects.
*
* @param {Any} grants
* @param {string|string[]} subjects Subject(s) to be extended. Single subject
* as a `String` or multiple subjects as an `Array`. Note that if a
* subject does not exist, it will be automatically created.
*
* @param {string|string[]} extenderRoles Subject(s) to inherit from.
* Single subject as a `String` or multiple subjects as an `Array`. Note
* that if a extender subject does not exist, it will throw.
*
* @throws {Error} If a subject is extended by itself, a non-existent subject or
* a cross-inherited subject.
*/
extendRole: function (grants, subjects, extenderRoles, replace) {
if (replace === void 0) { replace = false; }
// subjects cannot be omitted or an empty array
subjects = utils.toStringArray(subjects);
if (subjects.length === 0) {
throw new core_1.AccessControlError("Invalid subject(s): " + JSON.stringify(subjects));
}
// extenderRoles cannot be omitted or but can be an empty array
if (utils.isEmptyArray(extenderRoles))
return;
var arrExtRoles = utils.toStringArray(extenderRoles).concat();
if (arrExtRoles.length === 0) {
throw new core_1.AccessControlError("Cannot inherit invalid subject(s): " + JSON.stringify(extenderRoles));
}
var nonExistentExtRoles = utils.getNonExistentRoles(grants, arrExtRoles);
if (nonExistentExtRoles.length > 0) {
throw new core_1.AccessControlError("Cannot inherit non-existent subject(s): \"" + nonExistentExtRoles.join(', ') + "\"");
}
subjects.forEach(function (subjectName) {
var _a;
if (!grants[subjectName])
throw new core_1.AccessControlError("Subject not found: \"" + subjectName + "\"");
if (arrExtRoles.indexOf(subjectName) >= 0) {
throw new core_1.AccessControlError("Cannot extend subject \"" + subjectName + "\" by itself.");
}
// getCrossExtendingRole() returns false or the first
// cross-inherited subject, if found.
var crossInherited = utils.getCrossExtendingRole(grants, subjectName, arrExtRoles);
if (crossInherited) {
throw new core_1.AccessControlError("Cross inheritance is not allowed. Subject \"" + crossInherited + "\" already extends \"" + subjectName + "\".");
}
utils.validName(subjectName); // throws if false
var r = grants[subjectName];
if (!replace) {
r._extend_ = utils.uniqConcat((_a = r._extend_) !== null && _a !== void 0 ? _a : [], arrExtRoles);
}
else {
r._extend_ = arrExtRoles;
}
});
},
/**
* `utils.commitToGrants()` method already creates the subjects but it's
* executed when the chain is terminated with either `.extend()` or an
* action method (e.g. `.createOwn()`). In case the chain is not
* terminated, we'll still (pre)create the subject(s) with an empty object.
* @param {Any} grants
* @param {string|string[]} subjects
*/
preCreateRoles: function (grants, subjects) {
if (typeof subjects === 'string')
subjects = utils.toStringArray(subjects);
if (!Array.isArray(subjects) || subjects.length === 0) {
throw new core_1.AccessControlError("Invalid subject(s): " + JSON.stringify(subjects));
}
subjects.forEach(function (subject) {
if (utils.validName(subject) && !grants.hasOwnProperty(subject)) {
grants[subject] = {};
}
});
},
/**
* Commits the given `IAccessInfo` object to the grants model.
* CAUTION: if attributes is omitted, it will default to `['*']` which
* means "all attributes allowed".
* @param {Any} grants
* @param {IAccessInfo} access
* @param {boolean} normalizeAll
* Specifies whether to validate and normalize all properties of
* the inner `IAccessInfo` object, including `action` and `possession`.
* @throws {Error} If `IAccessInfo` object fails validation.
*/
commitToGrants: function (grants, access, normalizeAll) {
if (normalizeAll === void 0) { normalizeAll = false; }
access = utils.normalizeAccessInfo(access, normalizeAll);
// console.log(access);
// grant.subject also accepts an array, so treat it like it.
access.subject.forEach(function (subject) {
if (utils.validName(subject) && !grants.hasOwnProperty(subject)) {
grants[subject] = {};
}
var grantItem = grants[subject];
var ap = access.action + ':' + access.possession;
access.resource.forEach(function (res) {
if (utils.validName(res) && !grantItem.hasOwnProperty(res)) {
grantItem[res] = {};
}
// If possession (in action value or as a separate property) is
// omitted, it will default to "any". e.g. "create" —>
// "create:any"
grantItem[res][ap] = utils.toStringArray(access.attributes);
});
});
},
/**
* When more than one subject is passed, we union the permitted attributes
* for all given subjects; so we can check whether "at least one of these
* subjects" have the permission to execute this action.
* e.g. `can(['admin', 'user']).createAny('video')`
*
* @param {Any} grants
* @param {IQueryInfo} query
*
* @returns {string[]} - Array of union'ed attributes.
*/
getUnionAttrsOfRoles: function (grants, query) {
// throws if has any invalid property value
query = utils.normalizeQueryInfo(query);
var subject;
var resource;
var attrsList = [];
// get subjects and extended subjects in a flat array
var subjects = utils.getFlatRoles(grants, query.subject);
// iterate through subjects and add permission attributes (array) of
// each subject to attrsList (array).
subjects.forEach(function (subjectName, index) {
subject = grants[subjectName];
// no need to check subject existence #getFlatRoles() does that.
resource = subject[query.resource];
if (resource) {
// e.g. resource['create:own']
// If action has possession "any", it will also return
// `granted=true` for "own", if "own" is not defined.
attrsList.push((resource[query.action + ':' + query.possession]
|| resource[query.action + ':any']
|| []).concat());
// console.log(resource, 'for:', action + '.' + possession);
}
});
// union all arrays of (permitted resource) attributes (for each subject)
// into a single array.
var attrs = [];
var len = attrsList.length;
if (len > 0) {
attrs = attrsList[0];
var i = 1;
while (i < len) {
attrs = Notation.Glob.union(attrs, attrsList[i]);
i++;
}
}
return attrs;
},
/**
* Locks the given AccessControl instance by freezing underlying grants
* model and disabling all functionality to modify it.
* @param {AccessControl} ac
*/
lockAC: function (ac) {
var _ac = ac; // ts
if (!_ac._grants || Object.keys(_ac._grants).length === 0) {
throw new core_1.AccessControlError('Cannot lock empty or invalid grants model.');
}
var locked = ac.isLocked && Object.isFrozen(_ac._grants);
if (!locked)
locked = Boolean(utils.deepFreeze(_ac._grants));
/* istanbul ignore next */
if (!locked) {
throw new core_1.AccessControlError("Could not lock grants: " + typeof _ac._grants);
}
_ac._isLocked = locked;
},
// ----------------------
// NOTATION/GLOB UTILS
// ----------------------
/**
* Deep clones the source object while filtering its properties by the
* given attributes (glob notations). Includes all matched properties and
* removes the rest.
*
* @param {Object} object - Object to be filtered.
* @param {string[]} attributes - Array of glob notations.
*
* @returns {Object} - Filtered object.
*/
filter: function (object, attributes) {
if (!Array.isArray(attributes) || attributes.length === 0) {
return {};
}
var notation = new Notation(object);
return notation.filter(attributes).value;
},
/**
* Deep clones the source array of objects or a single object while
* filtering their properties by the given attributes (glob notations).
* Includes all matched properties and removes the rest of each object in
* the array.
*
* @param {Array|Object} arrOrObj - Array of objects or single object to be
* filtered.
* @param {string[]} attributes - Array of glob notations.
*
* @returns {Array|Object}
*/
filterAll: function (arrOrObj, attributes) {
if (!Array.isArray(arrOrObj)) {
return utils.filter(arrOrObj, attributes);
}
return arrOrObj.map(function (o) {
return utils.filter(o, attributes);
});
}
};
exports.utils = utils;