@microsoft/vscode-azext-azureauth 5.1.1 → 6.0.0-alpha.2

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +9 -79
  3. package/dist/cjs/src/contracts/AzureAccount.js +7 -0
  4. package/dist/cjs/src/contracts/AzureSubscriptionProviderRequestOptions.js +48 -0
  5. package/dist/cjs/src/index.js +13 -10
  6. package/dist/cjs/src/providers/AzureDevOpsSubscriptionProvider.js +178 -0
  7. package/dist/cjs/src/providers/AzureSubscriptionProviderBase.js +393 -0
  8. package/dist/cjs/src/providers/VSCodeAzureSubscriptionProvider.js +269 -0
  9. package/dist/cjs/src/utils/Limiter.js +41 -0
  10. package/dist/cjs/src/{NotSignedInError.js → utils/NotSignedInError.js} +3 -2
  11. package/dist/cjs/src/utils/configuredAzureEnv.js +14 -16
  12. package/dist/cjs/src/utils/dedupeSubscriptions.js +27 -0
  13. package/dist/cjs/src/utils/getMetricsForTelemetry.js +47 -0
  14. package/dist/cjs/src/{getSessionFromVSCode.js → utils/getSessionFromVSCode.js} +5 -2
  15. package/dist/cjs/src/utils/getSignalForToken.js +29 -0
  16. package/dist/cjs/src/utils/map/CaselessMap.js +71 -0
  17. package/dist/cjs/src/utils/map/TwoKeyCaselessMap.js +194 -0
  18. package/dist/cjs/src/utils/screen.js +62 -0
  19. package/dist/cjs/src/{signInToTenant.js → utils/signInToTenant.js} +15 -13
  20. package/dist/cjs/src/utils/tryGetTokenExpiration.js +25 -0
  21. package/dist/esm/src/contracts/AzureAccount.d.ts +5 -0
  22. package/dist/esm/src/contracts/AzureAccount.js +6 -0
  23. package/dist/esm/src/{AzureAuthentication.d.ts → contracts/AzureAuthentication.d.ts} +1 -1
  24. package/dist/esm/src/{AzureSubscription.d.ts → contracts/AzureSubscription.d.ts} +4 -4
  25. package/dist/esm/src/contracts/AzureSubscriptionProvider.d.ts +112 -0
  26. package/dist/esm/src/contracts/AzureSubscriptionProviderRequestOptions.d.ts +103 -0
  27. package/dist/esm/src/contracts/AzureSubscriptionProviderRequestOptions.js +44 -0
  28. package/dist/esm/src/contracts/AzureTenant.d.ts +15 -0
  29. package/dist/esm/src/index.d.ts +13 -10
  30. package/dist/esm/src/index.js +13 -10
  31. package/dist/esm/src/providers/AzureDevOpsSubscriptionProvider.d.ts +68 -0
  32. package/dist/esm/src/providers/AzureDevOpsSubscriptionProvider.js +140 -0
  33. package/dist/esm/src/providers/AzureSubscriptionProviderBase.d.ts +74 -0
  34. package/dist/esm/src/providers/AzureSubscriptionProviderBase.js +356 -0
  35. package/dist/esm/src/providers/VSCodeAzureSubscriptionProvider.d.ts +70 -0
  36. package/dist/esm/src/providers/VSCodeAzureSubscriptionProvider.js +232 -0
  37. package/dist/esm/src/utils/Limiter.d.ts +9 -0
  38. package/dist/esm/src/utils/Limiter.js +37 -0
  39. package/dist/esm/src/{NotSignedInError.d.ts → utils/NotSignedInError.d.ts} +2 -2
  40. package/dist/esm/src/{NotSignedInError.js → utils/NotSignedInError.js} +3 -2
  41. package/dist/esm/src/utils/configuredAzureEnv.d.ts +7 -4
  42. package/dist/esm/src/utils/configuredAzureEnv.js +14 -16
  43. package/dist/esm/src/utils/dedupeSubscriptions.d.ts +14 -0
  44. package/dist/esm/src/utils/dedupeSubscriptions.js +24 -0
  45. package/dist/esm/src/utils/getMetricsForTelemetry.d.ts +32 -0
  46. package/dist/esm/src/utils/getMetricsForTelemetry.js +44 -0
  47. package/dist/esm/src/{getSessionFromVSCode.js → utils/getSessionFromVSCode.js} +5 -2
  48. package/dist/esm/src/utils/getSignalForToken.d.ts +7 -0
  49. package/dist/esm/src/utils/getSignalForToken.js +26 -0
  50. package/dist/esm/src/utils/map/CaselessMap.d.ts +28 -0
  51. package/dist/esm/src/utils/map/CaselessMap.js +67 -0
  52. package/dist/esm/src/utils/map/TwoKeyCaselessMap.d.ts +49 -0
  53. package/dist/esm/src/utils/map/TwoKeyCaselessMap.js +190 -0
  54. package/dist/esm/src/utils/screen.d.ts +9 -0
  55. package/dist/esm/src/utils/screen.js +59 -0
  56. package/dist/esm/src/utils/signInToTenant.d.ts +7 -0
  57. package/dist/esm/src/{signInToTenant.js → utils/signInToTenant.js} +16 -14
  58. package/dist/esm/src/utils/tryGetTokenExpiration.d.ts +2 -0
  59. package/dist/esm/src/utils/tryGetTokenExpiration.js +22 -0
  60. package/package.json +33 -23
  61. package/AzureFederatedCredentialsGuide.md +0 -174
  62. package/dist/cjs/src/AzureDevOpsSubscriptionProvider.js +0 -215
  63. package/dist/cjs/src/VSCodeAzureSubscriptionProvider.js +0 -395
  64. package/dist/cjs/src/utils/getUnauthenticatedTenants.js +0 -23
  65. package/dist/cjs/src/utils/isGetSubscriptionsFilter.js +0 -27
  66. package/dist/esm/src/AzureDevOpsSubscriptionProvider.d.ts +0 -68
  67. package/dist/esm/src/AzureDevOpsSubscriptionProvider.js +0 -210
  68. package/dist/esm/src/AzureSubscriptionProvider.d.ts +0 -82
  69. package/dist/esm/src/AzureTenant.d.ts +0 -5
  70. package/dist/esm/src/VSCodeAzureSubscriptionProvider.d.ts +0 -116
  71. package/dist/esm/src/VSCodeAzureSubscriptionProvider.js +0 -358
  72. package/dist/esm/src/signInToTenant.d.ts +0 -6
  73. package/dist/esm/src/utils/getUnauthenticatedTenants.d.ts +0 -9
  74. package/dist/esm/src/utils/getUnauthenticatedTenants.js +0 -20
  75. package/dist/esm/src/utils/isGetSubscriptionsFilter.d.ts +0 -14
  76. package/dist/esm/src/utils/isGetSubscriptionsFilter.js +0 -23
  77. /package/dist/cjs/src/{AzureAuthentication.js → contracts/AzureAuthentication.js} +0 -0
  78. /package/dist/cjs/src/{AzureSubscription.js → contracts/AzureSubscription.js} +0 -0
  79. /package/dist/cjs/src/{AzureSubscriptionProvider.js → contracts/AzureSubscriptionProvider.js} +0 -0
  80. /package/dist/cjs/src/{AzureTenant.js → contracts/AzureTenant.js} +0 -0
  81. /package/dist/esm/src/{AzureAuthentication.js → contracts/AzureAuthentication.js} +0 -0
  82. /package/dist/esm/src/{AzureSubscription.js → contracts/AzureSubscription.js} +0 -0
  83. /package/dist/esm/src/{AzureSubscriptionProvider.js → contracts/AzureSubscriptionProvider.js} +0 -0
  84. /package/dist/esm/src/{AzureTenant.js → contracts/AzureTenant.js} +0 -0
  85. /package/dist/esm/src/{getSessionFromVSCode.d.ts → utils/getSessionFromVSCode.d.ts} +0 -0
@@ -0,0 +1,393 @@
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
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.AzureSubscriptionProviderBase = void 0;
41
+ const vscode = __importStar(require("vscode"));
42
+ const AzureSubscriptionProviderRequestOptions_1 = require("../contracts/AzureSubscriptionProviderRequestOptions");
43
+ const configuredAzureEnv_1 = require("../utils/configuredAzureEnv");
44
+ const dedupeSubscriptions_1 = require("../utils/dedupeSubscriptions");
45
+ const getSessionFromVSCode_1 = require("../utils/getSessionFromVSCode");
46
+ const getSignalForToken_1 = require("../utils/getSignalForToken");
47
+ const isAuthenticationWwwAuthenticateRequest_1 = require("../utils/isAuthenticationWwwAuthenticateRequest");
48
+ const Limiter_1 = require("../utils/Limiter");
49
+ const NotSignedInError_1 = require("../utils/NotSignedInError");
50
+ const screen_1 = require("../utils/screen");
51
+ const tryGetTokenExpiration_1 = require("../utils/tryGetTokenExpiration");
52
+ const EventDebounce = 5 * 1000; // 5 seconds minimum between `onRefreshSuggested` events
53
+ const EventSilenceTime = 5 * 1000; // 5 seconds after sign-in to silence `onRefreshSuggested` events
54
+ const TenantListConcurrency = 3; // We will try to list tenants for at most 3 accounts in parallel
55
+ const SubscriptionListConcurrency = 5; // We will try to list subscriptions for at most 5 account+tenants in parallel
56
+ let armSubs;
57
+ /**
58
+ * Base class for Azure subscription providers that use VS Code authentication.
59
+ * Handles actual communication with Azure via the Azure SDK, as well as
60
+ * controlling the firing of `onRefreshSuggested` events.
61
+ */
62
+ class AzureSubscriptionProviderBase {
63
+ logger;
64
+ sessionChangeListener;
65
+ refreshSuggestedEmitter = new vscode.EventEmitter();
66
+ lastRefreshSuggestedTime = 0;
67
+ suppressRefreshSuggestedEvents = false;
68
+ /**
69
+ * Constructs a new {@link AzureSubscriptionProviderBase}.
70
+ * @param logger (Optional) A logger to record information to
71
+ */
72
+ constructor(logger) {
73
+ this.logger = logger;
74
+ }
75
+ dispose() {
76
+ if (this.timeout) {
77
+ clearTimeout(this.timeout);
78
+ this.timeout = undefined;
79
+ }
80
+ this.sessionChangeListener?.dispose();
81
+ this.refreshSuggestedEmitter.dispose();
82
+ }
83
+ /**
84
+ * @inheritdoc
85
+ */
86
+ onRefreshSuggested(callback, thisArg, disposables) {
87
+ this.sessionChangeListener ??= vscode.authentication.onDidChangeSessions(evt => {
88
+ if (evt.provider.id === (0, configuredAzureEnv_1.getConfiguredAuthProviderId)()) {
89
+ this.fireRefreshSuggestedIfNeeded({ reason: 'sessionChange' });
90
+ }
91
+ });
92
+ return this.refreshSuggestedEmitter.event(callback, thisArg, disposables);
93
+ }
94
+ fireRefreshSuggestedIfNeeded(evtArgs) {
95
+ if (this.suppressRefreshSuggestedEvents || Date.now() < this.lastRefreshSuggestedTime + EventDebounce) {
96
+ // Suppress and/or debounce events to avoid flooding
97
+ return false;
98
+ }
99
+ this.log(`Firing onRefreshSuggested event due to reason: ${evtArgs.reason}`);
100
+ this.lastRefreshSuggestedTime = Date.now();
101
+ this.refreshSuggestedEmitter.fire(evtArgs);
102
+ return true;
103
+ }
104
+ /**
105
+ * @inheritdoc
106
+ */
107
+ async signIn(tenant, options = AzureSubscriptionProviderRequestOptions_1.DefaultSignInOptions) {
108
+ const prompt = options.promptIfNeeded ?? AzureSubscriptionProviderRequestOptions_1.DefaultSignInOptions.promptIfNeeded;
109
+ if (prompt) {
110
+ // If interactive, suppress without timeout until sign in is done (it can take a while when done interactively)
111
+ this.suppressRefreshSuggestedEvents = true;
112
+ }
113
+ else {
114
+ // If silent, suppress with normal timeout
115
+ this.silenceRefreshEvents();
116
+ }
117
+ const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(undefined, tenant?.tenantId, {
118
+ account: tenant?.account,
119
+ clearSessionPreference: options.clearSessionPreference ?? AzureSubscriptionProviderRequestOptions_1.DefaultSignInOptions.clearSessionPreference,
120
+ createIfNone: prompt,
121
+ silent: !prompt,
122
+ });
123
+ if (prompt) {
124
+ // Interactive sign in can take a while, so silence events for a bit longer
125
+ this.silenceRefreshEvents();
126
+ }
127
+ return !!session;
128
+ }
129
+ /**
130
+ * @inheritdoc
131
+ */
132
+ async getAvailableSubscriptions(options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
133
+ try {
134
+ const availableSubscriptions = [];
135
+ const tenantListLimiter = new Limiter_1.Limiter(TenantListConcurrency);
136
+ const tenantListPromises = [];
137
+ const subscriptionListLimiter = new Limiter_1.Limiter(SubscriptionListConcurrency);
138
+ const subscriptionListPromisesFlat = [];
139
+ let tenantsProcessed = 0;
140
+ const maximumTenants = options.maximumTenants ?? AzureSubscriptionProviderRequestOptions_1.DefaultOptions.maximumTenants;
141
+ const accounts = await this.getAccounts(options);
142
+ for (const account of accounts) {
143
+ this.throwIfCancelled(options.token);
144
+ tenantListPromises.push(tenantListLimiter.queue(async () => {
145
+ try {
146
+ if (tenantsProcessed >= maximumTenants) {
147
+ this.logForAccount(account, `Skipping account because maximum tenants of ${maximumTenants} has been reached`);
148
+ return;
149
+ }
150
+ const tenants = await this.getTenantsForAccount(account, options);
151
+ for (const tenant of tenants) {
152
+ this.throwIfCancelled(options.token);
153
+ if (tenantsProcessed >= maximumTenants) {
154
+ this.logForAccount(account, `Skipping remaining tenants because maximum tenants of ${maximumTenants} has been reached`);
155
+ break;
156
+ }
157
+ tenantsProcessed++;
158
+ subscriptionListPromisesFlat.push(subscriptionListLimiter.queue(async () => {
159
+ try {
160
+ const subscriptions = await this.getSubscriptionsForTenant(tenant, options);
161
+ availableSubscriptions.push(...subscriptions);
162
+ }
163
+ catch (err) {
164
+ if ((0, NotSignedInError_1.isNotSignedInError)(err)) {
165
+ this.logForTenant(tenant, 'Skipping account+tenant because it is not signed in');
166
+ return;
167
+ }
168
+ throw err;
169
+ }
170
+ }));
171
+ }
172
+ }
173
+ catch (err) {
174
+ if ((0, NotSignedInError_1.isNotSignedInError)(err)) {
175
+ this.logForAccount(account, 'Skipping account because it is not signed in');
176
+ return;
177
+ }
178
+ }
179
+ }));
180
+ }
181
+ await Promise.all(tenantListPromises);
182
+ await Promise.all(subscriptionListPromisesFlat);
183
+ return (0, dedupeSubscriptions_1.dedupeSubscriptions)(availableSubscriptions);
184
+ }
185
+ catch (err) {
186
+ // Intentionally not eating NotSignedInError here, if it is thrown by getAccounts()
187
+ this.remapLogRethrow(err, options.token);
188
+ }
189
+ finally {
190
+ this.throwIfCancelled(options.token);
191
+ }
192
+ }
193
+ /**
194
+ * @inheritdoc
195
+ */
196
+ async getAccounts(options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
197
+ try {
198
+ const startTime = Date.now();
199
+ this.log('Fetching accounts...');
200
+ this.silenceRefreshEvents();
201
+ const results = await vscode.authentication.getAccounts((0, configuredAzureEnv_1.getConfiguredAuthProviderId)());
202
+ if (results.length === 0) {
203
+ this.log('No accounts found');
204
+ throw new NotSignedInError_1.NotSignedInError();
205
+ }
206
+ this.log(`Fetched ${results.length} accounts (before filter) in ${Date.now() - startTime}ms`);
207
+ return Array.from(results);
208
+ }
209
+ catch (err) {
210
+ // Cancellation is not actually supported by vscode.authentication.getAccounts, but just in case it is added in the future...
211
+ this.remapLogRethrow(err, options.token);
212
+ }
213
+ finally {
214
+ this.throwIfCancelled(options.token);
215
+ }
216
+ }
217
+ /**
218
+ * @inheritdoc
219
+ */
220
+ async getUnauthenticatedTenantsForAccount(account, options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
221
+ try {
222
+ const startTime = Date.now();
223
+ const tenantListLimiter = new Limiter_1.Limiter(TenantListConcurrency);
224
+ const tenantListPromises = [];
225
+ const allTenants = await this.getTenantsForAccount(account, { ...options, filter: false });
226
+ const unauthenticatedTenants = [];
227
+ for (const tenant of allTenants) {
228
+ tenantListPromises.push(tenantListLimiter.queue(async () => {
229
+ this.throwIfCancelled(options.token);
230
+ this.silenceRefreshEvents();
231
+ const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(undefined, tenant.tenantId, {
232
+ account: account,
233
+ createIfNone: false,
234
+ silent: true,
235
+ });
236
+ if (!session) {
237
+ unauthenticatedTenants.push(tenant);
238
+ }
239
+ }));
240
+ }
241
+ await Promise.all(tenantListPromises);
242
+ this.logForAccount(account, `Found ${unauthenticatedTenants.length} unauthenticated tenants in ${Date.now() - startTime}ms`);
243
+ return unauthenticatedTenants;
244
+ }
245
+ finally {
246
+ this.throwIfCancelled(options.token);
247
+ }
248
+ }
249
+ /**
250
+ * @inheritdoc
251
+ */
252
+ async getTenantsForAccount(account, options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
253
+ try {
254
+ const startTime = Date.now();
255
+ this.logForAccount(account, 'Fetching tenants for account...');
256
+ const { client } = await this.getSubscriptionClient({ account: account, tenantId: undefined });
257
+ const allTenants = [];
258
+ for await (const tenant of client.tenants.list({ abortSignal: (0, getSignalForToken_1.getSignalForToken)(options.token) })) {
259
+ allTenants.push({
260
+ ...tenant,
261
+ tenantId: tenant.tenantId, // eslint-disable-line @typescript-eslint/no-non-null-assertion -- This is never null in practice
262
+ account: account,
263
+ });
264
+ }
265
+ this.logForAccount(account, `Fetched ${allTenants.length} tenants (before filter) in ${Date.now() - startTime}ms`);
266
+ return allTenants;
267
+ }
268
+ catch (err) {
269
+ this.remapLogRethrow(err, options.token);
270
+ }
271
+ finally {
272
+ this.throwIfCancelled(options.token);
273
+ }
274
+ }
275
+ /**
276
+ * @inheritdoc
277
+ */
278
+ async getSubscriptionsForTenant(tenant, options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
279
+ try {
280
+ const startTime = Date.now();
281
+ this.logForTenant(tenant, 'Fetching subscriptions for account+tenant...');
282
+ const { client, credential, authentication } = await this.getSubscriptionClient(tenant);
283
+ const environment = (0, configuredAzureEnv_1.getConfiguredAzureEnv)();
284
+ const allSubs = [];
285
+ for await (const subscription of client.subscriptions.list({ abortSignal: (0, getSignalForToken_1.getSignalForToken)(options.token) })) {
286
+ allSubs.push({
287
+ authentication: authentication,
288
+ environment: environment,
289
+ credential: credential,
290
+ isCustomCloud: environment.isCustomCloud,
291
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
292
+ name: subscription.displayName,
293
+ subscriptionId: subscription.subscriptionId,
294
+ /* eslint-enable @typescript-eslint/no-non-null-assertion */
295
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
296
+ tenantId: subscription.tenantId || tenant.tenantId, // In rare cases, a subscription may be listed but come from a different tenant
297
+ account: tenant.account,
298
+ });
299
+ }
300
+ this.logForTenant(tenant, `Fetched ${allSubs.length} subscriptions (before filter) in ${Date.now() - startTime}ms`);
301
+ return allSubs;
302
+ }
303
+ catch (err) {
304
+ this.remapLogRethrow(err, options.token);
305
+ }
306
+ finally {
307
+ this.throwIfCancelled(options.token);
308
+ }
309
+ }
310
+ /**
311
+ * Gets a {@link SubscriptionClient} plus extras for the given account+tenant.
312
+ * @param tenant (Optional) The account+tenant to get a subscription client for. If not specified, the default account and home tenant
313
+ * will be used.
314
+ * @returns A {@link SubscriptionClient}, {@link TokenCredential}, and {@link AzureAuthentication} for the given account+tenant.
315
+ */
316
+ async getSubscriptionClient(tenant) {
317
+ const credential = {
318
+ getToken: async (scopes, options) => {
319
+ this.silenceRefreshEvents();
320
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
321
+ const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(scopes, options?.tenantId || tenant.tenantId, { createIfNone: false, silent: true, account: tenant.account });
322
+ if (!session) {
323
+ throw new NotSignedInError_1.NotSignedInError();
324
+ }
325
+ return {
326
+ token: session.accessToken,
327
+ expiresOnTimestamp: (0, tryGetTokenExpiration_1.tryGetTokenExpiration)(session),
328
+ };
329
+ }
330
+ };
331
+ armSubs ??= await import('@azure/arm-resources-subscriptions');
332
+ return {
333
+ client: new armSubs.SubscriptionClient(credential, { endpoint: (0, configuredAzureEnv_1.getConfiguredAzureEnv)().resourceManagerEndpointUrl }),
334
+ credential: credential,
335
+ authentication: {
336
+ getSession: async () => {
337
+ this.silenceRefreshEvents();
338
+ const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(undefined, tenant.tenantId, { createIfNone: false, silent: true, account: tenant.account });
339
+ if (!session) {
340
+ throw new NotSignedInError_1.NotSignedInError();
341
+ }
342
+ return session;
343
+ },
344
+ getSessionWithScopes: async (scopeListOrRequest) => {
345
+ this.silenceRefreshEvents();
346
+ // in order to handle a challenge, we must enable createIfNone so
347
+ // that we can prompt the user to step-up their session with MFA
348
+ // otherwise, never prompt the user
349
+ const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(scopeListOrRequest, tenant.tenantId, { ...((0, isAuthenticationWwwAuthenticateRequest_1.isAuthenticationWwwAuthenticateRequest)(scopeListOrRequest) ? { createIfNone: true } : { silent: true }), account: tenant.account });
350
+ if (!session) {
351
+ throw new NotSignedInError_1.NotSignedInError();
352
+ }
353
+ return session;
354
+ },
355
+ }
356
+ };
357
+ }
358
+ log(message) {
359
+ this.logger?.debug(`[auth] ${message}`);
360
+ }
361
+ logForAccount(account, message) {
362
+ this.logger?.debug(`[auth] [account: ${(0, screen_1.screen)(account)}] ${message}`);
363
+ }
364
+ logForTenant(tenant, message) {
365
+ this.logger?.debug(`[auth] [account: ${(0, screen_1.screen)(tenant.account)}] [tenant: ${(0, screen_1.screen)(tenant)}] ${message}`);
366
+ }
367
+ throwIfCancelled(token) {
368
+ if (token?.isCancellationRequested) {
369
+ throw new vscode.CancellationError();
370
+ }
371
+ }
372
+ timeout;
373
+ silenceRefreshEvents() {
374
+ this.suppressRefreshSuggestedEvents = true;
375
+ if (this.timeout) {
376
+ clearTimeout(this.timeout);
377
+ this.timeout = undefined;
378
+ }
379
+ this.timeout = setTimeout(() => {
380
+ clearTimeout(this.timeout);
381
+ this.timeout = undefined;
382
+ this.suppressRefreshSuggestedEvents = false;
383
+ }, EventSilenceTime);
384
+ }
385
+ remapLogRethrow(err, token) {
386
+ this.throwIfCancelled(token);
387
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
388
+ this.logger?.error(`[auth] Error occurred: ${err}`);
389
+ throw err;
390
+ }
391
+ }
392
+ exports.AzureSubscriptionProviderBase = AzureSubscriptionProviderBase;
393
+ //# sourceMappingURL=AzureSubscriptionProviderBase.js.map
@@ -0,0 +1,269 @@
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
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.VSCodeAzureSubscriptionProvider = void 0;
41
+ const vscode = __importStar(require("vscode"));
42
+ const AzureSubscriptionProviderRequestOptions_1 = require("../contracts/AzureSubscriptionProviderRequestOptions"); // eslint-disable-line @typescript-eslint/no-unused-vars -- It is used in the doc comments
43
+ const dedupeSubscriptions_1 = require("../utils/dedupeSubscriptions");
44
+ const CaselessMap_1 = require("../utils/map/CaselessMap");
45
+ const TwoKeyCaselessMap_1 = require("../utils/map/TwoKeyCaselessMap");
46
+ const AzureSubscriptionProviderBase_1 = require("./AzureSubscriptionProviderBase");
47
+ const ConfigPrefix = 'azureResourceGroups';
48
+ const SelectedSubscriptionsConfigKey = 'selectedSubscriptions';
49
+ /**
50
+ * Extension of {@link AzureSubscriptionProviderBase} that adds caching of accounts, tenants, and subscriptions,
51
+ * as well as filtering and deduplication according to configured settings. Additionally, promise
52
+ * coalescence is added for {@link getAvailableSubscriptions}.
53
+ *
54
+ * @note See important notes about caching on {@link BaseOptions.noCache}
55
+ */
56
+ class VSCodeAzureSubscriptionProvider extends AzureSubscriptionProviderBase_1.AzureSubscriptionProviderBase {
57
+ accountCache = new CaselessMap_1.CaselessMap(); // Key is the account ID
58
+ tenantCache = new CaselessMap_1.CaselessMap(); // Key is the account ID
59
+ subscriptionCache = new TwoKeyCaselessMap_1.TwoKeyCaselessMap(); // Keys are account ID and tenant ID
60
+ availableSubscriptionsPromises = new Map(); // Key is from getOptionsCoalescenceKey
61
+ configChangeListener;
62
+ dispose() {
63
+ this.configChangeListener?.dispose();
64
+ super.dispose();
65
+ }
66
+ /**
67
+ * @inheritdoc
68
+ */
69
+ onRefreshSuggested(callback, thisArg, disposables) {
70
+ this.configChangeListener ??= vscode.workspace.onDidChangeConfiguration(e => {
71
+ if (e.affectsConfiguration(`${ConfigPrefix}.${SelectedSubscriptionsConfigKey}`)) {
72
+ this.fireRefreshSuggestedIfNeeded({ reason: 'subscriptionFilterChange' });
73
+ }
74
+ });
75
+ return super.onRefreshSuggested(callback, thisArg, disposables);
76
+ }
77
+ fireRefreshSuggestedIfNeeded(evtArgs) {
78
+ const actuallyFired = super.fireRefreshSuggestedIfNeeded(evtArgs);
79
+ if (actuallyFired && evtArgs.reason === 'sessionChange') {
80
+ // Clear just the account cache--tenants and subscriptions are probably still valid,
81
+ // but the session change event may been have fired due to account sign out
82
+ this.accountCache.clear();
83
+ }
84
+ return actuallyFired;
85
+ }
86
+ /**
87
+ * @inheritdoc
88
+ */
89
+ async getAvailableSubscriptions(options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
90
+ try {
91
+ const key = (0, AzureSubscriptionProviderRequestOptions_1.getCoalescenceKey)(options);
92
+ if (key && this.availableSubscriptionsPromises.has(key)) {
93
+ return await this.availableSubscriptionsPromises.get(key); // eslint-disable-line @typescript-eslint/no-non-null-assertion -- We just checked it has the key
94
+ }
95
+ else {
96
+ try {
97
+ const promise = super.getAvailableSubscriptions(options);
98
+ if (key) {
99
+ this.availableSubscriptionsPromises.set(key, promise);
100
+ }
101
+ return await promise;
102
+ }
103
+ finally {
104
+ if (key) {
105
+ this.availableSubscriptionsPromises.delete(key);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ finally {
111
+ this.throwIfCancelled(options.token);
112
+ }
113
+ }
114
+ /**
115
+ * @inheritdoc
116
+ */
117
+ async getAccounts(options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
118
+ try {
119
+ if (options.noCache ?? AzureSubscriptionProviderRequestOptions_1.DefaultOptions.noCache) {
120
+ this.accountCache.clear();
121
+ }
122
+ // If needed, refill the cache
123
+ if (this.accountCache.size === 0) {
124
+ const accounts = await super.getAccounts(options);
125
+ accounts.forEach(account => this.accountCache.set(account.id, account));
126
+ this.log(`Cached ${accounts.length} accounts`);
127
+ }
128
+ else {
129
+ this.log('Using cached accounts');
130
+ }
131
+ let results = Array.from(this.accountCache.values());
132
+ // If needed, filter according to configured filters
133
+ if (options.filter ?? AzureSubscriptionProviderRequestOptions_1.DefaultOptions.filter) {
134
+ const accountFilters = await this.getAccountFilters();
135
+ if (accountFilters.length > 0) {
136
+ this.log(`Filtering accounts to ${accountFilters.length} configured accounts`);
137
+ results = results.filter(account => accountFilters.includes(account.id.toLowerCase()));
138
+ }
139
+ }
140
+ this.log(`Returning ${results.length} accounts.`);
141
+ return results.sort((a, b) => a.label.localeCompare(b.label));
142
+ }
143
+ finally {
144
+ this.throwIfCancelled(options.token);
145
+ }
146
+ }
147
+ /**
148
+ * @inheritdoc
149
+ */
150
+ async getTenantsForAccount(account, options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
151
+ try {
152
+ // If needed, delete the cache for this account
153
+ if (options.noCache ?? AzureSubscriptionProviderRequestOptions_1.DefaultOptions.noCache) {
154
+ this.tenantCache.delete(account.id);
155
+ }
156
+ // If needed, refill the cache
157
+ if (!this.tenantCache.has(account.id)) {
158
+ const tenants = await super.getTenantsForAccount(account, options);
159
+ this.tenantCache.set(account.id, tenants);
160
+ this.logForAccount(account, `Cached ${tenants.length} tenants for account`);
161
+ }
162
+ else {
163
+ this.logForAccount(account, 'Using cached tenants for account');
164
+ }
165
+ let results = this.tenantCache.get(account.id); // eslint-disable-line @typescript-eslint/no-non-null-assertion -- We just filled it
166
+ // If needed, filter according to configured filters
167
+ if (options.filter ?? AzureSubscriptionProviderRequestOptions_1.DefaultOptions.filter) {
168
+ const tenantFilters = await this.getTenantFilters();
169
+ if (tenantFilters.length > 0) {
170
+ this.logForAccount(account, `Filtering tenants for account to ${tenantFilters.length} configured tenants`);
171
+ results = results.filter(tenant => tenantFilters.includes(tenant.tenantId.toLowerCase()));
172
+ }
173
+ }
174
+ this.logForAccount(account, `Returning ${results.length} tenants for account`);
175
+ // Finally, sort
176
+ return results.sort((a, b) => {
177
+ if (a.displayName && b.displayName) {
178
+ return a.displayName.localeCompare(b.displayName);
179
+ }
180
+ return a.tenantId.localeCompare(b.tenantId);
181
+ });
182
+ }
183
+ finally {
184
+ this.throwIfCancelled(options.token);
185
+ }
186
+ }
187
+ /**
188
+ * @inheritdoc
189
+ */
190
+ async getSubscriptionsForTenant(tenant, options = AzureSubscriptionProviderRequestOptions_1.DefaultOptions) {
191
+ try {
192
+ // If needed, delete the cache for this account+tenant
193
+ if (options.noCache ?? AzureSubscriptionProviderRequestOptions_1.DefaultOptions.noCache) {
194
+ this.subscriptionCache.delete(tenant.account.id, tenant.tenantId);
195
+ }
196
+ // If needed, refill the cache
197
+ if (!this.subscriptionCache.has(tenant.account.id, tenant.tenantId)) {
198
+ const subscriptions = await super.getSubscriptionsForTenant(tenant, options);
199
+ this.subscriptionCache.set(tenant.account.id, tenant.tenantId, subscriptions);
200
+ this.logForTenant(tenant, `Cached ${subscriptions.length} subscriptions for account+tenant`);
201
+ }
202
+ else {
203
+ this.logForTenant(tenant, 'Using cached subscriptions for account+tenant');
204
+ }
205
+ let results = this.subscriptionCache.get(tenant.account.id, tenant.tenantId); // eslint-disable-line @typescript-eslint/no-non-null-assertion -- We just filled it
206
+ // If needed, filter according to configured filters
207
+ if (options.filter ?? AzureSubscriptionProviderRequestOptions_1.DefaultOptions.filter) {
208
+ const subscriptionFilters = await this.getSubscriptionFilters();
209
+ if (subscriptionFilters.length > 0) {
210
+ this.logForTenant(tenant, `Filtering subscriptions for account+tenant to ${subscriptionFilters.length} configured subscriptions`);
211
+ results = results.filter(sub => subscriptionFilters.includes(sub.subscriptionId.toLowerCase()));
212
+ }
213
+ }
214
+ // If needed, dedupe according to options
215
+ if (options.dedupe ?? AzureSubscriptionProviderRequestOptions_1.DefaultOptions.dedupe) {
216
+ this.logForTenant(tenant, 'Deduping subscriptions for account+tenant');
217
+ results = (0, dedupeSubscriptions_1.dedupeSubscriptions)(results);
218
+ }
219
+ this.logForTenant(tenant, `Returning ${results.length} subscriptions for account+tenant`);
220
+ // Finally, sort
221
+ return results.sort((a, b) => a.name.localeCompare(b.name));
222
+ }
223
+ finally {
224
+ this.throwIfCancelled(options.token);
225
+ }
226
+ }
227
+ /**
228
+ * Gets the account filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
229
+ * override the settings with a custom filter, implement a child class with filter methods overridden.
230
+ *
231
+ * If no values are returned by `getAccountFilters()`, then all accounts will be scanned for subscriptions.
232
+ *
233
+ * @returns A list of account IDs that are configured in `azureResourceGroups.selectedSubscriptions`.
234
+ */
235
+ getAccountFilters() {
236
+ // TODO: implement account filtering based on configuration if needed
237
+ return Promise.resolve([]);
238
+ }
239
+ /**
240
+ * Gets the tenant filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
241
+ * override the settings with a custom filter, implement a child class with filter methods overridden.
242
+ *
243
+ * If no values are returned by `getTenantFilters()`, then all tenants will be scanned for subscriptions.
244
+ *
245
+ * @returns A list of unique tenant IDs that are configured in `azureResourceGroups.selectedSubscriptions`.
246
+ */
247
+ getTenantFilters() {
248
+ const config = vscode.workspace.getConfiguration(ConfigPrefix);
249
+ const fullSubscriptionIds = config.get(SelectedSubscriptionsConfigKey, []);
250
+ const tenantIds = fullSubscriptionIds.map(id => id.split('/')[0].toLowerCase());
251
+ return Promise.resolve(Array.from(new Set(tenantIds)));
252
+ }
253
+ /**
254
+ * Gets the subscription filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
255
+ * override the settings with a custom filter, implement a child class with filter methods overridden.
256
+ *
257
+ * If no values are returned by `getSubscriptionFilters()`, then all subscriptions will be scanned.
258
+ *
259
+ * @returns A list of unique subscription IDs that are configured in `azureResourceGroups.selectedSubscriptions`.
260
+ */
261
+ getSubscriptionFilters() {
262
+ const config = vscode.workspace.getConfiguration(ConfigPrefix);
263
+ const fullSubscriptionIds = config.get(SelectedSubscriptionsConfigKey, []);
264
+ const subscriptionIds = fullSubscriptionIds.map(id => id.split('/')[1].toLowerCase());
265
+ return Promise.resolve(Array.from(new Set(subscriptionIds)));
266
+ }
267
+ }
268
+ exports.VSCodeAzureSubscriptionProvider = VSCodeAzureSubscriptionProvider;
269
+ //# sourceMappingURL=VSCodeAzureSubscriptionProvider.js.map