Add constants and more tests

This commit is contained in:
Raymond Feng 2013-11-12 10:10:32 -08:00
parent 48a0242711
commit c3a1a85159
4 changed files with 186 additions and 103 deletions

View File

@ -125,12 +125,36 @@ var ACLSchema = {
var ACL = loopback.createModel('ACL', ACLSchema);
module.exports = {
ACL: ACL,
Scope: Scope,
ScopeACL: ScopeACL
ACL.ALL = '*';
ACL.ALLOW = 'ALLOW';
ACL.ALARM = 'ALARM';
ACL.AUDIT = 'AUDIT';
ACL.DENY = 'DENY';
ACL.READ = 'READ';
ACL.WRITE = 'WRITE';
ACL.EXECUTE = 'EXECUTE';
ACL.USER = 'USER';
ACL.APP = ACL.APPLICATION = 'APP';
ACL.ROLE = 'ROLE';
var permissionOrder = {
ALLOW: 1,
ALARM: 2,
AUDIT: 3,
DENY: 4
};
function overridePermission(p1, p2) {
p1 = permissionOrder[p1] ? p1 : ACL.ALLOW;
p2 = permissionOrder[p2] ? p2 : ACL.ALLOW;
var i1 = permissionOrder[p1];
var i2 = permissionOrder[p2];
return i1 > i2 ? p1 : p2;
}
/**
* Check if the given principal is allowed to access the model/property
* @param principalType
@ -142,23 +166,24 @@ module.exports = {
*/
ACL.checkPermission = function (principalType, principalId, model, property, accessType, callback) {
ACL.find({where: {principalType: principalType, principalId: principalId,
model: model, property: {inq: [property, '*']}, accessType: {inq: [accessType, '*']}}},
model: model, property: {inq: [property, ACL.ALL]}, accessType: {inq: [accessType, ACL.ALL]}}},
function (err, acls) {
if (err) {
callback && callback(err);
return;
}
var resolvedPermission = acls.reduce(function (previousValue, currentValue, index, array) {
// If the property is the same or the previous one is '*' (ALL)
if (previousValue.property === currentValue.property || (previousValue.property === '*' && currentValue.property)) {
// If the property is the same or the previous one is ACL.ALL (ALL)
if (previousValue.property === currentValue.property || (previousValue.property === ACL.ALL && currentValue.property)) {
previousValue.property = currentValue.property;
if (previousValue.accessType === currentValue.accessType || (previousValue.accessType === '*' && currentValue.accessType)) {
if (previousValue.accessType === currentValue.accessType || (previousValue.accessType === ACL.ALL && currentValue.accessType)) {
previousValue.accessType = currentValue.accessType;
}
currentValue.permission = overridePermission(previousValue.permission, currentValue.permission);
}
return previousValue;
}, {principalType: principalType, principalId: principalId, model: model, property: '*', accessType: '*', permission: 'Allow'});
callback && callback(resolvedPermission);
}, {principalType: principalType, principalId: principalId, model: model, property: ACL.ALL, accessType: ACL.ALL, permission: ACL.ALLOW});
callback && callback(null, resolvedPermission);
});
};
@ -175,25 +200,33 @@ Scope.checkPermission = function (scope, model, property, accessType, callback)
if (err) {
callback && callback(err);
} else {
scope.resources({where: {model: model, property: {inq: [property, '*']}, accessType: {inq: [accessType, '*']}}}, function (err, resources) {
scope.resources({where: {model: model, property: {inq: [property, ACL.ALL]}, accessType: {inq: [accessType, ACL.ALL]}}}, function (err, resources) {
if (err) {
callback && callback(err);
return;
}
// Try to resolve the permission
var resolvedPermission = resources.reduce(function (previousValue, currentValue, index, array) {
// If the property is the same or the previous one is '*' (ALL)
if (previousValue.property === currentValue.property || (previousValue.property === '*' && currentValue.property)) {
// If the property is the same or the previous one is ACL.ALL (ALL)
if (previousValue.property === currentValue.property || (previousValue.property === ACL.ALL && currentValue.property)) {
previousValue.property = currentValue.property;
if (previousValue.accessType === currentValue.accessType || (previousValue.accessType === '*' && currentValue.accessType)) {
if (previousValue.accessType === currentValue.accessType || (previousValue.accessType === ACL.ALL && currentValue.accessType)) {
previousValue.accessType = currentValue.accessType;
}
currentValue.permission = overridePermission(previousValue.permission, currentValue.permission);
}
return previousValue;
}, {model: model, property: '*', accessType: '*', permission: 'Allow'});
callback && callback(resolvedPermission);
}, {model: model, property: ACL.ALL, accessType: ACL.ALL, permission: ACL.ALLOW});
callback && callback(null, resolvedPermission);
}
);
}
});
};
};
module.exports = {
ACL: ACL,
Scope: Scope,
ScopeACL: ScopeACL
};

View File

@ -31,36 +31,56 @@ var RoleMapping = loopback.createModel('RoleMapping', RoleMappingSchema, {
}
});
RoleMapping.prototype.application = function(callback) {
if(this.principalType === 'application') {
// Principal types
RoleMapping.USER = 'USER';
RoleMapping.APP = RoleMapping.APPLICATION = 'APP';
RoleMapping.ROLE = 'ROLE';
/**
* Get the application principal
* @param callback
*/
RoleMapping.prototype.application = function (callback) {
if (this.principalType === RoleMapping.APPLICATION) {
loopback.Application.findById(this.principalId, callback);
} else {
process.nextTick(function() {
process.nextTick(function () {
callback && callback(null, null);
});
}
};
RoleMapping.prototype.user = function(callback) {
if(this.principalType === 'user') {
/**
* Get the user principal
* @param callback
*/
RoleMapping.prototype.user = function (callback) {
if (this.principalType === RoleMapping.USER) {
loopback.User.findById(this.principalId, callback);
} else {
process.nextTick(function() {
process.nextTick(function () {
callback && callback(null, null);
});
}
};
RoleMapping.prototype.childRole = function(callback) {
if(this.principalType === 'role') {
/**
* Get the child role principal
* @param callback
*/
RoleMapping.prototype.childRole = function (callback) {
if (this.principalType === RoleMapping.ROLE) {
loopback.User.findById(this.principalId, callback);
} else {
process.nextTick(function() {
process.nextTick(function () {
callback && callback(null, null);
});
}
};
/**
* Define the Role model with `hasMany` relation to RoleMapping
*/
var Role = loopback.createModel('Role', RoleSchema, {
relations: {
principals: {
@ -71,39 +91,39 @@ var Role = loopback.createModel('Role', RoleSchema, {
}
});
Role.once('dataSourceAttached', function() {
Role.prototype.users = function(callback) {
RoleMapping.find({where: {roleId: this.id, principalType: 'user'}}, function(err, mappings) {
if(err) {
// Set up the connection to users/applications/roles once the model
Role.once('dataSourceAttached', function () {
Role.prototype.users = function (callback) {
RoleMapping.find({where: {roleId: this.id, principalType: RoleMapping.USER}}, function (err, mappings) {
if (err) {
callback && callback(err);
return;
}
return mappings.map(function(m) {
return mappings.map(function (m) {
return m.principalId;
});
});
};
Role.prototype.applications = function(callback) {
RoleMapping.find({where: {roleId: this.id, principalType: 'application'}}, function(err, mappings) {
if(err) {
Role.prototype.applications = function (callback) {
RoleMapping.find({where: {roleId: this.id, principalType: RoleMapping.APPLICATION}}, function (err, mappings) {
if (err) {
callback && callback(err);
return;
}
return mappings.map(function(m) {
return mappings.map(function (m) {
return m.principalId;
});
});
};
Role.prototype.roles = function(callback) {
RoleMapping.find({where: {roleId: this.id, principalType: 'role'}}, function(err, mappings) {
if(err) {
Role.prototype.roles = function (callback) {
RoleMapping.find({where: {roleId: this.id, principalType: RoleMapping.ROLE}}, function (err, mappings) {
if (err) {
callback && callback(err);
return;
}
return mappings.map(function(m) {
return mappings.map(function (m) {
return m.principalId;
});
});
@ -111,7 +131,6 @@ Role.once('dataSourceAttached', function() {
});
// Special roles
Role.OWNER = '$owner'; // owner of the object
Role.RELATED = "$related"; // any User with a relationship to the object
@ -126,9 +145,9 @@ Role.EVERYONE = "$everyone"; // everyone
* @param role
* @param callback
*/
Role.isInRole = function(principalType, principalId, role, callback) {
Role.findOne({where: {name: role}}, function(err, role) {
if(err) {
Role.isInRole = function (principalType, principalId, role, callback) {
Role.findOne({where: {name: role}}, function (err, role) {
if (err) {
callback && callback(err);
return;
}
@ -138,19 +157,22 @@ Role.isInRole = function(principalType, principalId, role, callback) {
/**
* List roles for a given principal
* @param principalType
* @param principalId
* @param role
* @param callback
* @param {String} principalType
* @param {String|Number} principalId
* @param {Function} callback
*
* @callback callback
* @param err
* @param {String[]} An array of role ids
*/
Role.getRoles = function(principalType, principalId, role, callback) {
RoleMapping.find({where: {principalType: principalType, principalId: principalId}},function(err, mappings) {
if(err) {
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) {
mappings.forEach(function (m) {
roles.push(m.roleId);
});
callback && callback(null, roles);

View File

@ -6,6 +6,11 @@ var ACL = acl.ACL;
var ScopeACL = acl.ScopeACL;
var User = loopback.User;
function checkResult(err, result) {
// console.log(err, result);
assert(!err);
}
describe('security scopes', function () {
it("should allow access to models for the given scope by wildcard", function () {
@ -17,11 +22,12 @@ describe('security scopes', function () {
Scope.create({name: 'user', description: 'access user information'}, function (err, scope) {
// console.log(scope);
scope.resources.create({model: 'user', property: '*', accessType: '*', permission: 'Allow'}, function (err, resource) {
scope.resources.create({model: 'user', property: ACL.ALL, accessType: ACL.ALL, permission: ACL.ALLOW},
function (err, resource) {
// console.log(resource);
Scope.checkPermission('user', 'user', '*', '*', console.log);
Scope.checkPermission('user', 'user', 'name', '*', console.log);
Scope.checkPermission('user', 'user', 'name', 'Read', console.log);
Scope.checkPermission('user', 'user', ACL.ALL, ACL.ALL, checkResult);
Scope.checkPermission('user', 'user', 'name', ACL.ALL, checkResult);
Scope.checkPermission('user', 'user', 'name', ACL.READ, checkResult);
});
});
@ -36,11 +42,12 @@ describe('security scopes', function () {
Scope.create({name: 'user', description: 'access user information'}, function (err, scope) {
// console.log(scope);
scope.resources.create({model: 'user', property: 'name', accessType: 'Read', permission: 'Allow'}, function (err, resource) {
scope.resources.create({model: 'user', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW},
function (err, resource) {
// console.log(resource);
Scope.checkPermission('user', 'user', '*', '*', console.log);
Scope.checkPermission('user', 'user', 'name', '*', console.log);
Scope.checkPermission('user', 'user', 'name', 'Read', console.log);
Scope.checkPermission('user', 'user', ACL.ALL, ACL.ALL, checkResult);
Scope.checkPermission('user', 'user', 'name', ACL.ALL, checkResult);
Scope.checkPermission('user', 'user', 'name', ACL.READ, checkResult);
});
});
@ -54,11 +61,11 @@ describe('security ACLs', function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
ACL.attachTo(ds);
// console.log(Scope.relations);
ACL.create({principalType: 'user', principalId: 'u001', model: 'user', property: '*', accessType: '*', permission: 'Allow'}, function (err, acl) {
ACL.create({principalType: 'user', principalId: 'u001', model: 'user', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {
ACL.checkPermission('user', 'u001', 'user', 'u001', 'Read', console.log);
ACL.checkPermission('user', 'u001', 'user', 'u001', ACL.READ, checkResult);
});

View File

@ -5,56 +5,77 @@ var Role = role.Role;
var RoleMapping = role.RoleMapping;
var User = loopback.User;
describe('security models', function () {
function checkResult(err, result) {
// console.log(err, result);
assert(!err);
}
describe('roles', function () {
describe('role model', function () {
it("should define role/role relations", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
Role.attachTo(ds);
RoleMapping.attachTo(ds);
it("should define role/role relations", function () {
var ds = loopback.createDataSource({connector: 'memory'});
Role.attachTo(ds);
RoleMapping.attachTo(ds);
Role.create({name: 'user'}, function (err, userRole) {
Role.create({name: 'admin'}, function (err, adminRole) {
userRole.principals.create({principalType: 'role', principalId: adminRole.id}, function (err, mapping) {
Role.find(function(err, roles) {
assert.equal(roles.length, 2);
});
RoleMapping.find(function(err, mappings) {
assert.equal(mappings.length, 1);
});
userRole.principals(function(err, principals) {
assert.equal(principals.length, 1);
});
userRole.roles(function(err, roles) {
assert.equal(roles.length, 1);
});
Role.create({name: 'user'}, function (err, userRole) {
Role.create({name: 'admin'}, function (err, adminRole) {
userRole.principals.create({principalType: RoleMapping.ROLE, principalId: adminRole.id}, function (err, mapping) {
Role.find(function (err, roles) {
assert.equal(roles.length, 2);
});
RoleMapping.find(function (err, mappings) {
assert.equal(mappings.length, 1);
assert.equal(mappings[0].principalType, RoleMapping.ROLE);
assert.equal(mappings[0].principalId, adminRole.id);
});
userRole.principals(function (err, principals) {
assert.equal(principals.length, 1);
});
userRole.roles(function (err, roles) {
assert.equal(roles.length, 1);
});
});
});
});
it("should define role/user relations", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
User.attachTo(ds);
Role.attachTo(ds);
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {
console.log('User: ', user.id);
Role.create({name: 'userRole'}, function (err, role) {
role.principals.create({principalType: 'user', principalId: user.id}, function (err, p) {
Role.find(console.log);
role.principals(console.log);
role.users(console.log);
});
});
});
});
});
it("should define role/user relations", function () {
var ds = loopback.createDataSource({connector: 'memory'});
User.attachTo(ds);
Role.attachTo(ds);
RoleMapping.attachTo(ds);
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {
// console.log('User: ', user.id);
Role.create({name: 'userRole'}, function (err, role) {
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) {
Role.find(function(err, roles) {
assert(!err);
assert.equal(roles.length, 1);
assert.equal(roles[0].name, 'userRole');
});
role.principals(function(err, principals) {
assert(!err);
// console.log(principals);
assert.equal(principals.length, 1);
assert.equal(principals[0].principalType, RoleMapping.USER);
assert.equal(principals[0].principalId, user.id);
});
role.users(function(err, users) {
assert(!err);
assert.equal(users.length, 1);
assert.equal(users[0].principalType, RoleMapping.USER);
assert.equal(users[0].principalId, user.id);
});
});
});
});
});
});