From 67b934357be77167e7f31bc7a1a4a2992e5ff12a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sat, 9 Nov 2013 22:22:16 -0800 Subject: [PATCH] Start to build the ACL models --- lib/loopback.js | 2 +- lib/models/acl.js | 160 ++++++++++++++++++++++++++++++--------------- lib/models/role.js | 42 ++++++++---- test/acl.test.js | 28 ++++++++ test/role.test.js | 41 ++++++++++++ 5 files changed, 210 insertions(+), 63 deletions(-) create mode 100644 test/acl.test.js create mode 100644 test/role.test.js diff --git a/lib/loopback.js b/lib/loopback.js index 423a3f99..328f2ffa 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -90,7 +90,7 @@ loopback.errorHandler.title = 'Loopback'; */ loopback.createDataSource = function (name, options) { - var ds = new DataSource(name, options); + var ds = new DataSource(name, options, loopback.Model.dataSource); ds.createModel = function (name, properties, settings) { var ModelCtor = loopback.createModel(name, properties, settings); ModelCtor.attachTo(ds); diff --git a/lib/models/acl.js b/lib/models/acl.js index b3edeb83..b639d31a 100644 --- a/lib/models/acl.js +++ b/lib/models/acl.js @@ -1,14 +1,14 @@ /** -Schema ACL options + Schema ACL options -Object level permissions, for example, an album owned by a user + Object level permissions, for example, an album owned by a user -Factors to be authorized against: + Factors to be authorized against: -* model name: Album -* model instance properties: userId of the album, friends, shared -* methods -* app and/or user ids/roles + * model name: Album + * model instance properties: userId of the album, friends, shared + * methods + * app and/or user ids/roles ** loggedIn ** roles ** userId @@ -17,69 +17,127 @@ Factors to be authorized against: ** everyone ** relations: owner/friend/granted -Class level permissions, for example, Album + Class level permissions, for example, Album * model name: Album * methods -URL/Route level permissions + URL/Route level permissions * url pattern * application id * ip addresses * http headers -Map to oAuth 2.0 scopes + Map to oAuth 2.0 scopes -*/ + */ -var loopback = require('loopback'); +var loopback = require('../loopback'); -var ACLEntrySchema = { - /** - * Type of the principal - Application/User/Role - */ - principalType: String, - /** - * Id of the principal - such as appId, userId or roleId - */ - principalId: String, - - /** - * Name of the access type - READ/WRITE/EXEC - */ - accessType: String, - - /** - * ALARM - Generate an alarm, in a system dependent way, the access specified in the permissions component of the ACL entry. - * ALLOW - Explicitly grants access to the resource. - * AUDIT - Log, in a system dependent way, the access specified in the permissions component of the ACL entry. - * DENY - Explicitly denies access to the resource. - */ - permission: String +var ScopeSchema = { + name: {type: String, required: true}, + description: String }; -var AccessSchema = { - publicReadAccess: Boolean, - publicWriteAccess: Boolean, - publicExecAccess: Boolean, - permissions: [ACLEntrySchema] +var ScopeResourceAccessSchema = { + model: String, // The name of the model + property: String, // The name of the property, method, scope, or relation + + /** + * Name of the access type - READ/WRITE/EXEC + */ + accessType: String, + + /** + * ALARM - Generate an alarm, in a system dependent way, the access specified in the permissions component of the ACL entry. + * ALLOW - Explicitly grants access to the resource. + * AUDIT - Log, in a system dependent way, the access specified in the permissions component of the ACL entry. + * DENY - Explicitly denies access to the resource. + */ + permission: String, + scopeId: Number }; +var ScopeResourceAccess = loopback.createModel('ScopeResourceAccess', ScopeResourceAccessSchema, { + relations: { + scope: { + type: 'belongsTo', + model: 'Scope', + foreignKey: 'scopeId' + } + } +}); + +/** + * Scope has many resource access entries + * @type {createModel|*} + */ +var Scope = loopback.createModel('Scope', ScopeSchema, { + relations: { + resources: { + type: 'hasMany', + model: 'ScopeResourceAccess', + foreignKey: 'scopeId' + } + } +}); + var ACLSchema = { - /** - * Resource - */ - model: String, // The name of the model - property: String, // The name of the property - method: String, // The name of the method + model: String, // The name of the model + property: String, // The name of the property, method, scope, or relation - access: AccessSchema, // The access + /** + * Name of the access type - READ/WRITE/EXEC + */ + accessType: String, - status: String, - created: Date, - modified: Date + /** + * ALARM - Generate an alarm, in a system dependent way, the access specified in the permissions component of the ACL entry. + * ALLOW - Explicitly grants access to the resource. + * AUDIT - Log, in a system dependent way, the access specified in the permissions component of the ACL entry. + * DENY - Explicitly denies access to the resource. + */ + permission: String, + /** + * Type of the principal - Application/User/Role + */ + principalType: String, + /** + * Id of the principal - such as appId, userId or roleId + */ + principalId: String }; - var ACL = loopback.createModel('ACL', ACLSchema); -module.exports = ACL; \ No newline at end of file +module.exports = { + ACL: ACL, + Scope: Scope, + ScopeResourceAccess: ScopeResourceAccess +}; + +Scope.isAllowed = function (scope, model, property, accessType, callback) { + Scope.findOne({where: {name: scope}}, function (err, scope) { + if (err) { + callback && callback(err); + } else { + scope.resources({where: {model: model, property: {inq: [property, '*']}, accessType: {inq: [accessType, '*']}}}, function (err, resources) + { + if (err) { + callback && callback(err); + } else { + console.log('Resources: ', resources); + for (var r = 0; r < resources.length; r++) { + if (resources[r].permission === 'Allow') { + callback && callback(null, true); + return; + } + } + callback && callback(null, false); + } + + } + ) + ; + } + }); +}; \ No newline at end of file diff --git a/lib/models/role.js b/lib/models/role.js index e39ef688..9c1170d9 100644 --- a/lib/models/role.js +++ b/lib/models/role.js @@ -1,23 +1,43 @@ -var loopback = require('loopback'); +var loopback = require('../loopback'); // Role model var RoleSchema = { - id: {type: String, id: true}, // Id - name: {type: String, required: true}, // The name of a role - description: String, // Description - roles: [String], // A role can be an aggregate of other roles - users: [String], // A role contains a list of user ids + id: {type: String, id: true}, // Id + name: {type: String, required: true}, // The name of a role + description: String, // Description + // roles: [String], // A role can be an aggregate of other roles + // users: [String], // A role contains a list of user ids - // Timestamps - created: {type: Date, default: Date}, - modified: {type: Date, default: Date} + parent: String, + // Timestamps + created: {type: Date, default: Date}, + modified: {type: Date, default: Date} }; -var Role = loopback.createModel('Role', RoleSchema); +var Role = loopback.createModel('Role', RoleSchema, { + relations: { + roles: { + type: 'hasMany', + model: 'Role', + foreignKey: 'parent' + }, + users: { + type: 'hasAndBelongsToMany', + model: 'user', + foreignKey: 'userId' + }, + applications: { + type: 'hasAndBelongsToMany', + model: 'Application', + foreignKey: 'appId' + } + } +}); module.exports = Role; -Role.OWNER ='$owner'; // owner of the object +// Special roles +Role.OWNER = '$owner'; // owner of the object Role.RELATED = "$related"; // any User with a relationship to the object Role.AUTHENTICATED = "$authenticated"; // authenticated user Role.EVERYONE = "$everyone"; // everyone diff --git a/test/acl.test.js b/test/acl.test.js new file mode 100644 index 00000000..15ca5720 --- /dev/null +++ b/test/acl.test.js @@ -0,0 +1,28 @@ +var assert = require('assert'); +var loopback = require('../index'); +var acl = require('../lib/models/acl'); +var User = loopback.User; + +describe('security scopes', function () { + + it("should allow access to models", function () { + var ds = loopback.createDataSource({connector: loopback.Memory}); + acl.Scope.attachTo(ds); + acl.ScopeResourceAccess.attachTo(ds); + + // console.log(acl.Scope.relations); + + acl.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) { + console.log(resource); + acl.Scope.isAllowed('user', 'user', '*', '*', console.log); + }); + }); + + }); + +}); + + + diff --git a/test/role.test.js b/test/role.test.js new file mode 100644 index 00000000..81cdeffd --- /dev/null +++ b/test/role.test.js @@ -0,0 +1,41 @@ +var assert = require('assert'); +var loopback = require('../index'); +var Role = require('../lib/models/role'); +var User = loopback.User; + +describe('security models', function () { + + describe('roles', function () { + + it("Defines role/role relations", function () { + var ds = loopback.createDataSource({connector: loopback.Memory}); + Role.attachTo(ds); + + Role.create({name: 'user'}, function (err, role) { + role.roles.create({name: 'admin'}, function (err, role2) { + Role.find(console.log); + role.roles(console.log); + }); + }); + + }); + + it("Defines role/user relations", function () { + var ds = loopback.createDataSource({connector: loopback.Memory}); + User.attachTo(ds); + Role.attachTo(ds); + + Role.create({name: 'user'}, function (err, role) { + role.users.create({name: 'Raymond'}, function (err, user) { + console.log('User: ', user); + Role.find(console.log); + role.users(console.log); + }); + }); + + }); + }); +}); + + +