From d8647bb3c11ab286870c858df01fb81e71379cb7 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 16 Jan 2014 08:50:50 -0800 Subject: [PATCH 1/6] Make ACL model subclassing friendly --- lib/models/acl.js | 20 +++++++++++--------- lib/models/role.js | 27 ++++++++++++++++++--------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/models/acl.js b/lib/models/acl.js index 772b0171..46f4e19d 100644 --- a/lib/models/acl.js +++ b/lib/models/acl.js @@ -294,9 +294,9 @@ ACL.checkPermission = function checkPermission(principalType, principalId, var req = new AccessRequest(model, property, accessType); - var acls = ACL.getStaticACLs(model, property); + var acls = this.getStaticACLs(model, property); - var resolved = ACL.resolvePermission(acls, req); + var resolved = this.resolvePermission(acls, req); if(resolved && resolved.permission === ACL.DENY) { debug('Permission denied by statically resolved permission'); @@ -307,7 +307,8 @@ ACL.checkPermission = function checkPermission(principalType, principalId, return; } - ACL.find({where: {principalType: principalType, principalId: principalId, + var self = this; + this.find({where: {principalType: principalType, principalId: principalId, model: model, property: propertyQuery, accessType: accessTypeQuery}}, function (err, dynACLs) { if (err) { @@ -315,7 +316,7 @@ ACL.checkPermission = function checkPermission(principalType, principalId, return; } acls = acls.concat(dynACLs); - resolved = ACL.resolvePermission(acls, req); + resolved = self.resolvePermission(acls, req); if(resolved && resolved.permission === ACL.DEFAULT) { var modelClass = loopback.getModel(model); resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW; @@ -326,7 +327,7 @@ ACL.checkPermission = function checkPermission(principalType, principalId, ACL.prototype.debug = function() { if(debug.enabled) { - debug('---ACL---') + debug('---ACL---'); debug('model %s', this.model); debug('property %s', this.property); debug('principalType %s', this.principalType); @@ -361,9 +362,10 @@ ACL.checkAccess = function (context, callback) { var req = new AccessRequest(model.modelName, property, accessType); var effectiveACLs = []; - var staticACLs = ACL.getStaticACLs(model.modelName, property); + var staticACLs = this.getStaticACLs(model.modelName, property); - ACL.find({where: {model: model.modelName, property: propertyQuery, + var self = this; + this.find({where: {model: model.modelName, property: propertyQuery, accessType: accessTypeQuery}}, function (err, acls) { if (err) { callback && callback(err); @@ -403,7 +405,7 @@ ACL.checkAccess = function (context, callback) { callback && callback(err, null); return; } - var resolved = ACL.resolvePermission(effectiveACLs, req); + var resolved = self.resolvePermission(effectiveACLs, req); debug('checkAccess() returns: %j', resolved); callback && callback(null, resolved); }); @@ -437,7 +439,7 @@ ACL.checkAccessForToken = function (token, model, modelId, method, callback) { context.debug(); - ACL.checkAccess(context, function (err, access) { + this.checkAccess(context, function (err, access) { if (err) { callback && callback(err); return; diff --git a/lib/models/role.js b/lib/models/role.js index a76c585a..c9cee824 100644 --- a/lib/models/role.js +++ b/lib/models/role.js @@ -53,7 +53,8 @@ RoleMapping.ROLE = 'ROLE'; */ RoleMapping.prototype.application = function (callback) { if (this.principalType === RoleMapping.APPLICATION) { - loopback.Application.findById(this.principalId, callback); + var applicationModel = this.constructor.Application || loopback.Application; + applicationModel.findById(this.principalId, callback); } else { process.nextTick(function () { callback && callback(null, null); @@ -69,6 +70,7 @@ RoleMapping.prototype.application = function (callback) { */ RoleMapping.prototype.user = function (callback) { if (this.principalType === RoleMapping.USER) { + var userModel = this.constructor.User || loopback.User; loopback.User.findById(this.principalId, callback); } else { process.nextTick(function () { @@ -85,7 +87,8 @@ RoleMapping.prototype.user = function (callback) { */ RoleMapping.prototype.childRole = function (callback) { if (this.principalType === RoleMapping.ROLE) { - loopback.User.findById(this.principalId, callback); + var roleModel = this.constructor.Role || Role; + roleModel.findById(this.principalId, callback); } else { process.nextTick(function () { callback && callback(null, null); @@ -107,10 +110,13 @@ var Role = loopback.createModel('Role', RoleSchema, { } }); +Role.RoleMapping = RoleMapping; + // Set up the connection to users/applications/roles once the model Role.once('dataSourceAttached', function () { + var roleMappingModel = this.RoleMapping || RoleMapping; Role.prototype.users = function (callback) { - RoleMapping.find({where: {roleId: this.id, + roleMappingModel.find({where: {roleId: this.id, principalType: RoleMapping.USER}}, function (err, mappings) { if (err) { callback && callback(err); @@ -123,7 +129,7 @@ Role.once('dataSourceAttached', function () { }; Role.prototype.applications = function (callback) { - RoleMapping.find({where: {roleId: this.id, + roleMappingModel.find({where: {roleId: this.id, principalType: RoleMapping.APPLICATION}}, function (err, mappings) { if (err) { callback && callback(err); @@ -136,7 +142,7 @@ Role.once('dataSourceAttached', function () { }; Role.prototype.roles = function (callback) { - RoleMapping.find({where: {roleId: this.id, + roleMappingModel.find({where: {roleId: this.id, principalType: RoleMapping.ROLE}}, function (err, mappings) { if (err) { callback && callback(err); @@ -330,7 +336,8 @@ Role.isInRole = function (role, context, callback) { return; } - Role.findOne({where: {name: role}}, function (err, result) { + var roleMappingModel = this.RoleMapping || RoleMapping; + this.findOne({where: {name: role}}, function (err, result) { if (err) { callback && callback(err); return; @@ -346,7 +353,7 @@ Role.isInRole = function (role, context, callback) { var principalType = p.type || undefined; var principalId = p.id || undefined; if (principalType && principalId) { - RoleMapping.findOne({where: {roleId: result.id, + roleMappingModel.findOne({where: {roleId: result.id, principalType: principalType, principalId: principalId}}, function (err, result) { debug('Role mapping found: %j', result); @@ -388,11 +395,12 @@ Role.getRoles = function (context, callback) { } }; + var self = this; // Check against the smart roles var inRoleTasks = []; Object.keys(Role.resolvers).forEach(function (role) { inRoleTasks.push(function (done) { - Role.isInRole(role, context, function (err, inRole) { + self.isInRole(role, context, function (err, inRole) { if (!err && inRole) { addRole(role); done(); @@ -403,6 +411,7 @@ Role.getRoles = function (context, callback) { }); }); + var roleMappingModel = this.RoleMapping || RoleMapping; context.principals.forEach(function (p) { // Check against the role mappings var principalType = p.type || undefined; @@ -416,7 +425,7 @@ Role.getRoles = function (context, callback) { if (principalType && principalId) { // Please find() treat undefined matches all values inRoleTasks.push(function (done) { - RoleMapping.find({where: {principalType: principalType, + roleMappingModel.find({where: {principalType: principalType, principalId: principalId}}, function (err, mappings) { debug('Role mappings found: %s %j', err, mappings); if (err) { From 7212ebe805f57970af59deddbc0447d28db30bd6 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 16 Jan 2014 09:12:52 -0800 Subject: [PATCH 2/6] Remove the dangling require --- lib/models/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/models/index.js b/lib/models/index.js index 295acccd..ec204a42 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -6,7 +6,6 @@ exports.AccessToken = require('./access-token'); exports.Application = require('./application'); exports.ACL = require('./acl'); exports.Role = require('./role'); -exports.Installation = require('./installation'); From a6ff22c9c165e517ac9b3ae58605764e60bc0c9a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 16 Jan 2014 15:05:10 -0800 Subject: [PATCH 3/6] Make sure defaultPermission is checked --- lib/models/acl.js | 3 +++ test/acl.test.js | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/models/acl.js b/lib/models/acl.js index 46f4e19d..4e4a161c 100644 --- a/lib/models/acl.js +++ b/lib/models/acl.js @@ -406,6 +406,9 @@ ACL.checkAccess = function (context, callback) { return; } var resolved = self.resolvePermission(effectiveACLs, req); + if(resolved && resolved.permission === ACL.DEFAULT) { + resolved.permission = (model && model.settings.defaultPermission) || ACL.ALLOW; + } debug('checkAccess() returns: %j', resolved); callback && callback(null, resolved); }); diff --git a/test/acl.test.js b/test/acl.test.js index a7dd15ee..76449d5f 100644 --- a/test/acl.test.js +++ b/test/acl.test.js @@ -213,7 +213,8 @@ describe('security ACLs', function () { }, { acls: [ {principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW} - ] + ], + defaultPermission: 'DENY' }); ACL.create({principalType: ACL.USER, principalId: userId, model: 'Customer', property: ACL.ALL, @@ -243,6 +244,18 @@ describe('security ACLs', function () { }, function(err, access) { assert(!err && access.permission === ACL.ALLOW); }); + + ACL.checkAccess({ + principals: [ + {type: ACL.ROLE, id: Role.EVERYONE} + ], + model: 'Customer', + property: 'name', + accessType: ACL.READ + }, function(err, access) { + assert(!err && access.permission === ACL.DENY); + }); + }); }); }); From 32bdce53ee107bbb18efbf3b44c678c7cca6886b Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 17 Jan 2014 10:23:59 -0800 Subject: [PATCH 4/6] Fix the jsdoc for loopback.getModel() --- lib/loopback.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/loopback.js b/lib/loopback.js index 8adffd87..18c3795b 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -187,8 +187,9 @@ loopback.memory = function (name) { } /** - * Loop up a model class by name + * Look up a model class by name from all models created by loopback.createModel() * @param {String} modelName The model name + * @return {Model} The model class */ loopback.getModel = function(modelName) { return loopback.Model.modelBuilder.models[modelName]; From 5a6155e08efc29425dfc0ecf1e88197a00fc27c5 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 17 Jan 2014 10:40:41 -0800 Subject: [PATCH 5/6] Clean up loopback.js doc and add it to docs.json --- docs.json | 2 ++ lib/loopback.js | 25 +++++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs.json b/docs.json index 348ba7f7..2803f2db 100644 --- a/docs.json +++ b/docs.json @@ -2,6 +2,8 @@ "title": "LoopBack Documentation", "content": [ "lib/application.js", + {"title": "LoopBack", "depth": 3}, + "lib/loopback.js", {"title": "Middleware", "depth": 3}, "lib/middleware/token.js", "lib/models/access-token.js", diff --git a/lib/loopback.js b/lib/loopback.js index 18c3795b..d5db06df 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -1,4 +1,4 @@ -/** +/*! * Module dependencies. */ @@ -15,7 +15,15 @@ var express = require('express') , i8n = require('inflection'); /** - * Expose `createApplication()`. + * `loopback` is the main entry for LoopBack core module. It provides static + * methods to create models and data sources. The module itself is a function + * that creates loopback `app`. For example, + * + * + * ```js + * var loopback = require('loopback'); + * var app = loopback(); + * ``` */ var loopback = exports = module.exports = createApplication; @@ -47,7 +55,7 @@ function createApplication() { return app; } -/** +/*! * Expose express.middleware as loopback.* * for example `loopback.errorHandler` etc. */ @@ -59,7 +67,7 @@ for (var key in express) { , Object.getOwnPropertyDescriptor(express, key)); } -/** +/*! * Expose additional loopback middleware * for example `loopback.configure` etc. */ @@ -73,7 +81,7 @@ fs loopback[m.replace(/\.js$/, '')] = require('./middleware/' + m); }); -/** +/*! * Error handler title */ @@ -197,6 +205,9 @@ loopback.getModel = function(modelName) { /** * Set the default `dataSource` for a given `type`. + * @param {String} type The datasource type + * @param {Object|DataSource} dataSource The data source settings or instance + * @return {DataSource} The data source instance */ loopback.setDefaultDataSourceForType = function(type, dataSource) { @@ -212,6 +223,8 @@ loopback.setDefaultDataSourceForType = function(type, dataSource) { /** * Get the default `dataSource` for a given `type`. + * @param {String} type The datasource type + * @return {DataSource} The data source instance */ loopback.getDefaultDataSourceForType = function(type) { @@ -262,7 +275,7 @@ loopback.RoleMapping = require('./models/role').RoleMapping; loopback.ACL = require('./models/acl').ACL; loopback.Scope = require('./models/acl').Scope; -/** +/*! * Automatically attach these models to dataSources */ From 4c56be93c3379398b9495268996ea3d10c244d37 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 17 Jan 2014 11:05:26 -0800 Subject: [PATCH 6/6] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 968a63b6..8954e788 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "Platform", "mBaaS" ], - "version": "1.5.1", + "version": "1.5.2", "scripts": { "test": "mocha -R spec" },