Skip to content

Commit

Permalink
Merge pull request hapijs#234 from walmartlabs/user/eran
Browse files Browse the repository at this point in the history
Adding Oz support
  • Loading branch information
geek committed Nov 13, 2012
2 parents 24b1ce6 + 5c0305c commit b41604d
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 203 deletions.
41 changes: 41 additions & 0 deletions lib/auth/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Load modules

var Oz = require('./oz');
var Utils = require('../utils');
var Err = require('../error');
var Log = require('../log');


// Declare internals

var internals = {};


exports = module.exports = internals.Auth = function (server, options) {

Utils.assert(this.constructor === internals.Auth, 'Auth must be instantiated using new');
Utils.assert(options, 'Invalid options');
Utils.assert(options.scheme, 'Missing scheme');

// Built-in schemes

if (options.scheme === 'oz') {
this.scheme = new Oz.Scheme(server, options);
}
else if (options.scheme === 'basic') {

}
else {

}

Log.event(['info', 'config', 'auth'], server.settings.nickname + ': Authentication enabled');
return this;
};


internals.Auth.prototype.authenticate = function (request, next) {

return this.scheme.authenticate(request, next);
};

274 changes: 74 additions & 200 deletions lib/auth/oz.js
Original file line number Diff line number Diff line change
@@ -1,87 +1,90 @@
// Load modules

var Oz = require('oz');
var Utils = require('./utils');
var Err = require('./error');
var Types = require('joi').Types;
var Utils = require('../utils');
var Err = require('../error');


// Declare internals

var internals = {};


// Defaults
exports.Scheme = internals.Scheme = function (server, options) {

exports.defaults = {
tokenEndpoint: '/oauth/token',
encryptionPassword: null,
ozSettings: null,
Utils.assert(this.constructor === internals.Scheme, 'Scheme must be instantiated using new');
Utils.assert(options, 'Invalid options');
Utils.assert(options.scheme === 'oz', 'Wrong scheme');
Utils.assert(options.encryptionPassword, 'Missing encryption password');
Utils.assert(options.loadAppFunc && options.loadGrantFunc, 'Missing required methods in configuration');

verifyTicketFunc: null,
getAppFunc: null,
checkAuthorizationFunc: null,
extensionFunc: null,
this.settings = Utils.clone(options); // Options can be reused
this.settings.appEndpoint = this.settings.appEndpoint || '/oz/app';
this.settings.reissueEndpoint = this.settings.reissueEndpoint || '/oz/reissue';
this.settings.rsvpEndpoint = this.settings.rsvpEndpoint || '/oz/rsvp';
this.settings.isHttps = !!server.settings.tls;

tos: {
min: 'none' // Format: YYYYMMDD (e.g. '19700101')
// Setup Oz environment

if (this.settings.ozSettings) {
Oz.settings.set(this.settings.ozSettings);
}
};

// Add protocol endpoints

// Setup endpoints
server.addRoutes([
{ method: 'POST', path: this.settings.appEndpoint, config: this._endpoint('app') },
{ method: 'POST', path: this.settings.reissueEndpoint, config: this._endpoint('reissue') },
{ method: 'POST', path: this.settings.rsvpEndpoint, config: this._endpoint('rsvp') }
]);

exports.setup = function (server) {
return this;
};

if (server.settings.auth) {
Utils.assert(server.settings.auth.tokenEndpoint &&
server.settings.auth.verifyTicketFunc &&
server.settings.auth.getAppFunc &&
server.settings.auth.checkAuthorizationFunc &&
server.settings.auth.encryptionPassword, 'Invalid authentication configuration');

if (server.settings.auth.ozSettings) {
Oz.settings.set(server.settings.auth.ozSettings);
}
// Request an applicaiton ticket using Basic authentication

server.addRoute({
method: 'POST',
path: server.settings.auth.tokenEndpoint,
config: exports.token
});
internals.Scheme.prototype._endpoint = function (name) {

Log.event(['info', 'config'], server.settings.nickname + ': Authentication enabled');
}
};
var self = this;

var endpoint = {
auth: {
mode: 'none'
},
handler: function (request) {

// Token Authentication
Oz.endpoints[name](request.raw.req, request.payload, self.settings, function (err, response) {

if (err &&
err.wwwAuthenticateHeader) {

request.raw.res.setHeader('WWW-Authenticate', err.wwwAuthenticateHeader);
}

exports.authenticate = function (request, next) {
return request.reply(err || response);
});
}
};

/*
return endpoint;
};

if (this.config.auth.mode !== 'none') {
this.config.auth.scope = this.config.auth.scope || null;
this.config.auth.tos = this.config.auth.tos || this.server.settings.auth.tos.min;
this.config.auth.entity = this.config.auth.entity || 'user';

Utils.assert(['user', 'app', 'any'].indexOf(this.config.auth.entity) !== -1, 'Unknown authentication entity: ' + this.config.auth.entity);
}
*/
// Token Authentication

internals.Scheme.prototype.authenticate = function (request, next) {

if (request._route.config.auth.mode === 'none') {
return next();
}
var self = this;

var validate = function (err, ticket, attributes) {

var config = request._route.config.auth;

// Unauthenticated

if (err) {
if (request._route.config.auth.mode === 'optional' &&
if (config.mode === 'optional' &&
!request.raw.req.headers.authorization) {

request.session = null;
Expand All @@ -100,45 +103,48 @@ exports.authenticate = function (request, next) {

// Check scope

if (request._route.config.auth.scope &&
request.session.scope.indexOf(request._route.config.auth.scope) === -1) {
if (config.scope &&
ticket.scope.indexOf(config.scope) === -1) {

request.log(['auth', 'error', 'scope'], { got: request.session.scope, need: request._route.config.auth.scope });
return next(Err.forbidden('Insufficient scope (\'' + request._route.config.auth.scope + '\' expected for application ' + request.session.app + ')'));
request.log(['auth', 'error', 'scope'], { got: ticket.scope, need: config.scope });
return next(Err.forbidden('Insufficient scope (\'' + config.scope + '\' expected for application ' + ticket.app + ')'));
}

// User Mode: any
var entity = config.entity || 'user';

if (request._route.config.auth.entity === 'any') {
// Entity: any

if (entity === 'any') {
request.log(['auth']);
return next();
}

// User Mode: required
// Entity: required

if (request._route.config.auth.entity === 'user') {
if (!request.session.user) {
if (entity === 'user') {
if (!ticket.user) {
request.log(['auth', 'error'], 'User ticket required');
return next(Err.forbidden('Application ticket cannot be used on a user endpoint'));
}

// Check TOS

if (request._route.config.auth.tos !== 'none' &&
(!request.session.ext || !request.session.ext.tos || request.session.ext.tos < request._route.config.auth.tos)) {
var tos = (config.hasOwnProperty('tos') ? config.tos : self.settings.tos);
if (tos &&
(!ticket.ext || !ticket.ext.tos || ticket.ext.tos < tos)) {

request.log(['auth', 'error'], 'Insufficient TOS');
request.log(['auth', 'error', 'tos'], { min: tos, user: ticket.ext && ticket.ext.tos });
return next(Err.forbidden('Insufficient TOS accepted'));
}

request.log(['auth']);
return next();
}

// User Mode: none
// Entity: none

if (request._route.config.auth.entity === 'app') {
if (request.session.user) {
if (entity === 'app') {
if (ticket.user) {
request.log(['auth', 'error'], 'App ticket required');
return next(Err.forbidden('User ticket cannot be used on an application endpoint'));
}
Expand All @@ -147,149 +153,17 @@ exports.authenticate = function (request, next) {
return next();
}

// User Mode: unknown
// Entity: unknown

request.log(['auth', 'error'], 'Unknown entity mode: ' + request._route.config.auth.entity);
request.log(['auth', 'error'], 'Unknown entity mode: ' + entity);
return next(Err.internal('Unknown endpoint entity mode'));
};

if (request.session) {
return validate(null, request.session, null);
}

Oz.Request.authenticate(request.raw.req, request.server.settings.auth.encryptionPassword, { isHttps: request.server.settings.tls }, validate);
};


// Get session token

exports.token = {
schema: {
grant_type: Types.String(),
client_id: Types.String().required(),
client_secret: Types.String().emptyOk(),
grant: Types.String()
},
auth: {
mode: 'optional',
entity: 'any'
},
handler: function (request) {

var serverSettings = request.server.settings.auth;

// Load app information

if (!request.payload.grant_type) {
return issue(null, app);
}
else if (request.payload.grant_type === 'rsvp') {

if (!request.payload.grant) {
return request.reply(new Oz.Error('invalid_request', 'Missing grant'));
}

serverSettings.verifyTicketFunc(request.payload.grant, function (app, user) {

if (!app || !user) {
return request.reply(new Oz.Error('invalid_grant'));
}

return issue(user, app);
});
}
else if (serverSettings.extensionFunc) {
serverSettings.extensionFunc(ticket, function (app, user, action) {

if (!app || !user) {
return request.reply(new Oz.Error('invalid_grant'));
}

return issue(user, app, action);
});
}
else {
// Unsupported grant type
return request.reply(new Oz.Error('unsupported_grant_type', 'Unknown or unsupported grant type'));
}


serverSettings.getAppFunc(request.payload.client_id, function (app) {

if (!app) {
return request.reply(new Oz.Error('invalid_client', 'Invalid application identifier or secret'));
}

// Check app secret

if ((app.secret || '') !== (request.payload.client_secret || '')) {
// Bad app authentication
return request.reply(new Oz.Error('invalid_client', 'Invalid application identifier or secret'));
}

});

function issue(user, app, customResponseFields) {

var generate = function (grant) {

// Issue a new token

var ticketAttr = {
app: {
id: app.id,
scope: app.scope
},
options: {}
};

if (user) {
ticketAttr.user = {
id: user.id,
rsvp: user.rsvp
};
ticketAttr.options.ext = {
tos: user.tos
};
}

Oz.Ticket.generate(ticketAttr.app, ticketAttr.user, request.server.settings.auth.encryptionPassword, ticketAttr.options, function (err, ticket) {

if (err) {
return request.reply(err);
}

if (user) {
ticket.x_tos = ticketAttr.options.ext.tos
}

if (grant) {
ticket.rsvp = grant;
}

Utils.merge(ticket, customResponseFields);
return request.reply(ticket);
});
};

// Application ticket

if (!user) {
return generate();
}

// User ticket

serverSettings.checkAuthorizationFunc(request.session, app, user, function (err, rsvp) {

if (err) {
return request.reply(err);
}

return generate(rsvp);
});
}
}
Oz.request.authenticate(request.raw.req, this.settings.encryptionPassword, { isHttps: this.settings.isHttps }, validate);
};


Loading

0 comments on commit b41604d

Please sign in to comment.