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

report diagnostics on unsupported auth #5496

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"
}
}
}
Loading