From 592f3f9278f17b324a051d99155cf69416969c3a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 3 Feb 2014 16:00:01 -0800 Subject: [PATCH 1/7] Make sure the configured ACL submodel is used --- lib/models/model.js | 24 +++++++++++++++++------- test/model.test.js | 10 ++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/models/model.js b/lib/models/model.js index 47fad80c..78df36f0 100644 --- a/lib/models/model.js +++ b/lib/models/model.js @@ -117,10 +117,20 @@ Model.setup = function () { /*! * Get the reference to ACL in a lazy fashion to avoid race condition in require */ -var ACL = null; -function getACL() { - return ACL || (ACL = require('./acl').ACL); -} +var _aclModel = null; +Model._ACL = function getACL(ACL) { + if(ACL !== undefined) { + // The function is used as a setter + _aclModel = ACL; + } + if(_aclModel) { + return _aclModel; + } + var aclModel = require('./acl').ACL; + _aclModel = loopback.getModelByType(aclModel); + return _aclModel; +}; + /** * Check if the given access token can invoke the method @@ -137,9 +147,9 @@ function getACL() { Model.checkAccess = function(token, modelId, method, callback) { var ANONYMOUS = require('./access-token').ANONYMOUS; token = token || ANONYMOUS; - var ACL = getACL(); + var aclModel = Model._ACL(); var methodName = 'string' === typeof method? method: method && method.name; - ACL.checkAccessForToken(token, this.modelName, modelId, methodName, callback); + aclModel.checkAccessForToken(token, this.modelName, modelId, methodName, callback); }; /*! @@ -158,7 +168,7 @@ Model._getAccessTypeForMethod = function(method) { 'method is a required argument and must be a RemoteMethod object' ); - var ACL = getACL(); + var ACL = Model._ACL(); switch(method.name) { case'create': diff --git a/test/model.test.js b/test/model.test.js index 8cb112ab..b2222888 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -612,6 +612,16 @@ describe('Model', function() { } }); + describe('Model._getACLModel()', function() { + it('should return the subclass of ACL', function() { + var Model = require('../').Model; + var acl = ACL.extend('acl'); + Model._ACL(null); // Reset the ACL class for the base model + var model = Model._ACL(); + assert.equal(model, acl); + }); + }); + // describe('Model.hasAndBelongsToMany()', function() { // it("TODO: implement / document", function(done) { // /* example - From 4a076f13fd7e9ef6ba399a02bf52733a4084a2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 4 Feb 2014 20:27:14 +0100 Subject: [PATCH 2/7] Fix incorrect usage of `app` in app.test.js The `beforeEach` hook was using a local `var app`, the test was accessing global `app` created by `test/support.js`. --- test/app.test.js | 2 -- test/support.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/app.test.js b/test/app.test.js index a806f251..d9e6cdc5 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -78,8 +78,6 @@ describe('app', function() { describe('app.boot([options])', function () { beforeEach(function () { - var app = this.app = loopback(); - app.boot({ app: { port: 3000, diff --git a/test/support.js b/test/support.js index 394bc6d4..85aac797 100644 --- a/test/support.js +++ b/test/support.js @@ -17,7 +17,7 @@ request = require('supertest'); loopback.User.settings.saltWorkFactor = 4; beforeEach(function () { - app = loopback(); + this.app = app = loopback(); // setup default data sources loopback.setDefaultDataSourceForType('db', { From b13ff35697ff798f26467a98ddd19cf4a206d15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 4 Feb 2014 20:28:19 +0100 Subject: [PATCH 3/7] Make app.models unique per app instance Remove a source of confusion in unit-tests. --- lib/loopback.js | 5 +++++ test/app.test.js | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/loopback.js b/lib/loopback.js index fd96b8ea..568441d7 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -57,6 +57,11 @@ function createApplication() { utils.merge(app, proto); + // Create a new instance of models registry per each app instance + app.models = function() { + return proto.models.apply(this, arguments); + }; + return app; } diff --git a/test/app.test.js b/test/app.test.js index d9e6cdc5..eed3d7aa 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -76,6 +76,15 @@ describe('app', function() { }); }); + describe('app.models', function() { + it('is unique per app instance', function() { + var Color = app.model('Color', { dataSource: 'db' }); + expect(app.models.Color).to.equal(Color); + var anotherApp = loopback(); + expect(anotherApp.models.Color).to.equal(undefined); + }); + }); + describe('app.boot([options])', function () { beforeEach(function () { app.boot({ From 38f4748021b5ae6b9f0fd57d16bddb422ebbdfd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Wed, 5 Feb 2014 18:46:22 +0100 Subject: [PATCH 4/7] Add app.isAuthEnabled. The flag is set by `app.enableAuth` and can be used to detect whether the access control is enabled. --- lib/application.js | 4 +++- test/app.test.js | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/application.js b/lib/application.js index d616e1c0..ed0733e0 100644 --- a/lib/application.js +++ b/lib/application.js @@ -305,7 +305,9 @@ app.enableAuth = function() { next(); } }); -} + + this.isAuthEnabled = true; +}; /** * Initialize an application from an options object or a set of JSON and JavaScript files. diff --git a/test/app.test.js b/test/app.test.js index eed3d7aa..701d22fe 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -400,6 +400,14 @@ describe('app', function() { }); }); + describe('enableAuth', function() { + it('should set app.isAuthEnabled to true', function() { + expect(app.isAuthEnabled).to.not.equal(true); + app.enableAuth(); + expect(app.isAuthEnabled).to.equal(true); + }); + }); + describe('app.get("/", loopback.status())', function () { it('should return the status of the application', function (done) { var app = loopback(); From 618b563c6ed9e1f4dabbd364e4c4d3594e7e47f0 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 7 Feb 2014 11:14:01 -0800 Subject: [PATCH 5/7] Use hex encoding for application ids/keys base64 encoded ids/keys are not friendly for urls --- lib/models/application.js | 28 +++++++++++++--------- test/model.application.test.js | 43 ++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/lib/models/application.js b/lib/models/application.js index fe2f370c..2841a6e5 100644 --- a/lib/models/application.js +++ b/lib/models/application.js @@ -98,12 +98,13 @@ var crypto = require('crypto'); function generateKey(hmacKey, algorithm, encoding) { hmacKey = hmacKey || 'loopback'; - algorithm = algorithm || 'sha256'; - encoding = encoding || 'base64'; + algorithm = algorithm || 'sha1'; + encoding = encoding || 'hex'; var hmac = crypto.createHmac(algorithm, hmacKey); - var buf = crypto.randomBytes(64); + var buf = crypto.randomBytes(32); hmac.update(buf); - return hmac.digest('base64'); + var key = hmac.digest(encoding); + return key; } /** @@ -121,7 +122,7 @@ var Application = loopback.createModel('Application', ApplicationSchema); Application.beforeCreate = function (next) { var app = this; app.created = app.modified = new Date(); - app.id = generateKey('id', 'sha1'); + app.id = generateKey('id', 'md5'); app.clientKey = generateKey('client'); app.javaScriptKey = generateKey('javaScript'); app.restApiKey = generateKey('restApi'); @@ -208,13 +209,18 @@ Application.authenticate = function (appId, key, cb) { cb && cb(err, null); return; } - var matched = null; - ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey'].forEach(function (k) { - if (app[k] === key) { - matched = k; + var result = null; + var keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey']; + for (var i = 0; i < keyNames.length; i++) { + if (app[keyNames[i]] === key) { + result = { + application: app, + keyType: keyNames[i] + }; + break; } - }); - cb && cb(null, matched); + } + cb && cb(null, result); }); }; diff --git a/test/model.application.test.js b/test/model.application.test.js index d27a4750..d7f24df1 100644 --- a/test/model.application.test.js +++ b/test/model.application.test.js @@ -121,7 +121,8 @@ describe('Application', function () { it('Authenticate with application id & clientKey', function (done) { Application.authenticate(registeredApp.id, registeredApp.clientKey, function (err, result) { - assert.equal(result, 'clientKey'); + assert.equal(result.application.id, registeredApp.id); + assert.equal(result.keyType, 'clientKey'); done(err, result); }); }); @@ -129,7 +130,8 @@ describe('Application', function () { it('Authenticate with application id & javaScriptKey', function (done) { Application.authenticate(registeredApp.id, registeredApp.javaScriptKey, function (err, result) { - assert.equal(result, 'javaScriptKey'); + assert.equal(result.application.id, registeredApp.id); + assert.equal(result.keyType, 'javaScriptKey'); done(err, result); }); }); @@ -137,7 +139,8 @@ describe('Application', function () { it('Authenticate with application id & restApiKey', function (done) { Application.authenticate(registeredApp.id, registeredApp.restApiKey, function (err, result) { - assert.equal(result, 'restApiKey'); + assert.equal(result.application.id, registeredApp.id); + assert.equal(result.keyType, 'restApiKey'); done(err, result); }); }); @@ -145,7 +148,8 @@ describe('Application', function () { it('Authenticate with application id & masterKey', function (done) { Application.authenticate(registeredApp.id, registeredApp.masterKey, function (err, result) { - assert.equal(result, 'masterKey'); + assert.equal(result.application.id, registeredApp.id); + assert.equal(result.keyType, 'masterKey'); done(err, result); }); }); @@ -153,7 +157,8 @@ describe('Application', function () { it('Authenticate with application id & windowsKey', function (done) { Application.authenticate(registeredApp.id, registeredApp.windowsKey, function (err, result) { - assert.equal(result, 'windowsKey'); + assert.equal(result.application.id, registeredApp.id); + assert.equal(result.keyType, 'windowsKey'); done(err, result); }); }); @@ -170,13 +175,14 @@ describe('Application', function () { describe('Application subclass', function () { it('should use subclass model name', function (done) { var MyApp = Application.extend('MyApp'); - MyApp.attachTo(loopback.createDataSource({connector: loopback.Memory})); - MyApp.register('rfeng', 'MyApp2', - {description: 'My second mobile application'}, function (err, result) { + var ds = loopback.createDataSource({connector: loopback.Memory}); + MyApp.attachTo(ds); + MyApp.register('rfeng', 'MyApp123', + {description: 'My 123 mobile application'}, function (err, result) { var app = result; assert.equal(app.owner, 'rfeng'); - assert.equal(app.name, 'MyApp2'); - assert.equal(app.description, 'My second mobile application'); + assert.equal(app.name, 'MyApp123'); + assert.equal(app.description, 'My 123 mobile application'); assert(app.clientKey); assert(app.javaScriptKey); assert(app.restApiKey); @@ -184,14 +190,17 @@ describe('Application subclass', function () { assert(app.masterKey); assert(app.created); assert(app.modified); - MyApp.findById(app.id, function (err, myApp) { - assert(!err); - assert(myApp); - - Application.findById(app.id, function (err, myApp) { + // Remove all instances from Application model to avoid left-over data + Application.destroyAll(function () { + MyApp.findById(app.id, function (err, myApp) { assert(!err); - assert(myApp === null); - done(err, myApp); + assert(myApp); + + Application.findById(app.id, function (err, myApp) { + assert(!err); + assert(myApp === null); + done(err, myApp); + }); }); }); }); From c216ba03c1855a7e3e70885c0b1a7d5d96d247fa Mon Sep 17 00:00:00 2001 From: Aurelien Chivot Date: Tue, 11 Feb 2014 19:14:04 +0100 Subject: [PATCH 6/7] Documentation (generated) fix --- lib/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/application.js b/lib/application.js index ed0733e0..d1c2b17e 100644 --- a/lib/application.js +++ b/lib/application.js @@ -321,7 +321,7 @@ app.enableAuth = function() { * * **Options** * - * - `cwd` - _optional_ - the directory to use when loading JSON and JavaScript files + * - `appRootDir` - _optional_ - the directory to use when loading JSON and JavaScript files * - `models` - _optional_ - an object containing `Model` definitions * - `dataSources` - _optional_ - an object containing `DataSource` definitions * From 6e4a10bd948fc78f866366fe3ea4a4db97b2c1dc Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 12 Feb 2014 11:31:26 -0800 Subject: [PATCH 7/7] Bump version and update deps --- package.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index d504b4f6..510327cf 100644 --- a/package.json +++ b/package.json @@ -9,35 +9,35 @@ "Platform", "mBaaS" ], - "version": "1.6.1", + "version": "1.6.2", "scripts": { "test": "mocha -R spec" }, "dependencies": { - "debug": "~0.7.2", - "express": "~3.4.0", - "strong-remoting": "~1.2.1", - "inflection": "~1.2.5", - "passport": "~0.1.17", + "debug": "~0.7.4", + "express": "~3.4.8", + "strong-remoting": "~1.2.4", + "inflection": "~1.2.7", + "passport": "~0.2.0", "passport-local": "~0.1.6", - "nodemailer": "~0.5.7", - "ejs": "~0.8.4", + "nodemailer": "~0.6.0", + "ejs": "~0.8.5", "bcryptjs": "~0.7.10", "underscore.string": "~2.3.3", - "underscore": "~1.5.2", + "underscore": "~1.6.0", "uid2": "0.0.3", - "async": "~0.2.9" + "async": "~0.2.10" }, "peerDependencies": { - "loopback-datasource-juggler": "~1.2.13" + "loopback-datasource-juggler": "~1.3.0" }, "devDependencies": { - "loopback-datasource-juggler": "~1.2.13", - "mocha": "~1.14.0", + "loopback-datasource-juggler": "~1.3.0", + "mocha": "~1.17.1", "strong-task-emitter": "0.0.x", - "supertest": "~0.8.1", - "chai": "~1.8.1", - "loopback-testing": "~0.1.0" + "supertest": "~0.9.0", + "chai": "~1.9.0", + "loopback-testing": "~0.1.2" }, "repository": { "type": "git",