From 939e3ed76ac57447cb68e88d9176dfb8f897424a Mon Sep 17 00:00:00 2001 From: Ankit Saini Date: Mon, 4 Sep 2023 08:05:16 +0530 Subject: [PATCH 1/5] [IMPORT-820] Add extra API keys as headers or query params --- .../generateAuthForCollectionFromOpenAPI.js | 18 ++++- .../generateCollectionFromOpenAPI.js | 4 +- libV2/schemaUtils.js | 74 ++++++++++++++++++- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js b/libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js index f4558fbe7..319ef10b1 100644 --- a/libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js +++ b/libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js @@ -10,8 +10,8 @@ const _ = require('lodash'), * Generate auth for collection/request */ -module.exports = function (openapi, securitySet) { - let securityDef, helper; +module.exports = function (openapi, securitySet, shouldCheckForMultipleKeys) { + let securityDef, authHelper; // return false if security set is not defined // or is an empty array @@ -21,6 +21,7 @@ module.exports = function (openapi, securitySet) { } _.forEach(securitySet, (security) => { + let helper; if (_.isObject(security) && _.isEmpty(security)) { helper = { type: 'noauth' @@ -172,8 +173,19 @@ module.exports = function (openapi, securitySet) { // stop searching for helper if valid auth scheme is found if (!_.isEmpty(helper)) { + if (_.isEmpty(authHelper)) { + authHelper = helper; + } + else if (helper.type === 'apikey') { + authHelper.extraAPIKeys = !authHelper.extraAPIKeys ? [] : authHelper.extraAPIKeys; + authHelper.extraAPIKeys.push(helper); + } + } + + if (!_.isEmpty(authHelper) && !shouldCheckForMultipleKeys) { + // Already got a security schema no need to check further return false; } }); - return helper; + return authHelper; }; diff --git a/libV2/helpers/collection/generateCollectionFromOpenAPI.js b/libV2/helpers/collection/generateCollectionFromOpenAPI.js index 4c455868d..91319e3fc 100644 --- a/libV2/helpers/collection/generateCollectionFromOpenAPI.js +++ b/libV2/helpers/collection/generateCollectionFromOpenAPI.js @@ -1,6 +1,6 @@ const _ = require('lodash'), utils = require('../../utils'), - generateAuthrForCollectionFromOpenAPI = require('./generateAuthForCollectionFromOpenAPI'), + generateAuthForCollectionFromOpenAPI = require('./generateAuthForCollectionFromOpenAPI'), /** * Returns a description that's usable at the collection-level @@ -99,7 +99,7 @@ module.exports = function ({ openapi }) { name: utils.getCollectionName(_.get(openapi, 'info.title')), description: getCollectionDescription(openapi) }, - auth: generateAuthrForCollectionFromOpenAPI(openapi, openapi.security) + auth: generateAuthForCollectionFromOpenAPI(openapi, openapi.security) }, variables: collectionVariables }; diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 78fe122d5..83dd25447 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -1840,6 +1840,57 @@ let QUERYPARAM = 'query', }); return { responses, acceptHeader: requestAcceptHeader }; + }, + + areMultipleAPIKeysPresent = (openapi) => { + const securitySet = _.get(openapi, 'security', []); + let count = 0; + + _.forEach(securitySet, (security) => { + if (_.isEmpty(security)) { + return; + } + + let securityDef = _.get(openapi, ['securityDefs', Object.keys(security)[0]]); + + if (!_.isObject(securityDef)) { + return; + } + + if (securityDef.type === 'apiKey') { + count += 1; + } + }); + + return count > 1; + }, + + getExtraAPIKeyParams = (authHelper) => { + const extraAPIKeys = _.get(authHelper, 'extraAPIKeys', []); + let headers = [], + queryParams = []; + + // Remove extraAPIKeys property + _.set(authHelper, 'extraAPIKeys', undefined); + + extraAPIKeys.forEach((apiKey) => { + let keyValuePair = { + key: _.get(apiKey, 'apikey.0.value'), + value: _.get(apiKey, 'apikey.1.value') + }; + + if (_.get(apiKey, 'apikey.2.value') === 'header') { + headers.push(keyValuePair); + } + else { + queryParams.push(keyValuePair); + } + }); + + return { + headers, + queryParams + }; }; module.exports = { @@ -1861,13 +1912,30 @@ module.exports = { requestBody = resolveRequestBodyForPostmanRequest(context, operationItem[method]), request, securitySchema = _.get(operationItem, [method, 'security']), - authHelper = generateAuthForCollectionFromOpenAPI(context.openapi, securitySchema), - { alwaysInheritAuthentication } = context.computedOptions; - + authHelper = generateAuthForCollectionFromOpenAPI(context.openapi, securitySchema, true), + { alwaysInheritAuthentication } = context.computedOptions, + extraAPIKeys; headers.push(..._.get(requestBody, 'headers', [])); pathVariables.push(...baseUrlData.pathVariables); collectionVariables.push(...baseUrlData.collectionVariables); + // If more than one API key is present in the security definition then add it as + // extra headers + if (!_.isEmpty(authHelper) && !_.isEmpty(authHelper.extraAPIKeys)) { + extraAPIKeys = getExtraAPIKeyParams(authHelper); + } + + // If no security is present at request level then check if it is present at global level + // and if multiple api keys are present there then add them as extra headers + if (_.isEmpty(authHelper) && areMultipleAPIKeysPresent(context.openapi)) { + authHelper = generateAuthForCollectionFromOpenAPI(context.openapi, _.get(context.openapi, 'security', []), true); + extraAPIKeys = getExtraAPIKeyParams(authHelper); + } + + headers.push(...extraAPIKeys.headers); + queryParams.push(...extraAPIKeys.queryParams); + + url = _.get(baseUrlData, 'baseUrl', '') + url; request = { From 759abc5fc49d49cbef22e4e331e435c6196c356e Mon Sep 17 00:00:00 2001 From: Ankit Saini Date: Mon, 4 Sep 2023 10:09:53 +0530 Subject: [PATCH 2/5] [IMPORT-820] add extra API key support to v1 interface --- lib/schemaUtils.js | 93 ++++++++++++++++++- .../generateAuthForCollectionFromOpenAPI.js | 2 +- libV2/schemaUtils.js | 6 +- 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index 5eb5e7049..7dfe87123 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -1133,14 +1133,15 @@ module.exports = { * Gets helper object based on the root spec and the operation.security object * @param {*} openapi - the json object representing the OAS spec * @param {Array} securitySet - the security object at an operation level + * @param {boolean} shouldCheckForMultipleKeys - whether we should check for multiple api keys in security rule set * @returns {object} The authHelper to use while constructing the Postman Request. This is * not directly supported in the SDK - the caller needs to determine the header/body based on the return * value * @no-unit-test */ - getAuthHelper: function(openapi, securitySet) { + getAuthHelper: function(openapi, securitySet, shouldCheckForMultipleKeys) { var securityDef, - helper; + authHelper; // return false if security set is not defined // or is an empty array @@ -1150,8 +1151,10 @@ module.exports = { } _.forEach(securitySet, (security) => { + let helper; + if (_.isObject(security) && _.isEmpty(security)) { - helper = { + authHelper = { type: 'noauth' }; return false; @@ -1293,10 +1296,73 @@ module.exports = { // stop searching for helper if valid auth scheme is found if (!_.isEmpty(helper)) { + if (_.isEmpty(authHelper)) { + authHelper = helper; + } + else if (helper.type === 'apikey') { + authHelper.extraAPIKeys = !authHelper.extraAPIKeys ? [] : authHelper.extraAPIKeys; + authHelper.extraAPIKeys.push(helper); + } + } + + if (!_.isEmpty(authHelper) && !shouldCheckForMultipleKeys) { + // Already got a security schema no need to check further return false; } }); - return helper; + console.log(JSON.stringify(authHelper, null, 2)); + return authHelper; + }, + + getExtraAPIKeyParams: function (authHelper) { + const extraAPIKeys = _.get(authHelper, 'extraAPIKeys', []); + let headers = [], + queryParams = []; + + // Remove extraAPIKeys property + _.set(authHelper, 'extraAPIKeys', undefined); + + extraAPIKeys.forEach((apiKey) => { + let keyValuePair = { + key: _.get(apiKey, 'apikey.0.value'), + value: _.get(apiKey, 'apikey.1.value') + }; + + if (_.get(apiKey, 'apikey.2.value') === 'header') { + headers.push(keyValuePair); + } + else { + queryParams.push(keyValuePair); + } + }); + + return { + headers, + queryParams + }; + }, + + areMultipleAPIKeysPresent: function (openapi) { + const securitySet = _.get(openapi, 'security', []); + let count = 0; + + _.forEach(securitySet, (security) => { + if (_.isEmpty(security)) { + return; + } + + let securityDef = _.get(openapi, ['securityDefs', Object.keys(security)[0]]); + + if (!_.isObject(securityDef)) { + return; + } + + if (securityDef.type === 'apiKey') { + count += 1; + } + }); + + return count > 1; }, /** @@ -2650,7 +2716,7 @@ module.exports = { // handling authentication here (for http type only) if (!options.alwaysInheritAuthentication) { - authHelper = this.getAuthHelper(openapi, operation.security); + authHelper = this.getAuthHelper(openapi, operation.security, true); } // creating the request object @@ -2723,6 +2789,23 @@ module.exports = { } }); + let extraAPIKeys = {}; + if (!_.isEmpty(authHelper) && !_.isEmpty(authHelper.extraAPIKeys)) { + extraAPIKeys = this.getExtraAPIKeyParams(authHelper); + } + + if (_.isEmpty(authHelper) && this.areMultipleAPIKeysPresent(openapi)) { + extraAPIKeys = this.getExtraAPIKeyParams(this.getAuthHelper(openapi, openapi.security, true)); + } + + _.forEach(extraAPIKeys.queryParams, (param) => { + item.request.url.addQueryParams(param); + }); + + _.forEach(extraAPIKeys.headers, (header) => { + item.request.addHeader(header); + }); + // adding Request Body and Content-Type header if (reqBody) { if (reqBody.$ref) { diff --git a/libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js b/libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js index 319ef10b1..ce9f96c0b 100644 --- a/libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js +++ b/libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js @@ -23,7 +23,7 @@ module.exports = function (openapi, securitySet, shouldCheckForMultipleKeys) { _.forEach(securitySet, (security) => { let helper; if (_.isObject(security) && _.isEmpty(security)) { - helper = { + authHelper = { type: 'noauth' }; return false; diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 83dd25447..97774375e 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -1932,8 +1932,10 @@ module.exports = { extraAPIKeys = getExtraAPIKeyParams(authHelper); } - headers.push(...extraAPIKeys.headers); - queryParams.push(...extraAPIKeys.queryParams); + if (!_.isEmpty) { + headers.push(...extraAPIKeys.headers); + queryParams.push(...extraAPIKeys.queryParams); + } url = _.get(baseUrlData, 'baseUrl', '') + url; From a538acff6e0ba85d7ee4f54652426e5a567fa0e4 Mon Sep 17 00:00:00 2001 From: Ankit Saini Date: Mon, 4 Sep 2023 10:10:06 +0530 Subject: [PATCH 3/5] [IMPORT-820] Add unit test --- test/unit/helpersv2.test.js | 74 +++++++++++++++++++++++++++++++++++++ test/unit/util.test.js | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 test/unit/helpersv2.test.js diff --git a/test/unit/helpersv2.test.js b/test/unit/helpersv2.test.js new file mode 100644 index 000000000..f2c96872a --- /dev/null +++ b/test/unit/helpersv2.test.js @@ -0,0 +1,74 @@ +var expect = require('chai').expect, + generateAuthForCollectionFromOpenAPI = require('../../libV2/helpers/collection/generateAuthForCollectionFromOpenAPI'); + +describe('Helper function tests', function () { + describe('getAuthHelper method - Multiple API keys', function() { + it('Should include extra API keys if they are present and we ask for them', function() { + const openAPISpec = { + 'securityDefs': { + 'EmptyAuth': {}, + 'PostmanApiKeyAuth': { + 'type': 'apiKey', + 'in': 'header', + 'name': 'x-api-key', + 'description': 'Needs a valid and active user accessToken.' + }, + 'PostmanAccessTokenAuth': { + 'type': 'apiKey', + 'in': 'header', + 'name': 'x-access-token', + 'description': 'Needs a valid and active user accessToken.' + }, + 'ServiceBasicAuth': { + 'type': 'http', + 'scheme': 'basic', + 'description': 'Need basic-auth credential for a service' + } + } + }, + securitySet = [{ PostmanAccessTokenAuth: [] }, { PostmanApiKeyAuth: [] }], + helperData = generateAuthForCollectionFromOpenAPI(openAPISpec, securitySet, true); + + expect(helperData.type).to.be.equal('apikey'); + expect(helperData).to.have.property('apikey').with.lengthOf(3); + expect(helperData.apikey[0]).to.be.an('object'); + expect(helperData).to.deep.equal({ + 'type': 'apikey', + 'apikey': [ + { + 'key': 'key', + 'value': 'x-access-token' + }, + { + 'key': 'value', + 'value': '{{apiKey}}' + }, + { + 'key': 'in', + 'value': 'header' + } + ], + 'extraAPIKeys': [ + { + 'type': 'apikey', + 'apikey': [ + { + 'key': 'key', + 'value': 'x-api-key' + }, + { + 'key': 'value', + 'value': '{{apiKey}}' + }, + { + 'key': 'in', + 'value': 'header' + } + ] + } + ] + }); + + }); + }); +}) diff --git a/test/unit/util.test.js b/test/unit/util.test.js index 388f10a58..53a22fed3 100644 --- a/test/unit/util.test.js +++ b/test/unit/util.test.js @@ -3465,3 +3465,73 @@ describe('getAuthHelper method - OAuth2 Flows', function() { expect(helperData.oauth2[0].value).to.be.equal('write:pets read:pets'); }); }); + +describe('getAuthHelper method - Multiple API keys', function() { + it('Should include extra API keys if they are present and we ask for them', function() { + const openAPISpec = { + 'securityDefs': { + 'EmptyAuth': {}, + 'PostmanApiKeyAuth': { + 'type': 'apiKey', + 'in': 'header', + 'name': 'x-api-key', + 'description': 'Needs a valid and active user accessToken.' + }, + 'PostmanAccessTokenAuth': { + 'type': 'apiKey', + 'in': 'header', + 'name': 'x-access-token', + 'description': 'Needs a valid and active user accessToken.' + }, + 'ServiceBasicAuth': { + 'type': 'http', + 'scheme': 'basic', + 'description': 'Need basic-auth credential for a service' + } + } + }, + securitySet = [{ PostmanAccessTokenAuth: [] }, { PostmanApiKeyAuth: [] }], + helperData = SchemaUtils.getAuthHelper(openAPISpec, securitySet, true); + + expect(helperData.type).to.be.equal('apikey'); + expect(helperData).to.have.property('apikey').with.lengthOf(3); + expect(helperData.apikey[0]).to.be.an('object'); + expect(helperData).to.deep.equal({ + 'type': 'apikey', + 'apikey': [ + { + 'key': 'key', + 'value': 'x-access-token' + }, + { + 'key': 'value', + 'value': '{{apiKey}}' + }, + { + 'key': 'in', + 'value': 'header' + } + ], + 'extraAPIKeys': [ + { + 'type': 'apikey', + 'apikey': [ + { + 'key': 'key', + 'value': 'x-api-key' + }, + { + 'key': 'value', + 'value': '{{apiKey}}' + }, + { + 'key': 'in', + 'value': 'header' + } + ] + } + ] + }); + + }); +}); From 4272aa4d231507546fe0538659e22fc0cd9d45dc Mon Sep 17 00:00:00 2001 From: Ankit Saini Date: Mon, 4 Sep 2023 10:14:03 +0530 Subject: [PATCH 4/5] [IMPORT-820] Remove console log --- lib/schemaUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index 7dfe87123..a34014498 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -1310,7 +1310,6 @@ module.exports = { return false; } }); - console.log(JSON.stringify(authHelper, null, 2)); return authHelper; }, From 8dfa25888954f7a3e6c10f7700254c0fa97e9019 Mon Sep 17 00:00:00 2001 From: Ankit Saini Date: Mon, 4 Sep 2023 10:15:48 +0530 Subject: [PATCH 5/5] [IMPORT-820] Fix lint error --- test/unit/helpersv2.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/helpersv2.test.js b/test/unit/helpersv2.test.js index f2c96872a..3de32502f 100644 --- a/test/unit/helpersv2.test.js +++ b/test/unit/helpersv2.test.js @@ -71,4 +71,4 @@ describe('Helper function tests', function () { }); }); -}) +});