Skip to content

Commit

Permalink
feat(converter): add Reference Object removal support in security-sch…
Browse files Browse the repository at this point in the history
…eme-type plugin (#4001)

This change is specific to openapi-3-1-to-openapi-3-0-3 strategy.

Refs #4000
  • Loading branch information
char0n committed Apr 3, 2024
1 parent 833a918 commit 8266f5a
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 3 deletions.
Expand Up @@ -5,8 +5,18 @@ import {
SecuritySchemeElement,
isSecuritySchemeElement,
isComponentsElement,
isReferenceElement,
mediaTypes,
} from '@swagger-api/apidom-ns-openapi-3-1';
import { AnnotationElement, toValue, isObjectElement, Element } from '@swagger-api/apidom-core';
import {
Element,
ParseResultElement,
AnnotationElement,
isObjectElement,
toValue,
cloneDeep,
} from '@swagger-api/apidom-core';
import { dereferenceApiDOM, ReferenceSet, Reference, url } from '@swagger-api/apidom-reference';

import type { Toolbox } from '../toolbox';

Expand All @@ -17,6 +27,9 @@ type SecuritySchemeTypePluginOptions = {
const securitySchemeTypeRefractorPlugin =
({ annotations }: SecuritySchemeTypePluginOptions) =>
(toolbox: Toolbox) => {
let parseResultElement: ParseResultElement | undefined;
const isRemovableSecuritySchemeElement = (value: unknown): value is SecuritySchemeElement =>
isSecuritySchemeElement(value) && toValue(value.type) === 'mutualTLS';
const removedSecuritySchemes: SecuritySchemeElement[] = [];
const createAnnotation = <T extends Element>(element: T) =>
toolbox.createAnnotation.fromElement(
Expand All @@ -28,6 +41,9 @@ const securitySchemeTypeRefractorPlugin =

return {
visitor: {
ParseResultElement(element: ParseResultElement) {
parseResultElement = element;
},
OpenApi3_1Element(element: OpenApi3_1Element) {
if (!isComponentsElement(element.components)) return undefined;
if (!isObjectElement(element.components.securitySchemes)) return undefined;
Expand All @@ -40,11 +56,43 @@ const securitySchemeTypeRefractorPlugin =

return undefined;
},
ComponentsElement(element: ComponentsElement) {
async ComponentsElement(element: ComponentsElement) {
if (!isObjectElement(element.securitySchemes)) return undefined;

/**
* Removing Reference Objects pointing to removable Security Scheme Objects.
* We need to remove Reference Objects first as they might be pointing
* to Security Scheme Objects that are going to be removed.
*/
const baseURI = url.cwd();
const rootReference = Reference({ uri: baseURI, value: cloneDeep(parseResultElement!) });
for (const memberElement of element.securitySchemes) {
if (!isReferenceElement(memberElement.value)) continue; // eslint-disable-line no-continue

const { value: referenceElement } = memberElement;
const reference = Reference({
uri: `${baseURI}#reference`,
value: new ParseResultElement([referenceElement]),
});
const refSet = ReferenceSet({ refs: [reference, rootReference] });
// eslint-disable-next-line no-await-in-loop
const dereferenced = await dereferenceApiDOM(referenceElement, {
resolve: { baseURI: reference.uri },
parse: { mediaType: mediaTypes.latest() },
dereference: { refSet, immutable: false },
});

if (isRemovableSecuritySchemeElement(dereferenced)) {
element.securitySchemes.remove(toValue(memberElement.key));
annotations.push(createAnnotation(referenceElement));
}
}

/**
* Removing Security Scheme Objects.
*/
element.securitySchemes.forEach((value, key) => {
if (isSecuritySchemeElement(value) && toValue(value.type) === 'mutualTLS') {
if (isRemovableSecuritySchemeElement(value)) {
if (!removedSecuritySchemes.includes(value)) removedSecuritySchemes.push(value);
(element.securitySchemes as SecuritySchemeElement).remove(toValue(key));
annotations.push(createAnnotation(value));
Expand Down Expand Up @@ -80,6 +128,7 @@ const securitySchemeTypeRefractorPlugin =
},
post() {
removedSecuritySchemes.length = 0;
parseResultElement = undefined;
},
};
};
Expand Down
@@ -1,5 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`converter strategies openapi-3-1-to-openapi-3-0-3 security-scheme-type should remove Reference Objects pointing to removable Security Scheme Objects 1`] = `
{
"openapi": "3.0.3",
"paths": {
"/foo": {
"get": {
"summary": "foo",
"operationId": "foo",
"security": [
{
"mutualTLS-scheme2": [
"read",
"write"
]
},
{
"oauth2-scheme2": [
"read",
"write"
]
}
],
"responses": {
"200": {
"description": "foo"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey-scheme1": {
"$ref": "#/components/securitySchemes/apiKey-scheme2"
},
"apiKey-scheme2": {
"type": "oauth2",
"name": "oauth2-scheme",
"in": "header"
}
}
}
}
`;

exports[`converter strategies openapi-3-1-to-openapi-3-0-3 security-scheme-type should remove SecurityScheme object if it has "mutualTLS" type 1`] = `
{
"openapi": "3.0.3",
Expand Down
@@ -0,0 +1,50 @@
{
"openapi": "3.1.0",
"paths": {
"/foo": {
"get": {
"summary": "foo",
"operationId": "foo",
"security": [
{
"mutualTLS-scheme2": [
"read",
"write"
]
},
{
"oauth2-scheme2": [
"read",
"write"
]
}
],
"responses": {
"200": {
"description": "foo"
}
}
}
}
},
"components": {
"securitySchemes": {
"mutualTLS-scheme1": {
"$ref": "#/components/securitySchemes/mutualTLS-scheme2"
},
"apiKey-scheme1": {
"$ref": "#/components/securitySchemes/apiKey-scheme2"
},
"mutualTLS-scheme2": {
"type": "mutualTLS",
"name": "mutualTLS-scheme",
"in": "header"
},
"apiKey-scheme2": {
"type": "oauth2",
"name": "oauth2-scheme",
"in": "header"
}
}
}
}
Expand Up @@ -73,6 +73,21 @@ describe('converter', function () {
assert.strictEqual(endColumn, 13);
assert.strictEqual(endChar, 247);
});

specify(
'should remove Reference Objects pointing to removable Security Scheme Objects',
async function () {
const fixturePath = path.join(__dirname, 'fixtures', 'reference-objects.json');
const convertedParseResult = await convert(fixturePath, {
convert: {
sourceMediaType: openAPI31MediaTypes.findBy('3.1.0', 'json'),
targetMediaType: openAPI30MediaTypes.findBy('3.0.3', 'json'),
},
});

expect(toJSON(convertedParseResult.api!, undefined, 2)).toMatchSnapshot();
},
);
});
});
});
Expand Down

0 comments on commit 8266f5a

Please sign in to comment.