Initial auth implementation

This commit is contained in:
Ritchie Martori 2013-11-14 20:19:46 -08:00
parent 3eb04f64c3
commit 2f9403016c
6 changed files with 95 additions and 23 deletions

View File

@ -43,12 +43,6 @@ app.disuse = function (route) {
} }
} }
/**
* App models.
*/
app._models = [];
/** /**
* Expose a model. * Expose a model.
* *
@ -60,7 +54,7 @@ app.model = function (Model, config) {
assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor'); assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor');
assert(Model.pluralModelName, 'Model must have a "pluralModelName" property'); assert(Model.pluralModelName, 'Model must have a "pluralModelName" property');
this.remotes().exports[Model.pluralModelName] = Model; this.remotes().exports[Model.pluralModelName] = Model;
this._models.push(Model); this.models().push(Model);
Model.shared = true; Model.shared = true;
Model.app = this; Model.app = this;
Model.emit('attached', this); Model.emit('attached', this);
@ -82,20 +76,12 @@ app.model = function (Model, config) {
return Model; return Model;
} }
/**
* Get a Model by name.
*/
app.getModel = function (modelName) {
this.models
};
/** /**
* Get all exposed models. * Get all exposed models.
*/ */
app.models = function () { app.models = function () {
return this._models; return this._models || (this._models = []);
} }
/** /**
@ -144,7 +130,6 @@ app.docs = function (options) {
swagger(remotes, options); swagger(remotes, options);
} }
/*! /*!
* Get a handler of the specified type from the handler cache. * Get a handler of the specified type from the handler cache.
*/ */
@ -166,6 +151,52 @@ app.handler = function (type) {
app.dataSources = app.datasources = {}; app.dataSources = app.datasources = {};
/**
* Enable app wide authentication.
*/
app.enableAuth = function() {
var remotes = this.remotes();
remotes.before('**', function(ctx, next, method) {
var req = ctx.req;
var Model = method.ctor;
var modelInstance = ctx.instance;
var modelId = modelInstance && modelInstance.id;
// TODO(ritch) - this fallback could be less express dependent
if(modelInstance && !modelId) {
modelId = req.param('id');
}
if(req.accessToken) {
Model.checkAccess(
req.accessToken,
modelId,
method.name,
function(err, allowed) {
if(err) {
next(err);
} else if(allowed) {
next();
} else {
var e = new Error('Access Denied');
e.statusCode = 401;
next(e);
}
}
);
} else if(method.fn && method.fn.requireToken === false) {
next();
} else {
var e = new Error('Access Denied');
e.statusCode = 401;
next(e);
}
});
}
/** /**
* Initialize the app using JSON and JavaScript files. * Initialize the app using JSON and JavaScript files.
* *

View File

@ -252,7 +252,6 @@ loopback.RoleMapping = require('./models/role').RoleMapping;
loopback.ACL = require('./models/acl').ACL; loopback.ACL = require('./models/acl').ACL;
loopback.Scope = require('./models/acl').Scope; loopback.Scope = require('./models/acl').Scope;
/** /**
* Automatically attach these models to dataSources * Automatically attach these models to dataSources
*/ */

View File

@ -8,7 +8,8 @@ var Model = require('../loopback').Model
, crypto = require('crypto') , crypto = require('crypto')
, uid = require('uid2') , uid = require('uid2')
, DEFAULT_TTL = 1209600 // 2 weeks in seconds , DEFAULT_TTL = 1209600 // 2 weeks in seconds
, DEFAULT_TOKEN_LEN = 64; , DEFAULT_TOKEN_LEN = 64
, ACL = require('./acl').ACL;
/** /**
* Default AccessToken properties. * Default AccessToken properties.

View File

@ -274,6 +274,10 @@ Scope.checkPermission = function (scope, model, property, accessType, callback)
ACL.checkAccess = function (context, callback) { ACL.checkAccess = function (context, callback) {
context = context || {}; context = context || {};
var principals = context.principals || []; var principals = context.principals || [];
// add ROLE.EVERYONE
principals.unshift({principalType: ACL.ROLE, principalId: Role.EVERYONE});
var model = context.model; var model = context.model;
model = ('string' === typeof model) ? loopback.getModel(model) : model; model = ('string' === typeof model) ? loopback.getModel(model) : model;
var id = context.id; var id = context.id;
@ -369,7 +373,7 @@ ACL.checkAccessForToken = function(token, model, modelId, method, callback) {
callback && callback(err); callback && callback(err);
return; return;
} }
callback && callback(access.permission !== ACL.DENY); callback && callback(null, access.permission !== ACL.DENY);
}); });
}; };

View File

@ -1,5 +1,6 @@
var loopback = require('../'); var loopback = require('../');
var Token = loopback.AccessToken.extend('MyToken'); var Token = loopback.AccessToken.extend('MyToken');
var ACL = loopback.ACL;
describe('loopback.token(options)', function() { describe('loopback.token(options)', function() {
beforeEach(createTestingToken); beforeEach(createTestingToken);
@ -54,6 +55,27 @@ describe('AccessToken', function () {
}); });
}); });
describe('app.enableAuth()', function() {
this.timeout(0);
beforeEach(createTestingToken);
it('should prevent all remote method calls without an accessToken', function (done) {
createTestAppAndRequest(this.token, done)
.get('/tests')
.expect(401)
.end(done);
});
it('should prevent remote method calls if the accessToken doesnt have access', function (done) {
createTestAppAndRequest(this.token, done)
.del('/tests/123')
.expect(401)
.set('authorization', this.token.id)
.end(done);
});
});
function createTestingToken(done) { function createTestingToken(done) {
var test = this; var test = this;
Token.create({}, function (err, token) { Token.create({}, function (err, token) {
@ -86,6 +108,23 @@ function createTestApp(testToken, done) {
} }
res.send('ok'); res.send('ok');
}); });
app.use(loopback.rest());
app.enableAuth();
var TestModel = loopback.Model.extend('test', {}, {
acls: [
{
principalType: "ROLE",
principalId: "$everyone",
accessType: ACL.ALL,
permission: ACL.DENY,
property: 'removeById'
}
]
});
TestModel.attachTo(loopback.memory());
app.model(TestModel);
return app; return app;
} }

View File

@ -12,7 +12,6 @@ describe('User', function(){
User.setMaxListeners(0); User.setMaxListeners(0);
before(function () { before(function () {
debugger;
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'}); User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
}); });
@ -257,8 +256,7 @@ describe('User', function(){
assert(result.token); assert(result.token);
var lines = result.email.message.split('\n'); assert(~result.email.message.indexOf('To: bar@bat.com'));
assert(lines[3].indexOf('To: bar@bat.com') === 0);
done(); done();
}); });
}); });