diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index 5eb5e7049..a34014498 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,72 @@ 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; + 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 +2715,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 +2788,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 f4558fbe7..ce9f96c0b 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,8 +21,9 @@ module.exports = function (openapi, securitySet) { } _.forEach(securitySet, (security) => { + let helper; if (_.isObject(security) && _.isEmpty(security)) { - helper = { + authHelper = { type: 'noauth' }; return false; @@ -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..97774375e 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,32 @@ 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); + } + + if (!_.isEmpty) { + headers.push(...extraAPIKeys.headers); + queryParams.push(...extraAPIKeys.queryParams); + } + + url = _.get(baseUrlData, 'baseUrl', '') + url; request = { diff --git a/test/unit/helpersv2.test.js b/test/unit/helpersv2.test.js new file mode 100644 index 000000000..3de32502f --- /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' + } + ] + } + ] + }); + + }); +});