diff --git a/sdk/core/core-client/src/authorizeRequestOnClaimChallenge.ts b/sdk/core/core-client/src/authorizeRequestOnClaimChallenge.ts index 45a9be95a649..c4a2d3eddd7c 100644 --- a/sdk/core/core-client/src/authorizeRequestOnClaimChallenge.ts +++ b/sdk/core/core-client/src/authorizeRequestOnClaimChallenge.ts @@ -6,7 +6,7 @@ import { AuthorizeRequestOnChallengeOptions } from "@azure/core-rest-pipeline"; import { createClientLogger } from "@azure/logger"; import { decodeStringToString } from "./base64"; -const logger = createClientLogger("authorizeRequestOnClaimChallenge"); +const defaultLogger = createClientLogger("authorizeRequestOnClaimChallenge"); /** * Converts: `Bearer a="b", c="d", Bearer d="e", f="g"`. @@ -64,6 +64,7 @@ export async function authorizeRequestOnClaimChallenge( onChallengeOptions: AuthorizeRequestOnChallengeOptions ): Promise { const { scopes, response } = onChallengeOptions; + const logger = onChallengeOptions.logger || defaultLogger; const challenge = response.headers.get("WWW-Authenticate"); if (!challenge) { diff --git a/sdk/core/core-client/test/authorizeRequestOnClaimChallenge.spec.ts b/sdk/core/core-client/test/authorizeRequestOnClaimChallenge.spec.ts index 7dc78df85f47..0191b6c2bbef 100644 --- a/sdk/core/core-client/test/authorizeRequestOnClaimChallenge.spec.ts +++ b/sdk/core/core-client/test/authorizeRequestOnClaimChallenge.spec.ts @@ -352,4 +352,88 @@ describe("authorizeRequestOnClaimChallenge", function() { assert.deepEqual(finalSendRequestHeaders, [undefined, `Bearer ${getTokenResponse.token}`]); }); }); + + it(`a custom logger should log a reasonable message if no challenge is received`, async function() { + const request = createPipelineRequest({ url: "https://example.com" }); + const getAccessTokenParameters: { + scopes: string | string[]; + getTokenOptions: GetTokenOptions; + }[] = []; + + const allParams: any[] = []; + const logger: any = { + info: (...params: any) => allParams.push(params) + }; + + const result = await authorizeRequestOnClaimChallenge({ + async getAccessToken(scopes, getTokenOptions) { + getAccessTokenParameters.push({ scopes, getTokenOptions }); + return { + token: "accessToken", + expiresOnTimestamp: new Date().getTime() + }; + }, + scopes: [], + response: { + headers: createHttpHeaders(), + request, + status: 401 + }, + request, + logger + }); + + assert.isFalse(result, "We provided no challenge, so it should return false."); + + assert.equal( + allParams.map((x) => x.join(" ")).join("\n"), + `The WWW-Authenticate header was missing. Failed to perform the Continuous Access Evaluation authentication flow.` + ); + }); + + it(`a custom logger should log a reasonable message if a bad challenge is received`, async function() { + const request = createPipelineRequest({ url: "https://example.com" }); + const getAccessTokenParameters: { + scopes: string | string[]; + getTokenOptions: GetTokenOptions; + }[] = []; + + const allParams: any[] = []; + const logger: any = { + info: (...params: any) => allParams.push(params) + }; + + const result = await authorizeRequestOnClaimChallenge({ + async getAccessToken(scopes, getTokenOptions) { + getAccessTokenParameters.push({ scopes, getTokenOptions }); + return { + token: "accessToken", + expiresOnTimestamp: new Date().getTime() + }; + }, + scopes: [], + response: { + headers: createHttpHeaders({ + "WWW-Authenticate": [ + `Bearer authorization_uri="https://login.windows-ppe.net/", error="invalid_token"`, + `error_description="User session has been revoked"`, + `scope="https://endpoint/.default"`, + // Bad challenge + `claims=""` + ].join(", ") + }), + request, + status: 401 + }, + request, + logger + }); + + assert.isFalse(result, "We provided a bad challenge, so it should return false."); + + assert.equal( + allParams.map((x) => x.join(" ")).join("\n"), + `The WWW-Authenticate header was missing the necessary "claims" to perform the Continuous Access Evaluation authentication flow.` + ); + }); }); diff --git a/sdk/core/core-rest-pipeline/CHANGELOG.md b/sdk/core/core-rest-pipeline/CHANGELOG.md index 7912e01e69f6..36202fe494e7 100644 --- a/sdk/core/core-rest-pipeline/CHANGELOG.md +++ b/sdk/core/core-rest-pipeline/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features Added +- The `bearerTokenAuthenticationPolicy` now accepts a logger. - Changed behavior when sending HTTP headers to preserve the original casing of header names. Iterating over `HttpHeaders` now keeps the original name casing. There is also a new `preserveCase` option for `HttpHeaders.toJSON()`. See [PR #18517](https://github.com/Azure/azure-sdk-for-js/pull/18517) ### Breaking Changes diff --git a/sdk/core/core-rest-pipeline/review/core-rest-pipeline.api.md b/sdk/core/core-rest-pipeline/review/core-rest-pipeline.api.md index 367c3e9c04b6..e1c7d58244f9 100644 --- a/sdk/core/core-rest-pipeline/review/core-rest-pipeline.api.md +++ b/sdk/core/core-rest-pipeline/review/core-rest-pipeline.api.md @@ -8,6 +8,7 @@ import { AbortSignalLike } from '@azure/abort-controller'; import { AccessToken } from '@azure/core-auth'; +import { AzureLogger } from '@azure/logger'; import { Debugger } from '@azure/logger'; import { GetTokenOptions } from '@azure/core-auth'; import { OperationTracingOptions } from '@azure/core-tracing'; @@ -33,6 +34,7 @@ export interface Agent { // @public export interface AuthorizeRequestOnChallengeOptions { getAccessToken: (scopes: string[], options: GetTokenOptions) => Promise; + logger?: AzureLogger; request: PipelineRequest; response: PipelineResponse; scopes: string[]; @@ -41,6 +43,7 @@ export interface AuthorizeRequestOnChallengeOptions { // @public export interface AuthorizeRequestOptions { getAccessToken: (scopes: string[], options: GetTokenOptions) => Promise; + logger?: AzureLogger; request: PipelineRequest; scopes: string[]; } @@ -55,6 +58,7 @@ export const bearerTokenAuthenticationPolicyName = "bearerTokenAuthenticationPol export interface BearerTokenAuthenticationPolicyOptions { challengeCallbacks?: ChallengeCallbacks; credential?: TokenCredential; + logger?: AzureLogger; scopes: string | string[]; } diff --git a/sdk/core/core-rest-pipeline/src/policies/bearerTokenAuthenticationPolicy.ts b/sdk/core/core-rest-pipeline/src/policies/bearerTokenAuthenticationPolicy.ts index 37cfc549389b..e3eb6f397504 100644 --- a/sdk/core/core-rest-pipeline/src/policies/bearerTokenAuthenticationPolicy.ts +++ b/sdk/core/core-rest-pipeline/src/policies/bearerTokenAuthenticationPolicy.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-auth"; +import { AzureLogger } from "@azure/logger"; import { PipelineResponse, PipelineRequest, SendRequest } from "../interfaces"; import { PipelinePolicy } from "../pipeline"; import { createTokenCycler } from "../util/tokenCycler"; @@ -27,6 +28,10 @@ export interface AuthorizeRequestOptions { * Request that the policy is trying to fulfill. */ request: PipelineRequest; + /** + * A logger, if one was sent through the HTTP pipeline. + */ + logger?: AzureLogger; } /** @@ -49,6 +54,10 @@ export interface AuthorizeRequestOnChallengeOptions { * Response containing the challenge. */ response: PipelineResponse; + /** + * A logger, if one was sent through the HTTP pipeline. + */ + logger?: AzureLogger; } /** @@ -86,6 +95,10 @@ export interface BearerTokenAuthenticationPolicyOptions { * If provided, after a request is sent, if it has a challenge, it can be processed to re-send the original request with the relevant challenge information. */ challengeCallbacks?: ChallengeCallbacks; + /** + * A logger can be sent for debugging purposes. + */ + logger?: AzureLogger; } /** @@ -123,7 +136,7 @@ function getChallenge(response: PipelineResponse): string | undefined { export function bearerTokenAuthenticationPolicy( options: BearerTokenAuthenticationPolicyOptions ): PipelinePolicy { - const { credential, scopes, challengeCallbacks } = options; + const { credential, scopes, challengeCallbacks, logger } = options; const callbacks = { authorizeRequest: challengeCallbacks?.authorizeRequest ?? defaultAuthorizeRequest, authorizeRequestOnChallenge: challengeCallbacks?.authorizeRequestOnChallenge, @@ -164,7 +177,8 @@ export function bearerTokenAuthenticationPolicy( await callbacks.authorizeRequest({ scopes: Array.isArray(scopes) ? scopes : [scopes], request, - getAccessToken + getAccessToken, + logger }); let response: PipelineResponse; @@ -186,7 +200,8 @@ export function bearerTokenAuthenticationPolicy( scopes: Array.isArray(scopes) ? scopes : [scopes], request, response, - getAccessToken + getAccessToken, + logger }); if (shouldSendRequest) {