Skip to content

Commit

Permalink
Implement optimized allowedPermissions, fixing some bugs along the way.
Browse files Browse the repository at this point in the history
  • Loading branch information
GabeIsman committed Mar 23, 2016
1 parent 71bb0eb commit 354f6cc
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 6 deletions.
81 changes: 76 additions & 5 deletions lib/acl.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var Acl = function (backend, logger, options){
backend.getAsync = bluebird.promisify(backend.get);
backend.cleanAsync = bluebird.promisify(backend.clean);
backend.unionAsync = bluebird.promisify(backend.union);
backend.unionsAsync = bluebird.promisify(backend.unions);
};

/**
Expand Down Expand Up @@ -424,13 +425,17 @@ Acl.prototype.removePermissions = function(role, resources, permissions, 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();

if (this.backend.unions) {
return this.optimizedAllowedPermissions(userId, resources, cb);
}

var _this = this;
resources = makeArray(resources);

Expand All @@ -446,6 +451,55 @@ Acl.prototype.allowedPermissions = function(userId, resources, cb){
}).nodeify(cb);
};

/**
optimizedAllowedPermissions( userId, resources, function(err, obj) )
Returns all the allowable permissions a given user have to
access the given resources.
It returns a map of resource name to a list of permissions for that resource.
This is the same as allowedPermissions, it just takes advantage of the unions
function if available to reduce the number of backend queries.
@param {String|Number} User id.
@param {String|Array} resource(s) to ask permissions for.
@param {Function} Callback called when finished.
*/
Acl.prototype.optimizedAllowedPermissions = function(userId, resources, cb){
if (!userId) {
return cb(null, {});
}

contract(arguments)
.params('string|number', 'string|array', 'function|undefined')
.params('string|number', 'string|array')
.end();

resources = makeArray(resources);
var self = this;

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);
}

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);
};

/**
isAllowed( userId, resource, permissions, function(err, allowed) )
Expand Down Expand Up @@ -644,7 +698,7 @@ Acl.prototype.middleware = function(numPathComponents, userId, actions){
}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.allowedPermissions(_userId, resource, function(err, obj){
acl.logger.debug('Allowed permissions: '+util.inspect(obj));
});
}
Expand Down Expand Up @@ -748,7 +802,7 @@ Acl.prototype._allRoles = function(roleNames, cb){
// Return all roles in the hierarchy including the given roles.
//
Acl.prototype._allRoles = function(roleNames){
var _this = this, roles;
var _this = this;

return this._rolesParents(roleNames).then(function(parents){
if(parents.length > 0){
Expand All @@ -761,6 +815,21 @@ Acl.prototype._allRoles = function(roleNames){
});
};

//
// Return all roles in the hierarchy of the given user.
//
Acl.prototype._allUserRoles = function(userId) {
var _this = this;

return this.userRoles(userId).then(function(roles) {
if (roles && roles.length > 0) {
return _this._allRoles(roles);
} else {
return [];
}
});
};

//
// Returns an array with resources for the given roles.
//
Expand Down Expand Up @@ -848,10 +917,12 @@ function allowsBucket(role){
return 'allows_'+role;
}

function keyFromAllowsBucket(str) {
return str.replace(/^allows_/, '');
}


// -----------------------------------------------------------------------------------


exports = module.exports = Acl;


14 changes: 13 additions & 1 deletion lib/redis-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,25 @@ RedisBackend.prototype = {
var redisKeys = {};
var batch = this.redis.batch();
var self = this;

buckets.forEach(function(bucket) {
redisKeys[bucket] = self.bucketKey(bucket, keys);
batch.sunion(redisKeys[bucket], noop);
});

batch.exec(function(err, replies) {
var result = Array.isArray(replies) ? _.zipObject(buckets, replies) : {};
if (!Array.isArray(replies)) {
return {};
}

var result = {};
replies.forEach(function(reply, index) {
if (reply instanceof Error) {
throw reply;
}

result[buckets[index]] = reply;
});
cb(err, result);
});
},
Expand Down

0 comments on commit 354f6cc

Please sign in to comment.