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,
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
* [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-mysql](https://github.com/strongloop/loopback-connector-mysql)
* [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-rest](https://github.com/strongloop/loopback-connector-rest)
* [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-proxy](https://github.com/strongloop/loopback-example-proxy)
* [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-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')
, assert = require('assert')
, fs = require('fs')
, extend = require('util')._extend
, _ = require('underscore')
, RemoteObjects = require('strong-remoting')
, swagger = require('strong-remoting/ext/swagger')
@ -540,7 +541,10 @@ function dataSourcesFromConfig(config) {
}
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;
if(typeof dataSource === 'string') {
@ -553,6 +557,26 @@ function modelFromConfig(name, config, app) {
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) {
assert(dir, 'cannot require directory contents without directory name');

View File

@ -15,17 +15,37 @@ module.exports = rest;
*/
function rest() {
var tokenParser = null;
return function (req, res, next) {
var app = req.app;
var handler = app.handler('rest');
if(req.url === '/routes') {
res.send(handler.adapter.allRoutes());
} else if(req.url === '/models') {
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 {
handler(req, res, next);
}
}
};
}

View File

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

View File

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

View File

@ -32,6 +32,27 @@ describe('loopback.token(options)', function() {
.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 () {

View File

@ -14,10 +14,13 @@ function checkResult(err, result) {
assert(!err);
}
describe('security scopes', function () {
var ds = null;
before(function() {
ds = loopback.createDataSource({connector: loopback.Memory});
});
describe('security scopes', function () {
beforeEach(function() {
var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
testModel = loopback.Model.extend('testModel');
ACL.attachTo(ds);
Role.attachTo(ds);
@ -156,7 +159,6 @@ describe('security ACLs', function () {
});
it("should honor defaultPermission from the model", function () {
var ds = this.ds;
var Customer = ds.createModel('Customer', {
name: {
type: String,
@ -188,7 +190,6 @@ describe('security ACLs', function () {
});
it("should honor static ACLs from the model", function () {
var ds = this.ds;
var Customer = ds.createModel('Customer', {
name: {
type: String,
@ -226,7 +227,6 @@ describe('security ACLs', function () {
it("should check access against LDL, ACL, and Role", function () {
// var log = console.log;
var log = function() {};
var ds = this.ds;
// Create
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 () {
it('Sugar for defining a fully built model', function () {
var app = loopback();
describe('app.model(name, config)', function () {
var app;
beforeEach(function() {
app = loopback();
app.boot({
app: {port: 3000, host: '127.0.0.1'},
dataSources: {
@ -72,16 +74,39 @@ describe('app', function() {
}
}
});
});
it('Sugar for defining a fully built model', function () {
app.model('foo', {
dataSource: 'db'
});
var Foo = app.models.foo;
var f = new Foo;
var f = new Foo();
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() {

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);
});
}
});