Merge branch 'release/1.8.4' into production

This commit is contained in:
Raymond Feng 2014-05-27 10:25:07 -07:00
commit be0ca7e1ce
10 changed files with 243 additions and 41 deletions

View File

@ -19,7 +19,7 @@ For more details, see http://loopback.io/.
In addition to the [main LoopBack module](https://github.com/strongloop/loopback), LoopBack consists of numerous other modules that implement specific functionality, In addition to the [main LoopBack module](https://github.com/strongloop/loopback), LoopBack consists of numerous other modules that implement specific functionality,
as illustrated below: as illustrated below:
![LoopBack modules](./docs/assets/lb-modules.png "LoopBack modules") ![LoopBack modules](https://github.com/strongloop/loopback/raw/master/docs/assets/lb-modules.png "LoopBack modules")
* Frameworks * Frameworks
* [loopback](https://github.com/strongloop/loopback) * [loopback](https://github.com/strongloop/loopback)
@ -30,6 +30,7 @@ as illustrated below:
* [loopback-connector-mongodb](https://github.com/strongloop/loopback-connector-mongodb) * [loopback-connector-mongodb](https://github.com/strongloop/loopback-connector-mongodb)
* [loopback-connector-mysql](https://github.com/strongloop/loopback-connector-mysql) * [loopback-connector-mysql](https://github.com/strongloop/loopback-connector-mysql)
* [loopback-connector-oracle](https://github.com/strongloop/loopback-connector-oracle) * [loopback-connector-oracle](https://github.com/strongloop/loopback-connector-oracle)
* [loopback-connector-mssql](https://github.com/strongloop/loopback-connector-mssql)
* [loopback-connector-postgresql](https://github.com/strongloop/loopback-connector-postgresql) * [loopback-connector-postgresql](https://github.com/strongloop/loopback-connector-postgresql)
* [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest) * [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest)
* [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap) * [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap)
@ -57,7 +58,7 @@ as illustrated below:
* [loopback-example-access-control](https://github.com/strongloop/loopback-example-access-control) * [loopback-example-access-control](https://github.com/strongloop/loopback-example-access-control)
* [loopback-example-proxy](https://github.com/strongloop/loopback-example-proxy) * [loopback-example-proxy](https://github.com/strongloop/loopback-example-proxy)
* [strongloop-community/loopback-example-datagraph](https://github.com/strongloop-community/loopback-example-datagraph) * [strongloop-community/loopback-example-datagraph](https://github.com/strongloop-community/loopback-example-datagraph)
* [strongloop-community/loopback-mysql-example](https://github.com/strongloop-community/loopback-mysql-example) * [strongloop-community/loopback-example-database](https://github.com/strongloop-community/loopback-example-database)
* [strongloop-community/loopback-examples-ios](https://github.com/strongloop-community/loopback-examples-ios) * [strongloop-community/loopback-examples-ios](https://github.com/strongloop-community/loopback-examples-ios)
* [strongloop-community/loopback-example-ssl](https://github.com/strongloop-community/loopback-example-ssl) * [strongloop-community/loopback-example-ssl](https://github.com/strongloop-community/loopback-example-ssl)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 601 KiB

View File

@ -7,6 +7,7 @@ var DataSource = require('loopback-datasource-juggler').DataSource
, compat = require('./compat') , compat = require('./compat')
, assert = require('assert') , assert = require('assert')
, fs = require('fs') , fs = require('fs')
, extend = require('util')._extend
, _ = require('underscore') , _ = require('underscore')
, RemoteObjects = require('strong-remoting') , RemoteObjects = require('strong-remoting')
, swagger = require('strong-remoting/ext/swagger') , swagger = require('strong-remoting/ext/swagger')
@ -540,7 +541,10 @@ function dataSourcesFromConfig(config) {
} }
function modelFromConfig(name, config, app) { function modelFromConfig(name, config, app) {
var ModelCtor = require('./loopback').createModel(name, config.properties, config.options); var options = buildModelOptionsFromConfig(config);
var properties = config.properties;
var ModelCtor = require('./loopback').createModel(name, properties, options);
var dataSource = config.dataSource; var dataSource = config.dataSource;
if(typeof dataSource === 'string') { if(typeof dataSource === 'string') {
@ -553,6 +557,26 @@ function modelFromConfig(name, config, app) {
return ModelCtor; return ModelCtor;
} }
function buildModelOptionsFromConfig(config) {
var options = extend({}, config.options);
for (var key in config) {
if (['properties', 'options', 'dataSource'].indexOf(key) !== -1) {
// Skip items which have special meaning
continue;
}
if (options[key] !== undefined) {
// When both `config.key` and `config.options.key` are set,
// use the latter one to preserve backwards compatibility
// with loopback 1.x
continue;
}
options[key] = config[key];
}
return options;
}
function requireDir(dir, basenames) { function requireDir(dir, basenames) {
assert(dir, 'cannot require directory contents without directory name'); assert(dir, 'cannot require directory contents without directory name');

View File

@ -15,17 +15,37 @@ module.exports = rest;
*/ */
function rest() { function rest() {
var tokenParser = null;
return function (req, res, next) { return function (req, res, next) {
var app = req.app; var app = req.app;
var handler = app.handler('rest'); var handler = app.handler('rest');
if(req.url === '/routes') { if(req.url === '/routes') {
res.send(handler.adapter.allRoutes()); res.send(handler.adapter.allRoutes());
} else if(req.url === '/models') { } else if(req.url === '/models') {
return res.send(app.remotes().toJSON()); return res.send(app.remotes().toJSON());
} else if (app.isAuthEnabled) {
if (!tokenParser) {
// NOTE(bajtos) It would be better to search app.models for a model
// of type AccessToken instead of searching all loopback models.
// Unfortunately that's not supported now.
// Related discussions:
// https://github.com/strongloop/loopback/pull/167
// https://github.com/strongloop/loopback/commit/f07446a
var AccessToken = loopback.getModelByType(loopback.AccessToken);
tokenParser = loopback.token({ model: AccessToken });
}
tokenParser(req, res, function(err) {
if (err) {
next(err);
} else {
handler(req, res, next);
}
});
} else { } else {
handler(req, res, next); handler(req, res, next);
} }
} };
} }

View File

@ -53,12 +53,14 @@ function token(options) {
assert(TokenModel, 'loopback.token() middleware requires a AccessToken model'); assert(TokenModel, 'loopback.token() middleware requires a AccessToken model');
return function (req, res, next) { return function (req, res, next) {
if (req.accessToken !== undefined) return next();
TokenModel.findForRequest(req, options, function(err, token) { TokenModel.findForRequest(req, options, function(err, token) {
if(err) return next(err); if(err) return next(err);
if(token) { if(token) {
req.accessToken = token; req.accessToken = token;
next(); next();
} else { } else {
req.accessToken = null;
return next(); return next();
} }
}); });

View File

@ -3,57 +3,80 @@
"description": "LoopBack: Open Mobile Platform for Node.js", "description": "LoopBack: Open Mobile Platform for Node.js",
"homepage": "http://loopback.io", "homepage": "http://loopback.io",
"keywords": [ "keywords": [
"framework",
"web",
"API framework",
"REST API",
"StrongLoop", "StrongLoop",
"LoopBack", "LoopBack",
"Mobile", "Mobile",
"Backend", "mBaaS",
"Platform", "restful",
"mBaaS" "rest",
"api",
"app",
"auth",
"security",
"orm",
"database",
"oracle",
"mysql",
"nosql",
"mongo",
"mongodb",
"sqlserver",
"mssql",
"express",
"restify",
"koa",
"postgres",
"postgresql",
"soap"
], ],
"version": "1.8.3", "version": "1.8.4",
"scripts": { "scripts": {
"test": "mocha -R spec" "test": "mocha -R spec"
}, },
"dependencies": { "dependencies": {
"debug": "~0.7.4", "debug": "~0.8.1",
"express": "~3.4.8", "express": "~3.5.0",
"strong-remoting": "~1.3.1", "strong-remoting": "~1.4.0",
"inflection": "~1.3.5", "inflection": "~1.3.5",
"passport": "~0.2.0", "passport": "~0.2.0",
"passport-local": "~0.1.6", "passport-local": "~1.0.0",
"nodemailer": "~0.6.0", "nodemailer": "~0.6.5",
"ejs": "~0.8.5", "ejs": "~1.0.0",
"bcryptjs": "~0.7.12", "bcryptjs": "~0.7.12",
"underscore.string": "~2.3.3", "underscore.string": "~2.3.3",
"underscore": "~1.6.0", "underscore": "~1.6.0",
"uid2": "0.0.3", "uid2": "0.0.3",
"async": "~0.2.10" "async": "~0.9.0"
}, },
"peerDependencies": { "peerDependencies": {
"loopback-datasource-juggler": "^1.4.0 < 1.6.0" "loopback-datasource-juggler": ">=1.4.0 <1.6.0"
}, },
"devDependencies": { "devDependencies": {
"loopback-datasource-juggler": "^1.4.0 < 1.6.0", "loopback-datasource-juggler": ">=1.4.0 <1.6.0",
"mocha": "~1.17.1", "mocha": "~1.18.0",
"strong-task-emitter": "0.0.x", "strong-task-emitter": "0.0.x",
"supertest": "~0.9.0", "supertest": "~0.12.1",
"chai": "~1.9.0", "chai": "~1.9.1",
"loopback-testing": "~0.1.2", "loopback-testing": "~0.1.2",
"browserify": "~3.41.0", "browserify": "~4.1.5",
"grunt": "~0.4.2", "grunt": "~0.4.5",
"grunt-browserify": "~1.3.1", "grunt-browserify": "~2.1.0",
"grunt-contrib-uglify": "~0.3.2", "grunt-contrib-uglify": "~0.4.0",
"grunt-contrib-jshint": "~0.8.0", "grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-watch": "~0.5.3", "grunt-contrib-watch": "~0.6.1",
"karma-script-launcher": "~0.1.0", "karma-script-launcher": "~0.1.0",
"karma-chrome-launcher": "~0.1.2", "karma-chrome-launcher": "~0.1.3",
"karma-firefox-launcher": "~0.1.3", "karma-firefox-launcher": "~0.1.3",
"karma-html2js-preprocessor": "~0.1.0", "karma-html2js-preprocessor": "~0.1.0",
"karma-phantomjs-launcher": "~0.1.2", "karma-phantomjs-launcher": "~0.1.4",
"karma": "~0.10.9", "karma": "~0.12.16",
"karma-browserify": "~0.2.0", "karma-browserify": "~0.2.0",
"karma-mocha": "~0.1.1", "karma-mocha": "~0.1.3",
"grunt-karma": "~0.6.2" "grunt-karma": "~0.8.3"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -32,6 +32,27 @@ describe('loopback.token(options)', function() {
.end(done); .end(done);
}); });
}); });
it('should skip when req.token is already present', function(done) {
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({ model: Token }));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', this.token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
});
});
}); });
describe('AccessToken', function () { describe('AccessToken', function () {

View File

@ -14,10 +14,13 @@ function checkResult(err, result) {
assert(!err); assert(!err);
} }
describe('security scopes', function () { var ds = null;
before(function() {
ds = loopback.createDataSource({connector: loopback.Memory});
});
describe('security scopes', function () {
beforeEach(function() { beforeEach(function() {
var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
testModel = loopback.Model.extend('testModel'); testModel = loopback.Model.extend('testModel');
ACL.attachTo(ds); ACL.attachTo(ds);
Role.attachTo(ds); Role.attachTo(ds);
@ -156,7 +159,6 @@ describe('security ACLs', function () {
}); });
it("should honor defaultPermission from the model", function () { it("should honor defaultPermission from the model", function () {
var ds = this.ds;
var Customer = ds.createModel('Customer', { var Customer = ds.createModel('Customer', {
name: { name: {
type: String, type: String,
@ -188,7 +190,6 @@ describe('security ACLs', function () {
}); });
it("should honor static ACLs from the model", function () { it("should honor static ACLs from the model", function () {
var ds = this.ds;
var Customer = ds.createModel('Customer', { var Customer = ds.createModel('Customer', {
name: { name: {
type: String, type: String,
@ -226,7 +227,6 @@ describe('security ACLs', function () {
it("should check access against LDL, ACL, and Role", function () { it("should check access against LDL, ACL, and Role", function () {
// var log = console.log; // var log = console.log;
var log = function() {}; var log = function() {};
var ds = this.ds;
// Create // Create
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) { User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {

View File

@ -61,9 +61,11 @@ describe('app', function() {
}); });
}); });
describe('app.model(name, properties, options)', function () { describe('app.model(name, config)', function () {
it('Sugar for defining a fully built model', function () { var app;
var app = loopback();
beforeEach(function() {
app = loopback();
app.boot({ app.boot({
app: {port: 3000, host: '127.0.0.1'}, app: {port: 3000, host: '127.0.0.1'},
dataSources: { dataSources: {
@ -72,16 +74,39 @@ describe('app', function() {
} }
} }
}); });
});
it('Sugar for defining a fully built model', function () {
app.model('foo', { app.model('foo', {
dataSource: 'db' dataSource: 'db'
}); });
var Foo = app.models.foo; var Foo = app.models.foo;
var f = new Foo; var f = new Foo();
assert(f instanceof loopback.Model); assert(f instanceof loopback.Model);
}); });
it('interprets extra first-level keys as options', function() {
app.model('foo', {
dataSource: 'db',
base: 'User'
});
expect(app.models.foo.definition.settings.base).to.equal('User');
});
it('prefers config.options.key over config.key', function() {
app.model('foo', {
dataSource: 'db',
base: 'User',
options: {
base: 'Application'
}
});
expect(app.models.foo.definition.settings.base).to.equal('Application');
});
}); });
describe('app.models', function() { describe('app.models', function() {

View File

@ -0,0 +1,86 @@
describe('loopback.rest', function() {
beforeEach(function() {
app.dataSource('db', { connector: loopback.Memory });
});
it('works out-of-the-box', function(done) {
app.model('MyModel', { dataSource: 'db' });
app.use(loopback.rest());
request(app).get('/mymodels')
.expect(200)
.end(done);
});
it('includes loopback.token when necessary', function(done) {
givenUserModelWithAuth();
app.enableAuth();
app.use(loopback.rest());
givenLoggedInUser(function(err, token) {
if (err) return done(err);
expect(token).instanceOf(app.models.accessToken);
request(app).get('/users/' + token.userId)
.set('Authorization', token.id)
.expect(200)
.end(done);
});
});
it('does not include loopback.token when auth not enabled', function(done) {
var User = givenUserModelWithAuth();
User.getToken = function(req, cb) {
cb(null, req.accessToken ? req.accessToken.id : null);
};
loopback.remoteMethod(User.getToken, {
accepts: [{ type: 'object', http: { source: 'req' } }],
returns: [{ type: 'object', name: 'id' }]
});
app.use(loopback.rest());
givenLoggedInUser(function(err, token) {
if (err) return done(err);
request(app).get('/users/getToken')
.set('Authorization', token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body.id).to.equal(null);
done();
});
});
});
function givenUserModelWithAuth() {
// NOTE(bajtos) It is important to create a custom AccessToken model here,
// in order to overwrite the entry created by previous tests in
// the global model registry
app.model('accessToken', {
options: {
base: 'AccessToken'
},
dataSource: 'db'
});
return app.model('user', {
options: {
base: 'User',
relations: {
accessTokens: {
model: 'accessToken',
type: 'hasMany',
foreignKey: 'userId'
}
}
},
dataSource: 'db'
});
}
function givenLoggedInUser(cb) {
var credentials = { email: 'user@example.com', password: 'pwd' };
var User = app.models.user;
User.create(credentials,
function(err, user) {
if (err) return done(err);
User.login(credentials, cb);
});
}
});