Merge pull request #3163 from ebarault/feature/promisify-acl

Add promise support to built-in model ACL
This commit is contained in:
Miroslav Bajtoš 2017-01-31 14:54:26 +01:00 committed by GitHub
commit c099d8c731
3 changed files with 69 additions and 20 deletions

View File

@ -32,6 +32,7 @@
var g = require('../../lib/globalize'); var g = require('../../lib/globalize');
var loopback = require('../../lib/loopback'); var loopback = require('../../lib/loopback');
var utils = require('../../lib/utils');
var async = require('async'); var async = require('async');
var assert = require('assert'); var assert = require('assert');
var debug = require('debug')('loopback:security:acl'); var debug = require('debug')('loopback:security:acl');
@ -321,6 +322,7 @@ module.exports = function(ACL) {
ACL.checkPermission = function checkPermission(principalType, principalId, ACL.checkPermission = function checkPermission(principalType, principalId,
model, property, accessType, model, property, accessType,
callback) { callback) {
if (!callback) callback = utils.createPromiseCallback();
if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) { if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) {
principalId = principalId.toString(); principalId = principalId.toString();
} }
@ -340,9 +342,9 @@ module.exports = function(ACL) {
debug('Permission denied by statically resolved permission'); debug('Permission denied by statically resolved permission');
debug(' Resolved Permission: %j', resolved); debug(' Resolved Permission: %j', resolved);
process.nextTick(function() { process.nextTick(function() {
if (callback) callback(null, resolved); callback(null, resolved);
}); });
return; return callback.promise;
} }
var self = this; var self = this;
@ -350,8 +352,7 @@ module.exports = function(ACL) {
model: model, property: propertyQuery, accessType: accessTypeQuery}}, model: model, property: propertyQuery, accessType: accessTypeQuery}},
function(err, dynACLs) { function(err, dynACLs) {
if (err) { if (err) {
if (callback) callback(err); return callback(err);
return;
} }
acls = acls.concat(dynACLs); acls = acls.concat(dynACLs);
resolved = self.resolvePermission(acls, req); resolved = self.resolvePermission(acls, req);
@ -359,8 +360,9 @@ module.exports = function(ACL) {
var modelClass = self.registry.findModel(model); var modelClass = self.registry.findModel(model);
resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW; resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
} }
if (callback) callback(null, resolved); return callback(null, resolved);
}); });
return callback.promise;
}; };
ACL.prototype.debug = function() { ACL.prototype.debug = function() {
@ -388,6 +390,7 @@ module.exports = function(ACL) {
*/ */
ACL.checkAccessForContext = function(context, callback) { ACL.checkAccessForContext = function(context, callback) {
if (!callback) callback = utils.createPromiseCallback();
var self = this; var self = this;
self.resolveRelatedModels(); self.resolveRelatedModels();
var roleModel = self.roleModel; var roleModel = self.roleModel;
@ -417,10 +420,7 @@ module.exports = function(ACL) {
this.find({where: {model: model.modelName, property: propertyQuery, this.find({where: {model: model.modelName, property: propertyQuery,
accessType: accessTypeQuery}}, function(err, acls) { accessType: accessTypeQuery}}, function(err, acls) {
if (err) { if (err) return callback(err);
if (callback) callback(err);
return;
}
var inRoleTasks = []; var inRoleTasks = [];
acls = acls.concat(staticACLs); acls = acls.concat(staticACLs);
@ -452,10 +452,7 @@ module.exports = function(ACL) {
}); });
async.parallel(inRoleTasks, function(err, results) { async.parallel(inRoleTasks, function(err, results) {
if (err) { if (err) return callback(err, null);
if (callback) callback(err, null);
return;
}
var resolved = self.resolvePermission(effectiveACLs, req); var resolved = self.resolvePermission(effectiveACLs, req);
if (resolved && resolved.permission === ACL.DEFAULT) { if (resolved && resolved.permission === ACL.DEFAULT) {
@ -463,9 +460,10 @@ module.exports = function(ACL) {
} }
debug('---Resolved---'); debug('---Resolved---');
resolved.debug(); resolved.debug();
if (callback) callback(null, resolved); return callback(null, resolved);
}); });
}); });
return callback.promise;
}; };
/** /**
@ -480,7 +478,7 @@ module.exports = function(ACL) {
*/ */
ACL.checkAccessForToken = function(token, model, modelId, method, callback) { ACL.checkAccessForToken = function(token, model, modelId, method, callback) {
assert(token, 'Access token is required'); assert(token, 'Access token is required');
if (!callback) callback = utils.createPromiseCallback();
var context = new AccessContext({ var context = new AccessContext({
accessToken: token, accessToken: token,
model: model, model: model,
@ -490,12 +488,10 @@ module.exports = function(ACL) {
}); });
this.checkAccessForContext(context, function(err, access) { this.checkAccessForContext(context, function(err, access) {
if (err) { if (err) callback(err);
if (callback) callback(err); else callback(null, access.permission !== ACL.DENY);
return;
}
if (callback) callback(null, access.permission !== ACL.DENY);
}); });
return callback.promise;
}; };
ACL.resolveRelatedModels = function() { ACL.resolveRelatedModels = function() {
@ -515,6 +511,7 @@ module.exports = function(ACL) {
* @param {Function} cb Callback function * @param {Function} cb Callback function
*/ */
ACL.resolvePrincipal = function(type, id, cb) { ACL.resolvePrincipal = function(type, id, cb) {
cb = cb || utils.createPromiseCallback();
type = type || ACL.ROLE; type = type || ACL.ROLE;
this.resolveRelatedModels(); this.resolveRelatedModels();
switch (type) { switch (type) {
@ -536,6 +533,7 @@ module.exports = function(ACL) {
cb(err); cb(err);
}); });
} }
return cb.promise;
}; };
/** /**
@ -546,6 +544,7 @@ module.exports = function(ACL) {
* @param {Function} cb Callback function * @param {Function} cb Callback function
*/ */
ACL.isMappedToRole = function(principalType, principalId, role, cb) { ACL.isMappedToRole = function(principalType, principalId, role, cb) {
cb = cb || utils.createPromiseCallback();
var self = this; var self = this;
this.resolvePrincipal(principalType, principalId, this.resolvePrincipal(principalType, principalId,
function(err, principal) { function(err, principal) {
@ -568,5 +567,6 @@ module.exports = function(ACL) {
}); });
}); });
}); });
return cb.promise;
}; };
}; };

View File

@ -90,6 +90,41 @@ describe('security scopes', function() {
}); });
describe('security ACLs', function() { describe('security ACLs', function() {
it('supports checkPermission() returning a promise', function() {
return ACL.create({
principalType: ACL.USER,
principalId: 'u001',
model: 'testModel',
property: ACL.ALL,
accessType: ACL.ALL,
permission: ACL.ALLOW,
})
.then(function() {
return ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL);
})
.then(function(access) {
assert(access.permission === ACL.ALLOW);
});
});
it('supports checkAccessForContext() returning a promise', function() {
var testModel = ds.createModel('testModel', {
acls: [
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW},
],
});
return ACL.checkAccessForContext({
principals: [{type: ACL.USER, id: 'u001'}],
model: 'testModel',
accessType: ACL.ALL,
})
.then(function(access) {
assert(access.permission === ACL.ALLOW);
});
});
it('should order ACL entries based on the matching score', function() { it('should order ACL entries based on the matching score', function() {
var acls = [ var acls = [
{ {

View File

@ -539,6 +539,13 @@ describe('role model', function() {
}); });
}); });
it('supports ACL.resolvePrincipal() returning a promise', function() {
return ACL.resolvePrincipal(ACL.USER, user.id)
.then(function(u) {
expect(u.id).to.eql(user.id);
});
});
it('should resolve user by id', function(done) { it('should resolve user by id', function(done) {
ACL.resolvePrincipal(ACL.USER, user.id, function(err, u) { ACL.resolvePrincipal(ACL.USER, user.id, function(err, u) {
if (err) return done(err); if (err) return done(err);
@ -589,6 +596,13 @@ describe('role model', function() {
}); });
}); });
it('supports ACL.isMappedToRole() returning a promise', function() {
return ACL.isMappedToRole(ACL.USER, user.username, 'admin')
.then(function(flag) {
expect(flag).to.be.true();
});
});
it('should report isMappedToRole by user.username', function(done) { it('should report isMappedToRole by user.username', function(done) {
ACL.isMappedToRole(ACL.USER, user.username, 'admin', function(err, flag) { ACL.isMappedToRole(ACL.USER, user.username, 'admin', function(err, flag) {
if (err) return done(err); if (err) return done(err);