@microsoft/vscode-azext-azureauth 6.0.0-alpha.5 → 6.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@ __exportStar(require("./contracts/AzureTenant"), exports);
26
26
  // The `AzureDevOpsSubscriptionProvider` is intentionally not exported, it must be imported from `'@microsoft/vscode-azext-azureauth/azdo'`
27
27
  __exportStar(require("./providers/AzureSubscriptionProviderBase"), exports);
28
28
  __exportStar(require("./providers/VSCodeAzureSubscriptionProvider"), exports);
29
+ __exportStar(require("./utils/BearerChallengePolicy"), exports);
29
30
  __exportStar(require("./utils/configuredAzureEnv"), exports);
30
31
  __exportStar(require("./utils/dedupeSubscriptions"), exports);
31
32
  __exportStar(require("./utils/getMetricsForTelemetry"), exports);
@@ -38,6 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.AzureSubscriptionProviderBase = void 0;
41
+ const BearerChallengePolicy_1 = require("../utils/BearerChallengePolicy");
41
42
  const util_1 = require("util");
42
43
  const vscode = __importStar(require("vscode"));
43
44
  const AzureSubscriptionProviderRequestOptions_1 = require("../contracts/AzureSubscriptionProviderRequestOptions");
@@ -50,7 +51,7 @@ const Limiter_1 = require("../utils/Limiter");
50
51
  const NotSignedInError_1 = require("../utils/NotSignedInError");
51
52
  const screen_1 = require("../utils/screen");
52
53
  const tryGetTokenExpiration_1 = require("../utils/tryGetTokenExpiration");
53
- const EventDebounce = 5 * 1000; // 5 seconds minimum between `onRefreshSuggested` events
54
+ const EventDebounce = 2 * 1000; // 2 seconds minimum between `onRefreshSuggested` events
54
55
  const EventSilenceTime = 5 * 1000; // 5 seconds after sign-in to silence `onRefreshSuggested` events
55
56
  const TenantListConcurrency = 3; // We will try to list tenants for at most 3 accounts in parallel
56
57
  const SubscriptionListConcurrency = 5; // We will try to list subscriptions for at most 5 account+tenants in parallel
@@ -93,7 +94,11 @@ class AzureSubscriptionProviderBase {
93
94
  return this.refreshSuggestedEmitter.event(callback, thisArg, disposables);
94
95
  }
95
96
  fireRefreshSuggestedIfNeeded(evtArgs) {
96
- if (this.suppressRefreshSuggestedEvents || Date.now() < this.lastRefreshSuggestedTime + EventDebounce) {
97
+ // subscriptionFilterChange is an explicit user action and must never be suppressed,
98
+ // otherwise re-selecting a subscription shortly after unselecting one gets swallowed
99
+ // by the debounce/silence window that the first refresh triggered.
100
+ if (evtArgs.reason !== 'subscriptionFilterChange' &&
101
+ (this.suppressRefreshSuggestedEvents || Date.now() < this.lastRefreshSuggestedTime + EventDebounce)) {
97
102
  // Suppress and/or debounce events to avoid flooding
98
103
  return false;
99
104
  }
@@ -347,8 +352,15 @@ class AzureSubscriptionProviderBase {
347
352
  }
348
353
  };
349
354
  armSubs ??= await import('@azure/arm-resources-subscriptions');
355
+ const endpoint = (0, configuredAzureEnv_1.getConfiguredAzureEnv)().resourceManagerEndpointUrl;
356
+ const client = new armSubs.SubscriptionClient(credential, { endpoint });
357
+ client.pipeline.addPolicy(new BearerChallengePolicy_1.BearerChallengePolicy(async (challenge) => {
358
+ this.silenceRefreshEvents();
359
+ const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(challenge, tenant.tenantId, { createIfNone: true, account: tenant.account });
360
+ return session?.accessToken;
361
+ }, endpoint), { phase: 'Sign', afterPolicies: ['bearerTokenAuthenticationPolicy'] });
350
362
  return {
351
- client: new armSubs.SubscriptionClient(credential, { endpoint: (0, configuredAzureEnv_1.getConfiguredAzureEnv)().resourceManagerEndpointUrl }),
363
+ client,
352
364
  credential: credential,
353
365
  authentication: {
354
366
  getSession: async () => {
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Microsoft Corporation. All rights reserved.
4
+ * Licensed under the MIT License. See License.txt in the project root for license information.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BearerChallengePolicy = void 0;
8
+ exports.getDefaultScopeFromEndpoint = getDefaultScopeFromEndpoint;
9
+ function getDefaultScopeFromEndpoint(endpoint) {
10
+ let base = endpoint ?? 'https://management.azure.com/';
11
+ base = base.replace(/\/+$/, '');
12
+ return `${base}/.default`;
13
+ }
14
+ const challengeRetriedRequests = new WeakSet();
15
+ class BearerChallengePolicy {
16
+ getTokenForChallenge;
17
+ endpoint;
18
+ name = 'BearerChallengePolicy';
19
+ constructor(getTokenForChallenge, endpoint) {
20
+ this.getTokenForChallenge = getTokenForChallenge;
21
+ this.endpoint = endpoint;
22
+ }
23
+ async sendRequest(request, next) {
24
+ const initial = await next(request);
25
+ if (initial.status === 401 && !challengeRetriedRequests.has(request)) {
26
+ const header = initial.headers.get('WWW-Authenticate') || initial.headers.get('www-authenticate');
27
+ if (header) {
28
+ const scopes = [getDefaultScopeFromEndpoint(this.endpoint)];
29
+ challengeRetriedRequests.add(request);
30
+ const token = await this.getTokenForChallenge({ wwwAuthenticate: header, fallbackScopes: scopes });
31
+ if (token) {
32
+ request.headers.set('Authorization', `Bearer ${token}`);
33
+ return await next(request);
34
+ }
35
+ }
36
+ }
37
+ return initial;
38
+ }
39
+ }
40
+ exports.BearerChallengePolicy = BearerChallengePolicy;
41
+ //# sourceMappingURL=BearerChallengePolicy.js.map
@@ -10,7 +10,7 @@ export interface AzureSubscriptionProvider {
10
10
  /**
11
11
  * Fires when the list of available subscriptions may have changed, and a refresh is suggested.
12
12
  * The callback will be given a {@link RefreshSuggestedEvent} with more information.
13
- * @note This will be fired at most every 5 seconds, to avoid flooding. It is also suppressed
13
+ * @note This will be fired at most every 2 seconds, to avoid flooding. It is also suppressed
14
14
  * during operations this provider is performing itself, such as during sign-in.
15
15
  */
16
16
  onRefreshSuggested: vscode.Event<RefreshSuggestedEvent>;
@@ -6,6 +6,7 @@ export type * from './contracts/AzureSubscriptionProviderRequestOptions';
6
6
  export * from './contracts/AzureTenant';
7
7
  export * from './providers/AzureSubscriptionProviderBase';
8
8
  export * from './providers/VSCodeAzureSubscriptionProvider';
9
+ export * from './utils/BearerChallengePolicy';
9
10
  export * from './utils/configuredAzureEnv';
10
11
  export * from './utils/dedupeSubscriptions';
11
12
  export * from './utils/getMetricsForTelemetry';
@@ -10,6 +10,7 @@ export * from './contracts/AzureTenant';
10
10
  // The `AzureDevOpsSubscriptionProvider` is intentionally not exported, it must be imported from `'@microsoft/vscode-azext-azureauth/azdo'`
11
11
  export * from './providers/AzureSubscriptionProviderBase';
12
12
  export * from './providers/VSCodeAzureSubscriptionProvider';
13
+ export * from './utils/BearerChallengePolicy';
13
14
  export * from './utils/configuredAzureEnv';
14
15
  export * from './utils/dedupeSubscriptions';
15
16
  export * from './utils/getMetricsForTelemetry';
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the MIT License. See License.txt in the project root for license information.
4
4
  *--------------------------------------------------------------------------------------------*/
5
+ import { BearerChallengePolicy } from '../utils/BearerChallengePolicy';
5
6
  import { inspect } from 'util';
6
7
  import * as vscode from 'vscode';
7
8
  import { DefaultOptions, DefaultSignInOptions } from '../contracts/AzureSubscriptionProviderRequestOptions';
@@ -14,7 +15,7 @@ import { Limiter } from '../utils/Limiter';
14
15
  import { isNotSignedInError, NotSignedInError } from '../utils/NotSignedInError';
15
16
  import { screen } from '../utils/screen';
16
17
  import { tryGetTokenExpiration } from '../utils/tryGetTokenExpiration';
17
- const EventDebounce = 5 * 1000; // 5 seconds minimum between `onRefreshSuggested` events
18
+ const EventDebounce = 2 * 1000; // 2 seconds minimum between `onRefreshSuggested` events
18
19
  const EventSilenceTime = 5 * 1000; // 5 seconds after sign-in to silence `onRefreshSuggested` events
19
20
  const TenantListConcurrency = 3; // We will try to list tenants for at most 3 accounts in parallel
20
21
  const SubscriptionListConcurrency = 5; // We will try to list subscriptions for at most 5 account+tenants in parallel
@@ -57,7 +58,11 @@ export class AzureSubscriptionProviderBase {
57
58
  return this.refreshSuggestedEmitter.event(callback, thisArg, disposables);
58
59
  }
59
60
  fireRefreshSuggestedIfNeeded(evtArgs) {
60
- if (this.suppressRefreshSuggestedEvents || Date.now() < this.lastRefreshSuggestedTime + EventDebounce) {
61
+ // subscriptionFilterChange is an explicit user action and must never be suppressed,
62
+ // otherwise re-selecting a subscription shortly after unselecting one gets swallowed
63
+ // by the debounce/silence window that the first refresh triggered.
64
+ if (evtArgs.reason !== 'subscriptionFilterChange' &&
65
+ (this.suppressRefreshSuggestedEvents || Date.now() < this.lastRefreshSuggestedTime + EventDebounce)) {
61
66
  // Suppress and/or debounce events to avoid flooding
62
67
  return false;
63
68
  }
@@ -311,8 +316,15 @@ export class AzureSubscriptionProviderBase {
311
316
  }
312
317
  };
313
318
  armSubs ??= await import('@azure/arm-resources-subscriptions');
319
+ const endpoint = getConfiguredAzureEnv().resourceManagerEndpointUrl;
320
+ const client = new armSubs.SubscriptionClient(credential, { endpoint });
321
+ client.pipeline.addPolicy(new BearerChallengePolicy(async (challenge) => {
322
+ this.silenceRefreshEvents();
323
+ const session = await getSessionFromVSCode(challenge, tenant.tenantId, { createIfNone: true, account: tenant.account });
324
+ return session?.accessToken;
325
+ }, endpoint), { phase: 'Sign', afterPolicies: ['bearerTokenAuthenticationPolicy'] });
314
326
  return {
315
- client: new armSubs.SubscriptionClient(credential, { endpoint: getConfiguredAzureEnv().resourceManagerEndpointUrl }),
327
+ client,
316
328
  credential: credential,
317
329
  authentication: {
318
330
  getSession: async () => {
@@ -0,0 +1,10 @@
1
+ import type { PipelinePolicy, PipelineRequest, PipelineResponse, SendRequest } from '@azure/core-rest-pipeline';
2
+ import type * as vscode from 'vscode';
3
+ export declare function getDefaultScopeFromEndpoint(endpoint?: string): string;
4
+ export declare class BearerChallengePolicy implements PipelinePolicy {
5
+ private readonly getTokenForChallenge;
6
+ private readonly endpoint?;
7
+ readonly name = "BearerChallengePolicy";
8
+ constructor(getTokenForChallenge: (request: vscode.AuthenticationWwwAuthenticateRequest) => Promise<string | undefined>, endpoint?: string | undefined);
9
+ sendRequest(request: PipelineRequest, next: SendRequest): Promise<PipelineResponse>;
10
+ }
@@ -0,0 +1,36 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License. See License.txt in the project root for license information.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ export function getDefaultScopeFromEndpoint(endpoint) {
6
+ let base = endpoint ?? 'https://management.azure.com/';
7
+ base = base.replace(/\/+$/, '');
8
+ return `${base}/.default`;
9
+ }
10
+ const challengeRetriedRequests = new WeakSet();
11
+ export class BearerChallengePolicy {
12
+ getTokenForChallenge;
13
+ endpoint;
14
+ name = 'BearerChallengePolicy';
15
+ constructor(getTokenForChallenge, endpoint) {
16
+ this.getTokenForChallenge = getTokenForChallenge;
17
+ this.endpoint = endpoint;
18
+ }
19
+ async sendRequest(request, next) {
20
+ const initial = await next(request);
21
+ if (initial.status === 401 && !challengeRetriedRequests.has(request)) {
22
+ const header = initial.headers.get('WWW-Authenticate') || initial.headers.get('www-authenticate');
23
+ if (header) {
24
+ const scopes = [getDefaultScopeFromEndpoint(this.endpoint)];
25
+ challengeRetriedRequests.add(request);
26
+ const token = await this.getTokenForChallenge({ wwwAuthenticate: header, fallbackScopes: scopes });
27
+ if (token) {
28
+ request.headers.set('Authorization', `Bearer ${token}`);
29
+ return await next(request);
30
+ }
31
+ }
32
+ }
33
+ return initial;
34
+ }
35
+ }
36
+ //# sourceMappingURL=BearerChallengePolicy.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@microsoft/vscode-azext-azureauth",
3
3
  "author": "Microsoft Corporation",
4
- "version": "6.0.0-alpha.5",
4
+ "version": "6.0.0-alpha.7",
5
5
  "description": "Azure authentication helpers for Visual Studio Code",
6
6
  "tags": [
7
7
  "azure",
@@ -55,6 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@azure/arm-resources-subscriptions": "^2.1.0",
58
+ "@azure/core-rest-pipeline": "^1.22.2",
58
59
  "@azure/identity": "^4.13.0",
59
60
  "@azure/ms-rest-azure-env": "^2.0.0"
60
61
  },