options.loadGrantFunc(ticket.grant, function (err, grant, ext) { if (err || !grant || (grant.app !== ticket.app && grant.app !== ticket.dlg) || grant.user !== ticket.user || !grant.exp || grant.exp <= Hawk.utils.now()) { return callback(err || Hawk.utils.unauthorized('Invalid grant')); } return reissue(ticket, app, grant, ext); });
Ticket.parse(id, encryptionPassword, options, function (err, ticket) { if (err) { return callback(err); } // Check expiration if (ticket.exp <= Hawk.utils.now()) { return callback(Hawk.utils.unauthorized('Expired ticket')); } return callback(null, ticket); });
options.loadAppFunc(payload.delegateTo, (err, delegatedApp) => { if (err) { return callback(err); } if (!delegatedApp) { return callback(Hawk.utils.unauthorized('Invalid application')); } options.loadGrantFunc(ticket.grant, (err, grant, ext) => { if (err) { return callback(err); } if (!grant || grant.app !== ticket.app || grant.user !== ticket.user || !grant.exp || grant.exp <= Hawk.utils.now()) { return callback(Hawk.utils.unauthorized('Invalid grant')); } return delegate(ticket, delegatedApp, grant); }); });
exports.rsvp = function (app, grant, encryptionPassword, options) { options = options || {}; if (!app || !app.id) { throw Boom.internal('Invalid application object'); } if (!grant || !grant.id) { throw Boom.internal('Invalid grant object'); } if (!encryptionPassword) { throw Boom.internal('Invalid encryption password'); } options.ttl = options.ttl || internals.defaults.rsvpTTL; // Construct envelope const envelope = { app: app.id, exp: Hawk.utils.now() + options.ttl, grant: grant.id }; // Stringify and encrypt return Iron.seal(envelope, encryptionPassword, options.iron || Iron.defaults); };
exports.issue = function (app, grant, encryptionPassword, options, callback) { Hoek.toss(app && app.id, Boom.internal('Invalid application object'), callback); Hoek.toss(!grant || (grant.id && grant.user && grant.exp), Boom.internal('Invalid grant object'), callback); Hoek.toss(encryptionPassword, Boom.internal('Invalid encryption password'), callback); Hoek.toss(options, Boom.internal('Invalid options object'), callback); var scope = options.scope || (grant ? grant.scope : null) || app.scope || []; Hoek.toss(Scope.validate(scope), callback); // Construct ticket var exp = (Hawk.utils.now() + (options.ttl || exports.settings.ttl)); if (grant) { exp = Math.min(exp, grant.exp); } var ticket = { exp: exp, app: app.id, scope: scope }; if (options.ext) { ticket.ext = options.ext; } if (grant) { ticket.grant = grant.id; ticket.user = grant.user; } exports.generate(ticket, encryptionPassword, callback); };
exports.rsvp = function (app, grant, encryptionPassword, options, callback) { Hoek.toss(app && app.id, Boom.internal('Invalid application object'), callback); Hoek.toss(grant && grant.id, Boom.internal('Invalid grant object'), callback); Hoek.toss(encryptionPassword, Boom.internal('Invalid encryption password'), callback); Hoek.toss(options, Boom.internal('Invalid options object'), callback); options.ttl = options.ttl || 1 * 60 * 1000 // 1 minute // Construct envelope var envelope = { app: app.id, exp: Hawk.utils.now() + options.ttl, grant: grant.id }; // Stringify and encrypt Iron.seal(envelope, encryptionPassword, exports.settings, function (err, sealed) { if (err) { return callback(err); } var rsvp = sealed; return callback(null, rsvp); }); };
exports._authenticate = async function (req, encryptionPassword, checkExpiration, options) { options = options || {}; Hoek.assert(encryptionPassword, 'Invalid encryption password'); // Hawk credentials lookup method const credentialsFunc = async function (id) { // Parse ticket id const ticket = await Ticket.parse(id, encryptionPassword, options.ticket); // Check expiration if (checkExpiration && ticket.exp <= Hawk.utils.now()) { const error = Hawk.utils.unauthorized('Expired ticket'); error.output.payload.expired = true; throw error; } return ticket; }; // Hawk authentication const { credentials, artifacts } = await Hawk.server.authenticate(req, credentialsFunc, options.hawk); // Check application if (credentials.app !== artifacts.app) { throw Hawk.utils.unauthorized('Mismatching application id'); } if ((credentials.dlg || artifacts.dlg) && credentials.dlg !== artifacts.dlg) { throw Hawk.utils.unauthorized('Mismatching delegated application id'); } // Return result return { ticket: credentials, artifacts }; };
Ticket.parse(payload.rsvp, options.encryptionPassword, options.ticket || {}, (err, envelope) => { if (err) { return callback(err); } if (envelope.app !== ticket.app) { return callback(Boom.forbidden('Mismatching ticket and rsvp apps')); } const now = Hawk.utils.now(); if (envelope.exp <= now) { return callback(Boom.forbidden('Expired rsvp')); } options.loadGrantFunc(envelope.grant, (err, grant, ext) => { if (err) { return callback(err); } if (!grant || (grant.app !== ticket.app && (!envelope.delegate || grant.app !== envelope.delegate.dlg)) || !grant.exp || grant.exp <= now) { return callback(Boom.forbidden('Invalid grant')); } options.loadAppFunc(ticket.app, (err, app) => { if (err) { return callback(err); } if (!app) { return callback(Boom.forbidden('Invalid application')); } const ticketOptions = Hoek.shallow(options.ticket || {}); if (ext) { ticketOptions.ext = ext; } if (envelope.delegate) { ticketOptions.dlg = envelope.delegate.dlg; if (envelope.delegate.scope) { ticketOptions.scope = envelope.delegate.scope; } } Ticket.issue(app, grant, options.encryptionPassword, ticketOptions, callback); }); }); });
const credentialsFunc = async function (id) { // Parse ticket id const ticket = await Ticket.parse(id, encryptionPassword, options.ticket); // Check expiration if (checkExpiration && ticket.exp <= Hawk.utils.now()) { const error = Hawk.utils.unauthorized('Expired ticket'); error.output.payload.expired = true; throw error; } return ticket; };
exports.rsvp = function (app, grant, encryptionPassword, options, callback) { const fail = Hoek.nextTick(callback); if (!app || !app.id) { return fail(Boom.internal('Invalid application object')); } if (!grant || !grant.id) { return fail(Boom.internal('Invalid grant object')); } if (!encryptionPassword) { return fail(Boom.internal('Invalid encryption password')); } if (!options) { return fail(Boom.internal('Invalid options object')); } options.ttl = options.ttl || internals.defaults.rsvpTTL; // Construct envelope const envelope = { app: app.id, exp: Hawk.utils.now() + options.ttl, grant: grant.id }; // Add options for delegation rsvp if (options.dlg) { const delegateOptions = { dlg: options.dlg }; if (options.scope) { delegateOptions.scope = options.scope; } envelope.delegate = delegateOptions; } // Stringify and encrypt Iron.seal(envelope, encryptionPassword, options.iron || Iron.defaults, (err, sealed) => { if (err) { return callback(err); } const rsvp = sealed; return callback(null, rsvp); }); };
Hawk.server.authenticate(req, exports.credentialsFunc(encryptionPassword, options), options.hawk || {}, function (err, credentials, artifacts) { if (err) { return callback(err); } // Check application if (credentials.app !== artifacts.app) { return callback(Hawk.utils.unauthorized('Mismatching application id')); } if ((credentials.dlg || artifacts.dlg) && credentials.dlg !== artifacts.dlg) { return callback(Hawk.utils.unauthorized('Mismatching delegated application id')); } // Return result return callback(null, credentials, artifacts); });
exports.issue = function (app, grant, encryptionPassword, options) { options = options || {}; if (!app || !app.id) { throw Boom.internal('Invalid application object'); } if (grant && (!grant.id || !grant.user || !grant.exp)) { throw Boom.internal('Invalid grant object'); } if (!encryptionPassword) { throw Boom.internal('Invalid encryption password'); } const scope = (grant && grant.scope) || app.scope || []; Scope.validate(scope); if (grant && grant.scope && app.scope && !Scope.isSubset(app.scope, grant.scope)) { throw Boom.internal('Grant scope is not a subset of the application scope'); } // Construct ticket let exp = (Hawk.utils.now() + (options.ttl || internals.defaults.ticketTTL)); if (grant) { exp = Math.min(exp, grant.exp); } const ticket = { exp, app: app.id, scope }; if (grant) { ticket.grant = grant.id; ticket.user = grant.user; } if (options.delegate === false) { // Defaults to true ticket.delegate = false; } return exports.generate(ticket, encryptionPassword, options); };
Server.authenticate(req, options.encryptionPassword, options.hawk || {}, function (err, ticket, ext) { if (err) { return callback(err); } // Entity: any if (entity === 'any') { return callback(null, ticket); } // Entity: required if (entity === 'user') { if (!ticket.user) { return callback(Hawk.utils.unauthorized('Application ticket cannot be used on a user endpoint')); } return callback(null, ticket); } // Entity: none if (entity === 'app') { if (ticket.user) { return callback(Hawk.utils.unauthorized('User ticket cannot be used on an application endpoint')); } return callback(null, ticket); } // Entity: unknown return callback(Boom.internal('Unknown endpoint entity mode')); });
}, function(clientId, callback) { let ext = undefined; // Parse authorization header for ext let attrs = hawk.utils.parseAuthorizationHeader( req.authorization ); // Extra ext if (!(attrs instanceof Error)) { ext = attrs.ext; } // Get credentials with ext loadCredentials(clientId, ext, callback); }, {
it('should construct a valid ticket', function (done) { var encryptionPassword = '******'; var app = { id: '123' }; var grant = { id: 's81u29n1812', user: '******', exp: Hawk.utils.now() + 5000, scope: ['a', 'b'] }; var options = { ttl: 10 * 60 * 1000, scope: ['b'], ext: { x: 'welcome', 'private': 123 } }; Oz.ticket.issue(app, grant, encryptionPassword, options, function (err, envelope) { expect(err).to.not.exist; expect(envelope.ext.x).to.equal('welcome'); expect(envelope.exp).to.equal(grant.exp); expect(envelope.ext.private).to.not.exist; Oz.ticket.parse(envelope.id, encryptionPassword, function (err, ticket) { expect(err).to.not.exist; expect(ticket.ext.x).to.equal('welcome'); expect(ticket.ext.private).to.equal(123); Oz.ticket.reissue(ticket, encryptionPassword, {}, function (err, envelope2) { expect(envelope2.ext.x).to.equal('welcome'); expect(envelope2.id).to.not.equal(envelope.id); done(); }); }); }); });
options.loadAppFunc(ticket.app, (err, app) => { if (err) { return callback(err); } if (!app) { return callback(Hawk.utils.unauthorized('Invalid application')); } if (payload.issueTo && !app.delegate) { return callback(Boom.forbidden('Application has no delegation rights')); } // Application ticket if (!ticket.grant) { return reissue(ticket, app); } // User ticket options.loadGrantFunc(ticket.grant, (err, grant, ext) => { if (err) { return callback(err); } if (!grant || (grant.app !== ticket.app && grant.app !== ticket.dlg) || grant.user !== ticket.user || !grant.exp || grant.exp <= Hawk.utils.now()) { return callback(Hawk.utils.unauthorized('Invalid grant')); } return reissue(ticket, app, grant, ext); }); });
Ticket.parse(payload.rsvp, options.encryptionPassword, options, function (err, envelope) { if (err) { return callback(err); } if (envelope.app !== ticket.app) { return callback(Boom.forbidden('Mismatching ticket and rsvp apps')); } var now = Hawk.utils.now(); if (envelope.exp <= now) { return callback(Boom.forbidden('Expired rsvp')); } options.loadGrantFunc(envelope.grant, function (err, grant, ext) { if (err || !grant || grant.app !== ticket.app || !grant.exp || grant.exp <= now) { return callback(err || Boom.forbidden('Invalid grant')); } options.loadAppFunc(grant.app, function (err, app) { if (err || !app) { return callback(err || Boom.forbidden('Invalid application')); } var ticketOptions = Hoek.clone(options.ticket) || {}; if (ext) { ticketOptions.ext = ext; } Ticket.issue(app, grant, options.encryptionPassword, ticketOptions, callback); }); }); });
Server.authenticate(req, options.encryptionPassword, options, function (err, credentials, artifacts) { if (err) { return callback(err); } // Entity: app if (entity === 'app') { if (credentials.user) { return callback(Hawk.utils.unauthorized('User ticket cannot be used on an application endpoint')); } return callback(null, credentials); } // Entity: any return callback(null, credentials); });
exports.reissue = function (parentTicket, encryptionPassword, options, callback) { Hoek.toss(parentTicket, Boom.internal('Invalid parent ticket object'), callback); Hoek.toss(encryptionPassword, Boom.internal('Invalid encryption password'), callback); Hoek.toss(options, Boom.internal('Invalid options object'), callback); Hoek.toss(!options.scope || Scope.isSubset(parentTicket.scope, options.scope), Boom.forbidden('New scope is not a subset of the parent ticket scope'), callback); Hoek.toss(!options.issueTo || !parentTicket.dlg, Boom.badRequest('Cannot re-delegate'), callback); // Construct ticket var exp = (Hawk.utils.now() + (options.ttl || Settings.ticket.ttl)); if (options.grantExp) { exp = Math.min(exp, options.grantExp); } var ticket = { exp: exp, app: options.issueTo || parentTicket.app, scope: options.scope || parentTicket.scope }; if (options.ext || parentTicket.ext) { ticket.ext = options.ext || parentTicket.ext; } if (parentTicket.grant) { ticket.grant = parentTicket.grant; ticket.user = parentTicket.user; } if (options.issueTo) { ticket.dlg = parentTicket.app; } else if (parentTicket.dlg) { ticket.dlg = parentTicket.dlg; } exports.generate(ticket, encryptionPassword, callback); };
function(localStorageService, $location, $window, $http, $scope) { const hawk = require('hawk'); const host = $location.host(); const port = $location.port(); const loginUrl = `${$location.protocol()}://${host}:${port}/api/auth/login/`; const credentials = { id: $location.search().clientId, key: $location.search().accessToken, algorithm: 'sha256' }; this.loginError = null; var payload = { credentials: credentials, }; // Cribbed from taskcluster-tools. Save this for interacting with tc const results = $location.search(); if (results.certificate) { results.certificate = JSON.parse(results.certificate); payload.ext = hawk.utils.base64urlEncode(JSON.stringify({"certificate": results.certificate})); } const header = hawk.client.header(loginUrl, 'GET', payload); // send a request from client side to TH server signed with TC // creds from login.taskcluster.net $http.get(loginUrl, {headers: {"tcauth": header.field}}) .then(function(resp) { var user = resp.data; user.loggedin = true; localStorageService.set("user", user); localStorageService.set('taskcluster.credentials', results); $window.close(); }, function(data) { $scope.loginError = data.data.detail; }); }
_useHawkAuth: function (request) { var authHeaderKey = "Authorization"; var hawk_id = request.transformed.helperAttributes.hawk_id; var hawk_key = request.transformed.helperAttributes.hawk_key; var algorithm = request.transformed.helperAttributes.algorithm; var user = request.transformed.helperAttributes.user || undefined; var nonce = request.transformed.helperAttributes.nonce || Hawk.utils.randomString(6); var ext = request.transformed.helperAttributes.ext || undefined; var app = request.transformed.helperAttributes.app || undefined; var dlg = request.transformed.helperAttributes.dlg || undefined; var timestamp = request.transformed.helperAttributes.timestamp || undefined; var options = { credentials: { id: hawk_id, key: hawk_key, algorithm: algorithm }, nonce: nonce, ext: ext, app: app, dlg: dlg, timestamp: timestamp, user: user }; var result = Hawk.client.header(request.transformed.url, request.method, options); if (result.err) { Errors.requestError(request, new Error('Unable to compute Hawk Auth parameters: ' + result.err)); return; } var headerObj = Helpers.generateHeaderObj(request.transformed.headers); headerObj[authHeaderKey] = result.field; request.transformed.headers = Helpers.generateHeaderStringFromObj(headerObj); },
exports.credentialsFunc = function (encryptionPassword, options) { Hawk.utils.assert(encryptionPassword, 'Invalid encryption password'); return function (id, callback) { // Parse ticket id Ticket.parse(id, encryptionPassword, options, function (err, ticket) { if (err) { return callback(err); } // Check expiration if (ticket.exp <= Hawk.utils.now()) { return callback(Hawk.utils.unauthorized('Expired ticket')); } return callback(null, ticket); }); }; };
exports.issue = function (app, grant, encryptionPassword, options, callback) { const fail = Hoek.nextTick(callback); if (!app || !app.id) { return fail(Boom.internal('Invalid application object')); } if (grant && (!grant.id || !grant.user || !grant.exp)) { return fail(Boom.internal('Invalid grant object')); } if (!encryptionPassword) { return fail(Boom.internal('Invalid encryption password')); } if (!options) { return fail(Boom.internal('Invalid options object')); } const scope = options.scope || (grant && grant.scope) || app.scope || []; const error = Scope.validate(scope); if (error) { return fail(error); } if (grant && grant.scope && app.scope && !Scope.isSubset(app.scope, grant.scope)) { return fail(Boom.internal('Grant scope is not a subset of the application scope')); } // Construct ticket let exp = (Hawk.utils.now() + (options.ttl || internals.defaults.ticketTTL)); if (grant) { exp = Math.min(exp, grant.exp); } const ticket = { exp: exp, app: app.id, scope: scope }; if (grant) { ticket.grant = grant.id; ticket.user = grant.user; } if (options.delegate === false) { // Defaults to true ticket.delegate = false; } if (options.dlg) { ticket.dlg = options.dlg; } exports.generate(ticket, encryptionPassword, options, callback); };
Server.authenticate(req, options.encryptionPassword, options, (err, ticket, artifacts) => { if (err) { return callback(err); } if (!ticket.user) { return callback(Hawk.utils.unauthorized('App ticket cannot be delegated')); } if (ticket.dlg) { return callback(Boom.badRequest('Cannot re-delegate')); } if (ticket.delegate === false) { // Defaults to true return callback(Boom.forbidden('Ticket does not allow delegation')); } if (payload.scope) { error = Scope.validate(payload.scope); if (error) { return callback(error); } if (!Scope.isSubset(ticket.scope, payload.scope)) { return callback(Boom.forbidden('New scope is not a subset of the parent ticket scope')); } } // Load app options.loadAppFunc(ticket.app, (err, app) => { if (err) { return callback(err); } if (!app) { return callback(Hawk.utils.unauthorized('Invalid application')); } if (!app.delegate) { return callback(Boom.forbidden('Application has no delegation rights')); } // Load delegated app options.loadAppFunc(payload.delegateTo, (err, delegatedApp) => { if (err) { return callback(err); } if (!delegatedApp) { return callback(Hawk.utils.unauthorized('Invalid application')); } options.loadGrantFunc(ticket.grant, (err, grant, ext) => { if (err) { return callback(err); } if (!grant || grant.app !== ticket.app || grant.user !== ticket.user || !grant.exp || grant.exp <= Hawk.utils.now()) { return callback(Hawk.utils.unauthorized('Invalid grant')); } return delegate(ticket, delegatedApp, grant); }); }); }); });
exports.reissue = function (parentTicket, grant, encryptionPassword, options, callback) { const fail = Hoek.nextTick(callback); if (!parentTicket) { return fail(Boom.internal('Invalid parent ticket object')); } if (!encryptionPassword) { return fail(Boom.internal('Invalid encryption password')); } if (!options) { return fail(Boom.internal('Invalid options object')); } if (parentTicket.scope) { const error = Scope.validate(parentTicket.scope); if (error) { return fail(error); } } if (options.scope) { const error = Scope.validate(options.scope); if (error) { return fail(error); } if (!Scope.isSubset(parentTicket.scope, options.scope)) { return fail(Boom.forbidden('New scope is not a subset of the parent ticket scope')); } } if (options.delegate && parentTicket.delegate === false) { return fail(Boom.forbidden('Cannot override ticket delegate restriction')); } if (options.issueTo) { if (parentTicket.dlg) { return fail(Boom.badRequest('Cannot re-delegate')); } if (parentTicket.delegate === false) { // Defaults to true return fail(Boom.forbidden('Ticket does not allow delegation')); } } if (grant && (!grant.id || !grant.user || !grant.exp)) { return fail(Boom.internal('Invalid grant object')); } if (grant || parentTicket.grant) { if (!grant || !parentTicket.grant || parentTicket.grant !== grant.id) { return fail(Boom.internal('Parent ticket grant does not match options.grant')); } } // Construct ticket let exp = (Hawk.utils.now() + (options.ttl || internals.defaults.ticketTTL)); if (grant) { exp = Math.min(exp, grant.exp); } const ticket = { exp: exp, app: options.issueTo || parentTicket.app, scope: options.scope || parentTicket.scope }; if (!options.ext && parentTicket.ext) { options = Hoek.shallow(options); options.ext = parentTicket.ext; } if (grant) { ticket.grant = grant.id; ticket.user = grant.user; } if (options.issueTo) { ticket.dlg = parentTicket.app; } else if (parentTicket.dlg) { ticket.dlg = parentTicket.dlg; } if (options.delegate === false || // Defaults to true parentTicket.delegate === false) { ticket.delegate = false; } exports.generate(ticket, encryptionPassword, options, callback); };