Comparing version
@@ -124,17 +124,2 @@ import { Access, IAccessInfo, ICondition, Query, IQueryInfo, Permission } from './core'; | ||
/** | ||
* Removes all the given resources for all roles, at once. | ||
* Pass the `roles` argument to remove access to resources for those | ||
* roles only. | ||
* @chainable | ||
* | ||
* @param {String|Array<String>} resources - A single or array of resources to | ||
* be removed. | ||
* @param {String|Array<String>} [roles] - A single or array of roles to | ||
* be removed. If omitted, permissions for all roles to all given | ||
* resources will be removed. | ||
* | ||
* @returns {AccessControl} - `AccessControl` instance for chaining. | ||
*/ | ||
removeResources(resources: string | string[], roles?: string | string[]): AccessControl; | ||
/** | ||
* Gets all the unique roles that have at least one access information. | ||
@@ -150,9 +135,2 @@ * | ||
/** | ||
* Gets all the unique resources that are granted access for at | ||
* least one role. | ||
* | ||
* @returns {Array<String>} | ||
*/ | ||
getResources(): string[]; | ||
/** | ||
* Checks whether any permissions are granted to the given role. | ||
@@ -166,10 +144,2 @@ * | ||
/** | ||
* Checks whether any permissions are granted for the given resource. | ||
* | ||
* @param {String} resource - Resource to be checked. | ||
* | ||
* @returns {Boolean} | ||
*/ | ||
hasResource(resource: string): boolean; | ||
/** | ||
* Gets an instance of `Query` object. This is used to check whether | ||
@@ -306,10 +276,2 @@ * the defined access is allowed for the given role(s) and resource. | ||
/** | ||
* @private | ||
*/ | ||
private _eachRoleResource(callback); | ||
/** | ||
* @private | ||
*/ | ||
_removePermission(resources: string | string[], roles?: string | string[], action?: string): void; | ||
/** | ||
* Documented separately in AccessControlError | ||
@@ -316,0 +278,0 @@ * @private |
@@ -163,22 +163,2 @@ "use strict"; | ||
/** | ||
* Removes all the given resources for all roles, at once. | ||
* Pass the `roles` argument to remove access to resources for those | ||
* roles only. | ||
* @chainable | ||
* | ||
* @param {String|Array<String>} resources - A single or array of resources to | ||
* be removed. | ||
* @param {String|Array<String>} [roles] - A single or array of roles to | ||
* be removed. If omitted, permissions for all roles to all given | ||
* resources will be removed. | ||
* | ||
* @returns {AccessControl} - `AccessControl` instance for chaining. | ||
*/ | ||
AccessControl.prototype.removeResources = function (resources, roles) { | ||
// _removePermission has a third argument `action`. if | ||
// omitted (like below), removes the parent resource object. | ||
this._removePermission(resources, roles); | ||
return this; | ||
}; | ||
/** | ||
* Gets all the unique roles that have at least one access information. | ||
@@ -196,16 +176,2 @@ * | ||
/** | ||
* Gets all the unique resources that are granted access for at | ||
* least one role. | ||
* | ||
* @returns {Array<String>} | ||
*/ | ||
AccessControl.prototype.getResources = function () { | ||
// using an object for unique list | ||
var resources = {}; | ||
this._eachRoleResource(function (role, resource, permissions) { | ||
resources[resource] = null; | ||
}); | ||
return Object.keys(resources); | ||
}; | ||
/** | ||
* Checks whether any permissions are granted to the given role. | ||
@@ -221,16 +187,2 @@ * | ||
/** | ||
* Checks whether any permissions are granted for the given resource. | ||
* | ||
* @param {String} resource - Resource to be checked. | ||
* | ||
* @returns {Boolean} | ||
*/ | ||
AccessControl.prototype.hasResource = function (resource) { | ||
if (typeof resource !== 'string' || resource === '') { | ||
return false; | ||
} | ||
var resources = this.getResources(); | ||
return resources.indexOf(resource) >= 0; | ||
}; | ||
/** | ||
* Gets an instance of `Query` object. This is used to check whether | ||
@@ -384,39 +336,2 @@ * the defined access is allowed for the given role(s) and resource. | ||
}; | ||
/** | ||
* @private | ||
*/ | ||
AccessControl.prototype._eachRoleResource = function (callback) { | ||
var _this = this; | ||
var resources, resourceDefinition; | ||
this._eachRole(function (role) { | ||
resources = _this._grants[role]; | ||
utils_1.default.eachKey(resources, function (resource) { | ||
resourceDefinition = role[resource]; | ||
callback(role, resource, resourceDefinition); | ||
}); | ||
}); | ||
}; | ||
/** | ||
* @private | ||
*/ | ||
AccessControl.prototype._removePermission = function (resources, roles, action) { | ||
var _this = this; | ||
resources = utils_1.default.toStringArray(resources); | ||
if (roles) | ||
roles = utils_1.default.toStringArray(roles); | ||
this._eachRoleResource(function (role, resource, permissions) { | ||
if (resources.indexOf(resource) >= 0 | ||
// roles is optional. so remove if role is not defined. | ||
// if defined, check if the current role is in the list. | ||
&& (!roles || roles.indexOf(role) >= 0)) { | ||
if (action) { | ||
delete _this._grants[role][resource][action]; | ||
} | ||
else { | ||
// this is used for AccessControl#removeResources(). | ||
delete _this._grants[role][resource]; | ||
} | ||
} | ||
}); | ||
}; | ||
Object.defineProperty(AccessControl, "Error", { | ||
@@ -423,0 +338,0 @@ /** |
@@ -210,3 +210,2 @@ "use strict"; | ||
this._.attributes = attributes; | ||
this._.attributes = this._.attributes ? utils_1.default.toStringArray(this._.attributes) : ['*']; | ||
utils_1.default.commitToGrants(this._grants, this._); | ||
@@ -213,0 +212,0 @@ // important: reset attributes for chained methods |
@@ -14,3 +14,2 @@ import { IAccessInfo, IQueryInfo, ICondition } from './core'; | ||
getFlatRoles(grants: any, roles: string | string[], context?: any): string[]; | ||
normalizeAction(info: IAccessInfo | IQueryInfo): IAccessInfo | IQueryInfo; | ||
normalizeQueryInfo(query: IQueryInfo): IQueryInfo; | ||
@@ -17,0 +16,0 @@ normalizeAccessInfo(access: IAccessInfo): IAccessInfo; |
@@ -5,2 +5,3 @@ "use strict"; | ||
var Notation = require("notation"); | ||
var MicroMatch = require("micromatch"); | ||
// own modules | ||
@@ -18,3 +19,3 @@ var core_1 = require("./core"); | ||
if (Array.isArray(value)) | ||
return value; | ||
return value.slice(); | ||
if (typeof value === 'string') | ||
@@ -27,3 +28,3 @@ return value.trim().split(/\s*[;,]\s*/); | ||
if (Array.isArray(value)) | ||
return value; | ||
return value.slice(); | ||
return [value]; | ||
@@ -48,3 +49,3 @@ }, | ||
uniqConcat: function (arrA, arrB) { | ||
var arr = arrA.concat(); | ||
var arr = arrA.slice(); | ||
arrB.forEach(function (b) { | ||
@@ -57,3 +58,3 @@ if (arr.indexOf(b) < 0) | ||
subtractArray: function (arrA, arrB) { | ||
return arrA.concat().filter(function (a) { return arrB.indexOf(a) === -1; }); | ||
return arrA.slice().filter(function (a) { return arrB.indexOf(a) === -1; }); | ||
}, | ||
@@ -70,3 +71,3 @@ eachKey: function (o, callback) { | ||
throw new core_1.AccessControlError("Invalid role(s): " + JSON.stringify(roles)); | ||
var arr = roles.concat(); | ||
var arr = roles.slice(); | ||
roles.forEach(function (roleName) { | ||
@@ -87,6 +88,2 @@ var role = grants[roleName]; | ||
}, | ||
normalizeAction: function (info) { | ||
// validate and normalize action | ||
return info; | ||
}, | ||
normalizeQueryInfo: function (query) { | ||
@@ -165,17 +162,10 @@ // clone the object | ||
access = utils.normalizeAccessInfo(access); | ||
// console.log(access); | ||
// grant.role also accepts an array, so treat it like it. | ||
access.role.forEach(function (role) { | ||
if (!grants.hasOwnProperty(role)) | ||
grants[role] = {}; | ||
var grantItem = grants[role]; | ||
access.resource.forEach(function (resource) { | ||
grantItem[resource] = grantItem[resource] || {}; | ||
access.action.forEach(function (action) { | ||
grantItem[resource][action] = grantItem[resource][action] || []; | ||
grantItem[resource][action].push({ | ||
attributes: access.attributes, | ||
condition: access.condition | ||
}); | ||
}); | ||
grants[role] = grants[role] || {}; | ||
grants[role].grants = grants[role].grants || []; | ||
grants[role].grants.push({ | ||
resource: access.resource, | ||
action: access.action, | ||
attributes: access.attributes, | ||
condition: access.condition | ||
}); | ||
@@ -201,3 +191,2 @@ }); | ||
query = utils.normalizeQueryInfo(query); | ||
var attrsList = []; | ||
// get roles and extended roles in a flat array | ||
@@ -207,15 +196,13 @@ var roles = utils.getFlatRoles(grants, query.role, query.context); | ||
// each role to attrsList (array). | ||
roles.forEach(function (role, index) { | ||
var grantItem = grants[role]; | ||
if (grantItem) { | ||
var resource = grantItem[query.resource]; | ||
if (resource) { | ||
var actionAttrs = resource[query.action]; | ||
if (actionAttrs && actionAttrs.length) { | ||
attrsList = attrsList.concat(actionAttrs); | ||
} | ||
} | ||
} | ||
return roles.filter(function (role) { | ||
return grants[role] && grants[role].grants; | ||
}).map(function (role) { | ||
return grants[role].grants; | ||
}).reduce(function (allGrants, roleGrants) { | ||
return allGrants.concat(roleGrants); | ||
}, []).filter(function (grant) { | ||
return MicroMatch.some(query.resource, grant.resource) && MicroMatch.some(query.action, grant.action); | ||
}).map(function (grant) { | ||
return { attributes: grant.attributes.slice(), condition: grant.condition }; | ||
}); | ||
return attrsList; | ||
}, | ||
@@ -222,0 +209,0 @@ /** |
{ | ||
"name": "role-acl", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"description": "Role, Attribute and Condition based Access Control for Node.js", | ||
@@ -68,4 +68,5 @@ "main": "./index.js", | ||
"dependencies": { | ||
"notation": "^1.0.0" | ||
"micromatch": "^3.1.0", | ||
"notation": "^1.1.0" | ||
} | ||
} |
161
README.md
@@ -17,2 +17,3 @@ Role, Attribute and conditions based Access Control for Node.js | ||
- Define grants at once (e.g. from database result) or one by one. | ||
- Grant permissions by resources and actions define by glob notation. | ||
- Grant permissions by attributes defined by glob notation (with nested object support). | ||
@@ -79,3 +80,42 @@ - Ability to filter data (model) instance by allowed attributes. | ||
``` | ||
### Wildcard (glob notation) Resource and Actions Examples | ||
```js | ||
ac.grant({ | ||
role: 'politics/editor', | ||
action: '*', | ||
resource: 'article', | ||
condition: {Fn: 'EQUALS', args: {category: 'politics'}}, | ||
attributes: ['*'] | ||
}); | ||
ac.grant({ | ||
role: 'politics/writer', | ||
action: ['*', '!publish'], | ||
resource: 'article', | ||
condition: {Fn: 'EQUALS', args: {category: 'politics'}}, | ||
attributes: ['*'] | ||
}); | ||
ac.grant({ | ||
role: 'admin', | ||
action: '*', | ||
resource: '*', | ||
condition: {Fn: 'EQUALS', args: {category: 'politics'}}, | ||
attributes: ['*'] | ||
}); | ||
permission = ac.can('politics/editor').execute('publish').with({category: 'politics'}).on('article'); | ||
console(permission.attributes); // -> ['*'] | ||
console(permission.granted); // -> true | ||
permission = ac.can('admin').execute('publish').with({category: 'politics'}).on('article'); | ||
console(permission.attributes); // -> ['*'] | ||
console(permission.granted); // -> true | ||
permission = ac.can('admin').execute('publish').with({category: 'politics'}).on('blog'); | ||
console(permission.attributes); // -> ['*'] | ||
console(permission.granted); // -> true | ||
permission = ac.can('politics/writer').execute('publish').with({category: 'politics'}).on('article'); | ||
console(permission.granted); // -> false | ||
``` | ||
### Express.js Example | ||
@@ -132,15 +172,4 @@ | ||
permission = ac.can('sports/editor').execute('publish').with({category: 'politics'})).on('article'); | ||
console(permission.attributes).toEqual([]); | ||
console(permission.granted).toEqual(false); | ||
ac.grant({ | ||
role: 'politics/editor', | ||
action: 'publish', | ||
resource: 'article', | ||
condition: {Fn: 'EQUALS', args: {category: 'politics'}}, | ||
attributes: attrs | ||
}); | ||
permission = ac.can('politics/editor').execute('publish').with({category: 'politics'}).on('article'); | ||
console(permission.attributes).toEqual(attrs); | ||
console(permission.granted).toEqual(true); | ||
console(permission.attributes); // -> [] | ||
console(permission.granted); // -> false | ||
``` | ||
@@ -194,68 +223,53 @@ | ||
admin: { | ||
video: { | ||
'create': ['*'], | ||
'read': ['*'], | ||
'update': ['*'], | ||
'delete': ['*'] | ||
} | ||
grants: [ | ||
{ | ||
resource: 'video', action: '*', attributes: ['*'] | ||
} | ||
] | ||
}, | ||
user: { | ||
video: { | ||
'create:own': ['*'], | ||
'read:own': ['*'], | ||
'update:own': ['*'], | ||
'delete:own': ['*'] | ||
} | ||
grants: [ | ||
{ | ||
resource: 'video', action: 'create', attributes: ['*'] | ||
}, | ||
{ | ||
resource: 'video', action: 'read', attributes: ['*'] | ||
}, | ||
{ | ||
resource: 'video', action: 'update', attributes: ['*'] | ||
}, | ||
{ | ||
resource: 'video', action: 'delete', attributes: ['*'] | ||
}, | ||
] | ||
}, | ||
"sports/editor": { | ||
article: { | ||
"create:any": [ | ||
{ | ||
attributes: ["*"], | ||
condition: { | ||
Fn: 'EQUALS', | ||
args: { | ||
'category': 'sports' | ||
} | ||
grants: [ | ||
{ | ||
resource: 'article', | ||
action: '*', | ||
attributes: ["*"], | ||
condition: { | ||
Fn: 'EQUALS', | ||
args: { | ||
'category': 'sports' | ||
} | ||
} | ||
], | ||
"update:any": [ | ||
{ | ||
attributes: ["*"], | ||
condition: { | ||
Fn: 'EQUALS', | ||
args: { | ||
'category': 'sports' | ||
} | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
}, | ||
"sports/writer": { | ||
article: { | ||
"create:any": [ | ||
{ | ||
attributes: ["*", "!status"], | ||
condition: { | ||
Fn: 'EQUALS', | ||
args: { | ||
'category': 'sports' | ||
} | ||
grants: [ | ||
{ | ||
resource: 'article', | ||
action: ['create', 'update'], | ||
attributes: ["*", "!status"], | ||
condition: { | ||
Fn: 'EQUALS', | ||
args: { | ||
'category': 'sports' | ||
} | ||
} | ||
], | ||
"update:any": [ | ||
{ | ||
attributes: ["*", "!status"], | ||
condition: { | ||
Fn: 'EQUALS', | ||
args: { | ||
'category': 'sports' | ||
} | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
@@ -275,7 +289,8 @@ }; | ||
{ role: 'user', resource: 'video', action: 'create:own', attributes: ['*'] }, | ||
{ role: 'user', resource: 'video', action: 'create', attributes: ['*'] }, | ||
{ role: 'user', resource: 'video', action: 'read', attributes: ['*'] }, | ||
{ role: 'user', resource: 'video', action: 'update:own', attributes: ['*'] }, | ||
{ role: 'user', resource: 'video', action: 'delete:own', attributes: ['*'] }, | ||
{ role: 'user', resource: 'video', action: 'update', attributes: ['*'] }, | ||
{ role: 'user', resource: 'video', action: 'delete', attributes: ['*'] }, | ||
{ role: 'user', resource: 'photo', action: '*', attributes: ['*'] }, | ||
{ role: 'user', resource: 'article', action: ['*', '!delete'], attributes: ['*'] }, | ||
{ role: 'sports/editor', resource: 'article', action: 'create', attributes: ['*'], | ||
@@ -282,0 +297,0 @@ condition: { "Fn": "EQUALS", "args": { "category": "sports" } } |
363
4.31%106270
-3.89%2
100%2436
-5.36%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
Updated