From 46fbca1080279326d20078ac04f8699aa5e42b95 Mon Sep 17 00:00:00 2001 From: rentu <5545529+SLdragon@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:07:49 +0800 Subject: [PATCH] perf(spec-parser): update manifest for sso auth (#10464) * perf(spec-parser): update manifest for sso auth * perf: remove unused condition, and add a test case --------- Co-authored-by: turenlong --- .../src/common/spec-parser/manifestUpdater.ts | 64 +++++--- .../src/common/spec-parser/specParser.ts | 2 +- .../spec-parser/manifestUpdater.test.ts | 154 +++++++++++++++++- 3 files changed, 192 insertions(+), 28 deletions(-) diff --git a/packages/fx-core/src/common/spec-parser/manifestUpdater.ts b/packages/fx-core/src/common/spec-parser/manifestUpdater.ts index 5f64b4d0e3..9b774d04c0 100644 --- a/packages/fx-core/src/common/spec-parser/manifestUpdater.ts +++ b/packages/fx-core/src/common/spec-parser/manifestUpdater.ts @@ -6,7 +6,12 @@ import { OpenAPIV3 } from "openapi-types"; import fs from "fs-extra"; import path from "path"; import { ErrorType, WarningResult } from "./interfaces"; -import { getSafeRegistrationIdEnvName, parseApiInfo } from "./utils"; +import { + getSafeRegistrationIdEnvName, + isAPIKeyAuth, + isBearerTokenAuth, + parseApiInfo, +} from "./utils"; import { SpecParserError } from "./specParserError"; import { ConstantString } from "./constants"; import { @@ -21,47 +26,60 @@ export async function updateManifest( adaptiveCardFolder: string, spec: OpenAPIV3.Document, allowMultipleParameters: boolean, - apiKeyAuthName?: string + auth?: OpenAPIV3.SecuritySchemeObject ): Promise<[TeamsAppManifest, WarningResult[]]> { try { const originalManifest: TeamsAppManifest = await fs.readJSON(manifestPath); - + const updatedPart: any = {}; const [commands, warnings] = await generateCommands( spec, adaptiveCardFolder, manifestPath, allowMultipleParameters ); - const ComposeExtension: IComposeExtension = { + const composeExtension: IComposeExtension = { composeExtensionType: "apiBased", apiSpecificationFile: getRelativePath(manifestPath, outputSpecPath), commands: commands, }; - if (apiKeyAuthName) { - const safeApiSecretRegistrationId = getSafeRegistrationIdEnvName( - `${apiKeyAuthName}_${ConstantString.RegistrationIdPostfix}` - ); + if (auth) { + if (isAPIKeyAuth(auth)) { + auth = auth as OpenAPIV3.ApiKeySecurityScheme; + const safeApiSecretRegistrationId = getSafeRegistrationIdEnvName( + `${auth.name}_${ConstantString.RegistrationIdPostfix}` + ); + (composeExtension as any).authorization = { + authType: "apiSecretServiceAuth", + apiSecretServiceAuthConfiguration: { + apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`, + }, + }; + } else if (isBearerTokenAuth(auth)) { + (composeExtension as any).authorization = { + authType: "microsoftEntra", + microsoftEntraConfiguration: { + supportsSingleSignOn: true, + }, + }; - (ComposeExtension as any).authorization = { - authType: "apiSecretServiceAuth", - apiSecretServiceAuthConfiguration: { - apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`, - }, - }; + updatedPart.webApplicationInfo = { + id: "${{AAD_APP_CLIENT_ID}}", + resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}", + }; + } } - const updatedPart = { - description: { - short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens), - full: (spec.info.description ?? originalManifest.description.full)?.slice( - 0, - ConstantString.FullDescriptionMaxLens - ), - }, - composeExtensions: [ComposeExtension], + updatedPart.description = { + short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens), + full: (spec.info.description ?? originalManifest.description.full)?.slice( + 0, + ConstantString.FullDescriptionMaxLens + ), }; + updatedPart.composeExtensions = [composeExtension]; + const updatedManifest = { ...originalManifest, ...updatedPart }; return [updatedManifest, warnings]; diff --git a/packages/fx-core/src/common/spec-parser/specParser.ts b/packages/fx-core/src/common/spec-parser/specParser.ts index ee5c79f249..283802419e 100644 --- a/packages/fx-core/src/common/spec-parser/specParser.ts +++ b/packages/fx-core/src/common/spec-parser/specParser.ts @@ -291,7 +291,7 @@ export class SpecParser { adaptiveCardFolder, newSpec, this.options.allowMultipleParameters, - auth && auth.type === "apiKey" ? auth?.name : "" + auth ); await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 }); diff --git a/packages/fx-core/tests/common/spec-parser/manifestUpdater.test.ts b/packages/fx-core/tests/common/spec-parser/manifestUpdater.test.ts index c075499774..ec40987607 100644 --- a/packages/fx-core/tests/common/spec-parser/manifestUpdater.test.ts +++ b/packages/fx-core/tests/common/spec-parser/manifestUpdater.test.ts @@ -461,7 +461,7 @@ describe("manifestUpdater", () => { expect(warnings).to.deep.equal([]); }); - it("should contain auth property in manifest if pass the api key name", async () => { + it("should contain auth property in manifest if pass the api key auth", async () => { const manifestPath = "/path/to/your/manifest.json"; const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; const adaptiveCardFolder = "/path/to/your/adaptiveCards"; @@ -510,14 +510,156 @@ describe("manifestUpdater", () => { ], }; const readJSONStub = sinon.stub(fs, "readJSON").resolves(originalManifest); + const apiKeyAuth = { + type: "apiKey" as const, + name: "api_key_name", + in: "header", + }; + const [result, warnings] = await updateManifest( + manifestPath, + outputSpecPath, + adaptiveCardFolder, + spec, + false, + apiKeyAuth + ); + expect(result).to.deep.equal(expectedManifest); + expect(warnings).to.deep.equal([]); + }); + + it("should contain auth property in manifest if pass the sso auth", async () => { + const manifestPath = "/path/to/your/manifest.json"; + const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; + const adaptiveCardFolder = "/path/to/your/adaptiveCards"; + sinon.stub(fs, "pathExists").resolves(true); + const originalManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: "Original Short Description", full: "Original Full Description" }, + composeExtensions: [], + }; + const expectedManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: spec.info.title, full: spec.info.description }, + composeExtensions: [ + { + composeExtensionType: "apiBased", + apiSpecificationFile: "spec/outputSpec.yaml", + authorization: { + authType: "microsoftEntra", + microsoftEntraConfiguration: { + supportsSingleSignOn: true, + }, + }, + commands: [ + { + context: ["compose"], + type: "query", + title: "Get all pets", + description: "Returns all pets from the system that the user has access to", + id: "getPets", + parameters: [ + { name: "limit", title: "Limit", description: "Maximum number of pets to return" }, + ], + apiResponseRenderingTemplateFile: "adaptiveCards/getPets.json", + }, + { + context: ["compose"], + type: "query", + title: "Create a pet", + description: "Create a new pet in the store", + id: "createPet", + parameters: [{ name: "name", title: "Name", description: "Name of the pet" }], + apiResponseRenderingTemplateFile: "adaptiveCards/createPet.json", + }, + ], + }, + ], + webApplicationInfo: { + id: "${{AAD_APP_CLIENT_ID}}", + resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}", + }, + }; + const readJSONStub = sinon.stub(fs, "readJSON").resolves(originalManifest); + const oauth2 = { + type: "oauth2" as const, + flows: { + implicit: { + authorizationUrl: "https://example.com/api/oauth/dialog", + scopes: { + "write:pets": "modify pets in your account", + "read:pets": "read your pets", + }, + }, + }, + }; const [result, warnings] = await updateManifest( manifestPath, outputSpecPath, adaptiveCardFolder, spec, false, - "api_key_name" + oauth2 + ); + + expect(result).to.deep.equal(expectedManifest); + expect(warnings).to.deep.equal([]); + }); + + it("should not contain auth property in manifest if pass the unknown auth", async () => { + const manifestPath = "/path/to/your/manifest.json"; + const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; + const adaptiveCardFolder = "/path/to/your/adaptiveCards"; + sinon.stub(fs, "pathExists").resolves(true); + const originalManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: "Original Short Description", full: "Original Full Description" }, + composeExtensions: [], + }; + const expectedManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: spec.info.title, full: spec.info.description }, + composeExtensions: [ + { + composeExtensionType: "apiBased", + apiSpecificationFile: "spec/outputSpec.yaml", + commands: [ + { + context: ["compose"], + type: "query", + title: "Get all pets", + description: "Returns all pets from the system that the user has access to", + id: "getPets", + parameters: [ + { name: "limit", title: "Limit", description: "Maximum number of pets to return" }, + ], + apiResponseRenderingTemplateFile: "adaptiveCards/getPets.json", + }, + { + context: ["compose"], + type: "query", + title: "Create a pet", + description: "Create a new pet in the store", + id: "createPet", + parameters: [{ name: "name", title: "Name", description: "Name of the pet" }], + apiResponseRenderingTemplateFile: "adaptiveCards/createPet.json", + }, + ], + }, + ], + }; + const readJSONStub = sinon.stub(fs, "readJSON").resolves(originalManifest); + const basicAuth = { + type: "http" as const, + scheme: "basic", + }; + const [result, warnings] = await updateManifest( + manifestPath, + outputSpecPath, + adaptiveCardFolder, + spec, + false, + basicAuth ); expect(result).to.deep.equal(expectedManifest); @@ -573,14 +715,18 @@ describe("manifestUpdater", () => { ], }; const readJSONStub = sinon.stub(fs, "readJSON").resolves(originalManifest); - + const apiKeyAuth = { + type: "apiKey" as const, + name: "*api-key_name", + in: "header", + }; const [result, warnings] = await updateManifest( manifestPath, outputSpecPath, adaptiveCardFolder, spec, false, - "*api-key_name" + apiKeyAuth ); expect(result).to.deep.equal(expectedManifest);