From 178674ec9ac0bda73f362caaebbd8e91ba523dff Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 11 Dec 2013 09:06:21 -0800 Subject: [PATCH] Enhance getRoles() to support smart roles --- lib/models/role.js | 95 +++++++++++++++++++++++++++++++++++----------- test/role.test.js | 26 +++++++++---- 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/lib/models/role.js b/lib/models/role.js index 0323fb29..1bd4447f 100644 --- a/lib/models/role.js +++ b/lib/models/role.js @@ -1,5 +1,7 @@ var loopback = require('../loopback'); var debug = require('debug')('role'); +var assert = require('assert'); +var async = require('async'); // Role model var RoleSchema = { @@ -167,7 +169,7 @@ Role.registerResolver(Role.OWNER, function(role, context, callback) { var modelClass = context.model; var id = context.id; var userId = context.userId || context.principalId; - isOwner(modelClass, id, userId, callback); + Role.isOwner(modelClass, id, userId, callback); }); function isUserClass(modelClass) { @@ -175,8 +177,16 @@ function isUserClass(modelClass) { modelClass.prototype instanceof loopback.User; } -function isOwner(modelClass, id, userId, callback) { - debug('isOwner(): %s %s %s', modelClass && modelClass.modelName, id, userId); +/** + * Check if a given userId is the owner the model instance + * @param {Function} modelClass The model class + * @param {*} modelId The model id + * @param {*) userId The user id + * @param {Function} callback + */ +Role.isOwner = function isOwner(modelClass, modelId, userId, callback) { + assert(modelClass, 'Model class is required'); + debug('isOwner(): %s %s %s', modelClass && modelClass.modelName, modelId, userId); // No userId is present if(!userId) { process.nextTick(function() { @@ -188,12 +198,12 @@ function isOwner(modelClass, id, userId, callback) { // Is the modelClass User or a subclass of User? if(isUserClass(modelClass)) { process.nextTick(function() { - callback(null, id === userId); + callback(null, modelId === userId); }); return; } - modelClass.findById(id, function(err, inst) { + modelClass.findById(modelId, function(err, inst) { if(err || !inst) { callback && callback(err, false); return; @@ -222,7 +232,7 @@ function isOwner(modelClass, id, userId, callback) { callback && callback(null, false); } }); -} +}; Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) { if(!context) { @@ -231,15 +241,19 @@ Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) { }); return; } - var userId = context.principalId; - isAuthenticated(userId, callback); + Role.isAuthenticated(context, callback); }); -function isAuthenticated(userId, callback) { +/** + * Check if the user id is authenticated + * @param {Object} context The security context + * @param {Function} callback The callback function + */ +Role.isAuthenticated = function isAuthenticated(context, callback) { process.nextTick(function() { - callback && callback(null, !!userId); + callback && callback(null, !!context.principalId); }); -} +}; Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) { process.nextTick(function() { @@ -304,25 +318,60 @@ Role.isInRole = function (role, context, callback) { /** * List roles for a given principal - * @param {String} principalType - * @param {String|Number} principalId + * @param {Object} context The security context * @param {Function} callback * * @callback callback * @param err * @param {String[]} An array of role ids */ -Role.getRoles = function (principalType, principalId, callback) { - RoleMapping.find({where: {principalType: principalType, principalId: principalId}}, function (err, mappings) { - if (err) { - callback && callback(err); - return; - } - var roles = []; - mappings.forEach(function (m) { - roles.push(m.roleId); +Role.getRoles = function (context, callback) { + debug('getRoles(): %j', context); + var roles = []; + + // Check against the smart roles + var inRoleTasks = []; + Object.keys(Role.resolvers).forEach(function (role) { + inRoleTasks.push(function (done) { + Role.isInRole(role, context, function (err, inRole) { + if (!err && inRole) { + if (roles.indexOf(role) === -1) { + roles.push(role); + } + done(); + } else { + done(err, null); + } + }); }); - callback && callback(null, roles); + }); + + // Check against the role mappings + var principalType = context.principalType || undefined; + var principalId = context.principalId || undefined; + + if (principalType && principalId) { + // Please find() treat undefined matches all values + inRoleTasks.push(function (done) { + RoleMapping.find({where: {principalType: principalType, + principalId: principalId}}, function (err, mappings) { + if (err) { + done && done(err); + return; + } + mappings.forEach(function (m) { + if (roles.indexOf(m.roleId) === -1) { + roles.push(m.roleId); + } + }); + done && done(); + }); + }); + } + + async.parallel(inRoleTasks, function (err, results) { + debug('getRoles() return: %j %j', err, results); + callback && callback(err, roles); }); }; diff --git a/test/role.test.js b/test/role.test.js index c597c365..4417fd10 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -85,17 +85,27 @@ describe('role model', function () { assert(!err && exists === false); }); - Role.getRoles(RoleMapping.USER, user.id, function (err, roles) { - assert.equal(roles.length, 1); - assert.equal(roles[0], role.id); + Role.getRoles({principalType: RoleMapping.USER, principalId: user.id}, function (err, roles) { + assert.equal(roles.length, 3); // everyone, authenticated, userRole + assert(roles.indexOf(role.id) >=0); + assert(roles.indexOf(Role.EVERYONE) >=0); + assert(roles.indexOf(Role.AUTHENTICATED) >=0); }); - Role.getRoles(RoleMapping.APP, user.id, function (err, roles) { - assert.equal(roles.length, 0); + Role.getRoles({principalType: RoleMapping.APP, principalId: user.id}, function (err, roles) { + assert.equal(roles.length, 2); + assert(roles.indexOf(Role.EVERYONE) >=0); + assert(roles.indexOf(Role.AUTHENTICATED) >=0); }); - Role.getRoles(RoleMapping.USER, 100, function (err, roles) { - assert.equal(roles.length, 0); + Role.getRoles({principalType: RoleMapping.USER, principalId: 100}, function (err, roles) { + assert.equal(roles.length, 2); + assert(roles.indexOf(Role.EVERYONE) >=0); + assert(roles.indexOf(Role.AUTHENTICATED) >=0); + }); + Role.getRoles({principalType: RoleMapping.USER, principalId: null}, function (err, roles) { + assert.equal(roles.length, 2); + assert(roles.indexOf(Role.EVERYONE) >=0); + assert(roles.indexOf(Role.UNAUTHENTICATED) >=0); }); - }); }); });