made it possible to retrieve keys for a single user only

This commit is contained in:
tigerbot 2017-07-12 14:39:53 -06:00
parent faea77bd10
commit 95d1c0284a
3 changed files with 59 additions and 45 deletions

View File

@ -18,14 +18,13 @@ issuer components are these:
```
api: api.:hostname
authorization_dialog #/authorization_dialog
logout #/logout
create_jwk: :scheme//:hostname/api/issuer@oauth3.org/jwks/:sub
jwks: :scheme//:hostname/api/issuer@oauth3.org/jwks/:thumbprint.json
jwks: :scheme//:hostname/api/issuer@oauth3.org/jwks/:sub/:kid.json
grants: :scheme//:hostname/api/issuer@oauth3.org/grants/:sub/:azp?
credential_meta: :scheme//:hostname/api/issuer@oauth3.org/logins/meta/:type/:id
credential_otp: :scheme//:hostname/api/issuer@oauth3.org/otp
authorization_decision :scheme//:hostname/api/issuer@oauth3.org/authorization_decision
authorization_dialog :scheme//:hostname/api/issuer@oauth3.org/authorization_dialog
logout :scheme//:hostname/api/issuer@oauth3.org/#/logout
```
No `access_token` endpoint is strictly necessary. Since clients can create and
@ -64,11 +63,7 @@ var sub = sha256.digest('hex');
This way any issuer can transfer ownership of identity to any other issuer and
deterministically reproduce the ppid by virtue of the secret identity of the
subject and the public identity of the authorized party and the key is known to
be good if the issuer "iss" can supply the public key that verifies the token,
identified by its thumbprint "kid" (which the issuer knows without revealing
its ppid of the subject and without the authorized party needing to reveal its
ppid of the subject.
subject and the public identity of the authorized party.
JWKs
----
@ -86,9 +81,10 @@ signature verification.
[JWK](https://tools.ietf.org/html/rfc7517#section-4).
### Retrieving a JWK ###
* **URL** `:scheme//:hostname/api/issuer@oauth3.org/jwks/:kid.json`
* **URL** `:scheme//:hostname/api/issuer@oauth3.org/jwks/:sub/:kid.json`
* **Method** `GET`
* **Url Params**
* `sub`: The [subject](#subject) for the 3rd party needing to verify a token
* `kid`: The [JWK thumbprint](https://tools.ietf.org/html/rfc7638) of the key
Currently only `EC` and `RSA` key storage is supported. All provided parameters
@ -98,16 +94,13 @@ specified as part of the public key for the `kty` by the
GET request. This is to avoid compromising a key if the private portion or any
other potentially sensitive fields are given to us.
TODO: we need to somehow associate a key with a particular user without needing
the issuer's subject. Resources providers will not have that subject but will
need to be able to retrieve only public keys that actually belong to the user
that are trying to validate.
Grants
------
Grants represent the list of resources the user has allowed a party to access.
We store those permissions on the server so that users will not have to grant
the same privileges multiple times on different machines.
the same privileges multiple times on different machines. We also store the
[subject](#subject) between the user and the `azp` to allow us to only serve
public keys associated with the correct user when retrieving JWKs.
### Saving/Modifying Grants ###
* **URL** `:scheme//:hostname/api/issuer@oauth3.org/grants/:sub/:azp`
@ -116,6 +109,7 @@ the same privileges multiple times on different machines.
* `sub`: The [subject](#subject) using the issuer hostname as the `azp`
* `azp`: The authorized party the grants are for
* **Body Params**
* `sub`: The [subject](#subject) using `azp` from the url
* `scope`: A comma separated list of the permissions granted
### Retrieving Grants ###
@ -130,10 +124,10 @@ the same privileges multiple times on different machines.
* `scope`: A comma separated list of the permissions granted
* `updatedAt`: The ms timestamp for the most recent change to the grants
### Retrieving All Grants ###
### Retrieving All Grants For a User ###
* **URL** `:scheme//:hostname/api/issuer@oauth3.org/grants/:sub`
* **Method** `GET`
* **Url Params**
* `sub`: The [subject](#subject) using the issuer hostname as the `azp`
* **Response**: An array of objects with the same values as the simple grant
* **Response**: An array of objects with the same values as the single grant
get response.

View File

@ -14,6 +14,6 @@ module.exports = [
tablename: apiname + '_grants',
idname: 'id',
unique: ['id'],
indices: baseFields.concat([ 'sub', 'azp', 'scope' ]),
indices: baseFields.concat([ 'sub', 'azp', 'azpSub', 'scope' ]),
},
];

72
rest.js
View File

@ -89,11 +89,28 @@ module.exports.create = function (bigconf, deps, app) {
};
Jwks.restful.get = function (req, res) {
var promise = req.Store.find({ kid: req.params.kid }, { limit: 1 }).then(function (results) {
var store;
// The sub in params is the 3rd party PPID, but the keys are stored by the issuer PPID, so
// we need to look up the issuer PPID using the 3rd party PPID.
var promise = req.getSiteStore().then(function (_store) {
store = _store;
return store.IssuerOauth3OrgGrants.find({ azpSub: req.params.sub });
}).then(function (results) {
if (!results.length) {
throw new Error('no keys stored with kid "'+req.params.kid+'"');
throw new Error("unknown PPID '"+req.params.sub+"'");
}
if (results.length > 1) {
// This should not ever happen since there is a check for PPID collisions when saving
// grants, but it's probably better to have this check anyway just incase something
// happens that isn't currently accounted for.
throw new Error('PPID collision - unable to safely retrieve keys');
}
return store.IssuerOauth3OrgJwks.get(results[0].sub+'/'+req.params.kid);
}).then(function (jwk) {
if (!jwk) {
throw new Error("no keys stored with kid '"+req.params.kid+"' for PPID "+req.params.sub);
}
var jwk = results[0];
// We need to sanitize the key to make sure we don't deliver any private keys fields if
// we were given a key we could use to sign tokens on behalf of the user. We also don't
@ -132,31 +149,29 @@ module.exports.create = function (bigconf, deps, app) {
};
Grants.trim = function (grant) {
return {
sub: grant.sub,
azp: grant.azp,
// azpSub: grant.azpSub,
scope: grant.scope,
updatedAt: parseInt(grant.updatedAt, 10),
};
};
Grants.restful.getOne = function (req, res) {
var promise = req.Store.get(req.params.sub+'/'+req.params.azp).then(function (grant) {
if (!grant) {
throw new Error('no grants found');
}
return {
sub: grant.sub,
azp: grant.azp,
scope: grant.scope,
updatedAt: parseInt(grant.updatedAt, 10),
};
return Grants.trim(grant);
});
app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve grants");
};
Grants.restful.getAll = function (req, res) {
var promise = req.Store.find({ sub: req.params.sub }).then(function (results) {
return results.map(function (grant) {
return {
sub: grant.sub,
azp: grant.azp,
scope: grant.scope,
updatedAt: parseInt(grant.updatedAt, 10),
};
}).sort(function (grantA, grantB) {
return results.map(Grants.trim).sort(function (grantA, grantB) {
return (grantA.azp < grantB.azp) ? -1 : 1;
});
});
@ -165,15 +180,20 @@ module.exports.create = function (bigconf, deps, app) {
};
Grants.restful.saveNew = function (req, res) {
var promise = PromiseA.resolve().then(function () {
if (typeof req.body.scope !== 'string') {
throw new Error("malformed request: 'scope' should be a string");
if (typeof req.body.scope !== 'string' || typeof req.body.sub !== 'string') {
throw new Error("malformed request: 'sub' and 'scope' must be strings");
}
return req.Store.find({ azpSub: req.body.sub });
}).then(function (existing) {
if (existing.length) {
throw new Error("PPID collision detected, cannot save authorized party's sub");
}
var scope = req.body.scope.split(/[+ ,]+/g).join(',');
var grant = {
sub: req.params.sub,
azp: req.params.azp,
scope: scope,
sub: req.params.sub,
azp: req.params.azp,
azpSub: req.body.sub,
scope: req.body.scope.split(/[+ ,]+/g).join(','),
};
return req.Store.upsert(grant.sub+'/'+grant.azp, grant);
}).then(function () {
@ -183,9 +203,9 @@ module.exports.create = function (bigconf, deps, app) {
app.handlePromise(req, res, promise, '[issuer@oauth3.org] save grants');
};
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.get( '/jwks/:sub/:kid.json', Jwks.restful.get);
// Everything but getting keys is only for the issuer
app.use( '/jwks/:sub', authorizeIssuer, attachSiteStore.bind(null, 'IssuerOauth3OrgJwks'));
app.post( '/jwks/:sub', Jwks.restful.saveNew);
// Everything regarding grants is only for the issuer