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

perf(spec-parser): add bearer token auth support #11098

Merged
merged 2 commits into from
Mar 15, 2024
Merged
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
7 changes: 6 additions & 1 deletion packages/spec-parser/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ export interface ParseOptions {
*/
allowAPIKeyAuth?: boolean;

/**
* If true, the parser will allow Bearer Token authentication in the spec file.
*/
allowBearerTokenAuth?: boolean;

/**
* If true, the parser will allow multiple parameters in the spec file. Teams AI project would ignore this parameters and always true
*/
Expand Down Expand Up @@ -243,6 +248,6 @@ export interface ListAPIResult {
}

export interface AuthInfo {
authSchema: OpenAPIV3.SecuritySchemeObject;
authScheme: OpenAPIV3.SecuritySchemeObject;
name: string;
}
5 changes: 2 additions & 3 deletions packages/spec-parser/src/manifestUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,8 @@ export class ManifestUpdater {
};

if (authInfo) {
let auth = authInfo.authSchema;
if (Utils.isAPIKeyAuth(auth)) {
auth = auth as OpenAPIV3.ApiKeySecurityScheme;
const auth = authInfo.authScheme;
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(
`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`
);
Expand Down
1 change: 1 addition & 0 deletions packages/spec-parser/src/specParser.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class SpecParser {
allowSwagger: false,
allowAPIKeyAuth: false,
allowMultipleParameters: false,
allowBearerTokenAuth: false,
allowOauth2: false,
allowMethods: ["get", "post"],
projectType: ProjectType.SME,
Expand Down
3 changes: 2 additions & 1 deletion packages/spec-parser/src/specParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class SpecParser {
allowMissingId: true,
allowSwagger: true,
allowAPIKeyAuth: false,
allowBearerTokenAuth: false,
allowMultipleParameters: false,
allowOauth2: false,
allowMethods: ["get", "post"],
Expand Down Expand Up @@ -147,7 +148,7 @@ export class SpecParser {

for (const auths of authArray) {
if (auths.length === 1) {
apiResult.auth = auths[0].authSchema;
apiResult.auth = auths[0].authScheme;
break;
}
}
Expand Down
43 changes: 17 additions & 26 deletions packages/spec-parser/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,36 +284,23 @@ export class Utils {
return false;
}

static isSupportedAuth(authSchemaArray: AuthInfo[][], options: ParseOptions): boolean {
if (authSchemaArray.length === 0) {
static isSupportedAuth(authSchemeArray: AuthInfo[][], options: ParseOptions): boolean {
if (authSchemeArray.length === 0) {
return true;
}

if (options.allowAPIKeyAuth || options.allowOauth2) {
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
// Currently we don't support multiple auth in one operation
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
return false;
}

for (const auths of authSchemaArray) {
for (const auths of authSchemeArray) {
if (auths.length === 1) {
if (
!options.allowOauth2 &&
options.allowAPIKeyAuth &&
Utils.isAPIKeyAuth(auths[0].authSchema)
) {
return true;
} else if (
!options.allowAPIKeyAuth &&
options.allowOauth2 &&
Utils.isOAuthWithAuthCodeFlow(auths[0].authSchema)
) {
return true;
} else if (
options.allowAPIKeyAuth &&
options.allowOauth2 &&
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
Utils.isOAuthWithAuthCodeFlow(auths[0].authSchema))
(options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))
) {
return true;
}
Expand All @@ -324,12 +311,16 @@ export class Utils {
return false;
}

static isAPIKeyAuth(authSchema: OpenAPIV3.SecuritySchemeObject): boolean {
return authSchema.type === "apiKey";
static isBearerTokenAuth(authScheme: OpenAPIV3.SecuritySchemeObject): boolean {
return authScheme.type === "http" && authScheme.scheme === "bearer";
}

static isAPIKeyAuth(authScheme: OpenAPIV3.SecuritySchemeObject): boolean {
return authScheme.type === "apiKey";
}

static isOAuthWithAuthCodeFlow(authSchema: OpenAPIV3.SecuritySchemeObject): boolean {
if (authSchema.type === "oauth2" && authSchema.flows && authSchema.flows.authorizationCode) {
static isOAuthWithAuthCodeFlow(authScheme: OpenAPIV3.SecuritySchemeObject): boolean {
if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
return true;
}

Expand All @@ -350,7 +341,7 @@ export class Utils {
for (const name in security) {
const auth = securitySchemas[name] as OpenAPIV3.SecuritySchemeObject;
authArray.push({
authSchema: auth,
authScheme: auth,
name: name,
});
}
Expand Down
89 changes: 82 additions & 7 deletions packages/spec-parser/test/manifestUpdater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,7 @@ describe("manifestUpdater", () => {
};
const readJSONStub = sinon.stub(fs, "readJSON").resolves(originalManifest);
const apiKeyAuth: AuthInfo = {
authSchema: {
authScheme: {
type: "apiKey" as const,
name: "api_key_name",
in: "header",
Expand All @@ -1199,6 +1199,81 @@ describe("manifestUpdater", () => {
expect(warnings).to.deep.equal([]);
});

it("should contain auth property in manifest if pass the bearer token 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: "apiSecretServiceAuth",
apiSecretServiceAuthConfiguration: {
apiSecretRegistrationId: "${{BEARER_TOKEN_AUTH_REGISTRATION_ID}}",
},
},
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 bearerTokenAuth: AuthInfo = {
authScheme: {
type: "http" as const,
scheme: "bearer",
},
name: "bearer_token_auth",
};
const options: ParseOptions = {
allowMultipleParameters: false,
projectType: ProjectType.SME,
allowMethods: ["get", "post"],
};

const [result, warnings] = await ManifestUpdater.updateManifest(
manifestPath,
outputSpecPath,
spec,
options,
adaptiveCardFolder,
bearerTokenAuth
);

expect(result).to.deep.equal(expectedManifest);
expect(warnings).to.deep.equal([]);
});

it("should contain auth property in manifest if pass the oauth2 with auth code flow", async () => {
const manifestPath = "/path/to/your/manifest.json";
const outputSpecPath = "/path/to/your/spec/outputSpec.yaml";
Expand Down Expand Up @@ -1253,7 +1328,7 @@ describe("manifestUpdater", () => {
};
const readJSONStub = sinon.stub(fs, "readJSON").resolves(originalManifest);
const oauth2: AuthInfo = {
authSchema: {
authScheme: {
type: "oauth2",
flows: {
authorizationCode: {
Expand Down Expand Up @@ -1331,7 +1406,7 @@ describe("manifestUpdater", () => {
};
const readJSONStub = sinon.stub(fs, "readJSON").resolves(originalManifest);
const basicAuth: AuthInfo = {
authSchema: {
authScheme: {
type: "http" as const,
scheme: "basic",
},
Expand Down Expand Up @@ -1405,10 +1480,10 @@ describe("manifestUpdater", () => {
};
const readJSONStub = sinon.stub(fs, "readJSON").resolves(originalManifest);
const apiKeyAuth: AuthInfo = {
authSchema: {
type: "apiKey" as const,
name: "key_name",
in: "header",
authScheme: {
type: "http" as const,
scheme: "bearer",
bearerFormat: "JWT",
},
name: "*api-key_auth",
};
Expand Down
67 changes: 67 additions & 0 deletions packages/spec-parser/test/specParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,73 @@ describe("SpecParser", () => {
]);
});

it("should return a list of HTTP methods and paths for all GET with 1 parameter and bearer token auth security", async () => {
const specPath = "valid-spec.yaml";
const specParser = new SpecParser(specPath, { allowBearerTokenAuth: true });
const spec = {
components: {
securitySchemes: {
bearerTokenAuth: {
type: "http",
scheme: "bearer",
},
},
},
servers: [
{
url: "https://server1",
},
],
paths: {
"/user/{userId}": {
get: {
security: [{ bearerTokenAuth: [] }],
operationId: "getUserById",
parameters: [
{
name: "userId",
in: "path",
schema: {
type: "string",
},
},
],
responses: {
200: {
content: {
"application/json": {
schema: {
type: "object",
properties: {
name: {
type: "string",
},
},
},
},
},
},
},
},
},
},
};

const parseStub = sinon.stub(specParser.parser, "parse").resolves(spec as any);
const dereferenceStub = sinon.stub(specParser.parser, "dereference").resolves(spec as any);

const result = await specParser.list();

expect(result).to.deep.equal([
{
api: "GET /user/{userId}",
server: "https://server1",
auth: { type: "http", scheme: "bearer" },
operationId: "getUserById",
},
]);
});

it("should return correct auth information", async () => {
const specPath = "valid-spec.yaml";
const specParser = new SpecParser(specPath, { allowAPIKeyAuth: true });
Expand Down
Loading
Loading