Skip to content

Commit

Permalink
fix: allow multiple schemes in a security requirement object (#109)
Browse files Browse the repository at this point in the history
* Multiple security schemes are now considered as valid and flagged as used

* Fixes #108
  • Loading branch information
jormaechea authored and dpopp07 committed Oct 4, 2019
1 parent 80f556a commit f02ef2b
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,24 +74,25 @@ module.exports.validate = function({ resolvedSpec, isOAS3 }, config) {

function flagUsedDefinitions(security) {
security.forEach(scheme => {
// each object in this array should only have one key - the name of the scheme
const name = Object.keys(scheme)[0];

// make sure this scheme was in the security definitions, then label as used
if (definedSchemes[name]) {
definedSchemes[name].used = true;

const type = definedSchemes[name].type;
const scopesArray = scheme[name];

if (type.toLowerCase() === 'oauth2') {
scopesArray.forEach(scope => {
if (definedScopes[scope]) {
definedScopes[scope].used = true;
}
});
const schemeNames = Object.keys(scheme);

schemeNames.forEach(schemeName => {
// make sure this scheme was in the security definitions, then label as used
if (definedSchemes[schemeName]) {
definedSchemes[schemeName].used = true;

const type = definedSchemes[schemeName].type;
const scopesArray = scheme[schemeName];

if (type.toLowerCase() === 'oauth2') {
scopesArray.forEach(scope => {
if (definedScopes[scope]) {
definedScopes[scope].used = true;
}
});
}
}
}
});
});
}

Expand Down
111 changes: 50 additions & 61 deletions src/plugins/validation/2and3/semantic-validators/security-ibm.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,73 +59,62 @@ module.exports.validate = function({ jsSpec, isOAS3 }, config) {

function validateSecurityObject({ security, path }) {
security.forEach(schemeObject => {
// each object in this array should only have one key - the name of the scheme
const schemeNames = Object.keys(schemeObject);
const schemeName = schemeNames[0];

// if there is more than one key, they will be ignored. the structural validator should
// catch these but in case the spec changes in later versions of swagger,
// a non-configurable warning should be printed to alert the user
if (schemeNames.length > 1) {
result.warning.push({
path,
message:
'The validator expects only 1 key-value pair for each object in a security array.'
});
}

const schemeIsDefined =
securityDefinitions && securityDefinitions[schemeName];

// ensure the security scheme is defined
if (!schemeIsDefined) {
result.error.push({
path: `${path}.${schemeName}`,
message: 'security requirements must match a security definition'
});
} else {
const schemeType = securityDefinitions[schemeName].type;
const isNonEmptyArray = schemeObject[schemeName].length > 0;
const schemesWithNonEmptyArrays = isOAS3
? ['oauth2', 'openIdConnect']
: ['oauth2'];

const isSchemeWithNonEmptyArray = schemesWithNonEmptyArrays.includes(
schemeType
);

if (isNonEmptyArray && !isSchemeWithNonEmptyArray) {
const checkStatus = config.invalid_non_empty_security_array;
if (checkStatus !== 'off') {
result[checkStatus].push({
path: `${path}.${schemeName}`,
message: `For security scheme types other than ${schemesWithNonEmptyArrays.join(
' or '
)}, the value must be an empty array.`
});
schemeNames.forEach(schemeName => {
const schemeIsDefined =
securityDefinitions && securityDefinitions[schemeName];

// ensure the security scheme is defined
if (!schemeIsDefined) {
result.error.push({
path: `${path}.${schemeName}`,
message: 'security requirements must match a security definition'
});
} else {
const schemeType = securityDefinitions[schemeName].type;
const isNonEmptyArray = schemeObject[schemeName].length > 0;
const schemesWithNonEmptyArrays = isOAS3
? ['oauth2', 'openIdConnect']
: ['oauth2'];

const isSchemeWithNonEmptyArray = schemesWithNonEmptyArrays.includes(
schemeType
);

if (isNonEmptyArray && !isSchemeWithNonEmptyArray) {
const checkStatus = config.invalid_non_empty_security_array;
if (checkStatus !== 'off') {
result[checkStatus].push({
path: `${path}.${schemeName}`,
message: `For security scheme types other than ${schemesWithNonEmptyArrays.join(
' or '
)}, the value must be an empty array.`
});
}
}
}

if (isSchemeWithNonEmptyArray) {
// check for resolution of specific scopes
const scopes = schemeObject[schemeName];
if (Array.isArray(scopes)) {
// Check for unknown scopes
const securityDefinition = securityDefinitions[schemeName];
scopes.forEach((scope, i) => {
const scopeIsDefined = isOAS3
? checkOAS3Scopes(scope, securityDefinition)
: checkSwagger2Scopes(scope, securityDefinition);
if (!scopeIsDefined) {
result.error.push({
message: `Definition could not be resolved for security scope: ${scope}`,
path: `${path}.${schemeName}.${i}`
});
}
});
if (isSchemeWithNonEmptyArray) {
// check for resolution of specific scopes
const scopes = schemeObject[schemeName];
if (Array.isArray(scopes)) {
// Check for unknown scopes
const securityDefinition = securityDefinitions[schemeName];
scopes.forEach((scope, i) => {
const scopeIsDefined = isOAS3
? checkOAS3Scopes(scope, securityDefinition)
: checkSwagger2Scopes(scope, securityDefinition);
if (!scopeIsDefined) {
result.error.push({
message: `Definition could not be resolved for security scope: ${scope}`,
path: `${path}.${schemeName}.${i}`
});
}
});
}
}
}
}
});
});
}

Expand Down
43 changes: 42 additions & 1 deletion test/plugins/validation/2and3/security-definitions-ibm.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ describe('validation plugin - semantic - security-definitions-ibm', function() {
}
},
api_key: {
// eslint-disable-line camelcase
type: 'apiKey',
name: 'api_key',
in: 'header'
Expand Down Expand Up @@ -290,5 +289,47 @@ describe('validation plugin - semantic - security-definitions-ibm', function() {
expect(res.errors.length).toEqual(0);
expect(res.warnings.length).toEqual(0);
});

it('should not complain if all definitions are used with multiple auth schemes', function() {
const spec = {
components: {
securitySchemes: {
api_key: {
type: 'apiKey',
name: 'api_key',
in: 'header'
},
api_secret: {
type: 'apiKey',
name: 'api_secret',
in: 'header'
}
}
},
paths: {
'/': {
get: {
operationId: 'list',
summary: 'list everything',
security: [
{
api_key: [],
api_secret: []
}
],
responses: {
default: {
description: 'default response'
}
}
}
}
}
};

const res = validate({ resolvedSpec: spec, isOAS3: true }, config);
expect(res.errors.length).toEqual(0);
expect(res.warnings.length).toEqual(0);
});
});
});
29 changes: 29 additions & 0 deletions test/plugins/validation/2and3/security-ibm.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,5 +499,34 @@ describe('validation plugin - semantic - security-ibm', function() {
expect(res.errors.length).toEqual(0);
expect(res.warnings.length).toEqual(0);
});

it('should not complain when multiple security requirements are referenced', () => {
const spec = {
components: {
securitySchemes: {
api_key: {
type: 'apiKey',
name: 'api_key',
in: 'header'
},
api_secret: {
type: 'apiKey',
name: 'api_secret',
in: 'header'
}
}
},
security: [
{
api_key: [],
api_secret: []
}
]
};

const res = validate({ jsSpec: spec, isOAS3: true }, config);
expect(res.errors.length).toEqual(0);
expect(res.warnings.length).toEqual(0);
});
});
});

0 comments on commit f02ef2b

Please sign in to comment.