diff --git a/auth/authenticators/basic-authenticator.ts b/auth/authenticators/basic-authenticator.ts index 5e4a318af..beb7bdcae 100644 --- a/auth/authenticators/basic-authenticator.ts +++ b/auth/authenticators/basic-authenticator.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import extend = require('extend'); import { computeBasicAuthHeader, validateInput } from '../utils'; import { Authenticator } from './authenticator'; import { AuthenticateOptions } from './authenticator-interface'; @@ -71,7 +72,7 @@ export class BasicAuthenticator extends Authenticator { */ public authenticate(requestOptions: AuthenticateOptions): Promise { return new Promise((resolve) => { - requestOptions.headers = Object.assign({}, requestOptions.headers, this.authHeader); + requestOptions.headers = extend(true, {}, requestOptions.headers, this.authHeader); resolve(); }); } diff --git a/auth/authenticators/bearer-token-authenticator.ts b/auth/authenticators/bearer-token-authenticator.ts index 58017d9fe..e07310c7e 100644 --- a/auth/authenticators/bearer-token-authenticator.ts +++ b/auth/authenticators/bearer-token-authenticator.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import extend = require('extend'); import { validateInput } from '../utils'; import { Authenticator } from './authenticator'; import { AuthenticateOptions } from './authenticator-interface'; @@ -76,7 +77,7 @@ export class BearerTokenAuthenticator extends Authenticator { public authenticate(requestOptions: AuthenticateOptions): Promise { return new Promise((resolve) => { const authHeader = { Authorization: `Bearer ${this.bearerToken}` }; - requestOptions.headers = Object.assign({}, requestOptions.headers, authHeader); + requestOptions.headers = extend(true, {}, requestOptions.headers, authHeader); resolve(); }); } diff --git a/auth/authenticators/token-request-based-authenticator.ts b/auth/authenticators/token-request-based-authenticator.ts index efcc4bff5..db9dfaa0e 100644 --- a/auth/authenticators/token-request-based-authenticator.ts +++ b/auth/authenticators/token-request-based-authenticator.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import extend = require('extend'); import { OutgoingHttpHeaders } from 'http'; import { JwtTokenManager } from '../token-managers'; import { Authenticator } from './authenticator'; @@ -118,7 +119,7 @@ export class TokenRequestBasedAuthenticator extends Authenticator { public authenticate(requestOptions: AuthenticateOptions): Promise { return this.tokenManager.getToken().then(token => { const authHeader = { Authorization: `Bearer ${token}` }; - requestOptions.headers = Object.assign({}, requestOptions.headers, authHeader); + requestOptions.headers = extend(true, {}, requestOptions.headers, authHeader); return; }); } diff --git a/auth/token-managers/cp4d-token-manager.ts b/auth/token-managers/cp4d-token-manager.ts index 2259783a0..c397e42d2 100644 --- a/auth/token-managers/cp4d-token-manager.ts +++ b/auth/token-managers/cp4d-token-manager.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import extend = require('extend'); import { computeBasicAuthHeader, validateInput } from '../utils'; import { JwtTokenManager, JwtTokenManagerOptions } from './jwt-token-manager'; @@ -95,7 +96,7 @@ export class Cp4dTokenManager extends JwtTokenManager { options: { url: this.url, method: 'GET', - headers: Object.assign({}, this.headers, requiredHeaders), + headers: extend(true, {}, this.headers, requiredHeaders), rejectUnauthorized: !this.disableSslVerification, } }; diff --git a/auth/token-managers/iam-token-manager.ts b/auth/token-managers/iam-token-manager.ts index 71e2781a4..d6916f1ba 100644 --- a/auth/token-managers/iam-token-manager.ts +++ b/auth/token-managers/iam-token-manager.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import extend = require('extend'); import { OutgoingHttpHeaders } from 'http'; import logger from '../../lib/logger'; import { computeBasicAuthHeader, validateInput } from '../utils'; @@ -132,7 +133,7 @@ export class IamTokenManager extends JwtTokenManager { options: { url: this.url, method: 'POST', - headers: Object.assign({}, this.headers, requiredHeaders), + headers: extend(true, {}, this.headers, requiredHeaders), form: { grant_type: 'urn:ibm:params:oauth:grant-type:apikey', apikey: this.apikey, diff --git a/lib/request-wrapper.ts b/lib/request-wrapper.ts index fd87d8764..8a6792449 100644 --- a/lib/request-wrapper.ts +++ b/lib/request-wrapper.ts @@ -17,6 +17,7 @@ import axios from 'axios'; import { AxiosRequestConfig } from 'axios'; import axiosCookieJarSupport from 'axios-cookiejar-support'; +import extend = require('extend'); import FormData = require('form-data'); import https = require('https'); import querystring = require('querystring'); @@ -53,7 +54,7 @@ export class RequestWrapper { }; // merge axios config into default - Object.assign(axiosConfig, axiosOptions); + extend(true, axiosConfig, axiosOptions); // if the user explicitly sets `disableSslVerification` to true, // `rejectUnauthorized` must be set to false in the https agent @@ -137,7 +138,7 @@ export class RequestWrapper { * @throws {Error} */ public sendRequest(parameters): Promise { - const options = Object.assign({}, parameters.defaultOptions, parameters.options); + const options = extend(true, {}, parameters.defaultOptions, parameters.options); const { path, body, form, formData, qs, method, serviceUrl } = options; let { headers, url } = options; @@ -198,7 +199,7 @@ export class RequestWrapper { if (formData) { data = multipartForm; // form-data generates headers that MUST be included or the request will fail - headers = Object.assign({}, headers, multipartForm.getHeaders()); + headers = extend(true, {}, headers, multipartForm.getHeaders()); } // TEMPORARY: Disabling gzipping due to bug in axios until fix is released: diff --git a/package-lock.json b/package-lock.json index fc9cb75f5..c928185e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -401,6 +401,14 @@ "meow": "5.0.0", "resolve-from": "5.0.0", "resolve-global": "1.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/config-conventional": { @@ -416,6 +424,14 @@ "dev": true, "requires": { "lodash": "4.17.15" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/execute-rule": { @@ -453,6 +469,14 @@ "@commitlint/rules": "^8.3.4", "babel-runtime": "^6.23.0", "lodash": "4.17.15" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/load": { @@ -468,6 +492,14 @@ "cosmiconfig": "^5.2.0", "lodash": "4.17.15", "resolve-from": "^5.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/message": { @@ -509,6 +541,14 @@ "lodash": "4.17.15", "resolve-from": "^5.0.0", "resolve-global": "^1.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/rules": { @@ -1772,14 +1812,41 @@ }, "dependencies": { "conventional-changelog-angular": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", - "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz", + "integrity": "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + } } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true } } }, @@ -1870,14 +1937,41 @@ }, "dependencies": { "conventional-changelog-angular": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", - "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz", + "integrity": "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + } } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true } } }, @@ -2045,6 +2139,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" + }, "@types/yargs-parser": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", @@ -3061,12 +3160,12 @@ } }, "conventional-changelog-writer": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", - "integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz", + "integrity": "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "conventional-commits-filter": "^2.0.6", "dateformat": "^3.0.0", "handlebars": "^4.7.6", @@ -3084,6 +3183,31 @@ "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", "dev": true }, + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, "meow": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", @@ -4104,8 +4228,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -7862,9 +7985,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash._reinterpolate": { diff --git a/package.json b/package.json index 176f5aa52..39c93899b 100644 --- a/package.json +++ b/package.json @@ -55,12 +55,14 @@ "@types/file-type": "~5.2.1", "@types/isstream": "^0.1.0", "@types/node": "~10.14.19", + "@types/tough-cookie": "^4.0.0", "axios": "^0.18.1", "axios-cookiejar-support": "^1.0.0", "camelcase": "^5.3.1", "debug": "^4.1.1", "dotenv": "^6.2.0", "expect": "^26.1.0", + "extend": "^3.0.2", "file-type": "^7.7.1", "form-data": "^2.3.3", "isstream": "~0.1.2", diff --git a/test/unit/request-wrapper.test.js b/test/unit/request-wrapper.test.js index 8f5c1e29d..03b7badbb 100644 --- a/test/unit/request-wrapper.test.js +++ b/test/unit/request-wrapper.test.js @@ -282,6 +282,58 @@ describe('sendRequest', () => { done(); }); + it('should handle merging of different options objects', async done => { + const parameters = { + defaultOptions: { + qs: { + version: '2017-10-15', + }, + serviceUrl: 'https://example.ibm.com', + headers: { + 'test-header': 'test-header-value', + }, + }, + options: { + url: '/v1/environments', + method: 'GET', + qs: { + field: 'value', + }, + headers: { + 'add-header': 'add-header-value', + }, + }, + }; + + let serializedParams; + mockAxiosInstance.mockImplementation(requestParams => { + // This runs the paramsSerializer code in the payload we send with axios + serializedParams = requestParams.paramsSerializer(requestParams.params); + return Promise.resolve(axiosResolveValue); + }); + + const res = await requestWrapperInstance.sendRequest(parameters); + // assert results + expect(serializedParams).toBe('version=2017-10-15&field=value'); + expect(mockAxiosInstance.mock.calls[0][0].url).toEqual( + 'https://example.ibm.com/v1/environments' + ); + expect(mockAxiosInstance.mock.calls[0][0].headers).toEqual({ + // 'Accept-Encoding': 'gzip', + 'test-header': 'test-header-value', + 'add-header': 'add-header-value', + }); + expect(mockAxiosInstance.mock.calls[0][0].method).toEqual(parameters.options.method); + expect(mockAxiosInstance.mock.calls[0][0].params).toEqual({ + field: 'value', + version: '2017-10-15', + }); + expect(mockAxiosInstance.mock.calls[0][0].responseType).toEqual('json'); + expect(res).toEqual(expectedResult); + expect(mockAxiosInstance.mock.calls.length).toBe(1); + done(); + }); + it('should send a request with multiform data', async done => { const parameters = { defaultOptions: {