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
|
api: api.:hostname
|
||||||
|
authorization_dialog #/authorization_dialog
|
||||||
|
logout #/logout
|
||||||
create_jwk: :scheme//:hostname/api/issuer@oauth3.org/jwks/:sub
|
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?
|
grants: :scheme//:hostname/api/issuer@oauth3.org/grants/:sub/:azp?
|
||||||
credential_meta: :scheme//:hostname/api/issuer@oauth3.org/logins/meta/:type/:id
|
credential_meta: :scheme//:hostname/api/issuer@oauth3.org/logins/meta/:type/:id
|
||||||
credential_otp: :scheme//:hostname/api/issuer@oauth3.org/otp
|
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
|
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
|
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
|
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
|
subject and the public identity of the authorized party.
|
||||||
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.
|
|
||||||
|
|
||||||
JWKs
|
JWKs
|
||||||
----
|
----
|
||||||
@ -86,9 +81,10 @@ signature verification.
|
|||||||
[JWK](https://tools.ietf.org/html/rfc7517#section-4).
|
[JWK](https://tools.ietf.org/html/rfc7517#section-4).
|
||||||
|
|
||||||
### Retrieving a JWK ###
|
### 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`
|
* **Method** `GET`
|
||||||
* **Url Params**
|
* **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
|
* `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
|
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
|
GET request. This is to avoid compromising a key if the private portion or any
|
||||||
other potentially sensitive fields are given to us.
|
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
|
||||||
------
|
------
|
||||||
Grants represent the list of resources the user has allowed a party to access.
|
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
|
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 ###
|
### Saving/Modifying Grants ###
|
||||||
* **URL** `:scheme//:hostname/api/issuer@oauth3.org/grants/:sub/:azp`
|
* **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`
|
* `sub`: The [subject](#subject) using the issuer hostname as the `azp`
|
||||||
* `azp`: The authorized party the grants are for
|
* `azp`: The authorized party the grants are for
|
||||||
* **Body Params**
|
* **Body Params**
|
||||||
|
* `sub`: The [subject](#subject) using `azp` from the url
|
||||||
* `scope`: A comma separated list of the permissions granted
|
* `scope`: A comma separated list of the permissions granted
|
||||||
|
|
||||||
### Retrieving Grants ###
|
### Retrieving Grants ###
|
||||||
@ -130,10 +124,10 @@ the same privileges multiple times on different machines.
|
|||||||
* `scope`: A comma separated list of the permissions granted
|
* `scope`: A comma separated list of the permissions granted
|
||||||
* `updatedAt`: The ms timestamp for the most recent change to the grants
|
* `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`
|
* **URL** `:scheme//:hostname/api/issuer@oauth3.org/grants/:sub`
|
||||||
* **Method** `GET`
|
* **Method** `GET`
|
||||||
* **Url Params**
|
* **Url Params**
|
||||||
* `sub`: The [subject](#subject) using the issuer hostname as the `azp`
|
* `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.
|
get response.
|
||||||
|
@ -14,6 +14,6 @@ module.exports = [
|
|||||||
tablename: apiname + '_grants',
|
tablename: apiname + '_grants',
|
||||||
idname: 'id',
|
idname: 'id',
|
||||||
unique: ['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) {
|
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) {
|
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 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
|
// 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) {
|
Grants.restful.getOne = function (req, res) {
|
||||||
var promise = req.Store.get(req.params.sub+'/'+req.params.azp).then(function (grant) {
|
var promise = req.Store.get(req.params.sub+'/'+req.params.azp).then(function (grant) {
|
||||||
if (!grant) {
|
if (!grant) {
|
||||||
throw new Error('no grants found');
|
throw new Error('no grants found');
|
||||||
}
|
}
|
||||||
return {
|
return Grants.trim(grant);
|
||||||
sub: grant.sub,
|
|
||||||
azp: grant.azp,
|
|
||||||
scope: grant.scope,
|
|
||||||
updatedAt: parseInt(grant.updatedAt, 10),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve grants");
|
app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve grants");
|
||||||
};
|
};
|
||||||
Grants.restful.getAll = function (req, res) {
|
Grants.restful.getAll = function (req, res) {
|
||||||
var promise = req.Store.find({ sub: req.params.sub }).then(function (results) {
|
var promise = req.Store.find({ sub: req.params.sub }).then(function (results) {
|
||||||
return results.map(function (grant) {
|
return results.map(Grants.trim).sort(function (grantA, grantB) {
|
||||||
return {
|
|
||||||
sub: grant.sub,
|
|
||||||
azp: grant.azp,
|
|
||||||
scope: grant.scope,
|
|
||||||
updatedAt: parseInt(grant.updatedAt, 10),
|
|
||||||
};
|
|
||||||
}).sort(function (grantA, grantB) {
|
|
||||||
return (grantA.azp < grantB.azp) ? -1 : 1;
|
return (grantA.azp < grantB.azp) ? -1 : 1;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -165,15 +180,20 @@ module.exports.create = function (bigconf, deps, app) {
|
|||||||
};
|
};
|
||||||
Grants.restful.saveNew = function (req, res) {
|
Grants.restful.saveNew = function (req, res) {
|
||||||
var promise = PromiseA.resolve().then(function () {
|
var promise = PromiseA.resolve().then(function () {
|
||||||
if (typeof req.body.scope !== 'string') {
|
if (typeof req.body.scope !== 'string' || typeof req.body.sub !== 'string') {
|
||||||
throw new Error("malformed request: 'scope' should be a 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 = {
|
var grant = {
|
||||||
sub: req.params.sub,
|
sub: req.params.sub,
|
||||||
azp: req.params.azp,
|
azp: req.params.azp,
|
||||||
scope: scope,
|
azpSub: req.body.sub,
|
||||||
|
scope: req.body.scope.split(/[+ ,]+/g).join(','),
|
||||||
};
|
};
|
||||||
return req.Store.upsert(grant.sub+'/'+grant.azp, grant);
|
return req.Store.upsert(grant.sub+'/'+grant.azp, grant);
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
@ -183,9 +203,9 @@ module.exports.create = function (bigconf, deps, app) {
|
|||||||
app.handlePromise(req, res, promise, '[issuer@oauth3.org] save grants');
|
app.handlePromise(req, res, promise, '[issuer@oauth3.org] save grants');
|
||||||
};
|
};
|
||||||
|
|
||||||
app.use( '/jwks', attachSiteStore.bind(null, 'IssuerOauth3OrgJwks'));
|
app.get( '/jwks/:sub/:kid.json', Jwks.restful.get);
|
||||||
app.get( '/jwks/:kid.json', Jwks.restful.get);
|
// Everything but getting keys is only for the issuer
|
||||||
app.use( '/jwks/:sub', authorizeIssuer); // 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);
|
app.post( '/jwks/:sub', Jwks.restful.saveNew);
|
||||||
|
|
||||||
// Everything regarding grants is only for the issuer
|
// Everything regarding grants is only for the issuer
|
||||||
|
Loading…
x
Reference in New Issue
Block a user