Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new token manager for ICP4D #26

Merged
merged 19 commits into from
Jun 5, 2019
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ee1ddad
feat: add new token manager for ICP4D
dpopp07 May 28, 2019
cfa3e1b
feat: add `IcpTokenManagerV1` as a top-level export of the package
dpopp07 May 28, 2019
de711e7
docs: add some documentation on `authentication_type` in the README
dpopp07 May 28, 2019
bd902eb
chore: address pr comments
dpopp07 May 28, 2019
4f2f789
feat: carry `disable_ssl_verification` through to token managers
dpopp07 May 28, 2019
70cfc23
refactor: re-organize auth folder in sdk
dpopp07 May 29, 2019
d91cd06
refactor: remove disable_ssl_verification support in iam
dpopp07 May 29, 2019
4ba77ca
refactor: refactor the token expiration time calculation
dpopp07 May 30, 2019
81a5da2
test: add unit tests for JwtTokenManagerV1
dpopp07 May 30, 2019
f06beb5
Merge branch 'master' into icp4d-authentication
dpopp07 May 30, 2019
6d718fa
test: add unit tests for icp token manager
dpopp07 May 31, 2019
3adbe10
test: add unit tests for base service changes
dpopp07 May 31, 2019
12028fc
refactor: move computeBasicAuthHeader to its own module
dpopp07 Jun 3, 2019
799a876
refactor: rename icp_access_token to icp4d_access_token
dpopp07 Jun 3, 2019
0f4ece7
docs: update copyright date in iam-token-manager-v1.js
dpopp07 Jun 3, 2019
1fc9fb1
build: ignore linting .js files under auth directory
dpopp07 Jun 3, 2019
91cbaf0
test: update base service test to use icp4d_access_token
dpopp07 Jun 3, 2019
9ac1cee
docs: remove references to "refreshing" tokens
dpopp07 Jun 4, 2019
dc3165d
refactor: add parameter `icp4d_url`
dpopp07 Jun 5, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ node_modules/
**/*v*.js
!test/**/*.js
lib/*.js
auth/*.js
index.js
scripts/typedoc/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ doc/
.env
.eslintcache
lib/*.js
auth/*.js
iam-token-manager/*.js
index.js
.nyc_output
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,29 @@ import { BaseService } from 'ibm-cloud-sdk-core';
class YourSDK extends BaseService { ... }
```

## Authentication Types
There are several flavors of authentication supported in this package. To specify the intended authentication pattern to use, the user can pass in the parameter `authentication_type`. This parameter is optional, but it may become required in a future major release. The options for this parameter are `basic`, `iam`, and `icp4d`.

### basic
This indicates Basic Auth is to be used. Users will pass in a `username` and `password` and the SDK will generate a Basic Auth header to send with requests to the service.

### iam
This indicates that IAM token authentication is to be used. Users can pass in an `iam_apikey` or an `iam_access_token`. If an API key is used, the SDK will manage the token for the user. In either case, the SDK will generate a Bearer Auth header to send with requests to the service.

### icp4d
This indicates that the service is an instance of ICP4D, which has its own version of token authentication. Users can pass in a `username` and `password`, or an `icp4d_access_token`. If a username and password is given, the SDK will manage the token for the user.
A `url` is **required** for this type. In order to use an SDK-managed token with ICP4D authentication, this option **must** be passed in.

## Available Modules
### BaseService
This Class is the base class that all generated service-specific classes inherit from. It implements credentials handling and other shared behavior.

### IamTokenManagerV1
This Class contains logic for managing an IAM token over its lifetime. Tokens can be requested or set manually. When requested, the token manager will either return the current token, request a new token or refresh the current token if it is expired. If a token is manually set, it must be managed by the user.

### Icp4dTokenManagerV1
This Class is similar in function to IamTokenManagerV1. The only difference is that the `url` parameter is required, it takes a `username` and `password` instead of an API key, and manages tokens for instances of ICP4D. To use this token manager in an SDK, the parameter `authentication_type` must be set to `icp4d` in the constructor.

### isFileParam
This function takes an Object and returns `true` if the object is a Stream, a Buffer, has a `value` property, or has a `data` property that is a file param (checked recursively).

Expand Down
158 changes: 158 additions & 0 deletions auth/iam-token-manager-v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* Copyright 2019 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import extend = require('extend');
import { JwtTokenManagerV1 } from './jwt-token-manager-v1';
import { computeBasicAuthHeader } from './utils';

/**
* Check for only one of two elements being defined.
* Returns true if a is defined and b is undefined,
* or vice versa. Returns false if both are defined
* or both are undefined.
*
* @param {any} a - The first object
* @param {any} b - The second object
* @returns {boolean}
*/
function onlyOne(a: any, b: any): boolean {
return Boolean((a && !b) || (b && !a));
}

const CLIENT_ID_SECRET_WARNING = 'Warning: Client ID and Secret must BOTH be given, or the defaults will be used.';

export type Options = {
url?: string;
iamUrl?: string;
iamApikey?: string;
accessToken?: string;
iamAccessToken?: string;
iamClientId?: string;
iamClientSecret?: string;
}

// this interface is a representation of the response
// object from the IAM service, hence the snake_case
// parameter names
export interface IamTokenData {
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
expiration: number;
}

export class IamTokenManagerV1 extends JwtTokenManagerV1 {
private iamApikey: string;
private iamClientId: string;
private iamClientSecret: string;

/**
* IAM Token Manager Service
*
* Retreives, stores, and refreshes IAM tokens.
*
* @param {Object} options
* @param {String} options.iamApikey
* @param {String} options.iamAccessToken
* @param {String} options.iamUrl - url of the iam api to retrieve tokens from
* @constructor
*/
constructor(options: Options) {
super(options);

this.url = this.url || options.iamUrl || 'https://iam.cloud.ibm.com/identity/token';

if (options.iamApikey) {
this.iamApikey = options.iamApikey;
}
if (options.iamAccessToken) {
this.userAccessToken = options.iamAccessToken;
}
if (options.iamClientId) {
this.iamClientId = options.iamClientId;
}
if (options.iamClientSecret) {
this.iamClientSecret = options.iamClientSecret;
}
if (onlyOne(options.iamClientId, options.iamClientSecret)) {
// tslint:disable-next-line
console.log(CLIENT_ID_SECRET_WARNING);
}
}

/**
* Set the IAM 'client_id' and 'client_secret' values.
* These values are used to compute the Authorization header used
* when retrieving or refreshing the IAM access token.
* If these values are not set, then a default Authorization header
* will be used when interacting with the IAM token server.
*
* @param {string} iamClientId - The client id
* @param {string} iamClientSecret - The client secret
* @returns {void}
*/
public setIamAuthorizationInfo(iamClientId: string, iamClientSecret: string): void {
this.iamClientId = iamClientId;
this.iamClientSecret = iamClientSecret;
if (onlyOne(iamClientId, iamClientSecret)) {
// tslint:disable-next-line
console.log(CLIENT_ID_SECRET_WARNING);
}
}

/**
* Callback for handling response.
*
* @callback requestTokenCallback
* @param {Error} An error if there is one, null otherwise
* @param {Object} The response if request is successful, null otherwise
*/
/**
* Request an IAM token using an API key.
*
* @param {requestTokenCallback} callback - The callback that handles the response.
* @returns {void}
*/
protected requestToken(callback: Function): void {
// Use bx:bx as default auth header creds.
let clientId = 'bx';
let clientSecret = 'bx';

// If both the clientId and secret were specified by the user, then use them.
if (this.iamClientId && this.iamClientSecret) {
clientId = this.iamClientId;
clientSecret = this.iamClientSecret;
}

const parameters = {
options: {
url: this.url,
method: 'POST',
headers: {
'Content-type': 'application/x-www-form-urlencoded',
Authorization: computeBasicAuthHeader(clientId, clientSecret),
},
form: {
grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
apikey: this.iamApikey,
response_type: 'cloud_iam'
},
}
};
this.requestWrapperInstance.sendRequest(parameters, callback);
}
}
108 changes: 108 additions & 0 deletions auth/icp4d-token-manager-v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright 2019 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import extend = require('extend');
import { JwtTokenManagerV1 } from './jwt-token-manager-v1';
import { computeBasicAuthHeader } from './utils';

export type Options = {
url: string;
accessToken?: string;
username?: string;
password?: string;
disableSslVerification?: boolean;
}

// this interface is a representation of the response
// object from the ICP4D authentication service
export interface IcpTokenData {
username: string;
role: string;
permissions: string[];
sub: string;
iss: string;
aud: string;
uid: string;
_messageCode_: string;
message: string;
accessToken: string;
}

export class Icp4dTokenManagerV1 extends JwtTokenManagerV1 {
private username: string;
private password: string;

/**
* ICP Token Manager Service
*
* Retreives, stores, and refreshes ICP tokens.
*
* @param {Object} options
* @param {String} options.username
* @param {String} options.password
* @param {String} options.accessToken - user-managed access token
* @param {String} options.url - URL for the ICP4D cluster
* @param {Boolean} options.disableSslVerification - disable SSL verification for token request
* @constructor
*/
constructor(options: Options) {
super(options);

this.tokenName = 'accessToken';

if (this.url) {
this.url = this.url + '/v1/preauth/validateAuth';
} else {
throw new Error('`url` is a required parameter for Icp4dTokenManagerV1');
}

if (options.username) {
this.username = options.username;
}
if (options.password) {
this.password = options.password;
}
// username and password are required too, unless there's access token
this.rejectUnauthorized = !options.disableSslVerification;
}

/**
* Callback for handling response.
*
* @callback requestTokenCallback
* @param {Error} An error if there is one, null otherwise
* @param {Object} The response if request is successful, null otherwise
*/
/**
* Request an ICP token using a basic auth header.
*
* @param {requestTokenCallback} callback - The callback that handles the response.
* @returns {void}
*/
protected requestToken(callback: Function): void {
const parameters = {
options: {
url: this.url,
method: 'GET',
headers: {
Authorization: computeBasicAuthHeader(this.username, this.password),
},
rejectUnauthorized: this.rejectUnauthorized,
}
};
this.requestWrapperInstance.sendRequest(parameters, callback);
}
}
20 changes: 20 additions & 0 deletions auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright 2019 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export { IamTokenManagerV1 } from './iam-token-manager-v1';
export { Icp4dTokenManagerV1 } from './icp4d-token-manager-v1';
export { JwtTokenManagerV1 } from './jwt-token-manager-v1';
export * from './utils';
Loading