From c3a1a8515980533d3490e864eac77e2a25bb3990 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 12 Nov 2013 10:10:32 -0800 Subject: [PATCH] Add constants and more tests --- lib/models/acl.js | 67 ++++++++++++++++++++++-------- lib/models/role.js | 92 +++++++++++++++++++++++++---------------- test/acl.test.js | 29 ++++++++----- test/role.test.js | 101 +++++++++++++++++++++++++++------------------ 4 files changed, 186 insertions(+), 103 deletions(-) diff --git a/lib/models/acl.js b/lib/models/acl.js index 58282c4b..819d1985 100644 --- a/lib/models/acl.js +++ b/lib/models/acl.js @@ -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); } ); } }); -}; \ No newline at end of file +}; + + +module.exports = { + ACL: ACL, + Scope: Scope, + ScopeACL: ScopeACL +}; diff --git a/lib/models/role.js b/lib/models/role.js index b0aaf58f..4e1d7aa7 100644 --- a/lib/models/role.js +++ b/lib/models/role.js @@ -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); diff --git a/test/acl.test.js b/test/acl.test.js index e5d0731a..7a9b44fe 100644 --- a/test/acl.test.js +++ b/test/acl.test.js @@ -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); }); diff --git a/test/role.test.js b/test/role.test.js index 93797e2f..f94c0cce 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -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); + }); + }); + }); + }); + + }); + }); +