Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oz #234

Merged
merged 4 commits into from
Nov 13, 2012
Merged

Oz #234

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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