diff --git a/rest.js b/rest.js index d307ee5..c780f4a 100644 --- a/rest.js +++ b/rest.js @@ -83,15 +83,7 @@ module.exports.create = function (bigconf, deps, app) { jwk.kid = kid; jwk.sub = req.params.sub; - return req.Store.find({ kid: jwk.kid, sub: jwk.sub}).then(function (results) { - if (!results.length) { - return crypto.randomBytes(32).toString('hex'); - } else { - return results[0].id; - } - }).then(function (id) { - return req.Store.upsert(id, jwk); - }); + return req.Store.upsert(jwk.sub+'/'+jwk.kid, jwk); }).then(function () { return { success: true }; }); @@ -99,44 +91,66 @@ module.exports.create = function (bigconf, deps, app) { app.handlePromise(req, res, promise, "[issuer@oauth3.org] create 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 || req.query.sub) === (acc.id || acc.ppid || acc.appScopedId); + }); + if (!allowed) { + throw new Error("no account pairwise identifier matching '" + req.params.sub + "'"); + } + sub = req.params.sub || req.query.sub; + } + + return sub; + }); + }; + Grants.restful.get = function (req, res) { - var query = { - sub: req.params.sub || req.query.sub, - azp: req.params.azp || req.query.azp, - }; - var promise = req.Store.find(query, function (results) { - if (!results.length) { + var promise = Grants.authorizeReq(req).then(function (sub) { + return req.Store.get(sub+'/'+(req.params.azp || req.query.azp)); + }).then(function (result) { + if (!result) { throw new Error('no grants found'); } return { - sub: results[0].sub, - azp: results[0].azp, - scope: results[0].scope, + sub: result.sub, + azp: result.azp, + scope: result.scope, }; }); app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve grants"); }; Grants.restful.saveNew = function (req, res) { - var query = { - sub: req.params.sub, - azp: req.params.azp, - }; - var promise = PromiseA.resolve().then(function () { + var promise = Grants.authorizeReq(req).then(function (sub) { if (typeof req.body.scope !== 'string') { throw new Error("malformed request: 'scope' should be a string"); } - }).then(function () { - return req.Store.find(query, function (results) { - if (!results.length) { - return crypto.randomBytes(32).toString('hex'); - } else { - return results[0].id; - } - }); - }).then(function (id) { - query.scope = req.body.scope.replace(/ *, */g, ','); - return req.Store.upsert(id, query); + var scope = req.body.scope.split(/[+ ,]+/g).join(','); + + var grant = { + sub: sub, + azp: req.params.azp, + scope: scope, + }; + return req.Store.upsert(grant.sub+'/'+grant.azp, grant); }).then(function () { return {success: true}; }); @@ -149,8 +163,8 @@ module.exports.create = function (bigconf, deps, app) { app.post( '/jwks/:sub', Jwks.restful.saveNew); app.use( '/grants', attachSiteStore.bind(null, 'IssuerOauth3OrgGrants')); - app.get( '/grants', Grants.restful.check); - app.get( '/grants/:sub/:azp', Grants.restful.check); + app.get( '/grants', Grants.restful.get); + app.get( '/grants/:sub/:azp', Grants.restful.get); app.post( '/grants/:sub/:azp', Grants.restful.saveNew); app.use(detachSiteStore);