@microsoft/vscode-azext-azureauth 5.0.0 → 5.1.1
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 +14 -0
- package/dist/cjs/src/AzureDevOpsSubscriptionProvider.js +2 -3
- package/dist/cjs/src/VSCodeAzureSubscriptionProvider.js +100 -47
- package/dist/cjs/src/index.js +1 -0
- package/dist/cjs/src/utils/getUnauthenticatedTenants.js +4 -2
- package/dist/cjs/src/utils/isGetSubscriptionsFilter.js +27 -0
- package/dist/esm/src/AzureDevOpsSubscriptionProvider.d.ts +6 -6
- package/dist/esm/src/AzureDevOpsSubscriptionProvider.js +2 -3
- package/dist/esm/src/AzureSubscription.d.ts +2 -2
- package/dist/esm/src/AzureTenant.d.ts +2 -2
- package/dist/esm/src/VSCodeAzureSubscriptionProvider.d.ts +13 -14
- package/dist/esm/src/VSCodeAzureSubscriptionProvider.js +100 -47
- package/dist/esm/src/index.d.ts +1 -46
- package/dist/esm/src/index.js +1 -0
- package/dist/esm/src/utils/getUnauthenticatedTenants.d.ts +4 -1
- package/dist/esm/src/utils/getUnauthenticatedTenants.js +4 -2
- package/dist/esm/src/utils/isGetSubscriptionsFilter.d.ts +14 -0
- package/dist/esm/src/utils/isGetSubscriptionsFilter.js +23 -0
- package/package.json +3 -3
- package/dist/cjs/src/AzureAuthentication.d.ts +0 -21
- package/dist/cjs/src/AzureDevOpsSubscriptionProvider.d.ts +0 -68
- package/dist/cjs/src/AzureSubscription.d.ts +0 -49
- package/dist/cjs/src/AzureSubscriptionProvider.d.ts +0 -82
- package/dist/cjs/src/AzureTenant.d.ts +0 -5
- package/dist/cjs/src/NotSignedInError.d.ts +0 -15
- package/dist/cjs/src/VSCodeAzureSubscriptionProvider.d.ts +0 -117
- package/dist/cjs/src/getSessionFromVSCode.d.ts +0 -13
- package/dist/cjs/src/index.d.ts +0 -56
- package/dist/cjs/src/signInToTenant.d.ts +0 -6
- package/dist/cjs/src/utils/configuredAzureEnv.d.ts +0 -24
- package/dist/cjs/src/utils/getUnauthenticatedTenants.d.ts +0 -6
- package/dist/cjs/src/utils/isAuthenticationWwwAuthenticateRequest.d.ts +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 5.1.1 - 2025-10-28
|
|
4
|
+
|
|
5
|
+
* [#2111](https://github.com/microsoft/vscode-azuretools/pull/2111) Same as https://github.com/microsoft/vscode-azuretools/pull/2110 but a better fix.
|
|
6
|
+
|
|
7
|
+
## 5.1.0 - 2025-10-27
|
|
8
|
+
|
|
9
|
+
* [#2102](https://github.com/microsoft/vscode-azuretools/pull/2102) Fixes an issue causing infinite event loops especially in https://vscode.dev/azure
|
|
10
|
+
* [#2110](https://github.com/microsoft/vscode-azuretools/pull/2110) `vscode.authentication.onDidChangeSessions()` is no longer subscribed to unless the caller calls `AzureSubscriptionProvider.onDidSignIn()` or `AzureSubscriptionProvider.onDidSignOut()`.
|
|
11
|
+
|
|
12
|
+
## 5.0.0 - 2025-10-07
|
|
13
|
+
|
|
14
|
+
* [#2092](https://github.com/microsoft/vscode-azuretools/pull/2092) Converts from CJS only to CJS+ESM
|
|
15
|
+
* Adopts finalized auth challenges API
|
|
16
|
+
|
|
3
17
|
## 4.2.2 - 2025-09-10
|
|
4
18
|
|
|
5
19
|
* [#2073](https://github.com/microsoft/vscode-azuretools/pull/2073) Changes to adjust to proposed API changes
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.AzureDevOpsSubscriptionProvider = void 0;
|
|
8
8
|
exports.createAzureDevOpsSubscriptionProviderFactory = createAzureDevOpsSubscriptionProviderFactory;
|
|
9
|
-
const vscode_1 = require("vscode");
|
|
10
9
|
const configuredAzureEnv_1 = require("./utils/configuredAzureEnv");
|
|
11
10
|
let azureDevOpsSubscriptionProvider;
|
|
12
11
|
function createAzureDevOpsSubscriptionProviderFactory(initializer) {
|
|
@@ -144,8 +143,8 @@ class AzureDevOpsSubscriptionProvider {
|
|
|
144
143
|
}
|
|
145
144
|
};
|
|
146
145
|
}
|
|
147
|
-
onDidSignIn = () => { return
|
|
148
|
-
onDidSignOut = () => { return
|
|
146
|
+
onDidSignIn = () => { return { dispose() { } }; };
|
|
147
|
+
onDidSignOut = () => { return { dispose() { } }; };
|
|
149
148
|
}
|
|
150
149
|
exports.AzureDevOpsSubscriptionProvider = AzureDevOpsSubscriptionProvider;
|
|
151
150
|
/*
|
|
@@ -43,42 +43,92 @@ const getSessionFromVSCode_1 = require("./getSessionFromVSCode");
|
|
|
43
43
|
const NotSignedInError_1 = require("./NotSignedInError");
|
|
44
44
|
const configuredAzureEnv_1 = require("./utils/configuredAzureEnv");
|
|
45
45
|
const isAuthenticationWwwAuthenticateRequest_1 = require("./utils/isAuthenticationWwwAuthenticateRequest");
|
|
46
|
+
const isGetSubscriptionsFilter_1 = require("./utils/isGetSubscriptionsFilter");
|
|
46
47
|
const EventDebounce = 5 * 1000; // 5 seconds
|
|
48
|
+
let armSubs;
|
|
47
49
|
/**
|
|
48
50
|
* A class for obtaining Azure subscription information using VSCode's built-in authentication
|
|
49
51
|
* provider.
|
|
50
52
|
*/
|
|
51
|
-
class VSCodeAzureSubscriptionProvider
|
|
53
|
+
class VSCodeAzureSubscriptionProvider {
|
|
52
54
|
logger;
|
|
55
|
+
listenerDisposable;
|
|
53
56
|
onDidSignInEmitter = new vscode.EventEmitter();
|
|
54
|
-
lastSignInEventFired = 0;
|
|
55
|
-
suppressSignInEvents = false;
|
|
56
57
|
onDidSignOutEmitter = new vscode.EventEmitter();
|
|
57
|
-
|
|
58
|
+
lastEventFired = 0;
|
|
59
|
+
suppressEvents = false;
|
|
60
|
+
priorAccounts;
|
|
61
|
+
accountsRemovedPromise;
|
|
58
62
|
// So that customers can easily share logs, try to only log PII using trace level
|
|
59
63
|
constructor(logger) {
|
|
60
|
-
|
|
64
|
+
this.logger = logger;
|
|
65
|
+
void this.accountsRemoved(); // Initialize priorAccounts
|
|
66
|
+
}
|
|
67
|
+
dispose() {
|
|
68
|
+
this.listenerDisposable?.dispose();
|
|
69
|
+
this.onDidSignInEmitter.dispose();
|
|
70
|
+
this.onDidSignOutEmitter.dispose();
|
|
71
|
+
}
|
|
72
|
+
onDidSignIn(callback, thisArg, disposables) {
|
|
73
|
+
this.listenIfNeeded();
|
|
74
|
+
return this.onDidSignInEmitter.event(callback, thisArg, disposables);
|
|
75
|
+
}
|
|
76
|
+
onDidSignOut(callback, thisArg, disposables) {
|
|
77
|
+
this.listenIfNeeded();
|
|
78
|
+
return this.onDidSignOutEmitter.event(callback, thisArg, disposables);
|
|
79
|
+
}
|
|
80
|
+
listenIfNeeded() {
|
|
81
|
+
if (this.listenerDisposable) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.listenerDisposable = vscode.authentication.onDidChangeSessions(async (e) => {
|
|
61
85
|
// Ignore any sign in that isn't for the configured auth provider
|
|
62
86
|
if (e.provider.id !== (0, configuredAzureEnv_1.getConfiguredAuthProviderId)()) {
|
|
63
87
|
return;
|
|
64
88
|
}
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
if (this.suppressEvents || Date.now() < this.lastEventFired + EventDebounce) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.lastEventFired = Date.now();
|
|
93
|
+
if (!await this.accountsRemoved()) {
|
|
94
|
+
this.logger?.debug('auth: Firing onDidSignIn event');
|
|
95
|
+
this.onDidSignInEmitter.fire();
|
|
70
96
|
}
|
|
71
|
-
else
|
|
72
|
-
this.
|
|
97
|
+
else {
|
|
98
|
+
this.logger?.debug('auth: Firing onDidSignOut event');
|
|
73
99
|
this.onDidSignOutEmitter.fire();
|
|
74
100
|
}
|
|
75
101
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
}
|
|
103
|
+
async accountsRemoved() {
|
|
104
|
+
// If there's already an ongoing accountsRemoved operation, return its result
|
|
105
|
+
if (this.accountsRemovedPromise) {
|
|
106
|
+
return this.accountsRemovedPromise;
|
|
107
|
+
}
|
|
108
|
+
// Create a new promise for this operation
|
|
109
|
+
this.accountsRemovedPromise = (async () => {
|
|
110
|
+
try {
|
|
111
|
+
this.suppressEvents = true;
|
|
112
|
+
const currentAccounts = Array.from(await vscode.authentication.getAccounts((0, configuredAzureEnv_1.getConfiguredAuthProviderId)()));
|
|
113
|
+
const priorAccountCount = this.priorAccounts?.length ?? 0;
|
|
114
|
+
this.priorAccounts = currentAccounts;
|
|
115
|
+
// The only way a sign out happens is if an account is removed entirely from the list of accounts
|
|
116
|
+
if (currentAccounts.length === 0 || currentAccounts.length < priorAccountCount) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
this.suppressEvents = false;
|
|
123
|
+
}
|
|
124
|
+
})();
|
|
125
|
+
try {
|
|
126
|
+
return await this.accountsRemovedPromise;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
// Clear the promise when done so future calls can proceed
|
|
130
|
+
this.accountsRemovedPromise = undefined;
|
|
131
|
+
}
|
|
82
132
|
}
|
|
83
133
|
/**
|
|
84
134
|
* Gets a list of tenants available to the user.
|
|
@@ -124,24 +174,28 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
124
174
|
async getSubscriptions(filter = true) {
|
|
125
175
|
this.logger?.debug('auth: Loading subscriptions...');
|
|
126
176
|
const startTime = Date.now();
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
177
|
+
let tenantIdsToFilterBy;
|
|
178
|
+
if ((0, isGetSubscriptionsFilter_1.isGetSubscriptionsTenantFilter)(filter)) {
|
|
179
|
+
// Only filter by the tenant ID option if it is provided
|
|
180
|
+
tenantIdsToFilterBy = [filter.tenantId];
|
|
181
|
+
}
|
|
182
|
+
else if (filter === true) {
|
|
131
183
|
// Only filter by the configured filter if `filter` is true AND there are tenants in the configured filter
|
|
132
|
-
|
|
133
|
-
|
|
184
|
+
const configuredTenantFilter = await this.getTenantFilters();
|
|
185
|
+
if (configuredTenantFilter.length > 0) {
|
|
186
|
+
tenantIdsToFilterBy = configuredTenantFilter;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
134
189
|
const allSubscriptions = [];
|
|
135
190
|
let accountCount; // only used for logging
|
|
136
191
|
try {
|
|
137
|
-
this.
|
|
192
|
+
this.suppressEvents = true;
|
|
138
193
|
// Get the list of tenants from each account (filtered or all)
|
|
139
|
-
const accounts =
|
|
194
|
+
const accounts = (0, isGetSubscriptionsFilter_1.isGetSubscriptionsAccountFilter)(filter) ? [filter.account] : await vscode.authentication.getAccounts((0, configuredAzureEnv_1.getConfiguredAuthProviderId)());
|
|
140
195
|
accountCount = accounts.length;
|
|
141
196
|
for (const account of accounts) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const tenantId = tenant.tenantId;
|
|
197
|
+
const tenantIds = (0, isGetSubscriptionsFilter_1.isGetSubscriptionsTenantFilter)(filter) ? [filter.tenantId] : (await this.getTenants(account)).map(t => t.tenantId);
|
|
198
|
+
for (const tenantId of tenantIds) {
|
|
145
199
|
if (tenantIdsToFilterBy?.includes(tenantId) === false) {
|
|
146
200
|
continue;
|
|
147
201
|
}
|
|
@@ -153,12 +207,13 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
153
207
|
}
|
|
154
208
|
}
|
|
155
209
|
finally {
|
|
156
|
-
this.
|
|
210
|
+
this.suppressEvents = false;
|
|
157
211
|
}
|
|
158
212
|
// It's possible that by listing subscriptions in all tenants and the "home" tenant there could be duplicate subscriptions
|
|
159
213
|
// Thus, we remove duplicate subscriptions. However, if multiple accounts have the same subscription, we keep them.
|
|
214
|
+
// There are also cases where the same subscription could appear in different tenants under the same account so we also need to keep those
|
|
160
215
|
const subscriptionMap = new Map();
|
|
161
|
-
allSubscriptions.forEach(sub => subscriptionMap.set(`${sub.account.id}/${sub.subscriptionId}`, sub));
|
|
216
|
+
allSubscriptions.forEach(sub => subscriptionMap.set(`${sub.account.id}/${sub.tenantId}/${sub.subscriptionId}`, sub));
|
|
162
217
|
const uniqueSubscriptions = Array.from(subscriptionMap.values());
|
|
163
218
|
const endTime = Date.now();
|
|
164
219
|
this.logger?.debug(`auth: Got ${uniqueSubscriptions.length} subscriptions from ${accountCount} accounts in ${endTime - startTime}ms`);
|
|
@@ -214,18 +269,20 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
214
269
|
*/
|
|
215
270
|
async signIn(tenantId, account) {
|
|
216
271
|
this.logger?.debug(`auth: Signing in (account="${account?.label ?? 'none'}") (tenantId="${tenantId ?? 'none'}")`);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
272
|
+
try {
|
|
273
|
+
this.suppressEvents = true;
|
|
274
|
+
const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)([], tenantId, {
|
|
275
|
+
createIfNone: true,
|
|
276
|
+
// If no account is provided, then clear the session preference which tells VS Code to show the account picker
|
|
277
|
+
clearSessionPreference: !account,
|
|
278
|
+
account,
|
|
279
|
+
});
|
|
280
|
+
return !!session;
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
this.suppressEvents = false;
|
|
284
|
+
}
|
|
224
285
|
}
|
|
225
|
-
/**
|
|
226
|
-
* An event that is fired when the user signs in. Debounced to fire at most once every 5 seconds.
|
|
227
|
-
*/
|
|
228
|
-
onDidSignIn = this.onDidSignInEmitter.event;
|
|
229
286
|
/**
|
|
230
287
|
* Signs the user out
|
|
231
288
|
*
|
|
@@ -234,10 +291,6 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
234
291
|
signOut() {
|
|
235
292
|
throw new Error(vscode.l10n.t('Signing out programmatically is not supported. You must sign out by selecting the account in the Accounts menu and choosing Sign Out.'));
|
|
236
293
|
}
|
|
237
|
-
/**
|
|
238
|
-
* An event that is fired when the user signs out. Debounced to fire at most once every 5 seconds.
|
|
239
|
-
*/
|
|
240
|
-
onDidSignOut = this.onDidSignOutEmitter.event;
|
|
241
294
|
/**
|
|
242
295
|
* Gets the tenant filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
|
|
243
296
|
* override the settings with a custom filter, implement a child class with `getSubscriptionFilters()`
|
|
@@ -308,7 +361,7 @@ class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
308
361
|
* @returns A client, the credential used by the client, and the authentication function
|
|
309
362
|
*/
|
|
310
363
|
async getSubscriptionClient(account, tenantId, scopes) {
|
|
311
|
-
|
|
364
|
+
armSubs ||= await import('@azure/arm-resources-subscriptions');
|
|
312
365
|
const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(scopes, tenantId, { createIfNone: false, silent: true, account });
|
|
313
366
|
if (!session) {
|
|
314
367
|
throw new NotSignedInError_1.NotSignedInError();
|
package/dist/cjs/src/index.js
CHANGED
|
@@ -23,6 +23,7 @@ __exportStar(require("./AzureDevOpsSubscriptionProvider"), exports);
|
|
|
23
23
|
__exportStar(require("./AzureSubscription"), exports);
|
|
24
24
|
__exportStar(require("./AzureSubscriptionProvider"), exports);
|
|
25
25
|
__exportStar(require("./AzureTenant"), exports);
|
|
26
|
+
__exportStar(require("./getSessionFromVSCode"), exports);
|
|
26
27
|
__exportStar(require("./NotSignedInError"), exports);
|
|
27
28
|
__exportStar(require("./signInToTenant"), exports);
|
|
28
29
|
__exportStar(require("./utils/configuredAzureEnv"), exports);
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.getUnauthenticatedTenants = getUnauthenticatedTenants;
|
|
8
8
|
/**
|
|
9
|
+
* @param subscriptionProvider The {@link AzureSubscriptionProvider} to use
|
|
10
|
+
* @param account (Optional) The account to get unauthenticated tenants for
|
|
9
11
|
* @returns list of tenants that VS Code doesn't have sessions for
|
|
10
12
|
*/
|
|
11
|
-
async function getUnauthenticatedTenants(subscriptionProvider) {
|
|
12
|
-
const tenants = await subscriptionProvider.getTenants();
|
|
13
|
+
async function getUnauthenticatedTenants(subscriptionProvider, account) {
|
|
14
|
+
const tenants = await subscriptionProvider.getTenants(account);
|
|
13
15
|
const unauthenticatedTenants = [];
|
|
14
16
|
for await (const tenant of tenants) {
|
|
15
17
|
if (!await subscriptionProvider.isSignedIn(tenant.tenantId, tenant.account)) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License. See License.md in the project root for license information.
|
|
5
|
+
*--------------------------------------------------------------------------------------------*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.isGetSubscriptionsTenantFilter = isGetSubscriptionsTenantFilter;
|
|
8
|
+
exports.isGetSubscriptionsAccountFilter = isGetSubscriptionsAccountFilter;
|
|
9
|
+
/**
|
|
10
|
+
* Check if an object is a {@link GetSubscriptionsFilter} with a tenantId.
|
|
11
|
+
*/
|
|
12
|
+
function isGetSubscriptionsTenantFilter(obj) {
|
|
13
|
+
if (typeof obj === 'object' && !!obj && 'tenantId' in obj && typeof obj.tenantId === 'string' && !!obj.tenantId) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if an object is a {@link GetSubscriptionsFilter} with an account.
|
|
20
|
+
*/
|
|
21
|
+
function isGetSubscriptionsAccountFilter(obj) {
|
|
22
|
+
if (typeof obj === 'object' && !!obj && 'account' in obj && typeof obj.account === 'object' && !!obj.account) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=isGetSubscriptionsFilter.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { AzureSubscription } from './AzureSubscription';
|
|
3
|
-
import { AzureSubscriptionProvider, GetSubscriptionsFilter } from './AzureSubscriptionProvider';
|
|
4
|
-
import { AzureTenant } from './AzureTenant';
|
|
1
|
+
import type * as vscode from 'vscode';
|
|
2
|
+
import type { AzureSubscription } from './AzureSubscription';
|
|
3
|
+
import type { AzureSubscriptionProvider, GetSubscriptionsFilter } from './AzureSubscriptionProvider';
|
|
4
|
+
import type { AzureTenant } from './AzureTenant';
|
|
5
5
|
export interface AzureDevOpsSubscriptionProviderInitializer {
|
|
6
6
|
/**
|
|
7
7
|
* The resource ID of the Azure DevOps federated service connection,
|
|
@@ -63,6 +63,6 @@ export declare class AzureDevOpsSubscriptionProvider implements AzureSubscriptio
|
|
|
63
63
|
* @returns A client, the credential used by the client, and the authentication function
|
|
64
64
|
*/
|
|
65
65
|
private getSubscriptionClient;
|
|
66
|
-
onDidSignIn: Event<void>;
|
|
67
|
-
onDidSignOut: Event<void>;
|
|
66
|
+
onDidSignIn: vscode.Event<void>;
|
|
67
|
+
onDidSignOut: vscode.Event<void>;
|
|
68
68
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
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 { Disposable } from 'vscode';
|
|
6
5
|
import { getConfiguredAzureEnv } from './utils/configuredAzureEnv';
|
|
7
6
|
let azureDevOpsSubscriptionProvider;
|
|
8
7
|
export function createAzureDevOpsSubscriptionProviderFactory(initializer) {
|
|
@@ -140,8 +139,8 @@ export class AzureDevOpsSubscriptionProvider {
|
|
|
140
139
|
}
|
|
141
140
|
};
|
|
142
141
|
}
|
|
143
|
-
onDidSignIn = () => { return
|
|
144
|
-
onDidSignOut = () => { return
|
|
142
|
+
onDidSignIn = () => { return { dispose() { } }; };
|
|
143
|
+
onDidSignOut = () => { return { dispose() { } }; };
|
|
145
144
|
}
|
|
146
145
|
/*
|
|
147
146
|
* @param serviceConnectionId The resource ID of the Azure DevOps federated service connection,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TokenCredential } from '@azure/core-auth';
|
|
2
2
|
import type { Environment } from '@azure/ms-rest-azure-env';
|
|
3
|
-
import * as vscode from "vscode";
|
|
4
|
-
import { AzureAuthentication } from './AzureAuthentication';
|
|
3
|
+
import type * as vscode from "vscode";
|
|
4
|
+
import type { AzureAuthentication } from './AzureAuthentication';
|
|
5
5
|
/**
|
|
6
6
|
* A type representing an Azure subscription ID, not including the tenant ID.
|
|
7
7
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { TenantIdDescription } from "@azure/arm-resources-subscriptions";
|
|
2
|
-
import * as vscode from 'vscode';
|
|
1
|
+
import type { TenantIdDescription } from "@azure/arm-resources-subscriptions";
|
|
2
|
+
import type * as vscode from 'vscode';
|
|
3
3
|
export interface AzureTenant extends TenantIdDescription {
|
|
4
4
|
account: vscode.AuthenticationSessionAccountInformation;
|
|
5
5
|
}
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import * as vscode from 'vscode';
|
|
2
2
|
import { AzureSubscription, SubscriptionId, TenantId } from './AzureSubscription';
|
|
3
|
-
import { AzureSubscriptionProvider, GetSubscriptionsFilter } from './AzureSubscriptionProvider';
|
|
4
|
-
import { AzureTenant } from './AzureTenant';
|
|
3
|
+
import type { AzureSubscriptionProvider, GetSubscriptionsFilter } from './AzureSubscriptionProvider';
|
|
4
|
+
import type { 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
|
-
export declare class VSCodeAzureSubscriptionProvider
|
|
9
|
+
export declare class VSCodeAzureSubscriptionProvider implements AzureSubscriptionProvider, vscode.Disposable {
|
|
10
10
|
private readonly logger?;
|
|
11
|
+
private listenerDisposable;
|
|
11
12
|
private readonly onDidSignInEmitter;
|
|
12
|
-
private lastSignInEventFired;
|
|
13
|
-
private suppressSignInEvents;
|
|
14
13
|
private readonly onDidSignOutEmitter;
|
|
15
|
-
private
|
|
14
|
+
private lastEventFired;
|
|
15
|
+
private suppressEvents;
|
|
16
|
+
private priorAccounts;
|
|
17
|
+
private accountsRemovedPromise;
|
|
16
18
|
constructor(logger?: vscode.LogOutputChannel | undefined);
|
|
19
|
+
dispose(): void;
|
|
20
|
+
onDidSignIn(callback: () => unknown, thisArg?: unknown, disposables?: vscode.Disposable[]): vscode.Disposable;
|
|
21
|
+
onDidSignOut(callback: () => unknown, thisArg?: unknown, disposables?: vscode.Disposable[]): vscode.Disposable;
|
|
22
|
+
private listenIfNeeded;
|
|
23
|
+
private accountsRemoved;
|
|
17
24
|
/**
|
|
18
25
|
* Gets a list of tenants available to the user.
|
|
19
26
|
* Use {@link isSignedIn} to check if the user is signed in to a particular tenant.
|
|
@@ -62,20 +69,12 @@ export declare class VSCodeAzureSubscriptionProvider extends vscode.Disposable i
|
|
|
62
69
|
* @returns True if the user is signed in, false otherwise.
|
|
63
70
|
*/
|
|
64
71
|
signIn(tenantId?: string, account?: vscode.AuthenticationSessionAccountInformation): Promise<boolean>;
|
|
65
|
-
/**
|
|
66
|
-
* An event that is fired when the user signs in. Debounced to fire at most once every 5 seconds.
|
|
67
|
-
*/
|
|
68
|
-
readonly onDidSignIn: vscode.Event<void>;
|
|
69
72
|
/**
|
|
70
73
|
* Signs the user out
|
|
71
74
|
*
|
|
72
75
|
* @deprecated Not currently supported by VS Code auth providers
|
|
73
76
|
*/
|
|
74
77
|
signOut(): Promise<void>;
|
|
75
|
-
/**
|
|
76
|
-
* An event that is fired when the user signs out. Debounced to fire at most once every 5 seconds.
|
|
77
|
-
*/
|
|
78
|
-
readonly onDidSignOut: vscode.Event<void>;
|
|
79
78
|
/**
|
|
80
79
|
* Gets the tenant filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
|
|
81
80
|
* override the settings with a custom filter, implement a child class with `getSubscriptionFilters()`
|
|
@@ -7,42 +7,92 @@ import { getSessionFromVSCode } from './getSessionFromVSCode';
|
|
|
7
7
|
import { NotSignedInError } from './NotSignedInError';
|
|
8
8
|
import { getConfiguredAuthProviderId, getConfiguredAzureEnv } from './utils/configuredAzureEnv';
|
|
9
9
|
import { isAuthenticationWwwAuthenticateRequest } from './utils/isAuthenticationWwwAuthenticateRequest';
|
|
10
|
+
import { isGetSubscriptionsAccountFilter, isGetSubscriptionsTenantFilter } from './utils/isGetSubscriptionsFilter';
|
|
10
11
|
const EventDebounce = 5 * 1000; // 5 seconds
|
|
12
|
+
let armSubs;
|
|
11
13
|
/**
|
|
12
14
|
* A class for obtaining Azure subscription information using VSCode's built-in authentication
|
|
13
15
|
* provider.
|
|
14
16
|
*/
|
|
15
|
-
export class VSCodeAzureSubscriptionProvider
|
|
17
|
+
export class VSCodeAzureSubscriptionProvider {
|
|
16
18
|
logger;
|
|
19
|
+
listenerDisposable;
|
|
17
20
|
onDidSignInEmitter = new vscode.EventEmitter();
|
|
18
|
-
lastSignInEventFired = 0;
|
|
19
|
-
suppressSignInEvents = false;
|
|
20
21
|
onDidSignOutEmitter = new vscode.EventEmitter();
|
|
21
|
-
|
|
22
|
+
lastEventFired = 0;
|
|
23
|
+
suppressEvents = false;
|
|
24
|
+
priorAccounts;
|
|
25
|
+
accountsRemovedPromise;
|
|
22
26
|
// So that customers can easily share logs, try to only log PII using trace level
|
|
23
27
|
constructor(logger) {
|
|
24
|
-
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
void this.accountsRemoved(); // Initialize priorAccounts
|
|
30
|
+
}
|
|
31
|
+
dispose() {
|
|
32
|
+
this.listenerDisposable?.dispose();
|
|
33
|
+
this.onDidSignInEmitter.dispose();
|
|
34
|
+
this.onDidSignOutEmitter.dispose();
|
|
35
|
+
}
|
|
36
|
+
onDidSignIn(callback, thisArg, disposables) {
|
|
37
|
+
this.listenIfNeeded();
|
|
38
|
+
return this.onDidSignInEmitter.event(callback, thisArg, disposables);
|
|
39
|
+
}
|
|
40
|
+
onDidSignOut(callback, thisArg, disposables) {
|
|
41
|
+
this.listenIfNeeded();
|
|
42
|
+
return this.onDidSignOutEmitter.event(callback, thisArg, disposables);
|
|
43
|
+
}
|
|
44
|
+
listenIfNeeded() {
|
|
45
|
+
if (this.listenerDisposable) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.listenerDisposable = vscode.authentication.onDidChangeSessions(async (e) => {
|
|
25
49
|
// Ignore any sign in that isn't for the configured auth provider
|
|
26
50
|
if (e.provider.id !== getConfiguredAuthProviderId()) {
|
|
27
51
|
return;
|
|
28
52
|
}
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
if (this.suppressEvents || Date.now() < this.lastEventFired + EventDebounce) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.lastEventFired = Date.now();
|
|
57
|
+
if (!await this.accountsRemoved()) {
|
|
58
|
+
this.logger?.debug('auth: Firing onDidSignIn event');
|
|
59
|
+
this.onDidSignInEmitter.fire();
|
|
34
60
|
}
|
|
35
|
-
else
|
|
36
|
-
this.
|
|
61
|
+
else {
|
|
62
|
+
this.logger?.debug('auth: Firing onDidSignOut event');
|
|
37
63
|
this.onDidSignOutEmitter.fire();
|
|
38
64
|
}
|
|
39
65
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
}
|
|
67
|
+
async accountsRemoved() {
|
|
68
|
+
// If there's already an ongoing accountsRemoved operation, return its result
|
|
69
|
+
if (this.accountsRemovedPromise) {
|
|
70
|
+
return this.accountsRemovedPromise;
|
|
71
|
+
}
|
|
72
|
+
// Create a new promise for this operation
|
|
73
|
+
this.accountsRemovedPromise = (async () => {
|
|
74
|
+
try {
|
|
75
|
+
this.suppressEvents = true;
|
|
76
|
+
const currentAccounts = Array.from(await vscode.authentication.getAccounts(getConfiguredAuthProviderId()));
|
|
77
|
+
const priorAccountCount = this.priorAccounts?.length ?? 0;
|
|
78
|
+
this.priorAccounts = currentAccounts;
|
|
79
|
+
// The only way a sign out happens is if an account is removed entirely from the list of accounts
|
|
80
|
+
if (currentAccounts.length === 0 || currentAccounts.length < priorAccountCount) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
this.suppressEvents = false;
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
try {
|
|
90
|
+
return await this.accountsRemovedPromise;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
// Clear the promise when done so future calls can proceed
|
|
94
|
+
this.accountsRemovedPromise = undefined;
|
|
95
|
+
}
|
|
46
96
|
}
|
|
47
97
|
/**
|
|
48
98
|
* Gets a list of tenants available to the user.
|
|
@@ -88,24 +138,28 @@ export class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
88
138
|
async getSubscriptions(filter = true) {
|
|
89
139
|
this.logger?.debug('auth: Loading subscriptions...');
|
|
90
140
|
const startTime = Date.now();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
141
|
+
let tenantIdsToFilterBy;
|
|
142
|
+
if (isGetSubscriptionsTenantFilter(filter)) {
|
|
143
|
+
// Only filter by the tenant ID option if it is provided
|
|
144
|
+
tenantIdsToFilterBy = [filter.tenantId];
|
|
145
|
+
}
|
|
146
|
+
else if (filter === true) {
|
|
95
147
|
// Only filter by the configured filter if `filter` is true AND there are tenants in the configured filter
|
|
96
|
-
|
|
97
|
-
|
|
148
|
+
const configuredTenantFilter = await this.getTenantFilters();
|
|
149
|
+
if (configuredTenantFilter.length > 0) {
|
|
150
|
+
tenantIdsToFilterBy = configuredTenantFilter;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
98
153
|
const allSubscriptions = [];
|
|
99
154
|
let accountCount; // only used for logging
|
|
100
155
|
try {
|
|
101
|
-
this.
|
|
156
|
+
this.suppressEvents = true;
|
|
102
157
|
// Get the list of tenants from each account (filtered or all)
|
|
103
|
-
const accounts =
|
|
158
|
+
const accounts = isGetSubscriptionsAccountFilter(filter) ? [filter.account] : await vscode.authentication.getAccounts(getConfiguredAuthProviderId());
|
|
104
159
|
accountCount = accounts.length;
|
|
105
160
|
for (const account of accounts) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const tenantId = tenant.tenantId;
|
|
161
|
+
const tenantIds = isGetSubscriptionsTenantFilter(filter) ? [filter.tenantId] : (await this.getTenants(account)).map(t => t.tenantId);
|
|
162
|
+
for (const tenantId of tenantIds) {
|
|
109
163
|
if (tenantIdsToFilterBy?.includes(tenantId) === false) {
|
|
110
164
|
continue;
|
|
111
165
|
}
|
|
@@ -117,12 +171,13 @@ export class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
117
171
|
}
|
|
118
172
|
}
|
|
119
173
|
finally {
|
|
120
|
-
this.
|
|
174
|
+
this.suppressEvents = false;
|
|
121
175
|
}
|
|
122
176
|
// It's possible that by listing subscriptions in all tenants and the "home" tenant there could be duplicate subscriptions
|
|
123
177
|
// Thus, we remove duplicate subscriptions. However, if multiple accounts have the same subscription, we keep them.
|
|
178
|
+
// There are also cases where the same subscription could appear in different tenants under the same account so we also need to keep those
|
|
124
179
|
const subscriptionMap = new Map();
|
|
125
|
-
allSubscriptions.forEach(sub => subscriptionMap.set(`${sub.account.id}/${sub.subscriptionId}`, sub));
|
|
180
|
+
allSubscriptions.forEach(sub => subscriptionMap.set(`${sub.account.id}/${sub.tenantId}/${sub.subscriptionId}`, sub));
|
|
126
181
|
const uniqueSubscriptions = Array.from(subscriptionMap.values());
|
|
127
182
|
const endTime = Date.now();
|
|
128
183
|
this.logger?.debug(`auth: Got ${uniqueSubscriptions.length} subscriptions from ${accountCount} accounts in ${endTime - startTime}ms`);
|
|
@@ -178,18 +233,20 @@ export class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
178
233
|
*/
|
|
179
234
|
async signIn(tenantId, account) {
|
|
180
235
|
this.logger?.debug(`auth: Signing in (account="${account?.label ?? 'none'}") (tenantId="${tenantId ?? 'none'}")`);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
236
|
+
try {
|
|
237
|
+
this.suppressEvents = true;
|
|
238
|
+
const session = await getSessionFromVSCode([], tenantId, {
|
|
239
|
+
createIfNone: true,
|
|
240
|
+
// If no account is provided, then clear the session preference which tells VS Code to show the account picker
|
|
241
|
+
clearSessionPreference: !account,
|
|
242
|
+
account,
|
|
243
|
+
});
|
|
244
|
+
return !!session;
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
this.suppressEvents = false;
|
|
248
|
+
}
|
|
188
249
|
}
|
|
189
|
-
/**
|
|
190
|
-
* An event that is fired when the user signs in. Debounced to fire at most once every 5 seconds.
|
|
191
|
-
*/
|
|
192
|
-
onDidSignIn = this.onDidSignInEmitter.event;
|
|
193
250
|
/**
|
|
194
251
|
* Signs the user out
|
|
195
252
|
*
|
|
@@ -198,10 +255,6 @@ export class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
198
255
|
signOut() {
|
|
199
256
|
throw new Error(vscode.l10n.t('Signing out programmatically is not supported. You must sign out by selecting the account in the Accounts menu and choosing Sign Out.'));
|
|
200
257
|
}
|
|
201
|
-
/**
|
|
202
|
-
* An event that is fired when the user signs out. Debounced to fire at most once every 5 seconds.
|
|
203
|
-
*/
|
|
204
|
-
onDidSignOut = this.onDidSignOutEmitter.event;
|
|
205
258
|
/**
|
|
206
259
|
* Gets the tenant filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
|
|
207
260
|
* override the settings with a custom filter, implement a child class with `getSubscriptionFilters()`
|
|
@@ -272,7 +325,7 @@ export class VSCodeAzureSubscriptionProvider extends vscode.Disposable {
|
|
|
272
325
|
* @returns A client, the credential used by the client, and the authentication function
|
|
273
326
|
*/
|
|
274
327
|
async getSubscriptionClient(account, tenantId, scopes) {
|
|
275
|
-
|
|
328
|
+
armSubs ||= await import('@azure/arm-resources-subscriptions');
|
|
276
329
|
const session = await getSessionFromVSCode(scopes, tenantId, { createIfNone: false, silent: true, account });
|
|
277
330
|
if (!session) {
|
|
278
331
|
throw new NotSignedInError();
|