diff --git a/common/models/role.js b/common/models/role.js index e78dc4ed..1415f765 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -8,6 +8,7 @@ var loopback = require('../../lib/loopback'); var debug = require('debug')('loopback:security:role'); var assert = require('assert'); var async = require('async'); +var utils = require('../../lib/utils'); var AccessContext = require('../../lib/access-context').AccessContext; @@ -37,21 +38,40 @@ module.exports = function(Role) { * Fetch all users assigned to this role * @function Role.prototype#users * @param {object} [query] query object passed to model find call - * @param {Function} [callback] + * @callback {Function} [callback] The callback function + * @param {String|Error} err The error string or object + * @param {Array} list The list of users. + * @promise */ /** * Fetch all applications assigned to this role * @function Role.prototype#applications * @param {object} [query] query object passed to model find call - * @param {Function} [callback] + * @callback {Function} [callback] The callback function + * @param {String|Error} err The error string or object + * @param {Array} list The list of applications. + * @promise */ /** * Fetch all roles assigned to this role * @function Role.prototype#roles * @param {object} [query] query object passed to model find call - * @param {Function} [callback] + * @callback {Function} [callback] The callback function + * @param {String|Error} err The error string or object + * @param {Array} list The list of roles. + * @promise */ Role.prototype[rel] = function(query, callback) { + if (!callback) { + if (typeof query === 'function') { + callback = query; + query = {}; + } else { + callback = utils.createPromiseCallback(); + } + } + if (!query) query = {}; + roleModel.resolveRelatedModels(); var relsToModels = { users: roleModel.userModel, @@ -68,6 +88,7 @@ module.exports = function(Role) { var model = relsToModels[rel]; listByPrincipalType(this, model, relsToTypes[rel], query, callback); + return callback.promise; }; }); @@ -166,17 +187,23 @@ module.exports = function(Role) { * @param {Function} modelClass The model class * @param {*} modelId The model ID * @param {*} userId The user ID - * @param {Function} callback Callback function + * @callback {Function} [callback] The callback function + * @param {String|Error} err The error string or object + * @param {Boolean} isOwner True if the user is an owner. + * @promise */ Role.isOwner = function isOwner(modelClass, modelId, userId, callback) { assert(modelClass, 'Model class is required'); + if (!callback) callback = utils.createPromiseCallback(); + debug('isOwner(): %s %s userId: %s', modelClass && modelClass.modelName, modelId, userId); + // No userId is present if (!userId) { process.nextTick(function() { callback(null, false); }); - return; + return callback.promise; } // Is the modelClass User or a subclass of User? @@ -184,7 +211,7 @@ module.exports = function(Role) { process.nextTick(function() { callback(null, matches(modelId, userId)); }); - return; + return callback.promise; } modelClass.findById(modelId, function(err, inst) { @@ -222,6 +249,7 @@ module.exports = function(Role) { } } }); + return callback.promise; }; Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) { @@ -241,11 +269,14 @@ module.exports = function(Role) { * @callback {Function} callback Callback function. * @param {Error} err Error object. * @param {Boolean} isAuthenticated True if the user is authenticated. + * @promise */ Role.isAuthenticated = function isAuthenticated(context, callback) { + if (!callback) callback = utils.createPromiseCallback(); process.nextTick(function() { if (callback) callback(null, context.isAuthenticated()); }); + return callback.promise; }; Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) { @@ -269,12 +300,23 @@ module.exports = function(Role) { * @callback {Function} callback Callback function. * @param {Error} err Error object. * @param {Boolean} isInRole True if the principal is in the specified role. + * @promise */ Role.isInRole = function(role, context, callback) { if (!(context instanceof AccessContext)) { context = new AccessContext(context); } + if (!callback) { + callback = utils.createPromiseCallback(); + // historically, isInRole is returning the Role instance instead of true + // we are preserving that behaviour for callback-based invocation, + // but fixing it when invoked in Promise mode + callback.promise = callback.promise.then(function(isInRole) { + return !!isInRole; + }); + } + this.resolveRelatedModels(); debug('isInRole(): %s', role); @@ -291,7 +333,7 @@ module.exports = function(Role) { callback ); } - return; + return callback.promise; } if (context.principals.length === 0) { @@ -299,7 +341,7 @@ module.exports = function(Role) { process.nextTick(function() { if (callback) callback(null, false); }); - return; + return callback.promise; } var inRole = context.principals.some(function(p) { @@ -315,7 +357,7 @@ module.exports = function(Role) { process.nextTick(function() { if (callback) callback(null, true); }); - return; + return callback.promise; } var roleMappingModel = this.roleMappingModel; @@ -358,6 +400,7 @@ module.exports = function(Role) { if (callback) callback(null, inRole); }); }); + return callback.promise; }; /** @@ -367,13 +410,20 @@ module.exports = function(Role) { * @callback {Function} callback Callback function. * @param {Error} err Error object. * @param {String[]} roles An array of role IDs + * @promise */ Role.getRoles = function(context, options, callback) { - if (!callback && typeof options === 'function') { - callback = options; - options = {}; + if (!callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } else { + callback = utils.createPromiseCallback(); + } } + if (!options) options = {}; + if (!(context instanceof AccessContext)) { context = new AccessContext(context); } @@ -452,6 +502,7 @@ module.exports = function(Role) { debug('getRoles() returns: %j %j', err, roles); if (callback) callback(err, roles); }); + return callback.promise; }; Role.validatesUniquenessOf('name', {message: 'already exists'}); diff --git a/test/role.test.js b/test/role.test.js index c9390c89..dbee2cd0 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -305,6 +305,56 @@ describe('role model', function() { }); }); + it('supports isInRole() returning a Promise', function(done) { + var userData = {name: 'Raymond', email: 'x@y.com', password: 'foobar'}; + User.create(userData, function(err, user) { + if (err) return done(err); + Role.create({name: 'userRole'}, function(err, role) { + if (err) return done(err); + var principalData = { + principalType: RoleMapping.USER, + principalId: user.id, + }; + role.principals.create(principalData, function(err, p) { + if (err) return done(err); + Role.isInRole('userRole', principalData) + .then(function(inRole) { + expect(inRole).to.be.true(); + done(); + }) + .catch(done); + }); + }); + }); + }); + + it('supports getRole() returning a Promise', function(done) { + var userData = {name: 'Raymond', email: 'x@y.com', password: 'foobar'}; + User.create(userData, function(err, user) { + if (err) return done(err); + Role.create({name: 'userRole'}, function(err, role) { + if (err) return done(err); + var principalData = { + principalType: RoleMapping.USER, + principalId: user.id, + }; + role.principals.create(principalData, function(err, p) { + if (err) return done(err); + Role.getRoles(principalData) + .then(function(roles) { + expect(roles).to.eql([ + Role.AUTHENTICATED, + Role.EVERYONE, + role.id, + ]); + done(); + }) + .catch(done); + }); + }); + }); + }); + it('should support owner role resolver', function(done) { Role.registerResolver('returnPromise', function(role, context) { return new Promise(function(resolve) { @@ -730,6 +780,30 @@ describe('role model', function() { }); }); }); + + it('supports Promise API', function(done) { + var userData = {name: 'Raymond', email: 'x@y.com', password: 'foobar'}; + User.create(userData, function(err, user) { + if (err) return done(err); + Role.create({name: 'userRole'}, function(err, role) { + if (err) return done(err); + var principalData = { + principalType: RoleMapping.USER, + principalId: user.id, + }; + role.principals.create(principalData, function(err, p) { + if (err) return done(err); + role.users() + .then(function(users) { + var userIds = users.map(function(u) { return u.id; }); + expect(userIds).to.eql([user.id]); + done(); + }) + .catch(done); + }); + }); + }); + }); }); describe('isOwner', function() { @@ -759,5 +833,19 @@ describe('role model', function() { }); }); }); + + it('supports Promise API', function(done) { + var credentials = {email: 'test@example.com', password: 'pass'}; + User.create(credentials, function(err, user) { + if (err) return done(err); + + Role.isOwner(User, user.id, user.id) + .then(function(result) { + expect(result, 'isOwner result').to.equal(true); + done(); + }) + .catch(done); + }); + }); }); });