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' ]), | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  | |||||||
							
								
								
									
										68
									
								
								rest.js
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								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