Skip to content

Commit

Permalink
report diagnostics on unsupported auth (#5496)
Browse files Browse the repository at this point in the history
  • Loading branch information
ArcturusZhang authored Jan 14, 2025
1 parent dd36417 commit 74ec773
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function createModel(sdkContext: SdkContext<NetEmitterOptions>): CodeMode
Enums: Array.from(sdkTypeMap.enums.values()),
Models: Array.from(sdkTypeMap.models.values()),
Clients: inputClients,
Auth: processServiceAuthentication(sdkPackage),
Auth: processServiceAuthentication(sdkContext, sdkPackage),
};
return clientModel;

Expand Down
6 changes: 6 additions & 0 deletions packages/http-client-csharp/emitter/src/lib/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ const $lib = createTypeSpecLibrary({
"Cannot generate CSharp SDK since no public root client is defined in typespec file.",
},
},
"unsupported-auth": {
severity: "warning",
messages: {
default: paramMessage`${"message"}`,
},
},
},
emitter: {
options: NetEmitterOptionsSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

import {
SdkContext,
SdkCredentialParameter,
SdkCredentialType,
SdkHttpOperation,
SdkPackage,
} from "@azure-tools/typespec-client-generator-core";
import { NoTarget } from "@typespec/compiler";
import { Oauth2Auth, OAuth2Flow } from "@typespec/http";
import { NetEmitterOptions } from "../options.js";
import { InputAuth } from "../type/input-auth.js";
import { Logger } from "./logger.js";
import { reportDiagnostic } from "./lib.js";

export function processServiceAuthentication(
sdkContext: SdkContext<NetEmitterOptions>,
sdkPackage: SdkPackage<SdkHttpOperation>,
): InputAuth | undefined {
let authClientParameter: SdkCredentialParameter | undefined = undefined;
Expand All @@ -28,11 +32,11 @@ export function processServiceAuthentication(
return undefined;
}
if (authClientParameter.type.kind === "credential") {
return processAuthType(authClientParameter.type);
return processAuthType(sdkContext, authClientParameter.type);
}
const inputAuth: InputAuth = {};
for (const authType of authClientParameter.type.variantTypes) {
const auth = processAuthType(authType);
const auth = processAuthType(sdkContext, authType);
if (auth?.ApiKey) {
inputAuth.ApiKey = auth.ApiKey;
}
Expand All @@ -43,41 +47,61 @@ export function processServiceAuthentication(
return inputAuth;
}

function processAuthType(credentialType: SdkCredentialType): InputAuth | undefined {
function processAuthType(
sdkContext: SdkContext<NetEmitterOptions>,
credentialType: SdkCredentialType,
): InputAuth | undefined {
const scheme = credentialType.scheme;
switch (scheme.type) {
case "apiKey":
return { ApiKey: { Name: scheme.name } } as InputAuth;
if (scheme.in !== "header") {
reportDiagnostic(sdkContext.program, {
code: "unsupported-auth",
format: {
message: `Only header is supported for ApiKey authentication. ${scheme.in} is not supported.`,
},
target: credentialType.__raw ?? NoTarget,
});
return undefined;
}
return { ApiKey: { Name: scheme.name, In: scheme.in } } as InputAuth;
case "oauth2":
return processOAuth2(scheme);
case "http":
{
const schemeOrApiKeyPrefix = scheme.scheme;
switch (schemeOrApiKeyPrefix) {
case "basic":
Logger.getInstance().warn(
`${schemeOrApiKeyPrefix} auth method is currently not supported.`,
);
return undefined;
case "bearer":
return {
ApiKey: {
Name: "Authorization",
Prefix: "Bearer",
},
} as InputAuth;
default:
return {
ApiKey: {
Name: "Authorization",
Prefix: schemeOrApiKeyPrefix,
},
} as InputAuth;
}
case "http": {
const schemeOrApiKeyPrefix = scheme.scheme;
switch (schemeOrApiKeyPrefix) {
case "basic":
reportDiagnostic(sdkContext.program, {
code: "unsupported-auth",
format: { message: `${schemeOrApiKeyPrefix} auth method is currently not supported.` },
target: credentialType.__raw ?? NoTarget,
});
return undefined;
case "bearer":
return {
ApiKey: {
Name: "Authorization",
In: "header",
Prefix: "Bearer",
},
};
default:
return {
ApiKey: {
Name: "Authorization",
In: "header",
Prefix: schemeOrApiKeyPrefix,
},
};
}
break;
}
default:
throw new Error(`un-supported authentication scheme ${scheme.type}`);
reportDiagnostic(sdkContext.program, {
code: "unsupported-auth",
format: { message: `un-supported authentication scheme ${scheme.type}` },
target: credentialType.__raw ?? NoTarget,
});
return undefined;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@

export interface InputApiKeyAuth {
Name: string;
In: ApiKeyLocation;
Prefix?: string;
}

export type ApiKeyLocation = "header"; // | "query" | "cookie"; // we do not support query or cookie yet
70 changes: 70 additions & 0 deletions packages/http-client-csharp/emitter/test/Unit/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { TestHost } from "@typespec/compiler/testing";
import { ok, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
import { createModel } from "../../src/lib/client-model-builder.js";
import {
createEmitterContext,
createEmitterTestHost,
createNetSdkContext,
typeSpecCompile,
} from "./utils/test-util.js";

describe("Test auth", () => {
let runner: TestHost;

beforeEach(async () => {
runner = await createEmitterTestHost();
});

it("cookie header is not supported", async () => {
const program = await typeSpecCompile(
`
op test(): NoContentResponse;
`,
runner,
{
AuthDecorator: `@useAuth(ApiKeyAuth<ApiKeyLocation.cookie, "api-key-name">)`,
},
);
const context = createEmitterContext(program);
const sdkContext = await createNetSdkContext(context);
const root = createModel(sdkContext);
const diagnostics = context.program.diagnostics;

const noAuthDiagnostic = diagnostics.find(
(d) => d.code === "@typespec/http-client-csharp/unsupported-auth",
);
ok(noAuthDiagnostic);
strictEqual(
noAuthDiagnostic.message,
"Only header is supported for ApiKey authentication. cookie is not supported.",
);
strictEqual(root.Auth, undefined); // we do not support it therefore it falls back to undefined
});

it("query header is not supported", async () => {
const program = await typeSpecCompile(
`
op test(): NoContentResponse;
`,
runner,
{
AuthDecorator: `@useAuth(ApiKeyAuth<ApiKeyLocation.query, "api-key-name">)`,
},
);
const context = createEmitterContext(program);
const sdkContext = await createNetSdkContext(context);
const root = createModel(sdkContext);
const diagnostics = context.program.diagnostics;

const noAuthDiagnostic = diagnostics.find(
(d) => d.code === "@typespec/http-client-csharp/unsupported-auth",
);
ok(noAuthDiagnostic);
strictEqual(
noAuthDiagnostic.message,
"Only header is supported for ApiKey authentication. query is not supported.",
);
strictEqual(root.Auth, undefined); // we do not support it therefore it falls back to undefined
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface TypeSpecCompileOptions {
IsAzureCoreNeeded?: boolean;
IsTCGCNeeded?: boolean;
IsXmlNeeded?: boolean;
AuthDecorator?: string;
}

export async function typeSpecCompile(
Expand All @@ -54,9 +55,11 @@ export async function typeSpecCompile(
const needAzureCore = options?.IsAzureCoreNeeded ?? false;
const needTCGC = options?.IsTCGCNeeded ?? false;
const needXml = options?.IsXmlNeeded ?? false;
const authDecorator =
options?.AuthDecorator ?? `@useAuth(ApiKeyAuth<ApiKeyLocation.header, "api-key">)`;
const namespace = `
@versioned(Versions)
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "api-key">)
${authDecorator}
@service({
title: "Azure Csharp emitter Testing",
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@
"$id": "18",
"ApiKey": {
"$id": "19",
"Name": "x-ms-api-key"
"Name": "x-ms-api-key",
"In": "header"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
"ApiKey": {
"$id": "19",
"Name": "Authorization",
"In": "header",
"Prefix": "SharedAccessKey"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@
"$id": "12",
"ApiKey": {
"$id": "13",
"Name": "x-ms-api-key"
"Name": "x-ms-api-key",
"In": "header"
},
"OAuth2": {
"$id": "14",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3684,7 +3684,8 @@
"$id": "348",
"ApiKey": {
"$id": "349",
"Name": "my-api-key"
"Name": "my-api-key",
"In": "header"
}
}
}

0 comments on commit 74ec773

Please sign in to comment.