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

Add extra API keys as headers or query params #759

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
92 changes: 87 additions & 5 deletions lib/schemaUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<object>} 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
Expand All @@ -1150,8 +1151,10 @@ module.exports = {
}

_.forEach(securitySet, (security) => {
let helper;

if (_.isObject(security) && _.isEmpty(security)) {
helper = {
authHelper = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using authHelper here specifically if it will anyways get assigned helper in line no. 1300.

type: 'noauth'
};
return false;
Expand Down Expand Up @@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use else if ( === query ) { condition specifically to represent this better here. As API key can be mentioned inside cookie as well.

queryParams.push(keyValuePair);
}
});

return {
headers,
queryParams
};
},

areMultipleAPIKeysPresent: function (openapi) {
const securitySet = _.get(openapi, 'security', []);
let count = 0;

_.forEach(securitySet, (security) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how you should iterate for multiple API keys.

Take a look at this article: there are combinations of AND and OR in case when multiple keys are mentioned.

https://swagger.io/docs/specification/authentication/api-keys/?sbsearch=API%20Keys

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;
},

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 16 additions & 4 deletions libV2/helpers/collection/generateAuthForCollectionFromOpenAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
};
4 changes: 2 additions & 2 deletions libV2/helpers/collection/generateCollectionFromOpenAPI.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
};
Expand Down
76 changes: 73 additions & 3 deletions libV2/schemaUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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 = {
Expand Down
74 changes: 74 additions & 0 deletions test/unit/helpersv2.test.js
Original file line number Diff line number Diff line change
@@ -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'
}
]
}
]
});

});
});
});
Loading