made it possible to retrieve keys for a single user only
This commit is contained in:
parent
faea77bd10
commit
95d1c0284a
30
README.md
30
README.md
@ -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.
|
||||
|
@ -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
72
rest.js
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user