Merge pull request #3146 from strongloop/feature/promisify-role

Promise-ify built-in Role model
This commit is contained in:
Miroslav Bajtoš 2017-01-30 11:32:28 +01:00 committed by GitHub
commit 5fd79b14f4
2 changed files with 151 additions and 12 deletions

View File

@ -8,6 +8,7 @@ var loopback = require('../../lib/loopback');
var debug = require('debug')('loopback:security:role'); var debug = require('debug')('loopback:security:role');
var assert = require('assert'); var assert = require('assert');
var async = require('async'); var async = require('async');
var utils = require('../../lib/utils');
var AccessContext = require('../../lib/access-context').AccessContext; var AccessContext = require('../../lib/access-context').AccessContext;
@ -37,21 +38,40 @@ module.exports = function(Role) {
* Fetch all users assigned to this role * Fetch all users assigned to this role
* @function Role.prototype#users * @function Role.prototype#users
* @param {object} [query] query object passed to model find call * @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 * Fetch all applications assigned to this role
* @function Role.prototype#applications * @function Role.prototype#applications
* @param {object} [query] query object passed to model find call * @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 * Fetch all roles assigned to this role
* @function Role.prototype#roles * @function Role.prototype#roles
* @param {object} [query] query object passed to model find call * @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) { Role.prototype[rel] = function(query, callback) {
if (!callback) {
if (typeof query === 'function') {
callback = query;
query = {};
} else {
callback = utils.createPromiseCallback();
}
}
if (!query) query = {};
roleModel.resolveRelatedModels(); roleModel.resolveRelatedModels();
var relsToModels = { var relsToModels = {
users: roleModel.userModel, users: roleModel.userModel,
@ -68,6 +88,7 @@ module.exports = function(Role) {
var model = relsToModels[rel]; var model = relsToModels[rel];
listByPrincipalType(this, model, relsToTypes[rel], query, callback); 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 {Function} modelClass The model class
* @param {*} modelId The model ID * @param {*} modelId The model ID
* @param {*} userId The user 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) { Role.isOwner = function isOwner(modelClass, modelId, userId, callback) {
assert(modelClass, 'Model class is required'); assert(modelClass, 'Model class is required');
if (!callback) callback = utils.createPromiseCallback();
debug('isOwner(): %s %s userId: %s', modelClass && modelClass.modelName, modelId, userId); debug('isOwner(): %s %s userId: %s', modelClass && modelClass.modelName, modelId, userId);
// No userId is present // No userId is present
if (!userId) { if (!userId) {
process.nextTick(function() { process.nextTick(function() {
callback(null, false); callback(null, false);
}); });
return; return callback.promise;
} }
// Is the modelClass User or a subclass of User? // Is the modelClass User or a subclass of User?
@ -184,7 +211,7 @@ module.exports = function(Role) {
process.nextTick(function() { process.nextTick(function() {
callback(null, matches(modelId, userId)); callback(null, matches(modelId, userId));
}); });
return; return callback.promise;
} }
modelClass.findById(modelId, function(err, inst) { 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) { Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) {
@ -241,11 +269,14 @@ module.exports = function(Role) {
* @callback {Function} callback Callback function. * @callback {Function} callback Callback function.
* @param {Error} err Error object. * @param {Error} err Error object.
* @param {Boolean} isAuthenticated True if the user is authenticated. * @param {Boolean} isAuthenticated True if the user is authenticated.
* @promise
*/ */
Role.isAuthenticated = function isAuthenticated(context, callback) { Role.isAuthenticated = function isAuthenticated(context, callback) {
if (!callback) callback = utils.createPromiseCallback();
process.nextTick(function() { process.nextTick(function() {
if (callback) callback(null, context.isAuthenticated()); if (callback) callback(null, context.isAuthenticated());
}); });
return callback.promise;
}; };
Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) { Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) {
@ -269,12 +300,23 @@ module.exports = function(Role) {
* @callback {Function} callback Callback function. * @callback {Function} callback Callback function.
* @param {Error} err Error object. * @param {Error} err Error object.
* @param {Boolean} isInRole True if the principal is in the specified role. * @param {Boolean} isInRole True if the principal is in the specified role.
* @promise
*/ */
Role.isInRole = function(role, context, callback) { Role.isInRole = function(role, context, callback) {
if (!(context instanceof AccessContext)) { if (!(context instanceof AccessContext)) {
context = new AccessContext(context); 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(); this.resolveRelatedModels();
debug('isInRole(): %s', role); debug('isInRole(): %s', role);
@ -291,7 +333,7 @@ module.exports = function(Role) {
callback callback
); );
} }
return; return callback.promise;
} }
if (context.principals.length === 0) { if (context.principals.length === 0) {
@ -299,7 +341,7 @@ module.exports = function(Role) {
process.nextTick(function() { process.nextTick(function() {
if (callback) callback(null, false); if (callback) callback(null, false);
}); });
return; return callback.promise;
} }
var inRole = context.principals.some(function(p) { var inRole = context.principals.some(function(p) {
@ -315,7 +357,7 @@ module.exports = function(Role) {
process.nextTick(function() { process.nextTick(function() {
if (callback) callback(null, true); if (callback) callback(null, true);
}); });
return; return callback.promise;
} }
var roleMappingModel = this.roleMappingModel; var roleMappingModel = this.roleMappingModel;
@ -358,6 +400,7 @@ module.exports = function(Role) {
if (callback) callback(null, inRole); if (callback) callback(null, inRole);
}); });
}); });
return callback.promise;
}; };
/** /**
@ -367,12 +410,19 @@ module.exports = function(Role) {
* @callback {Function} callback Callback function. * @callback {Function} callback Callback function.
* @param {Error} err Error object. * @param {Error} err Error object.
* @param {String[]} roles An array of role IDs * @param {String[]} roles An array of role IDs
* @promise
*/ */
Role.getRoles = function(context, options, callback) { Role.getRoles = function(context, options, callback) {
if (!callback && typeof options === 'function') { if (!callback) {
if (typeof options === 'function') {
callback = options; callback = options;
options = {}; options = {};
} else {
callback = utils.createPromiseCallback();
} }
}
if (!options) options = {};
if (!(context instanceof AccessContext)) { if (!(context instanceof AccessContext)) {
context = new AccessContext(context); context = new AccessContext(context);
@ -452,6 +502,7 @@ module.exports = function(Role) {
debug('getRoles() returns: %j %j', err, roles); debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles); if (callback) callback(err, roles);
}); });
return callback.promise;
}; };
Role.validatesUniquenessOf('name', {message: 'already exists'}); Role.validatesUniquenessOf('name', {message: 'already exists'});

View File

@ -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) { it('should support owner role resolver', function(done) {
Role.registerResolver('returnPromise', function(role, context) { Role.registerResolver('returnPromise', function(role, context) {
return new Promise(function(resolve) { 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() { 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);
});
});
}); });
}); });