UNPKG

predix-uaa-client

Version:

Node module to get a token from UAA using client credentials or refresh tokens

425 lines (374 loc) 19.5 kB
'use strict' const expect = require('chai').expect; const request = require('request'); const sinon = require('sinon'); const match = sinon.match; const uaa_util = require('../index'); // ==================================================== // STUBS & HELPERS const url = 'https://test.uaa.predix.io/oauth/token'; const clientId = 'test'; const clientSecret = 'password'; const refreshToken = 'ABC'; const scopes = 'scope1,scope2'; afterEach((done) => { // Undo any sinon mocks if(request.get.restore) request.get.restore(); if(request.post.restore) request.post.restore(); // Clear the token cache uaa_util.clearCache(); done(); }); // ==================================================== // TESTS describe('#UAA Tokens', () => { it('should be able to fetch a client token from UAA', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token', expires_in: 123 })); // When called with client credentials (UAAUrl, ClientID, ClientSecret) // Should fetch a client token using these credentials. // In this case the client needs authorized_grant_types: client_credentials uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'client_credentials' }}))).to.be.ok; expect(stub.neverCalledWith(match({ form: { scopes: 'scopes' }}))).to.be.true; expect(token.access_token).to.equal('test-token'); done(); }).catch((err) => { done(err); }); }); it('should cache tokens', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-1', expires_in: 123 })); uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'client_credentials' }}))).to.be.ok; expect(token.access_token).to.equal('test-token-1'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-2', expires_in: 123 })); // Get it again, it should not call our stub again uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Stub should be called only once overall expect(stub.calledOnce).to.be.true; expect(token.access_token).to.equal('test-token-1'); done(); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }); it('should clear cache by key', (done) => { const testCacheKey = `${url}__${clientId}`; // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-1', expires_in: 123 })); uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'client_credentials' }}))).to.be.ok; expect(token.access_token).to.equal('test-token-1'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-2', expires_in: 123 })); //clear the key, and should call stub again. uaa_util.clearCache(testCacheKey); // Get it again, it should call our stub again uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Stub should be called twice overall expect(stub.calledTwice).to.be.true; expect(token.access_token).to.equal('test-token-2'); done(); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }); it('should fetch a new client token from UAA if expiring soon, but give the current one', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-1', expires_in: 10 })); uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'client_credentials' }}))).to.be.ok; expect(token.access_token).to.equal('test-token-1'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-2', expires_in: 1000 })); // Get it again, it should give the first token, but still call the stub again uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Stub should be called twice overall expect(stub.calledTwice).to.be.true; expect(token.access_token).to.equal('test-token-1'); // Get it one more time, to prove that we got another new token uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Stub should be called twice overall expect(stub.calledTwice).to.be.true; // But now have the new token expect(token.access_token).to.equal('test-token-2'); done(); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }); it('should fetch a new client token from UAA if already expired', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-1', expires_in: 0 })); uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'client_credentials' }}))).to.be.ok; expect(token.access_token).to.equal('test-token-1'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-2', expires_in: 1000 })); // Get it again, it should give us the new token uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Stub should be called twice overall expect(stub.calledTwice).to.be.true; expect(token.access_token).to.equal('test-token-2'); done(); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }); it('should fail if getting an error while calling UAA', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 403 }, null); uaa_util.getToken(url, clientId, clientSecret).then((token) => { done(new Error('Expected error, but got token')); }).catch((err) => { expect(err).to.be.an('error'); expect(err.statusCode).to.equal(403); done(); }).catch((err) => { done(err); }); }); it('should fail if no response while calling UAA', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, null, null); uaa_util.getToken(url, clientId, clientSecret).then((token) => { done(new Error('Expected error, but got token')); }).catch((err) => { expect(err).to.be.an('error'); expect(err.statusCode).to.equal(502); done(); }).catch((err) => { done(err); }); }); it('should fail if there was a network error while calling UAA', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(new Error('ECONNREFUSED, Connection refused'), null, null); uaa_util.getToken(url, clientId, clientSecret).then((token) => { done(new Error('Expected error, but got token')); }).catch((err) => { expect(err).to.be.an('error'); expect(err.statusCode).to.equal(502); done(); }).catch((err) => { done(err); }); }); it('should still return a token, if valid, even if fetching a new one has an error', (done) => { // Make the token appear to expire soon, but not yet let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-1', expires_in: 10 })); uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'client_credentials' }}))).to.be.ok; expect(token.access_token).to.equal('test-token-1'); // Make the next call fail stub.yields(null, { statusCode: 403 }, null); // Get it again, it should give the first token, but still call the stub again uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Stub should be called twice overall expect(stub.calledTwice).to.be.true; expect(token.access_token).to.equal('test-token-1'); done(); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }); it('should error with missing arguments', () => { // Check that the error message contains the missing property expect(uaa_util.getToken).to.throw(/uri/); expect(uaa_util.getToken).to.throw(/clientId/); expect(uaa_util.getToken).to.throw(/clientSecret/); }); it('should be able to refresh an access token from UAA', (done) => { // We expect a POST call with the client credentials as Basic Auth. // grant_type should be refresh token and refresh_token should be included let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token', expires_in: 123 })); // When called with a 4th arg of refreshToken (UAAUrl, ClientID, ClientSecret, refreshToken) // Should fetch an access token using the refresh. // In this case the client needs authorized_grant_types: refresh_token uaa_util.getToken(url, clientId, clientSecret, refreshToken).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'refresh_token' }}))).to.be.ok; expect(token.access_token).to.equal('test-token'); done(); }).catch((err) => { done(err); }); }); it('should never cache access tokens obtained by refresh token', (done) => { // We expect a POST call with the client credentials as Basic Auth. // grant_type should be refresh token and refresh_token should be included let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-1', expires_in: 123 })); // When called with a 4th arg of refreshToken (UAAUrl, ClientID, ClientSecret, refreshToken) // Should fetch an access token using the refresh. // In this case the client needs authorized_grant_types: refresh_token uaa_util.getToken(url, clientId, clientSecret, refreshToken).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'refresh_token' }}))).to.be.ok; expect(stub.calledWith(match({ form: { refresh_token: refreshToken }}))).to.be.ok; expect(token.access_token).to.equal('test-token-1'); // Fake out a new token for the next request stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-2', expires_in: 123 })); // Call again, should get a new token uaa_util.getToken(url, clientId, clientSecret, refreshToken).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledTwice).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'refresh_token' }}))).to.be.ok; expect(stub.calledWith(match({ form: { refresh_token: refreshToken }}))).to.be.ok; expect(token.access_token).to.equal('test-token-2'); done(); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }); it('should fail to refresh if getting an error while calling UAA', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 401 }, null); uaa_util.getToken(url, clientId, clientSecret, refreshToken).then((token) => { done(new Error('Expected error, but got token')); }).catch((err) => { expect(err).to.be.an('error'); expect(err.statusCode).to.equal(401); done(); }).catch((err) => { done(err); }); }); it('should allow only one pending token request per client/refresh/host/scopes combination', (done) => { // Make the token generation have a short delay let v = 55; let stub = sinon.stub(request, 'post', (opt, cb) => { setTimeout(() => { cb(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-'+(v++), expires_in: 1000 })); }, 10); }); // Multiple calls using the same client/refresh/host/scopes should be resolved together const prom1 = uaa_util.getToken(url, clientId, clientSecret); const prom2 = uaa_util.getToken(url, clientId, clientSecret); const prom3 = uaa_util.getToken(url, 'anotherUser', 'anotherPass'); const prom4 = uaa_util.getToken(url, 'anotherUser', 'anotherPass', null, scopes); prom1.then((token1) => { prom2.then((token2) => { prom3.then((token3) => { prom4.then((token4) => { // The call should go out only thrice, as 2 of the requests were the same expect(stub.calledThrice).to.be.true; expect(token1.access_token).to.equal('test-token-55'); expect(token2.access_token).to.equal('test-token-55'); expect(token3.access_token).to.equal('test-token-56'); expect(token4.access_token).to.equal('test-token-57'); done(); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }).catch((err) => { done(err); }); }); it('should expose the token type', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token-1', expires_in: 123, token_type: 'bearer' })); uaa_util.getToken(url, clientId, clientSecret).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'client_credentials' }}))).to.be.ok; expect(token.access_token).to.equal('test-token-1'); expect(token.token_type).to.equal('bearer'); done(); }).catch((err) => { done(err); }); }); it('should request scopes when calling UAA if scopes parameter given', (done) => { // We expect a POST call with the client credentials as Basic Auth. let stub = sinon.stub(request, 'post'); stub.yields(null, { statusCode: 200 }, JSON.stringify({ access_token: 'test-token', expires_in: 123 })); // When called with a 5th arg of scopes (UAAUrl, ClientID, ClientSecret, refreshToken, scopes) // Should fetch an access token requesting the scopes. // In this case the client needs authorized_grant_types: refresh_token uaa_util.getToken(url, clientId, clientSecret, null, scopes).then((token) => { // Result should be our fake token // Check that the UAA call was made correctly expect(stub.calledOnce).to.be.true; expect(stub.calledWith(match({ url }))).to.be.ok; expect(stub.calledWith(match({ form: { grant_type: 'client_credentials' }}))).to.be.ok; expect(stub.calledWith(match({ form: { scopes: scopes }}))).to.be.ok; expect(token.access_token).to.equal('test-token'); done(); }).catch((err) => { done(err); }); }) });