@microsoft/vscode-azext-azureauth 3.0.1 → 4.0.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.0.0 - 2024-12-06
4
+
5
+ ### What's new
6
+ Pass in a `vscode.LogOutputChannel` to the `VSCodeAzureSubscriptionProvider` constructor to enable logging. [#1851](https://github.com/microsoft/vscode-azuretools/pull/1851)
7
+
8
+ `AzureSubscriptionProvider.getTenants()` now returns `AzureTenant[]` instead of `TenantIdDescription[]`. This is a breaking change for implementors of `AzureSubscriptionProvider`. [#1849](https://github.com/microsoft/vscode-azuretools/pull/1849)
9
+
10
+ ### All Changes
11
+ * [#1849](https://github.com/microsoft/vscode-azuretools/pull/1849) Create `AzureTenant` interface which includes account property
12
+ * [#1850](https://github.com/microsoft/vscode-azuretools/pull/1850) Clean up `isSignedIn` implementation
13
+ * [#1851](https://github.com/microsoft/vscode-azuretools/pull/1851) Add logging to `VSCodeAzureSubscriptionProvider`
14
+
15
+ ## 3.1.0 - 2024-11-26
16
+
17
+ * [#1827](https://github.com/microsoft/vscode-azuretools/pull/1827) Add more comprehensive support for multi-account scenarios
18
+ * [#1815](https://github.com/microsoft/vscode-azuretools/issues/1815) Fix `VSCodeAzureSubscriptionProvider.getSubscriptions()` returning empty
19
+
3
20
  ## 3.0.1 - 2024-11-19
4
21
  * [#1819](https://github.com/microsoft/vscode-azuretools/pull/1819) Add account parameter to `AzureSubscriptionProvider.isSignedIn()` function to fix a multi-account issue [#1809](https://github.com/microsoft/vscode-azuretools/issues/1809)
5
22
  * [#1822](https://github.com/microsoft/vscode-azuretools/pull/1822) Add check in `VSCodeAzureSubscriptionProvider.getTenants()` to fix a multi-account issue [#1809](https://github.com/microsoft/vscode-azuretools/issues/1809)
@@ -1,7 +1,7 @@
1
- import type { TenantIdDescription } from '@azure/arm-resources-subscriptions';
2
1
  import { Event } from 'vscode';
3
2
  import { AzureSubscription } from './AzureSubscription';
4
3
  import { AzureSubscriptionProvider } from './AzureSubscriptionProvider';
4
+ import { AzureTenant } from './AzureTenant';
5
5
  export interface AzureDevOpsSubscriptionProviderInitializer {
6
6
  /**
7
7
  * The resource ID of the Azure DevOps federated service connection,
@@ -46,7 +46,7 @@ export declare class AzureDevOpsSubscriptionProvider implements AzureSubscriptio
46
46
  isSignedIn(): Promise<boolean>;
47
47
  signIn(): Promise<boolean>;
48
48
  signOut(): Promise<void>;
49
- getTenants(): Promise<TenantIdDescription[]>;
49
+ getTenants(): Promise<AzureTenant[]>;
50
50
  /**
51
51
  * Gets the subscriptions for a given tenant.
52
52
  *
@@ -87,6 +87,10 @@ class AzureDevOpsSubscriptionProvider {
87
87
  return __awaiter(this, void 0, void 0, function* () {
88
88
  return [{
89
89
  tenantId: (_a = this._tokenCredential) === null || _a === void 0 ? void 0 : _a.tenantId,
90
+ account: {
91
+ id: "test-account-id",
92
+ label: "test-account",
93
+ }
90
94
  }];
91
95
  });
92
96
  }
@@ -1,6 +1,6 @@
1
- import type { TenantIdDescription } from '@azure/arm-resources-subscriptions';
2
1
  import type * as vscode from 'vscode';
3
2
  import type { AzureSubscription } from './AzureSubscription';
3
+ import type { AzureTenant } from './AzureTenant';
4
4
  /**
5
5
  * An interface for obtaining Azure subscription information
6
6
  */
@@ -13,7 +13,7 @@ export interface AzureSubscriptionProvider {
13
13
  *
14
14
  * @returns A list of tenants.
15
15
  */
16
- getTenants(account?: vscode.AuthenticationSessionAccountInformation): Promise<TenantIdDescription[]>;
16
+ getTenants(account?: vscode.AuthenticationSessionAccountInformation): Promise<AzureTenant[]>;
17
17
  /**
18
18
  * Gets a list of Azure subscriptions available to the user.
19
19
  *
@@ -39,10 +39,11 @@ export interface AzureSubscriptionProvider {
39
39
  * Asks the user to sign in or pick an account to use.
40
40
  *
41
41
  * @param tenantId (Optional) Provide to sign in to a specific tenant.
42
+ * @param account (Optional) Provide to sign in to a specific account.
42
43
  *
43
44
  * @returns True if the user is signed in, false otherwise.
44
45
  */
45
- signIn(tenantId?: string): Promise<boolean>;
46
+ signIn(tenantId?: string, account?: vscode.AuthenticationSessionAccountInformation): Promise<boolean>;
46
47
  /**
47
48
  * An event that is fired when the user signs in. Debounced to fire at most once every 5 seconds.
48
49
  */
@@ -0,0 +1,5 @@
1
+ import { TenantIdDescription } from "@azure/arm-resources-subscriptions";
2
+ import * as vscode from 'vscode';
3
+ export interface AzureTenant extends TenantIdDescription {
4
+ account: vscode.AuthenticationSessionAccountInformation;
5
+ }
@@ -0,0 +1,7 @@
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
+ //# sourceMappingURL=AzureTenant.js.map
@@ -1,18 +1,19 @@
1
- import type { TenantIdDescription } from '@azure/arm-resources-subscriptions';
2
1
  import * as vscode from 'vscode';
3
2
  import { AzureSubscription, SubscriptionId, TenantId } from './AzureSubscription';
4
3
  import { AzureSubscriptionProvider } from './AzureSubscriptionProvider';
4
+ import { AzureTenant } from './AzureTenant';
5
5
  /**
6
6
  * A class for obtaining Azure subscription information using VSCode's built-in authentication
7
7
  * provider.
8
8
  */
9
9
  export declare class VSCodeAzureSubscriptionProvider extends vscode.Disposable implements AzureSubscriptionProvider {
10
+ private readonly logger?;
10
11
  private readonly onDidSignInEmitter;
11
12
  private lastSignInEventFired;
12
13
  private suppressSignInEvents;
13
14
  private readonly onDidSignOutEmitter;
14
15
  private lastSignOutEventFired;
15
- constructor();
16
+ constructor(logger?: vscode.LogOutputChannel | undefined);
16
17
  /**
17
18
  * Gets a list of tenants available to the user.
18
19
  * Use {@link isSignedIn} to check if the user is signed in to a particular tenant.
@@ -21,14 +22,15 @@ export declare class VSCodeAzureSubscriptionProvider extends vscode.Disposable i
21
22
  *
22
23
  * @returns A list of tenants.
23
24
  */
24
- getTenants(account?: vscode.AuthenticationSessionAccountInformation): Promise<TenantIdDescription[]>;
25
+ getTenants(account?: vscode.AuthenticationSessionAccountInformation): Promise<AzureTenant[]>;
25
26
  /**
26
27
  * Gets a list of Azure subscriptions available to the user.
27
28
  *
28
29
  * @param filter - Whether to filter the list returned, according to the list returned
29
30
  * by `getTenantFilters()` and `getSubscriptionFilters()`. Optional, default true.
30
31
  *
31
- * @returns A list of Azure subscriptions.
32
+ * @returns A list of Azure subscriptions. The list is sorted by subscription name.
33
+ * The list can contain duplicate subscriptions if they come from different accounts.
32
34
  *
33
35
  * @throws A {@link NotSignedInError} If the user is not signed in to Azure.
34
36
  * Use {@link isSignedIn} and/or {@link signIn} before this method to ensure
@@ -39,18 +41,23 @@ export declare class VSCodeAzureSubscriptionProvider extends vscode.Disposable i
39
41
  * Checks to see if a user is signed in.
40
42
  *
41
43
  * @param tenantId (Optional) Provide to check if a user is signed in to a specific tenant.
44
+ * @param account (Optional) Provide to check if a user is signed in to a specific account.
42
45
  *
43
46
  * @returns True if the user is signed in, false otherwise.
47
+ *
48
+ * If no tenant or account is provided, then
49
+ * checks all accounts for a session.
44
50
  */
45
51
  isSignedIn(tenantId?: string, account?: vscode.AuthenticationSessionAccountInformation): Promise<boolean>;
46
52
  /**
47
53
  * Asks the user to sign in or pick an account to use.
48
54
  *
49
55
  * @param tenantId (Optional) Provide to sign in to a specific tenant.
56
+ * @param account (Optional) Provide to sign in to a specific account.
50
57
  *
51
58
  * @returns True if the user is signed in, false otherwise.
52
59
  */
53
- signIn(tenantId?: string): Promise<boolean>;
60
+ signIn(tenantId?: string, account?: vscode.AuthenticationSessionAccountInformation): Promise<boolean>;
54
61
  /**
55
62
  * An event that is fired when the user signs in. Debounced to fire at most once every 5 seconds.
56
63
  */
@@ -31,7 +31,8 @@ const EventDebounce = 5 * 1000; // 5 seconds
31
31
  * provider.
32
32
  */
33
33
  class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
34
- constructor() {
34
+ // So that customers can easily share logs, try to only log PII using trace level
35
+ constructor(logger) {
35
36
  const disposable = vscode.authentication.onDidChangeSessions((e) => __awaiter(this, void 0, void 0, function* () {
36
37
  // Ignore any sign in that isn't for the configured auth provider
37
38
  if (e.provider.id !== (0, configuredAzureEnv_1.getConfiguredAuthProviderId)()) {
@@ -53,6 +54,7 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
53
54
  this.onDidSignOutEmitter.dispose();
54
55
  disposable.dispose();
55
56
  });
57
+ this.logger = logger;
56
58
  this.onDidSignInEmitter = new vscode.EventEmitter();
57
59
  this.lastSignInEventFired = 0;
58
60
  this.suppressSignInEvents = false;
@@ -77,51 +79,55 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
77
79
  */
78
80
  getTenants(account) {
79
81
  var _a, e_1, _b, _c, _d, e_2, _e, _f;
82
+ var _g;
80
83
  return __awaiter(this, void 0, void 0, function* () {
84
+ const startTimeMs = Date.now();
81
85
  const results = [];
82
86
  try {
83
- for (var _g = true, _h = __asyncValues(account ? [account] : yield vscode.authentication.getAccounts((0, configuredAzureEnv_1.getConfiguredAuthProviderId)())), _j; _j = yield _h.next(), _a = _j.done, !_a;) {
84
- _c = _j.value;
85
- _g = false;
87
+ for (var _h = true, _j = __asyncValues(account ? [account] : yield vscode.authentication.getAccounts((0, configuredAzureEnv_1.getConfiguredAuthProviderId)())), _k; _k = yield _j.next(), _a = _k.done, !_a;) {
88
+ _c = _k.value;
89
+ _h = false;
86
90
  try {
87
91
  account = _c;
88
92
  // Added check. Without this the getSubscriptionClient function throws the NotSignedInError
89
93
  if (yield this.isSignedIn(undefined, account)) {
90
94
  const { client } = yield this.getSubscriptionClient(account, undefined, undefined);
91
95
  try {
92
- for (var _k = true, _l = (e_2 = void 0, __asyncValues(client.tenants.list())), _m; _m = yield _l.next(), _d = _m.done, !_d;) {
93
- _f = _m.value;
94
- _k = false;
96
+ for (var _l = true, _m = (e_2 = void 0, __asyncValues(client.tenants.list())), _o; _o = yield _m.next(), _d = _o.done, !_d;) {
97
+ _f = _o.value;
98
+ _l = false;
95
99
  try {
96
100
  const tenant = _f;
97
- results.push(tenant);
101
+ results.push(Object.assign(Object.assign({}, tenant), { account }));
98
102
  }
99
103
  finally {
100
- _k = true;
104
+ _l = true;
101
105
  }
102
106
  }
103
107
  }
104
108
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
105
109
  finally {
106
110
  try {
107
- if (!_k && !_d && (_e = _l.return)) yield _e.call(_l);
111
+ if (!_l && !_d && (_e = _m.return)) yield _e.call(_m);
108
112
  }
109
113
  finally { if (e_2) throw e_2.error; }
110
114
  }
111
115
  }
112
116
  }
113
117
  finally {
114
- _g = true;
118
+ _h = true;
115
119
  }
116
120
  }
117
121
  }
118
122
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
119
123
  finally {
120
124
  try {
121
- if (!_g && !_a && (_b = _h.return)) yield _b.call(_h);
125
+ if (!_h && !_a && (_b = _j.return)) yield _b.call(_j);
122
126
  }
123
127
  finally { if (e_1) throw e_1.error; }
124
128
  }
129
+ const endTimeMs = Date.now();
130
+ (_g = this.logger) === null || _g === void 0 ? void 0 : _g.debug(`auth: Got ${results.length} tenants for account "${account === null || account === void 0 ? void 0 : account.label}" in ${endTimeMs - startTimeMs}ms`);
125
131
  return results;
126
132
  });
127
133
  }
@@ -131,21 +137,27 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
131
137
  * @param filter - Whether to filter the list returned, according to the list returned
132
138
  * by `getTenantFilters()` and `getSubscriptionFilters()`. Optional, default true.
133
139
  *
134
- * @returns A list of Azure subscriptions.
140
+ * @returns A list of Azure subscriptions. The list is sorted by subscription name.
141
+ * The list can contain duplicate subscriptions if they come from different accounts.
135
142
  *
136
143
  * @throws A {@link NotSignedInError} If the user is not signed in to Azure.
137
144
  * Use {@link isSignedIn} and/or {@link signIn} before this method to ensure
138
145
  * the user is signed in.
139
146
  */
140
147
  getSubscriptions(filter = true) {
148
+ var _a, _b;
141
149
  return __awaiter(this, void 0, void 0, function* () {
150
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug('auth: Loading subscriptions...');
151
+ const startTime = Date.now();
142
152
  const tenantIds = yield this.getTenantFilters();
143
153
  const shouldFilterTenants = filter && !!tenantIds.length; // If the list is empty it is treated as "no filter"
144
- const results = [];
154
+ const allSubscriptions = [];
155
+ let accountCount; // only used for logging
145
156
  try {
146
157
  this.suppressSignInEvents = true;
147
158
  // Get the list of tenants from each account
148
159
  const accounts = yield vscode.authentication.getAccounts((0, configuredAzureEnv_1.getConfiguredAuthProviderId)());
160
+ accountCount = accounts.length;
149
161
  for (const account of accounts) {
150
162
  for (const tenant of yield this.getTenants(account)) {
151
163
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -154,49 +166,89 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
154
166
  if (shouldFilterTenants && !tenantIds.includes(tenantId)) {
155
167
  continue;
156
168
  }
157
- // If the user is not signed in to this tenant, then skip it
158
- if (!(yield this.isSignedIn(tenantId, account))) {
159
- continue;
160
- }
161
169
  // For each tenant, get the list of subscriptions
162
- results.push(...yield this.getSubscriptionsForTenant(tenantId, account));
170
+ allSubscriptions.push(...yield this.getSubscriptionsForTenant(account, tenantId));
163
171
  }
172
+ // list subscriptions for the home tenant
173
+ allSubscriptions.push(...yield this.getSubscriptionsForTenant(account));
164
174
  }
165
175
  }
166
176
  finally {
167
177
  this.suppressSignInEvents = false;
168
178
  }
179
+ // It's possible that by listing subscriptions in all tenants and the "home" tenant there could be duplicate subscriptions
180
+ // Thus, we remove duplicate subscriptions. However, if multiple accounts have the same subscription, we keep them.
181
+ const subscriptionMap = new Map();
182
+ allSubscriptions.forEach(sub => subscriptionMap.set(`${sub.account.id}/${sub.subscriptionId}`, sub));
183
+ const uniqueSubscriptions = Array.from(subscriptionMap.values());
184
+ const endTime = Date.now();
185
+ (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`auth: Got ${uniqueSubscriptions.length} subscriptions from ${accountCount} accounts in ${endTime - startTime}ms`);
169
186
  const sortSubscriptions = (subscriptions) => subscriptions.sort((a, b) => a.name.localeCompare(b.name));
170
187
  const subscriptionIds = yield this.getSubscriptionFilters();
171
188
  if (filter && !!subscriptionIds.length) { // If the list is empty it is treated as "no filter"
172
- return sortSubscriptions(results.filter(sub => subscriptionIds.includes(sub.subscriptionId)));
189
+ return sortSubscriptions(uniqueSubscriptions.filter(sub => subscriptionIds.includes(sub.subscriptionId)));
173
190
  }
174
- return sortSubscriptions(results);
191
+ return sortSubscriptions(uniqueSubscriptions);
175
192
  });
176
193
  }
177
194
  /**
178
195
  * Checks to see if a user is signed in.
179
196
  *
180
197
  * @param tenantId (Optional) Provide to check if a user is signed in to a specific tenant.
198
+ * @param account (Optional) Provide to check if a user is signed in to a specific account.
181
199
  *
182
200
  * @returns True if the user is signed in, false otherwise.
201
+ *
202
+ * If no tenant or account is provided, then
203
+ * checks all accounts for a session.
183
204
  */
184
205
  isSignedIn(tenantId, account) {
206
+ var _a, _b;
185
207
  return __awaiter(this, void 0, void 0, function* () {
186
- const session = yield (0, getSessionFromVSCode_1.getSessionFromVSCode)([], tenantId, { createIfNone: false, silent: true, account });
187
- return !!session;
208
+ function silentlyCheckForSession(tenantId, account) {
209
+ return __awaiter(this, void 0, void 0, function* () {
210
+ return !!(yield (0, getSessionFromVSCode_1.getSessionFromVSCode)([], tenantId, { createIfNone: false, silent: true, account }));
211
+ });
212
+ }
213
+ const innerIsSignedIn = () => __awaiter(this, void 0, void 0, function* () {
214
+ // If no tenant or account is provided, then check all accounts for a session
215
+ if (!account && !tenantId) {
216
+ const accounts = yield vscode.authentication.getAccounts((0, configuredAzureEnv_1.getConfiguredAuthProviderId)());
217
+ if (accounts.length === 0) {
218
+ return false;
219
+ }
220
+ for (const account of accounts) {
221
+ if (yield silentlyCheckForSession(tenantId, account)) {
222
+ // If any account has a session, then return true because the user is signed in
223
+ return true;
224
+ }
225
+ }
226
+ }
227
+ return silentlyCheckForSession(tenantId, account);
228
+ });
229
+ const result = yield innerIsSignedIn();
230
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.trace(`auth: isSignedIn returned ${result} (account="${(_b = account === null || account === void 0 ? void 0 : account.label) !== null && _b !== void 0 ? _b : 'none'}") (tenantId="${tenantId !== null && tenantId !== void 0 ? tenantId : 'none'}")`);
231
+ return result;
188
232
  });
189
233
  }
190
234
  /**
191
235
  * Asks the user to sign in or pick an account to use.
192
236
  *
193
237
  * @param tenantId (Optional) Provide to sign in to a specific tenant.
238
+ * @param account (Optional) Provide to sign in to a specific account.
194
239
  *
195
240
  * @returns True if the user is signed in, false otherwise.
196
241
  */
197
- signIn(tenantId) {
242
+ signIn(tenantId, account) {
243
+ var _a, _b;
198
244
  return __awaiter(this, void 0, void 0, function* () {
199
- const session = yield (0, getSessionFromVSCode_1.getSessionFromVSCode)([], tenantId, { createIfNone: true, clearSessionPreference: true });
245
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`auth: Signing in (account="${(_b = account === null || account === void 0 ? void 0 : account.label) !== null && _b !== void 0 ? _b : 'none'}") (tenantId="${tenantId !== null && tenantId !== void 0 ? tenantId : 'none'}")`);
246
+ const session = yield (0, getSessionFromVSCode_1.getSessionFromVSCode)([], tenantId, {
247
+ createIfNone: true,
248
+ // If no account is provided, then clear the session preference which tells VS Code to show the account picker
249
+ clearSessionPreference: !account,
250
+ account,
251
+ });
200
252
  return !!session;
201
253
  });
202
254
  }
@@ -248,9 +300,14 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
248
300
  *
249
301
  * @returns The list of subscriptions for the tenant.
250
302
  */
251
- getSubscriptionsForTenant(tenantId, account) {
303
+ getSubscriptionsForTenant(account, tenantId) {
252
304
  var _a, e_3, _b, _c;
253
305
  return __awaiter(this, void 0, void 0, function* () {
306
+ // If the user is not signed in to this tenant or account, then return an empty list
307
+ // This is to prevent the NotSignedInError from being thrown in getSubscriptionClient
308
+ if (!(yield this.isSignedIn(tenantId, account))) {
309
+ return [];
310
+ }
254
311
  const { client, credential, authentication } = yield this.getSubscriptionClient(account, tenantId, undefined);
255
312
  const environment = (0, configuredAzureEnv_1.getConfiguredAzureEnv)();
256
313
  const subscriptions = [];
@@ -268,8 +325,8 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
268
325
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
269
326
  name: subscription.displayName,
270
327
  subscriptionId: subscription.subscriptionId,
328
+ tenantId: tenantId !== null && tenantId !== void 0 ? tenantId : subscription.tenantId,
271
329
  /* eslint-enable @typescript-eslint/no-non-null-assertion */
272
- tenantId: tenantId,
273
330
  account: account
274
331
  });
275
332
  }
@@ -1,5 +1,6 @@
1
1
  export * from './AzureAuthentication';
2
2
  export * from './AzureDevOpsSubscriptionProvider';
3
+ export * from './AzureTenant';
3
4
  export * from './AzureSubscription';
4
5
  export * from './AzureSubscriptionProvider';
5
6
  export * from './NotSignedInError';
package/out/src/index.js CHANGED
@@ -20,6 +20,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  __exportStar(require("./AzureAuthentication"), exports);
22
22
  __exportStar(require("./AzureDevOpsSubscriptionProvider"), exports);
23
+ __exportStar(require("./AzureTenant"), exports);
23
24
  __exportStar(require("./AzureSubscription"), exports);
24
25
  __exportStar(require("./AzureSubscriptionProvider"), exports);
25
26
  __exportStar(require("./NotSignedInError"), exports);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@microsoft/vscode-azext-azureauth",
3
3
  "author": "Microsoft Corporation",
4
- "version": "3.0.1",
4
+ "version": "4.0.0",
5
5
  "description": "Azure authentication helpers for Visual Studio Code",
6
6
  "tags": [
7
7
  "azure",