diff --git a/package-lock.json b/package-lock.json index 4117714..51a29f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@apiture/openapi-down-convert", - "version": "0.14.0", + "version": "0.14.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@apiture/openapi-down-convert", - "version": "0.14.0", + "version": "0.14.1", "license": "ISC", "dependencies": { "commander": "^9.4.1", diff --git a/package.json b/package.json index 4b1a1a9..1a5ab3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apiture/openapi-down-convert", - "version": "0.14.0", + "version": "0.14.1", "description": "Tool to down convert OpenAPI 3.1 to OpenAPI 3.0", "main": "lib/src/index.js", "bin": { diff --git a/src/cli.ts b/src/cli.ts index bf3c2fe..801b68c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -37,7 +37,8 @@ async function main(args: string[] = process.argv) { allOfTransform: Boolean(opts.allOf), authorizationUrl: opts.authorizationUrl, tokenUrl: opts.tokenUrl, - scopeDescriptionFile: opts.scopes, + scopeDescriptionFile: opts.oidcToOauth2 || opts.scopes, + convertOpenIdConnectToOAuth2: !! (opts.oidcToOauth2 || opts.scopes), convertSchemaComments: opts.convertSchemaComments, }; const converter = new Converter(source, cOpts); diff --git a/src/converter.ts b/src/converter.ts index 6d22bff..ab709ce 100644 --- a/src/converter.ts +++ b/src/converter.ts @@ -40,6 +40,13 @@ export interface ConverterOptions { authorizationUrl?: string; /** The tokenUrl for openIdConnect -> oauth2 transformation */ tokenUrl?: string; + /** + * If `true`, convert `openIdConnect` security scheme + * to `oauth2`. Some tools (even those which purport to support OAS 3.0) + * do not process the `openIdConnect` security scheme + * (I'm looking at you, openapi-generator) + */ + convertOpenIdConnectToOAuth2?: boolean; /** Name of YAML/JSON file with scope descriptions. * This is a simple map in the format * `{ scope1: "description of scope1", ... }` @@ -64,6 +71,7 @@ export class Converter { private scopeDescriptions = undefined; private convertSchemaComments = false; private returnCode = 0; + private convertOpenIdConnectToOAuth2: boolean; /** * Construct a new Converter @@ -76,7 +84,10 @@ export class Converter { this.allOfTransform = Boolean(options?.allOfTransform); this.authorizationUrl = options?.authorizationUrl || 'https://www.example.com/oauth2/authorize'; this.tokenUrl = options?.tokenUrl || 'https://www.example.com/oauth2/token'; - this.loadScopeDescriptions(options?.scopeDescriptionFile); + this.convertOpenIdConnectToOAuth2 = options?.convertOpenIdConnectToOAuth2 || !!(options?.scopeDescriptionFile); + if (this.convertOpenIdConnectToOAuth2) { + this.loadScopeDescriptions(options.scopeDescriptionFile); + } this.convertSchemaComments = options?.convertSchemaComments; } @@ -84,9 +95,6 @@ export class Converter { * @throws Error if the file cannot be read or parsed as YAML/JSON */ private loadScopeDescriptions(scopeDescriptionFile?: string) { - if (!scopeDescriptionFile) { - return; - } this.scopeDescriptions = yaml.load(fs.readFileSync(scopeDescriptionFile, 'utf8')); } @@ -136,8 +144,8 @@ export class Converter { this.removeLicenseIdentifier(); this.convertSchemaRef(); this.simplifyNonSchemaRef(); - if (this.scopeDescriptions) { - this.convertSecuritySchemes(); + if (this.convertOpenIdConnectToOAuth2) { + this.convertOpenIdConnectSecuritySchemesToOAuth2(); } this.convertJsonSchemaExamples(); this.convertJsonSchemaContentEncoding(); @@ -373,13 +381,16 @@ export class Converter { /** HTTP methods */ static readonly HTTP_METHODS = ['delete', 'get', 'head', 'options', 'patch', 'post', 'put', 'trace' ]; /** - * OpenAPI 3.1 defines a new `openIdConnect` security scheme. - * Down-convert the scheme to `oauth2` / authorization code flow. + * OpenAPI 3.0 defines a new `openIdConnect` security scheme + * but not all tools support `openIdConnect`, even if such tools + * claim support for OAS 3.0 + * This converts the `openIdConnect` security scheme + * to the `oauth2` security scheme. * Collect all the scopes used in any security requirements within * operations and add them to the scheme. Also define the * URLs to the `authorizationUrl` and `tokenUrl` of `oauth2`. */ - convertSecuritySchemes() { + convertOpenIdConnectSecuritySchemesToOAuth2() { const oauth2Scopes = (schemeName: string): object => { const scopes = {}; const paths = this.openapi30?.paths; @@ -410,8 +421,8 @@ export class Converter { scheme.type = 'oauth2'; const openIdConnectUrl = scheme.openIdConnectUrl; scheme.description = `OAuth2 Authorization Code Flow. The client may - GET the OpenID Connect configuration JSON from \`${openIdConnectUrl}\` - to get the correct \`authorizationUrl\` and \`tokenUrl\`.`; +GET the OpenID Connect configuration JSON from \`${openIdConnectUrl}\` +to get the correct \`authorizationUrl\` and \`tokenUrl\`.`; delete scheme.openIdConnectUrl; const scopes = oauth2Scopes(schemeName); scheme.flows = { diff --git a/test/converter.spec.ts b/test/converter.spec.ts index 38dbd10..09b321b 100644 --- a/test/converter.spec.ts +++ b/test/converter.spec.ts @@ -134,6 +134,7 @@ describe('resolver test suite', () => { authorizationUrl: 'https://www.example.com/test/authorize', tokenUrl: 'https://www.example.com/test/token', scopeDescriptionFile: path.join(__dirname, 'data/scopes.yaml'), + convertOpenIdConnectToOAuth2: true }; const converter = new Converter(input, options); const converted: any = converter.convert();