Skip to content

Commit

Permalink
fix: canonicalize iam url (#125)
Browse files Browse the repository at this point in the history
This change forces canonical use of an IAM URL. The standard operation path for a token
request is now unconditionally added at request time. If a user provides a URL that
includes the path, it will be removed.

As it turns out, there were no tests around URL behavior so I added tests that capture
it, including the behavior added in this change.
  • Loading branch information
dpopp07 authored Mar 9, 2021
1 parent 9e7aa87 commit 7ce6588
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 3 deletions.
24 changes: 21 additions & 3 deletions auth/token-managers/iam-token-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,25 @@ function onlyOne(a: any, b: any): boolean {
return Boolean((a && !b) || (b && !a));
}

/**
* Remove a given suffix if it exists.
*
* @param {string} str - The base string to operate on
* @param {string} suffix - The suffix to remove, if present
* @returns {string}
*/
function removeSuffix(str: string, suffix: string): string {
if (str.endsWith(suffix)) {
str = str.substring(0, str.lastIndexOf(suffix));
}

return str;
}

const CLIENT_ID_SECRET_WARNING = 'Warning: Client ID and Secret must BOTH be given, or the header will not be included.';
const SCOPE = 'scope';
const DEFAULT_IAM_URL = 'https://iam.cloud.ibm.com';
const OPERATION_PATH = '/identity/token';

/** Configuration options for IAM token retrieval. */
interface Options extends JwtTokenManagerOptions {
Expand Down Expand Up @@ -68,7 +85,7 @@ export class IamTokenManager extends JwtTokenManager {
* authorization header for IAM token requests.
* @param {string} [options.clientSecret] The `clientId` and `clientSecret` fields are used to form a "basic"
* authorization header for IAM token requests.
* @param {string} [url='https://iam.cloud.ibm.com/identity/token'] The IAM endpoint for token requests.
* @param {string} [url='https://iam.cloud.ibm.com'] The IAM endpoint for token requests.
* @param {boolean} [options.disableSslVerification] A flag that indicates
* whether verification of the token server's SSL certificate should be
* disabled or not.
Expand All @@ -83,7 +100,8 @@ export class IamTokenManager extends JwtTokenManager {

this.apikey = options.apikey;

this.url = this.url || 'https://iam.cloud.ibm.com/identity/token';
// Canonicalize the URL by removing the operation path if it was specified by the user.
this.url = this.url ? removeSuffix(this.url, OPERATION_PATH) : DEFAULT_IAM_URL;

if (options.clientId) {
this.clientId = options.clientId;
Expand Down Expand Up @@ -177,7 +195,7 @@ export class IamTokenManager extends JwtTokenManager {

const parameters = {
options: {
url: this.url,
url: this.url + OPERATION_PATH,
method: 'POST',
headers: extend(true, {}, this.headers, requiredHeaders),
form: {
Expand Down
44 changes: 44 additions & 0 deletions test/unit/iam-token-manager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,48 @@ describe('iam_token_manager_v1', function() {
expect(instance.refreshToken).toBe(REFRESH_TOKEN);
expect(instance.getRefreshToken()).toBe(REFRESH_TOKEN);
});

it('should use the default url if none is given', () => {
const instance = new IamTokenManager({
apikey: 'abcd-1234',
});

expect(instance.url).toBe('https://iam.cloud.ibm.com');
});

it('should accept a URL from the user if given', () => {
const url = 'some-url.com';
const instance = new IamTokenManager({
apikey: 'abcd-1234',
url,
});

expect(instance.url).toBe(url);
});

it('should remove the operation path from a user-given URL', () => {
const url = 'some-url.com';
const instance = new IamTokenManager({
apikey: 'abcd-1234',
url: url + '/identity/token',
});

expect(instance.url).toBe(url);
});

it('should add the operation path to the stored URL at request time', async () => {
const url = 'some-url.com';
const instance = new IamTokenManager({
apikey: 'abcd-1234',
url,
});

expect(instance.url).toBe(url);

mockSendRequest.mockImplementation(parameters => Promise.resolve(IAM_RESPONSE));
await instance.getToken();

const sendRequestArgs = mockSendRequest.mock.calls[0][0];
expect(sendRequestArgs.options.url).toBe(url + '/identity/token');
});
});

0 comments on commit 7ce6588

Please sign in to comment.