-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[core-rest-pipeline] Challenge callbacks #13888
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
Changes from 9 commits
9795395
10f90df
f37ce5a
1f682a6
85d52c1
8fcc45b
b1df5f9
2cce476
8fa9ec1
ebbed8a
272d240
fa0646c
a8a7a50
b0a8337
663d7eb
435776e
480ac2c
670ce25
d6b32a2
87d4b55
dda4039
ee68f6c
0a09732
83720b2
a68a673
5e0a716
10ca6e5
c9e6c47
9f31ecf
a789c76
633dfe9
b300a51
b44184a
b387796
5a4d88b
d260f63
9fe1712
e4ec342
ab1c2e4
892ee7c
d54547c
9da2dd6
6631d38
9a3e758
c1c1213
9c97a64
dbf2e1f
06e6854
e28bb35
373589d
1f2d6a3
fa118af
ce29fbf
e78d193
5ff8119
4b14121
0c2392a
5314922
e9b8a8d
0d89d9d
5b64e51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ export class AzureSASCredential implements SASCredential { | |
| // @public | ||
| export interface GetTokenOptions { | ||
| abortSignal?: AbortSignalLike; | ||
| claims?: string; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should claims be a Record<string, any> or just string?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pending architects' discussion. |
||
| requestOptions?: { | ||
| timeout?: number; | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -274,3 +274,17 @@ export type FormDataValue = string | Blob; | |
| * A simple object that provides form data, as if from a browser form. | ||
| */ | ||
| export type FormDataMap = { [key: string]: FormDataValue | FormDataValue[] }; | ||
|
|
||
| /** | ||
| * Allows the discovery of authentication properties. | ||
| */ | ||
| export interface AuthenticationContext { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this live in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really specific to the challenge methods in bearerTokenAuthenticationPolicy. We could also name it like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll go with |
||
| /** | ||
| * Scopes to overwrite during the get token request. | ||
| */ | ||
| scopes?: string | string[]; | ||
| /** | ||
| * Claims that can be used by the credential's getToken request. | ||
| */ | ||
| claims?: string; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| import { | ||
| PipelineResponse, | ||
| PipelineRequest, | ||
| SendRequest, | ||
| AuthenticationContext | ||
| } from "../interfaces"; | ||
| import { PipelinePolicy } from "../pipeline"; | ||
|
|
||
| /** | ||
| * The programmatic identifier of the challengeAuthenticationPolicy. | ||
| */ | ||
| export const challengeAuthenticationPolicyName = "challengeAuthenticationPolicy"; | ||
|
|
||
| /** | ||
| * Options to configure the challengeAuthenticationPolicy | ||
| */ | ||
| export interface ChallengeAuthenticationPolicyOptions { | ||
| /** | ||
| * Authentication context that can bind together the challengeAuthenticationPolicy with other policies. | ||
| * The second policy should also receive an authenticationContext in their options. | ||
| * By default, the only other policy that supports this is the bearerTokenAuthenticationPolicy. | ||
| */ | ||
| authenticationContext: AuthenticationContext; | ||
| /** | ||
| * Allows for the customization of the next request before its sent. | ||
| */ | ||
| prepareRequest?(request: PipelineRequest): Promise<void>; | ||
| /** | ||
| * Defines how to get the challenge from the PipelineResponse. | ||
| * By default we will retrieve the challenge only if the response status code was 401, | ||
| * and if the response contained the header "WWW-Authenticate" with a non-empty value. | ||
| */ | ||
| getChallenge?(response: PipelineResponse): string | undefined; | ||
| /** | ||
| * Updates the authentication context based on the challenge. | ||
| */ | ||
| processChallenge(challenge: string, context: AuthenticationContext): Promise<boolean>; | ||
| } | ||
|
|
||
| /** | ||
| * By default we will retrieve the challenge only if the response status code was 401, | ||
| * and if the response contained the header "WWW-Authenticate" with a non-empty value. | ||
| */ | ||
| function defaultGetChallenge(response: PipelineResponse): string | undefined { | ||
| const challenges = response.headers.get("WWW-Authenticate"); | ||
| if (response.status === 401 && challenges) { | ||
| return challenges; | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| /** | ||
| * Allows processing authentication challenges. | ||
| */ | ||
| export function challengeAuthenticationPolicy( | ||
|
sadasant marked this conversation as resolved.
Outdated
|
||
| options: ChallengeAuthenticationPolicyOptions | ||
| ): PipelinePolicy { | ||
| const { | ||
| prepareRequest, | ||
| getChallenge = defaultGetChallenge, | ||
| processChallenge, | ||
| authenticationContext | ||
| } = options; | ||
|
|
||
| return { | ||
| name: challengeAuthenticationPolicyName, | ||
| async sendRequest(request: PipelineRequest, next: SendRequest): Promise<PipelineResponse> { | ||
| if (prepareRequest) { | ||
| await prepareRequest(request); | ||
| } | ||
| const response = await next(request); | ||
| const challenge = getChallenge(response); | ||
| if ( | ||
| challenge && | ||
| processChallenge && | ||
| (await processChallenge(challenge, authenticationContext)) | ||
|
sadasant marked this conversation as resolved.
Outdated
|
||
| ) { | ||
| return next(request); | ||
| } | ||
| return response; | ||
| } | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| /** | ||
| * parseCAEChallenges Parses multiple challenges into an array of objects. | ||
| * Allows users to specify the challenge type through the TChallenge type parameter. | ||
| */ | ||
| export function parseCAEChallenges<TChallenge>(challenges: string): TChallenge[] { | ||
| if (!challenges) return [{} as TChallenge]; | ||
|
|
||
| // Parses a `key="value"` string into an object with { key: "key", value: "value" } | ||
| const parseKeyValue = (keyValue: string): { key: string; value: string } => | ||
| (keyValue.match(/(?<key>\w+(?==))="(?<value>[^"]*)"/) as any)?.groups || {}; | ||
|
sadasant marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Receives an array of `key="value"` strings | ||
| // And produces an object with properties based on those keys and values. | ||
| const groupKeyValues = (keyValues: string[]): TChallenge => | ||
| keyValues.reduce((parsedChallenge, keyValue) => { | ||
| const { key, value } = parseKeyValue(keyValue); | ||
| if (!key) { | ||
| return parsedChallenge; | ||
| } | ||
| return { | ||
| ...parsedChallenge, | ||
| [key]: value || "" | ||
| }; | ||
| }, {}) as TChallenge; | ||
|
|
||
| // Splits a string challenge composed of key="value" elements separated by comma | ||
| // into an array of `key="value"` strings. | ||
| const separateKeyValues = (challenge: string): string[] => | ||
| `${challenge}, `.match(/(\w+="[^"]*"(?=, ))/g) || []; | ||
|
sadasant marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Each set of challenges will be separated by "Bearer ". | ||
| const bearerSeparated = challenges.split("Bearer").filter((x) => x); | ||
|
|
||
| return bearerSeparated.map((challenge) => groupKeyValues(separateKeyValues(challenge))); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.