From 603af04de7e3e08d321cf301e1ceff7d1adbd282 Mon Sep 17 00:00:00 2001 From: shaikhriyaz Date: Wed, 3 May 2017 18:29:53 +0530 Subject: [PATCH 1/2] added customMiddleware added new middleware that takes userid, permissions and respurces as parameter and process them. --- lib/acl.js | 956 ++++++++++++++++++++++++++++------------------------- 1 file changed, 511 insertions(+), 445 deletions(-) diff --git a/lib/acl.js b/lib/acl.js index 9851717..21e0432 100644 --- a/lib/acl.js +++ b/lib/acl.js @@ -29,21 +29,21 @@ a resource, then he can get exclusive write operation on the locked resource. This lock should expire if the resource has not been accessed in some time. */ -"use strict"; +'use strict' -var _ = require('lodash'), - util = require('util'), -bluebird = require('bluebird'), -contract = require('./contract'); +var _ = require('lodash'), + util = require('util'), + bluebird = require('bluebird'), + contract = require('./contract') -contract.debug = true; +contract.debug = true -var Acl = function (backend, logger, options){ +var Acl = function (backend, logger, options) { contract(arguments) .params('object') - .params('object','object') - .params('object','object', 'object') - .end(); + .params('object', 'object') + .params('object', 'object', 'object') + .end() options = _.extend({ buckets: { @@ -54,21 +54,21 @@ var Acl = function (backend, logger, options){ roles: 'roles', users: 'users' } - }, options); + }, options) - this.logger = logger; - this.backend = backend; - this.options = options; + this.logger = logger + this.backend = backend + this.options = options // Promisify async methods - backend.endAsync = bluebird.promisify(backend.end); - backend.getAsync = bluebird.promisify(backend.get); - backend.cleanAsync = bluebird.promisify(backend.clean); - backend.unionAsync = bluebird.promisify(backend.union); + backend.endAsync = bluebird.promisify(backend.end) + backend.getAsync = bluebird.promisify(backend.get) + backend.cleanAsync = bluebird.promisify(backend.clean) + backend.unionAsync = bluebird.promisify(backend.union) if (backend.unions) { - backend.unionsAsync = bluebird.promisify(backend.unions); + backend.unionsAsync = bluebird.promisify(backend.unions) } -}; +} /** addUserRoles( userId, roles, function(err) ) @@ -80,29 +80,28 @@ var Acl = function (backend, logger, options){ @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.addUserRoles = function(userId, roles, cb){ +Acl.prototype.addUserRoles = function (userId, roles, cb) { contract(arguments) - .params('string|number','string|array','function') - .params('string|number','string|array') - .end(); + .params('string|number', 'string|array', 'function') + .params('string|number', 'string|array') + .end() - var transaction = this.backend.begin(); - this.backend.add(transaction, this.options.buckets.meta, 'users', userId); - this.backend.add(transaction, this.options.buckets.users, userId, roles); + var transaction = this.backend.begin() + this.backend.add(transaction, this.options.buckets.meta, 'users', userId) + this.backend.add(transaction, this.options.buckets.users, userId, roles) if (Array.isArray(roles)) { - var _this = this; + var _this = this - roles.forEach(function(role) { - _this.backend.add(transaction, _this.options.buckets.roles, role, userId); - }); - } - else { - this.backend.add(transaction, this.options.buckets.roles, roles, userId); + roles.forEach(function (role) { + _this.backend.add(transaction, _this.options.buckets.roles, role, userId) + }) + }else { + this.backend.add(transaction, this.options.buckets.roles, roles, userId) } - return this.backend.endAsync(transaction).nodeify(cb); -}; + return this.backend.endAsync(transaction).nodeify(cb) +} /** removeUserRoles( userId, roles, function(err) ) @@ -114,28 +113,27 @@ Acl.prototype.addUserRoles = function(userId, roles, cb){ @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.removeUserRoles = function(userId, roles, cb){ +Acl.prototype.removeUserRoles = function (userId, roles, cb) { contract(arguments) - .params('string|number','string|array','function') - .params('string|number','string|array') - .end(); + .params('string|number', 'string|array', 'function') + .params('string|number', 'string|array') + .end() - var transaction = this.backend.begin(); - this.backend.remove(transaction, this.options.buckets.users, userId, roles); + var transaction = this.backend.begin() + this.backend.remove(transaction, this.options.buckets.users, userId, roles) if (Array.isArray(roles)) { - var _this = this; + var _this = this - roles.forEach(function(role) { - _this.backend.remove(transaction, _this.options.buckets.roles, role, userId); - }); - } - else { - this.backend.remove(transaction, this.options.buckets.roles, roles, userId); + roles.forEach(function (role) { + _this.backend.remove(transaction, _this.options.buckets.roles, role, userId) + }) + }else { + this.backend.remove(transaction, this.options.buckets.roles, roles, userId) } - return this.backend.endAsync(transaction).nodeify(cb); -}; + return this.backend.endAsync(transaction).nodeify(cb) +} /** userRoles( userId, function(err, roles) ) @@ -146,9 +144,9 @@ Acl.prototype.removeUserRoles = function(userId, roles, cb){ @param {Function} Callback called when finished. @return {Promise} Promise resolved with an array of user roles */ -Acl.prototype.userRoles = function(userId, cb){ - return this.backend.getAsync(this.options.buckets.users, userId).nodeify(cb); -}; +Acl.prototype.userRoles = function (userId, cb) { + return this.backend.getAsync(this.options.buckets.users, userId).nodeify(cb) +} /** roleUsers( roleName, function(err, users) ) @@ -158,9 +156,9 @@ Acl.prototype.userRoles = function(userId, cb){ @param {Function} Callback called when finished. @return {Promise} Promise resolved with an array of users */ -Acl.prototype.roleUsers = function(roleName, cb){ - return this.backend.getAsync(this.options.buckets.roles, roleName).nodeify(cb); -}; +Acl.prototype.roleUsers = function (roleName, cb) { + return this.backend.getAsync(this.options.buckets.roles, roleName).nodeify(cb) +} /** hasRole( userId, rolename, function(err, is_in_role) ) @@ -172,11 +170,11 @@ Acl.prototype.roleUsers = function(roleName, cb){ @param {Function} Callback called when finished. @return {Promise} Promise resolved with boolean of whether user is in role */ -Acl.prototype.hasRole = function(userId, rolename, cb){ - return this.userRoles(userId).then(function(roles){ - return roles.indexOf(rolename) != -1; - }).nodeify(cb); -}; +Acl.prototype.hasRole = function (userId, rolename, cb) { + return this.userRoles(userId).then(function (roles) { + return roles.indexOf(rolename) != -1 + }).nodeify(cb) +} /** addRoleParents( role, parents, function(err) ) @@ -188,17 +186,17 @@ Acl.prototype.hasRole = function(userId, rolename, cb){ @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.addRoleParents = function(role, parents, cb){ +Acl.prototype.addRoleParents = function (role, parents, cb) { contract(arguments) - .params('string|number','string|array','function') - .params('string|number','string|array') - .end(); + .params('string|number', 'string|array', 'function') + .params('string|number', 'string|array') + .end() - var transaction = this.backend.begin(); - this.backend.add(transaction, this.options.buckets.meta, 'roles', role); - this.backend.add(transaction, this.options.buckets.parents, role, parents); - return this.backend.endAsync(transaction).nodeify(cb); -}; + var transaction = this.backend.begin() + this.backend.add(transaction, this.options.buckets.meta, 'roles', role) + this.backend.add(transaction, this.options.buckets.parents, role, parents) + return this.backend.endAsync(transaction).nodeify(cb) +} /** removeRoleParents( role, parents, function(err) ) @@ -212,27 +210,27 @@ Acl.prototype.addRoleParents = function(role, parents, cb){ @param {Function} Callback called when finished [optional]. @return {Promise} Promise resolved when finished. */ -Acl.prototype.removeRoleParents = function(role, parents, cb){ +Acl.prototype.removeRoleParents = function (role, parents, cb) { contract(arguments) .params('string', 'string|array', 'function') .params('string', 'string|array') .params('string', 'function') .params('string') - .end(); + .end() if (!cb && _.isFunction(parents)) { - cb = parents; - parents = null; + cb = parents + parents = null } - var transaction = this.backend.begin(); + var transaction = this.backend.begin() if (parents) { - this.backend.remove(transaction, this.options.buckets.parents, role, parents); + this.backend.remove(transaction, this.options.buckets.parents, role, parents) } else { - this.backend.del(transaction, this.options.buckets.parents, role); + this.backend.del(transaction, this.options.buckets.parents, role) } - return this.backend.endAsync(transaction).nodeify(cb); -}; + return this.backend.endAsync(transaction).nodeify(cb) +} /** removeRole( role, function(err) ) @@ -242,31 +240,31 @@ Acl.prototype.removeRoleParents = function(role, parents, cb){ @param {String} Role to be removed @param {Function} Callback called when finished. */ -Acl.prototype.removeRole = function(role, cb){ +Acl.prototype.removeRole = function (role, cb) { contract(arguments) - .params('string','function') - .params('string').end(); + .params('string', 'function') + .params('string').end() - var _this = this; + var _this = this // Note that this is not fully transactional. - return this.backend.getAsync(this.options.buckets.resources, role).then(function(resources){ - var transaction = _this.backend.begin(); + return this.backend.getAsync(this.options.buckets.resources, role).then(function (resources) { + var transaction = _this.backend.begin() - resources.forEach(function(resource){ - var bucket = allowsBucket(resource); - _this.backend.del(transaction, bucket, role); - }); + resources.forEach(function (resource) { + var bucket = allowsBucket(resource) + _this.backend.del(transaction, bucket, role) + }) - _this.backend.del(transaction, _this.options.buckets.resources, role); - _this.backend.del(transaction, _this.options.buckets.parents, role); + _this.backend.del(transaction, _this.options.buckets.resources, role) + _this.backend.del(transaction, _this.options.buckets.parents, role) _this.backend.del(transaction, _this.options.buckets.roles, role) - _this.backend.remove(transaction, _this.options.buckets.meta, 'roles', role); + _this.backend.remove(transaction, _this.options.buckets.meta, 'roles', role) // `users` collection keeps the removed role // because we don't know what users have `role` assigned. - return _this.backend.endAsync(transaction); - }).nodeify(cb); -}; + return _this.backend.endAsync(transaction) + }).nodeify(cb) +} /** removeResource( resource, function(err) ) @@ -277,22 +275,22 @@ Acl.prototype.removeRole = function(role, cb){ @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.removeResource = function(resource, cb){ +Acl.prototype.removeResource = function (resource, cb) { contract(arguments) .params('string', 'function') .params('string') - .end(); - - var _this = this; - return this.backend.getAsync(this.options.buckets.meta, 'roles').then(function(roles){ - var transaction = _this.backend.begin(); - _this.backend.del(transaction, allowsBucket(resource), roles); - roles.forEach(function(role){ - _this.backend.remove(transaction, _this.options.buckets.resources, role, resource); + .end() + + var _this = this + return this.backend.getAsync(this.options.buckets.meta, 'roles').then(function (roles) { + var transaction = _this.backend.begin() + _this.backend.del(transaction, allowsBucket(resource), roles) + roles.forEach(function (role) { + _this.backend.remove(transaction, _this.options.buckets.resources, role, resource) }) - return _this.backend.endAsync(transaction); + return _this.backend.endAsync(transaction) }).nodeify(cb) -}; +} /** allow( roles, resources, permissions, function(err) ) @@ -313,58 +311,57 @@ Acl.prototype.removeResource = function(resource, cb){ @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.allow = function(roles, resources, permissions, cb){ +Acl.prototype.allow = function (roles, resources, permissions, cb) { contract(arguments) - .params('string|array','string|array','string|array','function') - .params('string|array','string|array','string|array') - .params('array','function') + .params('string|array', 'string|array', 'string|array', 'function') + .params('string|array', 'string|array', 'string|array') + .params('array', 'function') .params('array') - .end(); + .end() - if((arguments.length == 1) || ((arguments.length===2)&&_.isObject(roles)&&_.isFunction(resources))){ - return this._allowEx(roles).nodeify(resources); - }else{ - var _this = this; + if ((arguments.length == 1) || ((arguments.length === 2) && _.isObject(roles) && _.isFunction(resources))) { + return this._allowEx(roles).nodeify(resources) + }else { + var _this = this - roles = makeArray(roles); - resources = makeArray(resources); + roles = makeArray(roles) + resources = makeArray(resources) - var transaction = _this.backend.begin(); + var transaction = _this.backend.begin() - _this.backend.add(transaction, _this.options.buckets.meta, 'roles', roles); + _this.backend.add(transaction, _this.options.buckets.meta, 'roles', roles) - resources.forEach(function(resource){ - roles.forEach(function(role){ - _this.backend.add(transaction, allowsBucket(resource), role, permissions); - }); - }); + resources.forEach(function (resource) { + roles.forEach(function (role) { + _this.backend.add(transaction, allowsBucket(resource), role, permissions) + }) + }) - roles.forEach(function(role){ - _this.backend.add(transaction, _this.options.buckets.resources, role, resources); - }); + roles.forEach(function (role) { + _this.backend.add(transaction, _this.options.buckets.resources, role, resources) + }) - return _this.backend.endAsync(transaction).nodeify(cb); + return _this.backend.endAsync(transaction).nodeify(cb) } -}; - +} -Acl.prototype.removeAllow = function(role, resources, permissions, cb){ +Acl.prototype.removeAllow = function (role, resources, permissions, cb) { contract(arguments) - .params('string','string|array','string|array','function') - .params('string','string|array','string|array') - .params('string','string|array','function') - .params('string','string|array') - .end(); - - resources = makeArray(resources); - if(cb || (permissions && !_.isFunction(permissions))){ - permissions = makeArray(permissions); - }else { - cb = permissions; - permissions = null; - } + .params('string', 'string|array', 'string|array', 'function') + .params('string', 'string|array', 'string|array') + .params('string', 'string|array', 'function') + .params('string', 'string|array') + .end() + + resources = makeArray(resources) + if (cb || (permissions && !_.isFunction(permissions))) { + permissions = makeArray(permissions) + }else { + cb = permissions + permissions = null + } - return this.removePermissions(role, resources, permissions, cb); + return this.removePermissions(role, resources, permissions, cb) } /** @@ -378,37 +375,36 @@ Acl.prototype.removeAllow = function(role, resources, permissions, cb){ @param {String|Array} @param {String|Array} */ -Acl.prototype.removePermissions = function(role, resources, permissions, cb){ - - var _this = this; - - var transaction = _this.backend.begin(); - resources.forEach(function(resource){ - var bucket = allowsBucket(resource); - if(permissions){ - _this.backend.remove(transaction, bucket, role, permissions); - }else{ - _this.backend.del(transaction, bucket, role); - _this.backend.remove(transaction, _this.options.buckets.resources, role, resource); +Acl.prototype.removePermissions = function (role, resources, permissions, cb) { + var _this = this + + var transaction = _this.backend.begin() + resources.forEach(function (resource) { + var bucket = allowsBucket(resource) + if (permissions) { + _this.backend.remove(transaction, bucket, role, permissions) + }else { + _this.backend.del(transaction, bucket, role) + _this.backend.remove(transaction, _this.options.buckets.resources, role, resource) } - }); + }) // Remove resource from role if no rights for that role exists. // Not fully atomic... - return _this.backend.endAsync(transaction).then(function(){ - var transaction = _this.backend.begin(); - return bluebird.all(resources.map(function(resource){ - var bucket = allowsBucket(resource); - return _this.backend.getAsync(bucket, role).then(function(permissions){ - if(permissions.length==0){ - _this.backend.remove(transaction, _this.options.buckets.resources, role, resource); + return _this.backend.endAsync(transaction).then(function () { + var transaction = _this.backend.begin() + return bluebird.all(resources.map(function (resource) { + var bucket = allowsBucket(resource) + return _this.backend.getAsync(bucket, role).then(function (permissions) { + if (permissions.length == 0) { + _this.backend.remove(transaction, _this.options.buckets.resources, role, resource) } - }); - })).then(function(){ - return _this.backend.endAsync(transaction); - }); - }).nodeify(cb); -}; + }) + })).then(function () { + return _this.backend.endAsync(transaction) + }) + }).nodeify(cb) +} /** allowedPermissions( userId, resources, function(err, obj) ) @@ -423,33 +419,33 @@ Acl.prototype.removePermissions = function(role, resources, permissions, cb){ @param {String|Array} resource(s) to ask permissions for. @param {Function} Callback called when finished. */ -Acl.prototype.allowedPermissions = function(userId, resources, cb){ +Acl.prototype.allowedPermissions = function (userId, resources, cb) { if (!userId) - return cb(null, {}); + return cb(null, {}) contract(arguments) .params('string|number', 'string|array', 'function') .params('string|number', 'string|array') - .end(); + .end() if (this.backend.unionsAsync) { - return this.optimizedAllowedPermissions(userId, resources, cb); + return this.optimizedAllowedPermissions(userId, resources, cb) } - var _this = this; - resources = makeArray(resources); - - return _this.userRoles(userId).then(function(roles){ - var result = {}; - return bluebird.all(resources.map(function(resource){ - return _this._resourcePermissions(roles, resource).then(function(permissions){ - result[resource] = permissions; - }); - })).then(function(){ - return result; - }); - }).nodeify(cb); -}; + var _this = this + resources = makeArray(resources) + + return _this.userRoles(userId).then(function (roles) { + var result = {} + return bluebird.all(resources.map(function (resource) { + return _this._resourcePermissions(roles, resource).then(function (permissions) { + result[resource] = permissions + }) + })).then(function () { + return result + }) + }).nodeify(cb) +} /** optimizedAllowedPermissions( userId, resources, function(err, obj) ) @@ -466,39 +462,39 @@ Acl.prototype.allowedPermissions = function(userId, resources, cb){ @param {String|Array} resource(s) to ask permissions for. @param {Function} Callback called when finished. */ -Acl.prototype.optimizedAllowedPermissions = function(userId, resources, cb){ +Acl.prototype.optimizedAllowedPermissions = function (userId, resources, cb) { if (!userId) { - return cb(null, {}); + return cb(null, {}) } contract(arguments) .params('string|number', 'string|array', 'function|undefined') .params('string|number', 'string|array') - .end(); + .end() - resources = makeArray(resources); - var self = this; + resources = makeArray(resources) + var self = this - return this._allUserRoles(userId).then(function(roles) { - var buckets = resources.map(allowsBucket); + return this._allUserRoles(userId).then(function (roles) { + var buckets = resources.map(allowsBucket) if (roles.length === 0) { - var emptyResult = {}; - buckets.forEach(function(bucket) { - emptyResult[bucket] = []; - }); - return bluebird.resolve(emptyResult); + var emptyResult = {} + buckets.forEach(function (bucket) { + emptyResult[bucket] = [] + }) + return bluebird.resolve(emptyResult) } - return self.backend.unionsAsync(buckets, roles); - }).then(function(response) { - var result = {}; - Object.keys(response).forEach(function(bucket) { - result[keyFromAllowsBucket(bucket)] = response[bucket]; - }); + return self.backend.unionsAsync(buckets, roles) + }).then(function (response) { + var result = {} + Object.keys(response).forEach(function (bucket) { + result[keyFromAllowsBucket(bucket)] = response[bucket] + }) - return result; - }).nodeify(cb); -}; + return result + }).nodeify(cb) +} /** isAllowed( userId, resource, permissions, function(err, allowed) ) @@ -511,22 +507,22 @@ Acl.prototype.optimizedAllowedPermissions = function(userId, resources, cb){ @param {String|Array} asked permissions. @param {Function} Callback called wish the result. */ -Acl.prototype.isAllowed = function(userId, resource, permissions, cb){ +Acl.prototype.isAllowed = function (userId, resource, permissions, cb) { contract(arguments) .params('string|number', 'string', 'string|array', 'function') .params('string|number', 'string', 'string|array') - .end(); + .end() - var _this = this; + var _this = this - return this.backend.getAsync(this.options.buckets.users, userId).then(function(roles){ - if(roles.length){ - return _this.areAnyRolesAllowed(roles, resource, permissions); - }else{ - return false; + return this.backend.getAsync(this.options.buckets.users, userId).then(function (roles) { + if (roles.length) { + return _this.areAnyRolesAllowed(roles, resource, permissions) + }else { + return false } - }).nodeify(cb); -}; + }).nodeify(cb) +} /** areAnyRolesAllowed( roles, resource, permissions, function(err, allowed) ) @@ -538,21 +534,21 @@ Acl.prototype.isAllowed = function(userId, resource, permissions, cb){ @param {String|Array} asked permissions. @param {Function} Callback called with the result. */ -Acl.prototype.areAnyRolesAllowed = function(roles, resource, permissions, cb){ +Acl.prototype.areAnyRolesAllowed = function (roles, resource, permissions, cb) { contract(arguments) .params('string|array', 'string', 'string|array', 'function') .params('string|array', 'string', 'string|array') - .end(); + .end() - roles = makeArray(roles); - permissions = makeArray(permissions); + roles = makeArray(roles) + permissions = makeArray(permissions) - if(roles.length===0){ - return bluebird.resolve(false).nodeify(cb); - }else{ - return this._checkPermissions(roles, resource, permissions).nodeify(cb); + if (roles.length === 0) { + return bluebird.resolve(false).nodeify(cb) + }else { + return this._checkPermissions(roles, resource, permissions).nodeify(cb) } -}; +} /** whatResources(role, function(err, {resourceName: [permissions]}) @@ -567,44 +563,44 @@ Acl.prototype.areAnyRolesAllowed = function(roles, resource, permissions, cb){ @param {String[Array} Permissions @param {Function} Callback called wish the result. */ -Acl.prototype.whatResources = function(roles, permissions, cb){ +Acl.prototype.whatResources = function (roles, permissions, cb) { contract(arguments) .params('string|array') - .params('string|array','string|array') - .params('string|array','function') - .params('string|array','string|array','function') - .end(); - - roles = makeArray(roles); - if (_.isFunction(permissions)){ - cb = permissions; - permissions = undefined; - }else if(permissions){ - permissions = makeArray(permissions); + .params('string|array', 'string|array') + .params('string|array', 'function') + .params('string|array', 'string|array', 'function') + .end() + + roles = makeArray(roles) + if (_.isFunction(permissions)) { + cb = permissions + permissions = undefined + }else if (permissions) { + permissions = makeArray(permissions) } - return this.permittedResources(roles, permissions, cb); -}; - -Acl.prototype.permittedResources = function(roles, permissions, cb){ - var _this = this; - var result = _.isUndefined(permissions) ? {} : []; - return this._rolesResources(roles).then(function(resources){ - return bluebird.all(resources.map(function(resource){ - return _this._resourcePermissions(roles, resource).then(function(p){ - if(permissions){ - var commonPermissions = _.intersection(permissions, p); - if(commonPermissions.length>0){ - result.push(resource); + return this.permittedResources(roles, permissions, cb) +} + +Acl.prototype.permittedResources = function (roles, permissions, cb) { + var _this = this + var result = _.isUndefined(permissions) ? {} : [] + return this._rolesResources(roles).then(function (resources) { + return bluebird.all(resources.map(function (resource) { + return _this._resourcePermissions(roles, resource).then(function (p) { + if (permissions) { + var commonPermissions = _.intersection(permissions, p) + if (commonPermissions.length > 0) { + result.push(resource) } - }else{ - result[resource] = p; + }else { + result[resource] = p } - }); - })).then(function(){ - return result; - }); - }).nodeify(cb); + }) + })).then(function () { + return result + }) + }).nodeify(cb) } /** @@ -616,313 +612,383 @@ Acl.prototype.permittedResources = function(roles, permissions, cb){ */ /* Acl.prototype.clean = function(callback){ - var acl = this; + var acl = this this.redis.keys(this.prefix+'*', function(err, keys){ if(keys.length){ acl.redis.del(keys, function(err){ - callback(err); - }); + callback(err) + }) }else{ - callback(); + callback() } - }); -}; + }) +} */ /** Express Middleware */ -Acl.prototype.middleware = function(numPathComponents, userId, actions){ +Acl.prototype.middleware = function (numPathComponents, userId, actions) { contract(arguments) .params() - .params('number') - .params('number','string|number|function') - .params('number','string|number|function', 'string|array') - .end(); + .params('number', 'string|number|function') + .params('number', 'string|number|function') + .params('number', 'string|number|function', 'string|array') + .end() - var acl = this; + var acl = this - function HttpError(errorCode, msg){ - this.errorCode = errorCode; - this.message = msg; - this.name = this.constructor.name; + function HttpError (errorCode, msg) { + this.errorCode = errorCode + this.message = msg + this.name = this.constructor.name - Error.captureStackTrace(this, this.constructor); - this.constructor.prototype.__proto__ = Error.prototype; + Error.captureStackTrace(this, this.constructor) + this.constructor.prototype.__proto__ = Error.prototype } - return function(req, res, next){ + return function (req, res, next) { var _userId = userId, - _actions = actions, - resource, - url; + _actions = actions, + resource = numPathComponents, + url // call function to fetch userId - if(typeof userId === 'function'){ - _userId = userId(req, res); + if (typeof userId === 'function') { + _userId = userId(req, res) } if (!userId) { - if((req.session) && (req.session.userId)){ - _userId = req.session.userId; - }else if((req.user) && (req.user.id)){ - _userId = req.user.id; - }else{ - next(new HttpError(401, 'User not authenticated')); - return; + if ((req.session) && (req.session.userId)) { + _userId = req.session.userId + }else if ((req.user) && (req.user.id)) { + _userId = req.user.id + }else { + next(new HttpError(401, 'User not authenticated')) + return } } // Issue #80 - Additional check if (!_userId) { - next(new HttpError(401, 'User not authenticated')); - return; + next(new HttpError(401, 'User not authenticated')) + return } - url = req.originalUrl.split('?')[0]; - if(!numPathComponents){ - resource = url; - }else{ - resource = url.split('/').slice(0,numPathComponents+1).join('/'); - } + + url = req.originalUrl.split('?')[0] + if (!numPathComponents) { + resource = url + }else { + resource = url.split('/').slice(0, numPathComponents + 1).join('/') + } + - if(!_actions){ - _actions = req.method.toLowerCase(); + if (!_actions) { + _actions = req.method.toLowerCase() } - acl.logger?acl.logger.debug('Requesting '+_actions+' on '+resource+' by user '+_userId):null; + acl.logger ? acl.logger.debug('Requesting ' + _actions + ' on ' + resource + ' by user ' + _userId) : null - acl.isAllowed(_userId, resource, _actions, function(err, allowed){ - if (err){ - next(new Error('Error checking permissions to access resource')); - }else if(allowed === false){ + acl.isAllowed(_userId, resource, _actions, function (err, allowed) { + if (err) { + next(new Error('Error checking permissions to access resource')) + }else if (allowed === false) { if (acl.logger) { - acl.logger.debug('Not allowed '+_actions+' on '+resource+' by user '+_userId); - acl.allowedPermissions(_userId, resource, function(err, obj){ - acl.logger.debug('Allowed permissions: '+util.inspect(obj)); - }); + acl.logger.debug('Not allowed ' + _actions + ' on ' + resource + ' by user ' + _userId) + acl.allowedPermissions(_userId, resource, function (err, obj) { + acl.logger.debug('Allowed permissions: ' + util.inspect(obj)) + }) } - next(new HttpError(403,'Insufficient permissions to access resource')); - }else{ - acl.logger?acl.logger.debug('Allowed '+_actions+' on '+resource+' by user '+_userId):null; - next(); + next(new HttpError(403, 'Insufficient permissions to access resource')) + }else { + acl.logger ? acl.logger.debug('Allowed ' + _actions + ' on ' + resource + ' by user ' + _userId) : null + next() } - }); - }; -}; + }) + } +} + +Acl.prototype.customMiddleware = function (userId, actions, resource) { + contract(arguments) + .params('string|number|function', 'string|array','string|array') + .end() + + var acl = this + + function HttpError (errorCode, msg) { + this.errorCode = errorCode + this.message = msg + this.name = this.constructor.name + + Error.captureStackTrace(this, this.constructor) + this.constructor.prototype.__proto__ = Error.prototype + } + + return function (req, res, next) { + var _userId = userId, + _actions = actions, + _resource = resource, + url + // call function to fetch userId + if (typeof userId === 'function') { + _userId = userId(req, res) + } + if (!userId) { + if ((req.session) && (req.session.userId)) { + _userId = req.session.userId + }else if ((req.user) && (req.user.id)) { + _userId = req.user.id + }else { + next(new HttpError(401, 'User not authenticated')) + return + } + } + + // Issue #80 - Additional check + if (!_userId) { + next(new HttpError(401, 'User not authenticated')) + return + } + + if (!_actions) { + _actions = req.method.toLowerCase() + } + + acl.logger ? acl.logger.debug('Requesting ' + _actions + ' on ' + _resource + ' by user ' + _userId) : null + acl.isAllowed(_userId, _resource, _actions, function (err, allowed) { + if (err) { + console.log(err) + next(new Error('Error checking permissions to access resource')) + }else if (allowed === false) { + if (acl.logger) { + acl.logger.debug('Not allowed ' + _actions + ' on ' + _resource + ' by user ' + _userId) + acl.allowedPermissions(_userId, _resource, function (err, obj) { + acl.logger.debug('Allowed permissions: ' + util.inspect(obj)) + }) + } + next(new HttpError(403, 'Insufficient permissions to access resource')) + }else { + acl.logger ? acl.logger.debug('Allowed ' + _actions + ' on ' + _resource + ' by user ' + _userId) : null + next() + } + }) + } +} /** Error handler for the Express middleware @param {String} [contentType] (html|json) defaults to plain text */ -Acl.prototype.middleware.errorHandler = function(contentType){ - var method = 'end'; +Acl.prototype.middleware.errorHandler = function (contentType) { + var method = 'end' - if(contentType){ + if (contentType) { switch (contentType) { - case 'json': method = 'json'; break; - case 'html': method = 'send'; break; + case 'json': + method = 'json' + break + case 'html': + method = 'send' + break } } - return function(err, req, res, next){ - if(err.name !== 'HttpError' || !err.errorCode) return next(err); - res.status(err.errorCode)[method](err.message); - }; -}; - + return function (err, req, res, next) { + if (err.name !== 'HttpError' || !err.errorCode) return next(err) + res.status(err.errorCode)[method](err.message) + } +} -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Private methods // -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Same as allow but accepts a more compact input. // -Acl.prototype._allowEx = function(objs){ - var _this = this; - objs = makeArray(objs); - - var demuxed = []; - objs.forEach(function(obj){ - var roles = obj.roles; - obj.allows.forEach(function(allow){ +Acl.prototype._allowEx = function (objs) { + var _this = this + objs = makeArray(objs) + + var demuxed = [] + objs.forEach(function (obj) { + var roles = obj.roles + obj.allows.forEach(function (allow) { demuxed.push({ - roles:roles, - resources:allow.resources, - permissions:allow.permissions}); - }); - }); + roles: roles, + resources: allow.resources, + permissions: allow.permissions}) + }) + }) - return bluebird.reduce(demuxed, function(values, obj){ - return _this.allow(obj.roles, obj.resources, obj.permissions); - }, null); -}; + return bluebird.reduce(demuxed, function (values, obj) { + return _this.allow(obj.roles, obj.resources, obj.permissions) + }, null) +} // // Returns the parents of the given roles // -Acl.prototype._rolesParents = function(roles){ - return this.backend.unionAsync(this.options.buckets.parents, roles); -}; +Acl.prototype._rolesParents = function (roles) { + return this.backend.unionAsync(this.options.buckets.parents, roles) +} // // Return all roles in the hierarchy including the given roles. // /* Acl.prototype._allRoles = function(roleNames, cb){ - var _this = this, roles; + var _this = this, roles _this._rolesParents(roleNames, function(err, parents){ - roles = _.union(roleNames, parents); + roles = _.union(roleNames, parents) async.whilst( function (){ - return parents.length >0; + return parents.length >0 }, function (cb) { _this._rolesParents(parents, function(err, result){ if(!err){ - roles = _.union(roles, parents); - parents = result; + roles = _.union(roles, parents) + parents = result } - cb(err); - }); + cb(err) + }) }, function(err){ - cb(err, roles); + cb(err, roles) } - ); - }); -}; + ) + }) +} */ // // Return all roles in the hierarchy including the given roles. // -Acl.prototype._allRoles = function(roleNames){ - var _this = this; - - return this._rolesParents(roleNames).then(function(parents){ - if(parents.length > 0){ - return _this._allRoles(parents).then(function(parentRoles){ - return _.union(roleNames, parentRoles); - }); - }else{ - return roleNames; +Acl.prototype._allRoles = function (roleNames) { + var _this = this + + return this._rolesParents(roleNames).then(function (parents) { + if (parents.length > 0) { + return _this._allRoles(parents).then(function (parentRoles) { + return _.union(roleNames, parentRoles) + }) + }else { + return roleNames } - }); -}; + }) +} // // Return all roles in the hierarchy of the given user. // -Acl.prototype._allUserRoles = function(userId) { - var _this = this; +Acl.prototype._allUserRoles = function (userId) { + var _this = this - return this.userRoles(userId).then(function(roles) { + return this.userRoles(userId).then(function (roles) { if (roles && roles.length > 0) { - return _this._allRoles(roles); + return _this._allRoles(roles) } else { - return []; + return [] } - }); -}; + }) +} // // Returns an array with resources for the given roles. // -Acl.prototype._rolesResources = function(roles){ - var _this = this; - roles = makeArray(roles); +Acl.prototype._rolesResources = function (roles) { + var _this = this + roles = makeArray(roles) - return this._allRoles(roles).then(function(allRoles){ - var result = []; + return this._allRoles(roles).then(function (allRoles) { + var result = [] // check if bluebird.map simplifies this code - return bluebird.all(allRoles.map(function(role){ - return _this.backend.getAsync(_this.options.buckets.resources, role).then(function(resources){ - result = result.concat(resources); - }); - })).then(function(){ - return result; - }); - }); -}; + return bluebird.all(allRoles.map(function (role) { + return _this.backend.getAsync(_this.options.buckets.resources, role).then(function (resources) { + result = result.concat(resources) + }) + })).then(function () { + return result + }) + }) +} // // Returns the permissions for the given resource and set of roles // -Acl.prototype._resourcePermissions = function(roles, resource){ - var _this = this; - - if(roles.length===0){ - return bluebird.resolve([]); - }else{ - return this.backend.unionAsync(allowsBucket(resource), roles).then(function(resourcePermissions){ - return _this._rolesParents(roles).then(function(parents){ - if(parents && parents.length){ - return _this._resourcePermissions(parents, resource).then(function(morePermissions){ - return _.union(resourcePermissions, morePermissions); - }); - }else{ - return resourcePermissions; +Acl.prototype._resourcePermissions = function (roles, resource) { + var _this = this + + if (roles.length === 0) { + return bluebird.resolve([]) + }else { + return this.backend.unionAsync(allowsBucket(resource), roles).then(function (resourcePermissions) { + return _this._rolesParents(roles).then(function (parents) { + if (parents && parents.length) { + return _this._resourcePermissions(parents, resource).then(function (morePermissions) { + return _.union(resourcePermissions, morePermissions) + }) + }else { + return resourcePermissions } - }); - }); + }) + }) } -}; +} // // NOTE: This function will not handle circular dependencies and result in a crash. // -Acl.prototype._checkPermissions = function(roles, resource, permissions){ - var _this = this; +Acl.prototype._checkPermissions = function (roles, resource, permissions) { + var _this = this - return this.backend.unionAsync(allowsBucket(resource), roles).then(function(resourcePermissions){ - if (resourcePermissions.indexOf('*') !== -1){ - return true; - }else{ - permissions = permissions.filter(function(p){ - return resourcePermissions.indexOf(p) === -1; - }); - - if(permissions.length === 0){ - return true; - }else{ - return _this.backend.unionAsync(_this.options.buckets.parents, roles).then(function(parents){ - if(parents && parents.length){ - return _this._checkPermissions(parents, resource, permissions); - }else{ - return false; + return this.backend.unionAsync(allowsBucket(resource), roles).then(function (resourcePermissions) { + if (resourcePermissions.indexOf('*') !== -1) { + return true + }else { + permissions = permissions.filter(function (p) { + return resourcePermissions.indexOf(p) === -1 + }) + + if (permissions.length === 0) { + return true + }else { + return _this.backend.unionAsync(_this.options.buckets.parents, roles).then(function (parents) { + if (parents && parents.length) { + return _this._checkPermissions(parents, resource, permissions) + }else { + return false } - }); + }) } } - }); -}; + }) +} -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Helpers // -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- -function makeArray(arr){ - return Array.isArray(arr) ? arr : [arr]; +function makeArray (arr) { + return Array.isArray(arr) ? arr : [arr] } -function allowsBucket(role){ - return 'allows_'+role; +function allowsBucket (role) { + return 'allows_' + role } -function keyFromAllowsBucket(str) { - return str.replace(/^allows_/, ''); +function keyFromAllowsBucket (str) { + return str.replace(/^allows_/, '') } - // ----------------------------------------------------------------------------------- - -exports = module.exports = Acl; +exports = module.exports = Acl From a2312d509ab79678d6c31ac6ffc75a08c5b2cd3f Mon Sep 17 00:00:00 2001 From: Riyaz Date: Fri, 21 Jul 2017 12:37:42 +0800 Subject: [PATCH 2/2] custom middleware custom middleware --- lib/acl.js | 890 +++++++++++++++++++++++++++-------------------------- 1 file changed, 446 insertions(+), 444 deletions(-) diff --git a/lib/acl.js b/lib/acl.js index 21e0432..4f46bbc 100644 --- a/lib/acl.js +++ b/lib/acl.js @@ -29,21 +29,21 @@ a resource, then he can get exclusive write operation on the locked resource. This lock should expire if the resource has not been accessed in some time. */ -'use strict' +"use strict"; -var _ = require('lodash'), - util = require('util'), - bluebird = require('bluebird'), - contract = require('./contract') +var _ = require('lodash'), + util = require('util'), +bluebird = require('bluebird'), +contract = require('./contract'); -contract.debug = true +contract.debug = true; -var Acl = function (backend, logger, options) { +var Acl = function (backend, logger, options){ contract(arguments) .params('object') - .params('object', 'object') - .params('object', 'object', 'object') - .end() + .params('object','object') + .params('object','object', 'object') + .end(); options = _.extend({ buckets: { @@ -54,21 +54,21 @@ var Acl = function (backend, logger, options) { roles: 'roles', users: 'users' } - }, options) + }, options); - this.logger = logger - this.backend = backend - this.options = options + this.logger = logger; + this.backend = backend; + this.options = options; // Promisify async methods - backend.endAsync = bluebird.promisify(backend.end) - backend.getAsync = bluebird.promisify(backend.get) - backend.cleanAsync = bluebird.promisify(backend.clean) - backend.unionAsync = bluebird.promisify(backend.union) + backend.endAsync = bluebird.promisify(backend.end); + backend.getAsync = bluebird.promisify(backend.get); + backend.cleanAsync = bluebird.promisify(backend.clean); + backend.unionAsync = bluebird.promisify(backend.union); if (backend.unions) { - backend.unionsAsync = bluebird.promisify(backend.unions) + backend.unionsAsync = bluebird.promisify(backend.unions); } -} +}; /** addUserRoles( userId, roles, function(err) ) @@ -80,28 +80,29 @@ var Acl = function (backend, logger, options) { @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.addUserRoles = function (userId, roles, cb) { +Acl.prototype.addUserRoles = function(userId, roles, cb){ contract(arguments) - .params('string|number', 'string|array', 'function') - .params('string|number', 'string|array') - .end() + .params('string|number','string|array','function') + .params('string|number','string|array') + .end(); - var transaction = this.backend.begin() - this.backend.add(transaction, this.options.buckets.meta, 'users', userId) - this.backend.add(transaction, this.options.buckets.users, userId, roles) + var transaction = this.backend.begin(); + this.backend.add(transaction, this.options.buckets.meta, 'users', userId); + this.backend.add(transaction, this.options.buckets.users, userId, roles); if (Array.isArray(roles)) { - var _this = this + var _this = this; - roles.forEach(function (role) { - _this.backend.add(transaction, _this.options.buckets.roles, role, userId) - }) - }else { - this.backend.add(transaction, this.options.buckets.roles, roles, userId) + roles.forEach(function(role) { + _this.backend.add(transaction, _this.options.buckets.roles, role, userId); + }); + } + else { + this.backend.add(transaction, this.options.buckets.roles, roles, userId); } - return this.backend.endAsync(transaction).nodeify(cb) -} + return this.backend.endAsync(transaction).nodeify(cb); +}; /** removeUserRoles( userId, roles, function(err) ) @@ -113,27 +114,28 @@ Acl.prototype.addUserRoles = function (userId, roles, cb) { @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.removeUserRoles = function (userId, roles, cb) { +Acl.prototype.removeUserRoles = function(userId, roles, cb){ contract(arguments) - .params('string|number', 'string|array', 'function') - .params('string|number', 'string|array') - .end() + .params('string|number','string|array','function') + .params('string|number','string|array') + .end(); - var transaction = this.backend.begin() - this.backend.remove(transaction, this.options.buckets.users, userId, roles) + var transaction = this.backend.begin(); + this.backend.remove(transaction, this.options.buckets.users, userId, roles); if (Array.isArray(roles)) { - var _this = this + var _this = this; - roles.forEach(function (role) { - _this.backend.remove(transaction, _this.options.buckets.roles, role, userId) - }) - }else { - this.backend.remove(transaction, this.options.buckets.roles, roles, userId) + roles.forEach(function(role) { + _this.backend.remove(transaction, _this.options.buckets.roles, role, userId); + }); + } + else { + this.backend.remove(transaction, this.options.buckets.roles, roles, userId); } - return this.backend.endAsync(transaction).nodeify(cb) -} + return this.backend.endAsync(transaction).nodeify(cb); +}; /** userRoles( userId, function(err, roles) ) @@ -144,9 +146,9 @@ Acl.prototype.removeUserRoles = function (userId, roles, cb) { @param {Function} Callback called when finished. @return {Promise} Promise resolved with an array of user roles */ -Acl.prototype.userRoles = function (userId, cb) { - return this.backend.getAsync(this.options.buckets.users, userId).nodeify(cb) -} +Acl.prototype.userRoles = function(userId, cb){ + return this.backend.getAsync(this.options.buckets.users, userId).nodeify(cb); +}; /** roleUsers( roleName, function(err, users) ) @@ -156,9 +158,9 @@ Acl.prototype.userRoles = function (userId, cb) { @param {Function} Callback called when finished. @return {Promise} Promise resolved with an array of users */ -Acl.prototype.roleUsers = function (roleName, cb) { - return this.backend.getAsync(this.options.buckets.roles, roleName).nodeify(cb) -} +Acl.prototype.roleUsers = function(roleName, cb){ + return this.backend.getAsync(this.options.buckets.roles, roleName).nodeify(cb); +}; /** hasRole( userId, rolename, function(err, is_in_role) ) @@ -170,11 +172,11 @@ Acl.prototype.roleUsers = function (roleName, cb) { @param {Function} Callback called when finished. @return {Promise} Promise resolved with boolean of whether user is in role */ -Acl.prototype.hasRole = function (userId, rolename, cb) { - return this.userRoles(userId).then(function (roles) { - return roles.indexOf(rolename) != -1 - }).nodeify(cb) -} +Acl.prototype.hasRole = function(userId, rolename, cb){ + return this.userRoles(userId).then(function(roles){ + return roles.indexOf(rolename) != -1; + }).nodeify(cb); +}; /** addRoleParents( role, parents, function(err) ) @@ -186,17 +188,17 @@ Acl.prototype.hasRole = function (userId, rolename, cb) { @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.addRoleParents = function (role, parents, cb) { +Acl.prototype.addRoleParents = function(role, parents, cb){ contract(arguments) - .params('string|number', 'string|array', 'function') - .params('string|number', 'string|array') - .end() + .params('string|number','string|array','function') + .params('string|number','string|array') + .end(); - var transaction = this.backend.begin() - this.backend.add(transaction, this.options.buckets.meta, 'roles', role) - this.backend.add(transaction, this.options.buckets.parents, role, parents) - return this.backend.endAsync(transaction).nodeify(cb) -} + var transaction = this.backend.begin(); + this.backend.add(transaction, this.options.buckets.meta, 'roles', role); + this.backend.add(transaction, this.options.buckets.parents, role, parents); + return this.backend.endAsync(transaction).nodeify(cb); +}; /** removeRoleParents( role, parents, function(err) ) @@ -210,27 +212,27 @@ Acl.prototype.addRoleParents = function (role, parents, cb) { @param {Function} Callback called when finished [optional]. @return {Promise} Promise resolved when finished. */ -Acl.prototype.removeRoleParents = function (role, parents, cb) { +Acl.prototype.removeRoleParents = function(role, parents, cb){ contract(arguments) .params('string', 'string|array', 'function') .params('string', 'string|array') .params('string', 'function') .params('string') - .end() + .end(); if (!cb && _.isFunction(parents)) { - cb = parents - parents = null + cb = parents; + parents = null; } - var transaction = this.backend.begin() + var transaction = this.backend.begin(); if (parents) { - this.backend.remove(transaction, this.options.buckets.parents, role, parents) + this.backend.remove(transaction, this.options.buckets.parents, role, parents); } else { - this.backend.del(transaction, this.options.buckets.parents, role) + this.backend.del(transaction, this.options.buckets.parents, role); } - return this.backend.endAsync(transaction).nodeify(cb) -} + return this.backend.endAsync(transaction).nodeify(cb); +}; /** removeRole( role, function(err) ) @@ -240,31 +242,31 @@ Acl.prototype.removeRoleParents = function (role, parents, cb) { @param {String} Role to be removed @param {Function} Callback called when finished. */ -Acl.prototype.removeRole = function (role, cb) { +Acl.prototype.removeRole = function(role, cb){ contract(arguments) - .params('string', 'function') - .params('string').end() + .params('string','function') + .params('string').end(); - var _this = this + var _this = this; // Note that this is not fully transactional. - return this.backend.getAsync(this.options.buckets.resources, role).then(function (resources) { - var transaction = _this.backend.begin() + return this.backend.getAsync(this.options.buckets.resources, role).then(function(resources){ + var transaction = _this.backend.begin(); - resources.forEach(function (resource) { - var bucket = allowsBucket(resource) - _this.backend.del(transaction, bucket, role) - }) + resources.forEach(function(resource){ + var bucket = allowsBucket(resource); + _this.backend.del(transaction, bucket, role); + }); - _this.backend.del(transaction, _this.options.buckets.resources, role) - _this.backend.del(transaction, _this.options.buckets.parents, role) + _this.backend.del(transaction, _this.options.buckets.resources, role); + _this.backend.del(transaction, _this.options.buckets.parents, role); _this.backend.del(transaction, _this.options.buckets.roles, role) - _this.backend.remove(transaction, _this.options.buckets.meta, 'roles', role) + _this.backend.remove(transaction, _this.options.buckets.meta, 'roles', role); // `users` collection keeps the removed role // because we don't know what users have `role` assigned. - return _this.backend.endAsync(transaction) - }).nodeify(cb) -} + return _this.backend.endAsync(transaction); + }).nodeify(cb); +}; /** removeResource( resource, function(err) ) @@ -275,22 +277,22 @@ Acl.prototype.removeRole = function (role, cb) { @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.removeResource = function (resource, cb) { +Acl.prototype.removeResource = function(resource, cb){ contract(arguments) .params('string', 'function') .params('string') - .end() - - var _this = this - return this.backend.getAsync(this.options.buckets.meta, 'roles').then(function (roles) { - var transaction = _this.backend.begin() - _this.backend.del(transaction, allowsBucket(resource), roles) - roles.forEach(function (role) { - _this.backend.remove(transaction, _this.options.buckets.resources, role, resource) + .end(); + + var _this = this; + return this.backend.getAsync(this.options.buckets.meta, 'roles').then(function(roles){ + var transaction = _this.backend.begin(); + _this.backend.del(transaction, allowsBucket(resource), roles); + roles.forEach(function(role){ + _this.backend.remove(transaction, _this.options.buckets.resources, role, resource); }) - return _this.backend.endAsync(transaction) + return _this.backend.endAsync(transaction); }).nodeify(cb) -} +}; /** allow( roles, resources, permissions, function(err) ) @@ -311,57 +313,58 @@ Acl.prototype.removeResource = function (resource, cb) { @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.allow = function (roles, resources, permissions, cb) { +Acl.prototype.allow = function(roles, resources, permissions, cb){ contract(arguments) - .params('string|array', 'string|array', 'string|array', 'function') - .params('string|array', 'string|array', 'string|array') - .params('array', 'function') + .params('string|array','string|array','string|array','function') + .params('string|array','string|array','string|array') + .params('array','function') .params('array') - .end() + .end(); - if ((arguments.length == 1) || ((arguments.length === 2) && _.isObject(roles) && _.isFunction(resources))) { - return this._allowEx(roles).nodeify(resources) - }else { - var _this = this + if((arguments.length == 1) || ((arguments.length===2)&&_.isObject(roles)&&_.isFunction(resources))){ + return this._allowEx(roles).nodeify(resources); + }else{ + var _this = this; - roles = makeArray(roles) - resources = makeArray(resources) + roles = makeArray(roles); + resources = makeArray(resources); - var transaction = _this.backend.begin() + var transaction = _this.backend.begin(); - _this.backend.add(transaction, _this.options.buckets.meta, 'roles', roles) + _this.backend.add(transaction, _this.options.buckets.meta, 'roles', roles); - resources.forEach(function (resource) { - roles.forEach(function (role) { - _this.backend.add(transaction, allowsBucket(resource), role, permissions) - }) - }) + resources.forEach(function(resource){ + roles.forEach(function(role){ + _this.backend.add(transaction, allowsBucket(resource), role, permissions); + }); + }); - roles.forEach(function (role) { - _this.backend.add(transaction, _this.options.buckets.resources, role, resources) - }) + roles.forEach(function(role){ + _this.backend.add(transaction, _this.options.buckets.resources, role, resources); + }); - return _this.backend.endAsync(transaction).nodeify(cb) + return _this.backend.endAsync(transaction).nodeify(cb); } -} +}; -Acl.prototype.removeAllow = function (role, resources, permissions, cb) { - contract(arguments) - .params('string', 'string|array', 'string|array', 'function') - .params('string', 'string|array', 'string|array') - .params('string', 'string|array', 'function') - .params('string', 'string|array') - .end() - resources = makeArray(resources) - if (cb || (permissions && !_.isFunction(permissions))) { - permissions = makeArray(permissions) - }else { - cb = permissions - permissions = null - } +Acl.prototype.removeAllow = function(role, resources, permissions, cb){ + contract(arguments) + .params('string','string|array','string|array','function') + .params('string','string|array','string|array') + .params('string','string|array','function') + .params('string','string|array') + .end(); + + resources = makeArray(resources); + if(cb || (permissions && !_.isFunction(permissions))){ + permissions = makeArray(permissions); + }else { + cb = permissions; + permissions = null; + } - return this.removePermissions(role, resources, permissions, cb) + return this.removePermissions(role, resources, permissions, cb); } /** @@ -375,36 +378,37 @@ Acl.prototype.removeAllow = function (role, resources, permissions, cb) { @param {String|Array} @param {String|Array} */ -Acl.prototype.removePermissions = function (role, resources, permissions, cb) { - var _this = this - - var transaction = _this.backend.begin() - resources.forEach(function (resource) { - var bucket = allowsBucket(resource) - if (permissions) { - _this.backend.remove(transaction, bucket, role, permissions) - }else { - _this.backend.del(transaction, bucket, role) - _this.backend.remove(transaction, _this.options.buckets.resources, role, resource) +Acl.prototype.removePermissions = function(role, resources, permissions, cb){ + + var _this = this; + + var transaction = _this.backend.begin(); + resources.forEach(function(resource){ + var bucket = allowsBucket(resource); + if(permissions){ + _this.backend.remove(transaction, bucket, role, permissions); + }else{ + _this.backend.del(transaction, bucket, role); + _this.backend.remove(transaction, _this.options.buckets.resources, role, resource); } - }) + }); // Remove resource from role if no rights for that role exists. // Not fully atomic... - return _this.backend.endAsync(transaction).then(function () { - var transaction = _this.backend.begin() - return bluebird.all(resources.map(function (resource) { - var bucket = allowsBucket(resource) - return _this.backend.getAsync(bucket, role).then(function (permissions) { - if (permissions.length == 0) { - _this.backend.remove(transaction, _this.options.buckets.resources, role, resource) + return _this.backend.endAsync(transaction).then(function(){ + var transaction = _this.backend.begin(); + return bluebird.all(resources.map(function(resource){ + var bucket = allowsBucket(resource); + return _this.backend.getAsync(bucket, role).then(function(permissions){ + if(permissions.length==0){ + _this.backend.remove(transaction, _this.options.buckets.resources, role, resource); } - }) - })).then(function () { - return _this.backend.endAsync(transaction) - }) - }).nodeify(cb) -} + }); + })).then(function(){ + return _this.backend.endAsync(transaction); + }); + }).nodeify(cb); +}; /** allowedPermissions( userId, resources, function(err, obj) ) @@ -419,33 +423,33 @@ Acl.prototype.removePermissions = function (role, resources, permissions, cb) { @param {String|Array} resource(s) to ask permissions for. @param {Function} Callback called when finished. */ -Acl.prototype.allowedPermissions = function (userId, resources, cb) { +Acl.prototype.allowedPermissions = function(userId, resources, cb){ if (!userId) - return cb(null, {}) + return cb(null, {}); contract(arguments) .params('string|number', 'string|array', 'function') .params('string|number', 'string|array') - .end() + .end(); if (this.backend.unionsAsync) { - return this.optimizedAllowedPermissions(userId, resources, cb) + return this.optimizedAllowedPermissions(userId, resources, cb); } - var _this = this - resources = makeArray(resources) - - return _this.userRoles(userId).then(function (roles) { - var result = {} - return bluebird.all(resources.map(function (resource) { - return _this._resourcePermissions(roles, resource).then(function (permissions) { - result[resource] = permissions - }) - })).then(function () { - return result - }) - }).nodeify(cb) -} + var _this = this; + resources = makeArray(resources); + + return _this.userRoles(userId).then(function(roles){ + var result = {}; + return bluebird.all(resources.map(function(resource){ + return _this._resourcePermissions(roles, resource).then(function(permissions){ + result[resource] = permissions; + }); + })).then(function(){ + return result; + }); + }).nodeify(cb); +}; /** optimizedAllowedPermissions( userId, resources, function(err, obj) ) @@ -462,39 +466,39 @@ Acl.prototype.allowedPermissions = function (userId, resources, cb) { @param {String|Array} resource(s) to ask permissions for. @param {Function} Callback called when finished. */ -Acl.prototype.optimizedAllowedPermissions = function (userId, resources, cb) { +Acl.prototype.optimizedAllowedPermissions = function(userId, resources, cb){ if (!userId) { - return cb(null, {}) + return cb(null, {}); } contract(arguments) .params('string|number', 'string|array', 'function|undefined') .params('string|number', 'string|array') - .end() + .end(); - resources = makeArray(resources) - var self = this + resources = makeArray(resources); + var self = this; - return this._allUserRoles(userId).then(function (roles) { - var buckets = resources.map(allowsBucket) + return this._allUserRoles(userId).then(function(roles) { + var buckets = resources.map(allowsBucket); if (roles.length === 0) { - var emptyResult = {} - buckets.forEach(function (bucket) { - emptyResult[bucket] = [] - }) - return bluebird.resolve(emptyResult) + var emptyResult = {}; + buckets.forEach(function(bucket) { + emptyResult[bucket] = []; + }); + return bluebird.resolve(emptyResult); } - return self.backend.unionsAsync(buckets, roles) - }).then(function (response) { - var result = {} - Object.keys(response).forEach(function (bucket) { - result[keyFromAllowsBucket(bucket)] = response[bucket] - }) + return self.backend.unionsAsync(buckets, roles); + }).then(function(response) { + var result = {}; + Object.keys(response).forEach(function(bucket) { + result[keyFromAllowsBucket(bucket)] = response[bucket]; + }); - return result - }).nodeify(cb) -} + return result; + }).nodeify(cb); +}; /** isAllowed( userId, resource, permissions, function(err, allowed) ) @@ -507,22 +511,22 @@ Acl.prototype.optimizedAllowedPermissions = function (userId, resources, cb) { @param {String|Array} asked permissions. @param {Function} Callback called wish the result. */ -Acl.prototype.isAllowed = function (userId, resource, permissions, cb) { +Acl.prototype.isAllowed = function(userId, resource, permissions, cb){ contract(arguments) .params('string|number', 'string', 'string|array', 'function') .params('string|number', 'string', 'string|array') - .end() + .end(); - var _this = this + var _this = this; - return this.backend.getAsync(this.options.buckets.users, userId).then(function (roles) { - if (roles.length) { - return _this.areAnyRolesAllowed(roles, resource, permissions) - }else { - return false + return this.backend.getAsync(this.options.buckets.users, userId).then(function(roles){ + if(roles.length){ + return _this.areAnyRolesAllowed(roles, resource, permissions); + }else{ + return false; } - }).nodeify(cb) -} + }).nodeify(cb); +}; /** areAnyRolesAllowed( roles, resource, permissions, function(err, allowed) ) @@ -534,21 +538,21 @@ Acl.prototype.isAllowed = function (userId, resource, permissions, cb) { @param {String|Array} asked permissions. @param {Function} Callback called with the result. */ -Acl.prototype.areAnyRolesAllowed = function (roles, resource, permissions, cb) { +Acl.prototype.areAnyRolesAllowed = function(roles, resource, permissions, cb){ contract(arguments) .params('string|array', 'string', 'string|array', 'function') .params('string|array', 'string', 'string|array') - .end() + .end(); - roles = makeArray(roles) - permissions = makeArray(permissions) + roles = makeArray(roles); + permissions = makeArray(permissions); - if (roles.length === 0) { - return bluebird.resolve(false).nodeify(cb) - }else { - return this._checkPermissions(roles, resource, permissions).nodeify(cb) + if(roles.length===0){ + return bluebird.resolve(false).nodeify(cb); + }else{ + return this._checkPermissions(roles, resource, permissions).nodeify(cb); } -} +}; /** whatResources(role, function(err, {resourceName: [permissions]}) @@ -563,44 +567,44 @@ Acl.prototype.areAnyRolesAllowed = function (roles, resource, permissions, cb) { @param {String[Array} Permissions @param {Function} Callback called wish the result. */ -Acl.prototype.whatResources = function (roles, permissions, cb) { +Acl.prototype.whatResources = function(roles, permissions, cb){ contract(arguments) .params('string|array') - .params('string|array', 'string|array') - .params('string|array', 'function') - .params('string|array', 'string|array', 'function') - .end() - - roles = makeArray(roles) - if (_.isFunction(permissions)) { - cb = permissions - permissions = undefined - }else if (permissions) { - permissions = makeArray(permissions) + .params('string|array','string|array') + .params('string|array','function') + .params('string|array','string|array','function') + .end(); + + roles = makeArray(roles); + if (_.isFunction(permissions)){ + cb = permissions; + permissions = undefined; + }else if(permissions){ + permissions = makeArray(permissions); } - return this.permittedResources(roles, permissions, cb) -} - -Acl.prototype.permittedResources = function (roles, permissions, cb) { - var _this = this - var result = _.isUndefined(permissions) ? {} : [] - return this._rolesResources(roles).then(function (resources) { - return bluebird.all(resources.map(function (resource) { - return _this._resourcePermissions(roles, resource).then(function (p) { - if (permissions) { - var commonPermissions = _.intersection(permissions, p) - if (commonPermissions.length > 0) { - result.push(resource) + return this.permittedResources(roles, permissions, cb); +}; + +Acl.prototype.permittedResources = function(roles, permissions, cb){ + var _this = this; + var result = _.isUndefined(permissions) ? {} : []; + return this._rolesResources(roles).then(function(resources){ + return bluebird.all(resources.map(function(resource){ + return _this._resourcePermissions(roles, resource).then(function(p){ + if(permissions){ + var commonPermissions = _.intersection(permissions, p); + if(commonPermissions.length>0){ + result.push(resource); } - }else { - result[resource] = p + }else{ + result[resource] = p; } - }) - })).then(function () { - return result - }) - }).nodeify(cb) + }); + })).then(function(){ + return result; + }); + }).nodeify(cb); } /** @@ -612,102 +616,100 @@ Acl.prototype.permittedResources = function (roles, permissions, cb) { */ /* Acl.prototype.clean = function(callback){ - var acl = this + var acl = this; this.redis.keys(this.prefix+'*', function(err, keys){ if(keys.length){ acl.redis.del(keys, function(err){ - callback(err) - }) + callback(err); + }); }else{ - callback() + callback(); } - }) -} + }); +}; */ /** Express Middleware */ -Acl.prototype.middleware = function (numPathComponents, userId, actions) { +Acl.prototype.middleware = function(numPathComponents, userId, actions){ contract(arguments) .params() - .params('number', 'string|number|function') - .params('number', 'string|number|function') - .params('number', 'string|number|function', 'string|array') - .end() + .params('number') + .params('number','string|number|function') + .params('number','string|number|function', 'string|array') + .end(); - var acl = this + var acl = this; - function HttpError (errorCode, msg) { - this.errorCode = errorCode - this.message = msg - this.name = this.constructor.name + function HttpError(errorCode, msg){ + this.errorCode = errorCode; + this.message = msg; + this.name = this.constructor.name; - Error.captureStackTrace(this, this.constructor) - this.constructor.prototype.__proto__ = Error.prototype + Error.captureStackTrace(this, this.constructor); + this.constructor.prototype.__proto__ = Error.prototype; } - return function (req, res, next) { + return function(req, res, next){ var _userId = userId, - _actions = actions, - resource = numPathComponents, - url + _actions = actions, + resource, + url; // call function to fetch userId - if (typeof userId === 'function') { - _userId = userId(req, res) + if(typeof userId === 'function'){ + _userId = userId(req, res); } if (!userId) { - if ((req.session) && (req.session.userId)) { - _userId = req.session.userId - }else if ((req.user) && (req.user.id)) { - _userId = req.user.id - }else { - next(new HttpError(401, 'User not authenticated')) - return + if((req.session) && (req.session.userId)){ + _userId = req.session.userId; + }else if((req.user) && (req.user.id)){ + _userId = req.user.id; + }else{ + next(new HttpError(401, 'User not authenticated')); + return; } } // Issue #80 - Additional check if (!_userId) { - next(new HttpError(401, 'User not authenticated')) - return + next(new HttpError(401, 'User not authenticated')); + return; } - - url = req.originalUrl.split('?')[0] - if (!numPathComponents) { - resource = url - }else { - resource = url.split('/').slice(0, numPathComponents + 1).join('/') - } - + url = req.originalUrl.split('?')[0]; + if(!numPathComponents){ + resource = url; + }else{ + resource = url.split('/').slice(0,numPathComponents+1).join('/'); + } - if (!_actions) { - _actions = req.method.toLowerCase() + if(!_actions){ + _actions = req.method.toLowerCase(); } - acl.logger ? acl.logger.debug('Requesting ' + _actions + ' on ' + resource + ' by user ' + _userId) : null + acl.logger?acl.logger.debug('Requesting '+_actions+' on '+resource+' by user '+_userId):null; - acl.isAllowed(_userId, resource, _actions, function (err, allowed) { - if (err) { - next(new Error('Error checking permissions to access resource')) - }else if (allowed === false) { + acl.isAllowed(_userId, resource, _actions, function(err, allowed){ + if (err){ + next(new Error('Error checking permissions to access resource')); + }else if(allowed === false){ if (acl.logger) { - acl.logger.debug('Not allowed ' + _actions + ' on ' + resource + ' by user ' + _userId) - acl.allowedPermissions(_userId, resource, function (err, obj) { - acl.logger.debug('Allowed permissions: ' + util.inspect(obj)) - }) + acl.logger.debug('Not allowed '+_actions+' on '+resource+' by user '+_userId); + acl.allowedPermissions(_userId, resource, function(err, obj){ + acl.logger.debug('Allowed permissions: '+util.inspect(obj)); + }); } - next(new HttpError(403, 'Insufficient permissions to access resource')) - }else { - acl.logger ? acl.logger.debug('Allowed ' + _actions + ' on ' + resource + ' by user ' + _userId) : null - next() + next(new HttpError(403,'Insufficient permissions to access resource')); + }else{ + acl.logger?acl.logger.debug('Allowed '+_actions+' on '+resource+' by user '+_userId):null; + next(); } - }) - } -} + }); + }; +}; Acl.prototype.customMiddleware = function (userId, actions, resource) { contract(arguments) @@ -776,219 +778,219 @@ Acl.prototype.customMiddleware = function (userId, actions, resource) { }) } } + /** Error handler for the Express middleware @param {String} [contentType] (html|json) defaults to plain text */ -Acl.prototype.middleware.errorHandler = function (contentType) { - var method = 'end' +Acl.prototype.middleware.errorHandler = function(contentType){ + var method = 'end'; - if (contentType) { + if(contentType){ switch (contentType) { - case 'json': - method = 'json' - break - case 'html': - method = 'send' - break + case 'json': method = 'json'; break; + case 'html': method = 'send'; break; } } - return function (err, req, res, next) { - if (err.name !== 'HttpError' || !err.errorCode) return next(err) - res.status(err.errorCode)[method](err.message) - } -} + return function(err, req, res, next){ + if(err.name !== 'HttpError' || !err.errorCode) return next(err); + res.status(err.errorCode)[method](err.message); + }; +}; -// ----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- // // Private methods // -// ----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // // Same as allow but accepts a more compact input. // -Acl.prototype._allowEx = function (objs) { - var _this = this - objs = makeArray(objs) - - var demuxed = [] - objs.forEach(function (obj) { - var roles = obj.roles - obj.allows.forEach(function (allow) { +Acl.prototype._allowEx = function(objs){ + var _this = this; + objs = makeArray(objs); + + var demuxed = []; + objs.forEach(function(obj){ + var roles = obj.roles; + obj.allows.forEach(function(allow){ demuxed.push({ - roles: roles, - resources: allow.resources, - permissions: allow.permissions}) - }) - }) + roles:roles, + resources:allow.resources, + permissions:allow.permissions}); + }); + }); - return bluebird.reduce(demuxed, function (values, obj) { - return _this.allow(obj.roles, obj.resources, obj.permissions) - }, null) -} + return bluebird.reduce(demuxed, function(values, obj){ + return _this.allow(obj.roles, obj.resources, obj.permissions); + }, null); +}; // // Returns the parents of the given roles // -Acl.prototype._rolesParents = function (roles) { - return this.backend.unionAsync(this.options.buckets.parents, roles) -} +Acl.prototype._rolesParents = function(roles){ + return this.backend.unionAsync(this.options.buckets.parents, roles); +}; // // Return all roles in the hierarchy including the given roles. // /* Acl.prototype._allRoles = function(roleNames, cb){ - var _this = this, roles + var _this = this, roles; _this._rolesParents(roleNames, function(err, parents){ - roles = _.union(roleNames, parents) + roles = _.union(roleNames, parents); async.whilst( function (){ - return parents.length >0 + return parents.length >0; }, function (cb) { _this._rolesParents(parents, function(err, result){ if(!err){ - roles = _.union(roles, parents) - parents = result + roles = _.union(roles, parents); + parents = result; } - cb(err) - }) + cb(err); + }); }, function(err){ - cb(err, roles) + cb(err, roles); } - ) - }) -} + ); + }); +}; */ // // Return all roles in the hierarchy including the given roles. // -Acl.prototype._allRoles = function (roleNames) { - var _this = this - - return this._rolesParents(roleNames).then(function (parents) { - if (parents.length > 0) { - return _this._allRoles(parents).then(function (parentRoles) { - return _.union(roleNames, parentRoles) - }) - }else { - return roleNames +Acl.prototype._allRoles = function(roleNames){ + var _this = this; + + return this._rolesParents(roleNames).then(function(parents){ + if(parents.length > 0){ + return _this._allRoles(parents).then(function(parentRoles){ + return _.union(roleNames, parentRoles); + }); + }else{ + return roleNames; } - }) -} + }); +}; // // Return all roles in the hierarchy of the given user. // -Acl.prototype._allUserRoles = function (userId) { - var _this = this +Acl.prototype._allUserRoles = function(userId) { + var _this = this; - return this.userRoles(userId).then(function (roles) { + return this.userRoles(userId).then(function(roles) { if (roles && roles.length > 0) { - return _this._allRoles(roles) + return _this._allRoles(roles); } else { - return [] + return []; } - }) -} + }); +}; // // Returns an array with resources for the given roles. // -Acl.prototype._rolesResources = function (roles) { - var _this = this - roles = makeArray(roles) +Acl.prototype._rolesResources = function(roles){ + var _this = this; + roles = makeArray(roles); - return this._allRoles(roles).then(function (allRoles) { - var result = [] + return this._allRoles(roles).then(function(allRoles){ + var result = []; // check if bluebird.map simplifies this code - return bluebird.all(allRoles.map(function (role) { - return _this.backend.getAsync(_this.options.buckets.resources, role).then(function (resources) { - result = result.concat(resources) - }) - })).then(function () { - return result - }) - }) -} + return bluebird.all(allRoles.map(function(role){ + return _this.backend.getAsync(_this.options.buckets.resources, role).then(function(resources){ + result = result.concat(resources); + }); + })).then(function(){ + return result; + }); + }); +}; // // Returns the permissions for the given resource and set of roles // -Acl.prototype._resourcePermissions = function (roles, resource) { - var _this = this - - if (roles.length === 0) { - return bluebird.resolve([]) - }else { - return this.backend.unionAsync(allowsBucket(resource), roles).then(function (resourcePermissions) { - return _this._rolesParents(roles).then(function (parents) { - if (parents && parents.length) { - return _this._resourcePermissions(parents, resource).then(function (morePermissions) { - return _.union(resourcePermissions, morePermissions) - }) - }else { - return resourcePermissions +Acl.prototype._resourcePermissions = function(roles, resource){ + var _this = this; + + if(roles.length===0){ + return bluebird.resolve([]); + }else{ + return this.backend.unionAsync(allowsBucket(resource), roles).then(function(resourcePermissions){ + return _this._rolesParents(roles).then(function(parents){ + if(parents && parents.length){ + return _this._resourcePermissions(parents, resource).then(function(morePermissions){ + return _.union(resourcePermissions, morePermissions); + }); + }else{ + return resourcePermissions; } - }) - }) + }); + }); } -} +}; // // NOTE: This function will not handle circular dependencies and result in a crash. // -Acl.prototype._checkPermissions = function (roles, resource, permissions) { - var _this = this - - return this.backend.unionAsync(allowsBucket(resource), roles).then(function (resourcePermissions) { - if (resourcePermissions.indexOf('*') !== -1) { - return true - }else { - permissions = permissions.filter(function (p) { - return resourcePermissions.indexOf(p) === -1 - }) +Acl.prototype._checkPermissions = function(roles, resource, permissions){ + var _this = this; - if (permissions.length === 0) { - return true - }else { - return _this.backend.unionAsync(_this.options.buckets.parents, roles).then(function (parents) { - if (parents && parents.length) { - return _this._checkPermissions(parents, resource, permissions) - }else { - return false + return this.backend.unionAsync(allowsBucket(resource), roles).then(function(resourcePermissions){ + if (resourcePermissions.indexOf('*') !== -1){ + return true; + }else{ + permissions = permissions.filter(function(p){ + return resourcePermissions.indexOf(p) === -1; + }); + + if(permissions.length === 0){ + return true; + }else{ + return _this.backend.unionAsync(_this.options.buckets.parents, roles).then(function(parents){ + if(parents && parents.length){ + return _this._checkPermissions(parents, resource, permissions); + }else{ + return false; } - }) + }); } } - }) -} + }); +}; -// ----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // // Helpers // -// ----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- -function makeArray (arr) { - return Array.isArray(arr) ? arr : [arr] +function makeArray(arr){ + return Array.isArray(arr) ? arr : [arr]; } -function allowsBucket (role) { - return 'allows_' + role +function allowsBucket(role){ + return 'allows_'+role; } -function keyFromAllowsBucket (str) { - return str.replace(/^allows_/, '') +function keyFromAllowsBucket(str) { + return str.replace(/^allows_/, ''); } + // ----------------------------------------------------------------------------------- -exports = module.exports = Acl + +exports = module.exports = Acl;