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