From faea77bd10e45b4e2551051c7953a6b20772eab6 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Wed, 12 Jul 2017 14:35:25 -0600 Subject: [PATCH] restricted saving keys to the issuer only --- rest.js | 88 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/rest.js b/rest.js index bb360e4..17fe3c9 100644 --- a/rest.js +++ b/rest.js @@ -20,6 +20,46 @@ module.exports.create = function (bigconf, deps, app) { next(); } + + function authorizeIssuer(req, res, next) { + var promise = PromiseA.resolve().then(function () { + // It might seem unnecessary to wrap a promise in another promise, but this way it will + // catch the error thrown when a token isn't provided and verifyAsync isn't a function. + return req.oauth3.verifyAsync(); + }).then(function (token) { + // Now that we've confirmed the token is valid we also need to make sure the authorized party + // is us. + // TODO: For the time being the only verify-able tokens are the ones we issued, but this + // will not always be the case. We will need a better way to verify the authorized party. + if (token.iss !== token.azp || token.iss !== token.aud) { + throw new Error("token does not allow access to requested resource"); + } + + var sub = token.sub || token.ppid || (token.acx && (token.acx.id || token.acx.appScopedId)); + if (!sub) { + if (!Array.isArray(token.axs) || !token.axs.length) { + throw new Error("no account pairwise identifier"); + } + + var allowed = token.axs.some(function (acc) { + return req.params.sub === (acc.id || acc.ppid || acc.appScopedId); + }); + if (!allowed) { + throw new Error("no account pairwise identifier matching '" + req.params.sub + "'"); + } + sub = req.params.sub; + } + + if (req.params.sub !== sub) { + throw new Error("token does not allow access to resources for '"+req.params.sub+"'"); + } + next(); + }); + + app.handleRejection(req, res, promise, '[issuer@oauth3.org] authorize req as issuer'); + } + + Jwks.thumbprint = function (jwk) { // To produce a thumbprint we need to create a JSON string with only the required keys for // the key type, with the keys sorted lexicographically and no white space. We then need @@ -88,44 +128,12 @@ module.exports.create = function (bigconf, deps, app) { return { success: true }; }); - app.handlePromise(req, res, promise, "[issuer@oauth3.org] create JWK"); + app.handlePromise(req, res, promise, "[issuer@oauth3.org] save JWK"); }; - Grants.authorizeReq = function (req) { - return PromiseA.resolve().then(function () { - // It might seem unnecessary to wrap a promise in another promise, but this way it will - // catch the error thrown when a token isn't found and verifyAsync isn't a function. - return req.oauth3.verifyAsync(); - }).then(function (token) { - // Just because the token is valid doesn't mean the token is authorized to get or save grants. - // The only place that should be allowed to access grants on behalf of the user is the issuer. - if (token.iss !== token.azp) { - throw new Error("token does not allow access to grants"); - } - - var sub = token.sub || token.ppid || (token.acx && (token.acx.id || token.acx.appScopedId)); - if (!sub) { - if (!Array.isArray(token.axs) || !token.axs.length) { - throw new Error("no account pairwise identifier"); - } - - var allowed = token.axs.some(function (acc) { - return req.params.sub === (acc.id || acc.ppid || acc.appScopedId); - }); - if (!allowed) { - throw new Error("no account pairwise identifier matching '" + req.params.sub + "'"); - } - sub = req.params.sub; - } - - return sub; - }); - }; Grants.restful.getOne = function (req, res) { - var promise = Grants.authorizeReq(req).then(function (sub) { - return req.Store.get(sub+'/'+req.params.azp); - }).then(function (grant) { + var promise = req.Store.get(req.params.sub+'/'+req.params.azp).then(function (grant) { if (!grant) { throw new Error('no grants found'); } @@ -140,9 +148,7 @@ module.exports.create = function (bigconf, deps, app) { app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve grants"); }; Grants.restful.getAll = function (req, res) { - var promise = Grants.authorizeReq(req).then(function (sub) { - return req.Store.find({ sub: sub }); - }).then(function (results) { + var promise = req.Store.find({ sub: req.params.sub }).then(function (results) { return results.map(function (grant) { return { sub: grant.sub, @@ -158,14 +164,14 @@ module.exports.create = function (bigconf, deps, app) { app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve grants"); }; Grants.restful.saveNew = function (req, res) { - var promise = Grants.authorizeReq(req).then(function (sub) { + var promise = PromiseA.resolve().then(function () { if (typeof req.body.scope !== 'string') { throw new Error("malformed request: 'scope' should be a string"); } var scope = req.body.scope.split(/[+ ,]+/g).join(','); var grant = { - sub: sub, + sub: req.params.sub, azp: req.params.azp, scope: scope, }; @@ -179,9 +185,11 @@ module.exports.create = function (bigconf, deps, app) { app.use( '/jwks', attachSiteStore.bind(null, 'IssuerOauth3OrgJwks')); app.get( '/jwks/:kid.json', Jwks.restful.get); + app.use( '/jwks/:sub', authorizeIssuer); // Everything but getting keys is only for the issuer app.post( '/jwks/:sub', Jwks.restful.saveNew); - app.use( '/grants', attachSiteStore.bind(null, 'IssuerOauth3OrgGrants')); + // Everything regarding grants is only for the issuer + app.use( '/grants/:sub', authorizeIssuer, attachSiteStore.bind(null, 'IssuerOauth3OrgGrants')); app.get( '/grants/:sub', Grants.restful.getAll); app.get( '/grants/:sub/:azp', Grants.restful.getOne); app.post( '/grants/:sub/:azp', Grants.restful.saveNew);