Add constants and more tests
This commit is contained in:
parent
48a0242711
commit
c3a1a85159
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue