Skip to content

Commit 354f6cc

Browse files
committed
Implement optimized allowedPermissions, fixing some bugs along the way.
1 parent 71bb0eb commit 354f6cc

File tree

2 files changed

+89
-6
lines changed

2 files changed

+89
-6
lines changed

lib/acl.js

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var Acl = function (backend, logger, options){
6565
backend.getAsync = bluebird.promisify(backend.get);
6666
backend.cleanAsync = bluebird.promisify(backend.clean);
6767
backend.unionAsync = bluebird.promisify(backend.union);
68+
backend.unionsAsync = bluebird.promisify(backend.unions);
6869
};
6970

7071
/**
@@ -424,13 +425,17 @@ Acl.prototype.removePermissions = function(role, resources, permissions, cb){
424425
*/
425426
Acl.prototype.allowedPermissions = function(userId, resources, cb){
426427
if (!userId)
427-
return cb(null, []);
428+
return cb(null, {});
428429

429430
contract(arguments)
430431
.params('string|number', 'string|array', 'function')
431432
.params('string|number', 'string|array')
432433
.end();
433434

435+
if (this.backend.unions) {
436+
return this.optimizedAllowedPermissions(userId, resources, cb);
437+
}
438+
434439
var _this = this;
435440
resources = makeArray(resources);
436441

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

454+
/**
455+
optimizedAllowedPermissions( userId, resources, function(err, obj) )
456+
457+
Returns all the allowable permissions a given user have to
458+
access the given resources.
459+
460+
It returns a map of resource name to a list of permissions for that resource.
461+
462+
This is the same as allowedPermissions, it just takes advantage of the unions
463+
function if available to reduce the number of backend queries.
464+
465+
@param {String|Number} User id.
466+
@param {String|Array} resource(s) to ask permissions for.
467+
@param {Function} Callback called when finished.
468+
*/
469+
Acl.prototype.optimizedAllowedPermissions = function(userId, resources, cb){
470+
if (!userId) {
471+
return cb(null, {});
472+
}
473+
474+
contract(arguments)
475+
.params('string|number', 'string|array', 'function|undefined')
476+
.params('string|number', 'string|array')
477+
.end();
478+
479+
resources = makeArray(resources);
480+
var self = this;
481+
482+
return this._allUserRoles(userId).then(function(roles) {
483+
var buckets = resources.map(allowsBucket);
484+
if (roles.length === 0) {
485+
var emptyResult = {};
486+
buckets.forEach(function(bucket) {
487+
emptyResult[bucket] = [];
488+
});
489+
return bluebird.resolve(emptyResult);
490+
}
491+
492+
return self.backend.unionsAsync(buckets, roles);
493+
}).then(function(response) {
494+
var result = {};
495+
Object.keys(response).forEach(function(bucket) {
496+
result[keyFromAllowsBucket(bucket)] = response[bucket];
497+
});
498+
499+
return result;
500+
}).nodeify(cb);
501+
};
502+
449503
/**
450504
isAllowed( userId, resource, permissions, function(err, allowed) )
451505
@@ -644,7 +698,7 @@ Acl.prototype.middleware = function(numPathComponents, userId, actions){
644698
}else if(allowed === false){
645699
if (acl.logger) {
646700
acl.logger.debug('Not allowed '+_actions+' on '+resource+' by user '+_userId);
647-
acl.allowedPermissions(_userId, resource, function(err, obj){
701+
acl.allowedPermissions(_userId, resource, function(err, obj){
648702
acl.logger.debug('Allowed permissions: '+util.inspect(obj));
649703
});
650704
}
@@ -748,7 +802,7 @@ Acl.prototype._allRoles = function(roleNames, cb){
748802
// Return all roles in the hierarchy including the given roles.
749803
//
750804
Acl.prototype._allRoles = function(roleNames){
751-
var _this = this, roles;
805+
var _this = this;
752806

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

818+
//
819+
// Return all roles in the hierarchy of the given user.
820+
//
821+
Acl.prototype._allUserRoles = function(userId) {
822+
var _this = this;
823+
824+
return this.userRoles(userId).then(function(roles) {
825+
if (roles && roles.length > 0) {
826+
return _this._allRoles(roles);
827+
} else {
828+
return [];
829+
}
830+
});
831+
};
832+
764833
//
765834
// Returns an array with resources for the given roles.
766835
//
@@ -848,10 +917,12 @@ function allowsBucket(role){
848917
return 'allows_'+role;
849918
}
850919

920+
function keyFromAllowsBucket(str) {
921+
return str.replace(/^allows_/, '');
922+
}
923+
851924

852925
// -----------------------------------------------------------------------------------
853926

854927

855928
exports = module.exports = Acl;
856-
857-

lib/redis-backend.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,25 @@ RedisBackend.prototype = {
7171
var redisKeys = {};
7272
var batch = this.redis.batch();
7373
var self = this;
74+
7475
buckets.forEach(function(bucket) {
7576
redisKeys[bucket] = self.bucketKey(bucket, keys);
7677
batch.sunion(redisKeys[bucket], noop);
7778
});
7879

7980
batch.exec(function(err, replies) {
80-
var result = Array.isArray(replies) ? _.zipObject(buckets, replies) : {};
81+
if (!Array.isArray(replies)) {
82+
return {};
83+
}
84+
85+
var result = {};
86+
replies.forEach(function(reply, index) {
87+
if (reply instanceof Error) {
88+
throw reply;
89+
}
90+
91+
result[buckets[index]] = reply;
92+
});
8193
cb(err, result);
8294
});
8395
},

0 commit comments

Comments
 (0)