Merge pull request #1584 from strongloop/feature/add-more-acl-utils
Enhance the ACL related models
This commit is contained in:
commit
06cece038e
|
@ -496,4 +496,76 @@ module.exports = function(ACL) {
|
|||
if (callback) callback(null, access.permission !== ACL.DENY);
|
||||
});
|
||||
};
|
||||
|
||||
ACL.resolveRelatedModels = function() {
|
||||
if (!this.roleModel) {
|
||||
var reg = this.registry;
|
||||
this.roleModel = reg.getModelByType(loopback.Role);
|
||||
this.roleMappingModel = reg.getModelByType(loopback.RoleMapping);
|
||||
this.userModel = reg.getModelByType(loopback.User);
|
||||
this.applicationModel = reg.getModelByType(loopback.Application);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a principal by type/id
|
||||
* @param {String} type Principal type - ROLE/APP/USER
|
||||
* @param {String|Number} id Principal id or name
|
||||
* @param {Function} cb Callback function
|
||||
*/
|
||||
ACL.resolvePrincipal = function(type, id, cb) {
|
||||
type = type || ACL.ROLE;
|
||||
this.resolveRelatedModels();
|
||||
switch (type) {
|
||||
case ACL.ROLE:
|
||||
this.roleModel.findOne({where: {or: [{name: id}, {id: id}]}}, cb);
|
||||
break;
|
||||
case ACL.USER:
|
||||
this.userModel.findOne(
|
||||
{where: {or: [{username: id}, {email: id}, {id: id}]}}, cb);
|
||||
break;
|
||||
case ACL.APP:
|
||||
this.applicationModel.findOne(
|
||||
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb);
|
||||
break;
|
||||
default:
|
||||
process.nextTick(function() {
|
||||
var err = new Error('Invalid principal type: ' + type);
|
||||
err.statusCode = 400;
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given principal is mapped to the role
|
||||
* @param {String} principalType Principal type
|
||||
* @param {String|*} principalId Principal id/name
|
||||
* @param {String|*} role Role id/name
|
||||
* @param {Function} cb Callback function
|
||||
*/
|
||||
ACL.isMappedToRole = function(principalType, principalId, role, cb) {
|
||||
var self = this;
|
||||
this.resolvePrincipal(principalType, principalId,
|
||||
function(err, principal) {
|
||||
if (err) return cb(err);
|
||||
if (principal != null) {
|
||||
principalId = principal.id;
|
||||
}
|
||||
principalType = principalType || 'ROLE';
|
||||
self.resolvePrincipal('ROLE', role, function(err, role) {
|
||||
if (err || !role) return cb(err, role);
|
||||
self.roleMappingModel.findOne({
|
||||
where: {
|
||||
roleId: role.id,
|
||||
principalType: principalType,
|
||||
principalId: String(principalId)
|
||||
}
|
||||
}, function(err, result) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, !!result);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -17,6 +17,15 @@ module.exports = function(RoleMapping) {
|
|||
RoleMapping.APP = RoleMapping.APPLICATION = 'APP';
|
||||
RoleMapping.ROLE = 'ROLE';
|
||||
|
||||
RoleMapping.resolveRelatedModels = function() {
|
||||
if (!this.userModel) {
|
||||
var reg = this.registry;
|
||||
this.roleModel = reg.getModelByType(loopback.Role);
|
||||
this.userModel = reg.getModelByType(loopback.User);
|
||||
this.applicationModel = reg.getModelByType(loopback.Application);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the application principal
|
||||
* @callback {Function} callback
|
||||
|
@ -24,11 +33,10 @@ module.exports = function(RoleMapping) {
|
|||
* @param {Application} application
|
||||
*/
|
||||
RoleMapping.prototype.application = function(callback) {
|
||||
var registry = this.constructor.registry;
|
||||
this.constructor.resolveRelatedModels();
|
||||
|
||||
if (this.principalType === RoleMapping.APPLICATION) {
|
||||
var applicationModel = this.constructor.Application ||
|
||||
registry.getModelByType('Application');
|
||||
var applicationModel = this.constructor.applicationModel;
|
||||
applicationModel.findById(this.principalId, callback);
|
||||
} else {
|
||||
process.nextTick(function() {
|
||||
|
@ -44,10 +52,9 @@ module.exports = function(RoleMapping) {
|
|||
* @param {User} user
|
||||
*/
|
||||
RoleMapping.prototype.user = function(callback) {
|
||||
var RoleMapping = this.constructor;
|
||||
this.constructor.resolveRelatedModels();
|
||||
if (this.principalType === RoleMapping.USER) {
|
||||
var userModel = RoleMapping.User ||
|
||||
RoleMapping.registry.getModelByType('User');
|
||||
var userModel = this.constructor.userModel;
|
||||
userModel.findById(this.principalId, callback);
|
||||
} else {
|
||||
process.nextTick(function() {
|
||||
|
@ -63,11 +70,10 @@ module.exports = function(RoleMapping) {
|
|||
* @param {User} childUser
|
||||
*/
|
||||
RoleMapping.prototype.childRole = function(callback) {
|
||||
var registry = this.constructor.registry;
|
||||
this.constructor.resolveRelatedModels();
|
||||
|
||||
if (this.principalType === RoleMapping.ROLE) {
|
||||
var roleModel = this.constructor.Role ||
|
||||
registry.getModelByType(loopback.Role);
|
||||
var roleModel = this.constructor.roleModel;
|
||||
roleModel.findById(this.principalId, callback);
|
||||
} else {
|
||||
process.nextTick(function() {
|
||||
|
|
|
@ -6,9 +6,6 @@ var async = require('async');
|
|||
var AccessContext = require('../../lib/access-context').AccessContext;
|
||||
|
||||
var RoleMapping = loopback.RoleMapping;
|
||||
var Role = loopback.Role;
|
||||
var User = loopback.User;
|
||||
var Application = loopback.Application;
|
||||
|
||||
assert(RoleMapping, 'RoleMapping model must be defined before Role model');
|
||||
|
||||
|
@ -31,19 +28,19 @@ module.exports = function(Role) {
|
|||
return new Date();
|
||||
};
|
||||
|
||||
Role.resolveRelatedModels = function() {
|
||||
if (!this.userModel) {
|
||||
var reg = this.registry;
|
||||
this.roleMappingModel = reg.getModelByType(loopback.RoleMapping);
|
||||
this.userModel = reg.getModelByType(loopback.User);
|
||||
this.applicationModel = reg.getModelByType(loopback.Application);
|
||||
}
|
||||
};
|
||||
|
||||
// Set up the connection to users/applications/roles once the model
|
||||
Role.once('dataSourceAttached', function() {
|
||||
var registry = Role.registry;
|
||||
var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
|
||||
var principalTypesToModels = {};
|
||||
Role.once('dataSourceAttached', function(roleModel) {
|
||||
|
||||
principalTypesToModels[RoleMapping.USER] = User;
|
||||
principalTypesToModels[RoleMapping.APPLICATION] = Application;
|
||||
principalTypesToModels[RoleMapping.ROLE] = Role;
|
||||
|
||||
Object.keys(principalTypesToModels).forEach(function(principalType) {
|
||||
var model = principalTypesToModels[principalType];
|
||||
var pluralName = model.pluralModelName.toLowerCase();
|
||||
['users', 'applications', 'roles'].forEach(function(rel) {
|
||||
/**
|
||||
* Fetch all users assigned to this role
|
||||
* @function Role.prototype#users
|
||||
|
@ -62,8 +59,23 @@ module.exports = function(Role) {
|
|||
* @param {object} [query] query object passed to model find call
|
||||
* @param {Function} [callback]
|
||||
*/
|
||||
Role.prototype[pluralName] = function(query, callback) {
|
||||
listByPrincipalType(model, principalType, query, callback);
|
||||
Role.prototype[rel] = function(query, callback) {
|
||||
roleModel.resolveRelatedModels();
|
||||
var relsToModels = {
|
||||
users: roleModel.userModel,
|
||||
applications: roleModel.applicationModel,
|
||||
roles: roleModel
|
||||
};
|
||||
|
||||
var ACL = loopback.ACL;
|
||||
var relsToTypes = {
|
||||
users: ACL.USER,
|
||||
applications: ACL.APP,
|
||||
roles: ACL.ROLE
|
||||
};
|
||||
|
||||
var model = relsToModels[rel];
|
||||
listByPrincipalType(model, relsToTypes[rel], query, callback);
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -81,7 +93,7 @@ module.exports = function(Role) {
|
|||
query = {};
|
||||
}
|
||||
|
||||
roleMappingModel.find({
|
||||
roleModel.roleMappingModel.find({
|
||||
where: {roleId: this.id, principalType: principalType}
|
||||
}, function(err, mappings) {
|
||||
var ids;
|
||||
|
@ -272,7 +284,7 @@ module.exports = function(Role) {
|
|||
context = new AccessContext(context);
|
||||
}
|
||||
|
||||
var registry = this.registry;
|
||||
this.resolveRelatedModels();
|
||||
|
||||
debug('isInRole(): %s', role);
|
||||
context.debug();
|
||||
|
@ -309,7 +321,7 @@ module.exports = function(Role) {
|
|||
return;
|
||||
}
|
||||
|
||||
var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
|
||||
var roleMappingModel = this.roleMappingModel;
|
||||
this.findOne({where: {name: role}}, function(err, result) {
|
||||
if (err) {
|
||||
if (callback) callback(err);
|
||||
|
@ -364,7 +376,7 @@ module.exports = function(Role) {
|
|||
context = new AccessContext(context);
|
||||
}
|
||||
var roles = [];
|
||||
var registry = this.registry;
|
||||
this.resolveRelatedModels();
|
||||
|
||||
var addRole = function(role) {
|
||||
if (role && roles.indexOf(role) === -1) {
|
||||
|
@ -391,7 +403,7 @@ module.exports = function(Role) {
|
|||
});
|
||||
});
|
||||
|
||||
var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
|
||||
var roleMappingModel = this.roleMappingModel;
|
||||
context.principals.forEach(function(p) {
|
||||
// Check against the role mappings
|
||||
var principalType = p.type || undefined;
|
||||
|
|
|
@ -13,6 +13,13 @@ var loopback = require('../../lib/loopback');
|
|||
*/
|
||||
|
||||
module.exports = function(Scope) {
|
||||
Scope.resolveRelatedModels = function() {
|
||||
if (!this.aclModel) {
|
||||
var reg = this.registry;
|
||||
this.aclModel = reg.getModelByType(loopback.ACL);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given scope is allowed to access the model/property
|
||||
* @param {String} scope The scope name
|
||||
|
@ -24,17 +31,17 @@ module.exports = function(Scope) {
|
|||
* @param {AccessRequest} result The access permission
|
||||
*/
|
||||
Scope.checkPermission = function(scope, model, property, accessType, callback) {
|
||||
var ACL = loopback.ACL;
|
||||
var registry = this.registry;
|
||||
assert(ACL,
|
||||
this.resolveRelatedModels();
|
||||
var aclModel = this.aclModel;
|
||||
assert(aclModel,
|
||||
'ACL model must be defined before Scope.checkPermission is called');
|
||||
|
||||
this.findOne({where: {name: scope}}, function(err, scope) {
|
||||
if (err) {
|
||||
if (callback) callback(err);
|
||||
} else {
|
||||
var aclModel = registry.getModelByType(ACL);
|
||||
aclModel.checkPermission(ACL.SCOPE, scope.id, model, property, accessType, callback);
|
||||
aclModel.checkPermission(
|
||||
aclModel.SCOPE, scope.id, model, property, accessType, callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,6 +6,8 @@ var RoleMapping = loopback.RoleMapping;
|
|||
var User = loopback.User;
|
||||
var Application = loopback.Application;
|
||||
var ACL = loopback.ACL;
|
||||
var async = require('async');
|
||||
var expect = require('chai').expect;
|
||||
|
||||
function checkResult(err, result) {
|
||||
// console.log(err, result);
|
||||
|
@ -19,9 +21,18 @@ describe('role model', function() {
|
|||
ds = loopback.createDataSource({connector: 'memory'});
|
||||
// Re-attach the models so that they can have isolated store to avoid
|
||||
// pollutions from other tests
|
||||
ACL.attachTo(ds);
|
||||
User.attachTo(ds);
|
||||
Role.attachTo(ds);
|
||||
RoleMapping.attachTo(ds);
|
||||
Application.attachTo(ds);
|
||||
ACL.roleModel = Role;
|
||||
ACL.roleMappingModel = RoleMapping;
|
||||
ACL.userModel = User;
|
||||
ACL.applicationModel = Application;
|
||||
Role.roleMappingModel = RoleMapping;
|
||||
Role.userModel = User;
|
||||
Role.applicationModel = Application;
|
||||
});
|
||||
|
||||
it('should define role/role relations', function() {
|
||||
|
@ -205,6 +216,134 @@ describe('role model', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isMappedToRole', function() {
|
||||
var user, app, role;
|
||||
|
||||
beforeEach(function(done) {
|
||||
User.create({
|
||||
username: 'john',
|
||||
email: 'john@gmail.com',
|
||||
password: 'jpass'
|
||||
}, function(err, u) {
|
||||
if (err) return done(err);
|
||||
user = u;
|
||||
User.create({
|
||||
username: 'mary',
|
||||
email: 'mary@gmail.com',
|
||||
password: 'mpass'
|
||||
}, function(err, u) {
|
||||
if (err) return done(err);
|
||||
Application.create({
|
||||
name: 'demo'
|
||||
}, function(err, a) {
|
||||
if (err) return done(err);
|
||||
app = a;
|
||||
Role.create({
|
||||
name: 'admin'
|
||||
}, function(err, r) {
|
||||
if (err) return done(err);
|
||||
role = r;
|
||||
var principals = [
|
||||
{
|
||||
principalType: ACL.USER,
|
||||
principalId: user.id
|
||||
},
|
||||
{
|
||||
principalType: ACL.APP,
|
||||
principalId: app.id
|
||||
}
|
||||
];
|
||||
async.each(principals, function(p, done) {
|
||||
role.principals.create(p, done);
|
||||
}, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve user by id', function(done) {
|
||||
ACL.resolvePrincipal(ACL.USER, user.id, function(err, u) {
|
||||
if (err) return done(err);
|
||||
expect(u.id).to.eql(user.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve user by username', function(done) {
|
||||
ACL.resolvePrincipal(ACL.USER, user.username, function(err, u) {
|
||||
if (err) return done(err);
|
||||
expect(u.username).to.eql(user.username);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve user by email', function(done) {
|
||||
ACL.resolvePrincipal(ACL.USER, user.email, function(err, u) {
|
||||
if (err) return done(err);
|
||||
expect(u.email).to.eql(user.email);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve app by id', function(done) {
|
||||
ACL.resolvePrincipal(ACL.APP, app.id, function(err, a) {
|
||||
if (err) return done(err);
|
||||
expect(a.id).to.eql(app.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve app by name', function(done) {
|
||||
ACL.resolvePrincipal(ACL.APP, app.name, function(err, a) {
|
||||
if (err) return done(err);
|
||||
expect(a.name).to.eql(app.name);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should report isMappedToRole by user.username', function(done) {
|
||||
ACL.isMappedToRole(ACL.USER, user.username, 'admin', function(err, flag) {
|
||||
if (err) return done(err);
|
||||
expect(flag).to.eql(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should report isMappedToRole by user.email', function(done) {
|
||||
ACL.isMappedToRole(ACL.USER, user.email, 'admin', function(err, flag) {
|
||||
if (err) return done(err);
|
||||
expect(flag).to.eql(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should report isMappedToRole by user.username for mismatch',
|
||||
function(done) {
|
||||
ACL.isMappedToRole(ACL.USER, 'mary', 'admin', function(err, flag) {
|
||||
if (err) return done(err);
|
||||
expect(flag).to.eql(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should report isMappedToRole by app.name', function(done) {
|
||||
ACL.isMappedToRole(ACL.APP, app.name, 'admin', function(err, flag) {
|
||||
if (err) return done(err);
|
||||
expect(flag).to.eql(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should report isMappedToRole by app.name', function(done) {
|
||||
ACL.isMappedToRole(ACL.APP, app.name, 'admin', function(err, flag) {
|
||||
if (err) return done(err);
|
||||
expect(flag).to.eql(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue