@microsoft/vscode-azext-azureauth 4.2.2 → 5.1.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 +10 -0
- package/dist/cjs/src/AzureDevOpsSubscriptionProvider.js +215 -0
- package/dist/cjs/src/NotSignedInError.js +63 -0
- package/dist/cjs/src/VSCodeAzureSubscriptionProvider.js +385 -0
- package/dist/cjs/src/getSessionFromVSCode.js +108 -0
- package/{out → dist/cjs}/src/index.js +2 -1
- package/dist/cjs/src/signInToTenant.js +79 -0
- package/dist/cjs/src/utils/configuredAzureEnv.js +128 -0
- package/dist/cjs/src/utils/getUnauthenticatedTenants.js +23 -0
- package/dist/cjs/src/utils/isGetSubscriptionsFilter.js +27 -0
- package/{out → dist/esm}/src/AzureAuthentication.d.ts +2 -2
- package/dist/esm/src/AzureAuthentication.js +6 -0
- package/{out → dist/esm}/src/AzureDevOpsSubscriptionProvider.d.ts +6 -6
- package/dist/esm/src/AzureDevOpsSubscriptionProvider.js +210 -0
- package/{out → dist/esm}/src/AzureSubscription.d.ts +2 -2
- package/dist/esm/src/AzureSubscription.js +6 -0
- package/dist/esm/src/AzureSubscriptionProvider.js +6 -0
- package/{out → dist/esm}/src/AzureTenant.d.ts +2 -2
- package/dist/esm/src/AzureTenant.js +6 -0
- package/{out → dist/esm}/src/NotSignedInError.js +4 -9
- package/{out → dist/esm}/src/VSCodeAzureSubscriptionProvider.d.ts +8 -13
- package/dist/esm/src/VSCodeAzureSubscriptionProvider.js +348 -0
- package/{out → dist/esm}/src/getSessionFromVSCode.d.ts +3 -3
- package/dist/esm/src/getSessionFromVSCode.js +72 -0
- package/{out → dist/esm}/src/index.d.ts +2 -1
- package/dist/esm/src/index.js +16 -0
- package/dist/esm/src/signInToTenant.js +43 -0
- package/dist/esm/src/utils/configuredAzureEnv.js +90 -0
- package/dist/esm/src/utils/getUnauthenticatedTenants.d.ts +9 -0
- package/dist/esm/src/utils/getUnauthenticatedTenants.js +20 -0
- package/dist/esm/src/utils/isAuthenticationWwwAuthenticateRequest.js +12 -0
- package/dist/esm/src/utils/isGetSubscriptionsFilter.d.ts +14 -0
- package/dist/esm/src/utils/isGetSubscriptionsFilter.js +23 -0
- package/package.json +13 -12
- package/out/src/AzureDevOpsSubscriptionProvider.js +0 -252
- package/out/src/VSCodeAzureSubscriptionProvider.js +0 -384
- package/out/src/getSessionFromVSCode.js +0 -76
- package/out/src/signInToTenant.js +0 -64
- package/out/src/utils/configuredAzureEnv.js +0 -94
- package/out/src/utils/getUnauthenticatedTenants.d.ts +0 -6
- package/out/src/utils/getUnauthenticatedTenants.js +0 -52
- /package/{out → dist/cjs}/src/AzureAuthentication.js +0 -0
- /package/{out → dist/cjs}/src/AzureSubscription.js +0 -0
- /package/{out → dist/cjs}/src/AzureSubscriptionProvider.js +0 -0
- /package/{out → dist/cjs}/src/AzureTenant.js +0 -0
- /package/{out → dist/cjs}/src/utils/isAuthenticationWwwAuthenticateRequest.js +0 -0
- /package/{out → dist/esm}/src/AzureSubscriptionProvider.d.ts +0 -0
- /package/{out → dist/esm}/src/NotSignedInError.d.ts +0 -0
- /package/{out → dist/esm}/src/signInToTenant.d.ts +0 -0
- /package/{out → dist/esm}/src/utils/configuredAzureEnv.d.ts +0 -0
- /package/{out → dist/esm}/src/utils/isAuthenticationWwwAuthenticateRequest.d.ts +0 -0
|
@@ -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
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=AzureTenant.js.map
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/*---------------------------------------------------------------------------------------------
|
|
3
2
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
4
3
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
5
4
|
*--------------------------------------------------------------------------------------------*/
|
|
6
|
-
|
|
7
|
-
exports.NotSignedInError = void 0;
|
|
8
|
-
exports.isNotSignedInError = isNotSignedInError;
|
|
9
|
-
const vscode = require("vscode");
|
|
5
|
+
import * as vscode from 'vscode';
|
|
10
6
|
/**
|
|
11
7
|
* An error indicating the user is not signed in.
|
|
12
8
|
*/
|
|
13
|
-
class NotSignedInError extends Error {
|
|
9
|
+
export class NotSignedInError extends Error {
|
|
10
|
+
isNotSignedInError = true;
|
|
14
11
|
constructor() {
|
|
15
12
|
super(vscode.l10n.t('You are not signed in to an Azure account. Please sign in.'));
|
|
16
|
-
this.isNotSignedInError = true;
|
|
17
13
|
}
|
|
18
14
|
}
|
|
19
|
-
exports.NotSignedInError = NotSignedInError;
|
|
20
15
|
/**
|
|
21
16
|
* Tests if an object is a `NotSignedInError`. This should be used instead of `instanceof`.
|
|
22
17
|
*
|
|
@@ -24,7 +19,7 @@ exports.NotSignedInError = NotSignedInError;
|
|
|
24
19
|
*
|
|
25
20
|
* @returns True if the object is a NotSignedInError, false otherwise
|
|
26
21
|
*/
|
|
27
|
-
function isNotSignedInError(error) {
|
|
22
|
+
export function isNotSignedInError(error) {
|
|
28
23
|
return !!error && typeof error === 'object' && error.isNotSignedInError === true;
|
|
29
24
|
}
|
|
30
25
|
//# sourceMappingURL=NotSignedInError.js.map
|
|
@@ -1,19 +1,22 @@
|
|
|
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 readonly onDidSignInEmitter;
|
|
12
11
|
private lastSignInEventFired;
|
|
13
12
|
private suppressSignInEvents;
|
|
14
|
-
private readonly onDidSignOutEmitter;
|
|
15
13
|
private lastSignOutEventFired;
|
|
14
|
+
private priorAccounts;
|
|
16
15
|
constructor(logger?: vscode.LogOutputChannel | undefined);
|
|
16
|
+
onDidSignIn(callback: () => any, thisArg?: any, disposables?: vscode.Disposable[]): vscode.Disposable;
|
|
17
|
+
onDidSignOut(callback: () => any, thisArg?: any, disposables?: vscode.Disposable[]): vscode.Disposable;
|
|
18
|
+
private onDidChangeSessions;
|
|
19
|
+
dispose(): void;
|
|
17
20
|
/**
|
|
18
21
|
* Gets a list of tenants available to the user.
|
|
19
22
|
* Use {@link isSignedIn} to check if the user is signed in to a particular tenant.
|
|
@@ -62,20 +65,12 @@ export declare class VSCodeAzureSubscriptionProvider extends vscode.Disposable i
|
|
|
62
65
|
* @returns True if the user is signed in, false otherwise.
|
|
63
66
|
*/
|
|
64
67
|
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
68
|
/**
|
|
70
69
|
* Signs the user out
|
|
71
70
|
*
|
|
72
71
|
* @deprecated Not currently supported by VS Code auth providers
|
|
73
72
|
*/
|
|
74
73
|
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
74
|
/**
|
|
80
75
|
* Gets the tenant filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
|
|
81
76
|
* override the settings with a custom filter, implement a child class with `getSubscriptionFilters()`
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import * as vscode from 'vscode';
|
|
6
|
+
import { getSessionFromVSCode } from './getSessionFromVSCode';
|
|
7
|
+
import { NotSignedInError } from './NotSignedInError';
|
|
8
|
+
import { getConfiguredAuthProviderId, getConfiguredAzureEnv } from './utils/configuredAzureEnv';
|
|
9
|
+
import { isAuthenticationWwwAuthenticateRequest } from './utils/isAuthenticationWwwAuthenticateRequest';
|
|
10
|
+
import { isGetSubscriptionsAccountFilter, isGetSubscriptionsTenantFilter } from './utils/isGetSubscriptionsFilter';
|
|
11
|
+
const EventDebounce = 5 * 1000; // 5 seconds
|
|
12
|
+
let armSubs;
|
|
13
|
+
/**
|
|
14
|
+
* A class for obtaining Azure subscription information using VSCode's built-in authentication
|
|
15
|
+
* provider.
|
|
16
|
+
*/
|
|
17
|
+
export class VSCodeAzureSubscriptionProvider {
|
|
18
|
+
logger;
|
|
19
|
+
lastSignInEventFired = 0;
|
|
20
|
+
suppressSignInEvents = false;
|
|
21
|
+
lastSignOutEventFired = 0;
|
|
22
|
+
priorAccounts;
|
|
23
|
+
// So that customers can easily share logs, try to only log PII using trace level
|
|
24
|
+
constructor(logger) {
|
|
25
|
+
this.logger = logger;
|
|
26
|
+
// Load accounts initially, then onDidChangeSessions can compare against them
|
|
27
|
+
void vscode.authentication.getAccounts(getConfiguredAuthProviderId()).then(accounts => {
|
|
28
|
+
this.priorAccounts = Array.from(accounts); // The Array.from is to get rid of the readonly marker on the array returned by the API
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
onDidSignIn(callback, thisArg, disposables) {
|
|
33
|
+
return this.onDidChangeSessions(true, callback, thisArg, disposables);
|
|
34
|
+
}
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
onDidSignOut(callback, thisArg, disposables) {
|
|
37
|
+
return this.onDidChangeSessions(false, callback, thisArg, disposables);
|
|
38
|
+
}
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
onDidChangeSessions(signIn, callback, thisArg, disposables) {
|
|
41
|
+
const isASignInEvent = async () => {
|
|
42
|
+
const currentAccounts = Array.from(await vscode.authentication.getAccounts(getConfiguredAuthProviderId())); // The Array.from is to get rid of the readonly marker on the array returned by the API
|
|
43
|
+
const priorAccountCount = this.priorAccounts?.length ?? 0;
|
|
44
|
+
this.priorAccounts = currentAccounts;
|
|
45
|
+
// The only way a sign out happens is if an account is removed entirely from the list of accounts
|
|
46
|
+
if (currentAccounts.length === 0 || currentAccounts.length < priorAccountCount) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
const wrappedCallback = () => {
|
|
52
|
+
const immediate = setImmediate(() => {
|
|
53
|
+
clearImmediate(immediate);
|
|
54
|
+
void callback.call(thisArg);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const disposable = vscode.authentication.onDidChangeSessions(async (e) => {
|
|
58
|
+
// Ignore any sign in that isn't for the configured auth provider
|
|
59
|
+
if (e.provider.id !== getConfiguredAuthProviderId()) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (signIn) {
|
|
63
|
+
if (this.suppressSignInEvents || Date.now() < this.lastSignInEventFired + EventDebounce) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
else if (await isASignInEvent()) {
|
|
67
|
+
this.lastSignInEventFired = Date.now();
|
|
68
|
+
wrappedCallback();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
if (Date.now() < this.lastSignOutEventFired + EventDebounce) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
else if (!await isASignInEvent()) {
|
|
76
|
+
this.lastSignOutEventFired = Date.now();
|
|
77
|
+
wrappedCallback();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
disposables?.push(disposable);
|
|
82
|
+
return disposable;
|
|
83
|
+
}
|
|
84
|
+
dispose() {
|
|
85
|
+
// No-op, this class no longer has disposables
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Gets a list of tenants available to the user.
|
|
89
|
+
* Use {@link isSignedIn} to check if the user is signed in to a particular tenant.
|
|
90
|
+
*
|
|
91
|
+
* @param account (Optional) A specific account to get tenants for. If not provided, all accounts will be used.
|
|
92
|
+
*
|
|
93
|
+
* @returns A list of tenants.
|
|
94
|
+
*/
|
|
95
|
+
async getTenants(account) {
|
|
96
|
+
const startTimeMs = Date.now();
|
|
97
|
+
const results = [];
|
|
98
|
+
for await (account of account ? [account] : await vscode.authentication.getAccounts(getConfiguredAuthProviderId())) {
|
|
99
|
+
// Added check. Without this the getSubscriptionClient function throws the NotSignedInError
|
|
100
|
+
if (await this.isSignedIn(undefined, account)) {
|
|
101
|
+
const { client } = await this.getSubscriptionClient(account, undefined, undefined);
|
|
102
|
+
for await (const tenant of client.tenants.list()) {
|
|
103
|
+
results.push({ ...tenant, account });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const endTimeMs = Date.now();
|
|
108
|
+
this.logger?.debug(`auth: Got ${results.length} tenants for account "${account?.label}" in ${endTimeMs - startTimeMs}ms`);
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Gets a list of Azure subscriptions available to the user.
|
|
113
|
+
*
|
|
114
|
+
* @param filter - Whether to filter the list returned. When:
|
|
115
|
+
* - `true`: according to the list returned by `getTenantFilters()` and `getSubscriptionFilters()`.
|
|
116
|
+
* - `false`: return all subscriptions.
|
|
117
|
+
* - `GetSubscriptionsFilter`: according to the values in the filter.
|
|
118
|
+
*
|
|
119
|
+
* Optional, default true.
|
|
120
|
+
*
|
|
121
|
+
* @returns A list of Azure subscriptions. The list is sorted by subscription name.
|
|
122
|
+
* The list can contain duplicate subscriptions if they come from different accounts.
|
|
123
|
+
*
|
|
124
|
+
* @throws A {@link NotSignedInError} If the user is not signed in to Azure.
|
|
125
|
+
* Use {@link isSignedIn} and/or {@link signIn} before this method to ensure
|
|
126
|
+
* the user is signed in.
|
|
127
|
+
*/
|
|
128
|
+
async getSubscriptions(filter = true) {
|
|
129
|
+
this.logger?.debug('auth: Loading subscriptions...');
|
|
130
|
+
const startTime = Date.now();
|
|
131
|
+
let tenantIdsToFilterBy;
|
|
132
|
+
if (isGetSubscriptionsTenantFilter(filter)) {
|
|
133
|
+
// Only filter by the tenant ID option if it is provided
|
|
134
|
+
tenantIdsToFilterBy = [filter.tenantId];
|
|
135
|
+
}
|
|
136
|
+
else if (filter === true) {
|
|
137
|
+
// Only filter by the configured filter if `filter` is true AND there are tenants in the configured filter
|
|
138
|
+
const configuredTenantFilter = await this.getTenantFilters();
|
|
139
|
+
if (configuredTenantFilter.length > 0) {
|
|
140
|
+
tenantIdsToFilterBy = configuredTenantFilter;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const allSubscriptions = [];
|
|
144
|
+
let accountCount; // only used for logging
|
|
145
|
+
try {
|
|
146
|
+
this.suppressSignInEvents = true;
|
|
147
|
+
// Get the list of tenants from each account (filtered or all)
|
|
148
|
+
const accounts = isGetSubscriptionsAccountFilter(filter) ? [filter.account] : await vscode.authentication.getAccounts(getConfiguredAuthProviderId());
|
|
149
|
+
accountCount = accounts.length;
|
|
150
|
+
for (const account of accounts) {
|
|
151
|
+
const tenantIds = isGetSubscriptionsTenantFilter(filter) ? [filter.tenantId] : (await this.getTenants(account)).map(t => t.tenantId);
|
|
152
|
+
for (const tenantId of tenantIds) {
|
|
153
|
+
if (tenantIdsToFilterBy?.includes(tenantId) === false) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
// For each tenant, get the list of subscriptions
|
|
157
|
+
allSubscriptions.push(...await this.getSubscriptionsForTenant(account, tenantId));
|
|
158
|
+
}
|
|
159
|
+
// list subscriptions for the home tenant
|
|
160
|
+
allSubscriptions.push(...await this.getSubscriptionsForTenant(account));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
this.suppressSignInEvents = false;
|
|
165
|
+
}
|
|
166
|
+
// It's possible that by listing subscriptions in all tenants and the "home" tenant there could be duplicate subscriptions
|
|
167
|
+
// Thus, we remove duplicate subscriptions. However, if multiple accounts have the same subscription, we keep them.
|
|
168
|
+
// There are also cases where the same subscription could appear in different tenants under the same account so we also need to keep those
|
|
169
|
+
const subscriptionMap = new Map();
|
|
170
|
+
allSubscriptions.forEach(sub => subscriptionMap.set(`${sub.account.id}/${sub.tenantId}/${sub.subscriptionId}`, sub));
|
|
171
|
+
const uniqueSubscriptions = Array.from(subscriptionMap.values());
|
|
172
|
+
const endTime = Date.now();
|
|
173
|
+
this.logger?.debug(`auth: Got ${uniqueSubscriptions.length} subscriptions from ${accountCount} accounts in ${endTime - startTime}ms`);
|
|
174
|
+
const sortSubscriptions = (subscriptions) => subscriptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
175
|
+
const subscriptionIds = await this.getSubscriptionFilters();
|
|
176
|
+
if (filter === true && !!subscriptionIds.length) { // If the list is empty it is treated as "no filter"
|
|
177
|
+
return sortSubscriptions(uniqueSubscriptions.filter(sub => subscriptionIds.includes(sub.subscriptionId)));
|
|
178
|
+
}
|
|
179
|
+
return sortSubscriptions(uniqueSubscriptions);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Checks to see if a user is signed in.
|
|
183
|
+
*
|
|
184
|
+
* @param tenantId (Optional) Provide to check if a user is signed in to a specific tenant.
|
|
185
|
+
* @param account (Optional) Provide to check if a user is signed in to a specific account.
|
|
186
|
+
*
|
|
187
|
+
* @returns True if the user is signed in, false otherwise.
|
|
188
|
+
*
|
|
189
|
+
* If no tenant or account is provided, then
|
|
190
|
+
* checks all accounts for a session.
|
|
191
|
+
*/
|
|
192
|
+
async isSignedIn(tenantId, account) {
|
|
193
|
+
async function silentlyCheckForSession(tenantId, account) {
|
|
194
|
+
return !!await getSessionFromVSCode([], tenantId, { createIfNone: false, silent: true, account });
|
|
195
|
+
}
|
|
196
|
+
const innerIsSignedIn = async () => {
|
|
197
|
+
// If no tenant or account is provided, then check all accounts for a session
|
|
198
|
+
if (!account && !tenantId) {
|
|
199
|
+
const accounts = await vscode.authentication.getAccounts(getConfiguredAuthProviderId());
|
|
200
|
+
if (accounts.length === 0) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
for (const account of accounts) {
|
|
204
|
+
if (await silentlyCheckForSession(tenantId, account)) {
|
|
205
|
+
// If any account has a session, then return true because the user is signed in
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return silentlyCheckForSession(tenantId, account);
|
|
211
|
+
};
|
|
212
|
+
const result = await innerIsSignedIn();
|
|
213
|
+
this.logger?.trace(`auth: isSignedIn returned ${result} (account="${account?.label ?? 'none'}") (tenantId="${tenantId ?? 'none'}")`);
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Asks the user to sign in or pick an account to use.
|
|
218
|
+
*
|
|
219
|
+
* @param tenantId (Optional) Provide to sign in to a specific tenant.
|
|
220
|
+
* @param account (Optional) Provide to sign in to a specific account.
|
|
221
|
+
*
|
|
222
|
+
* @returns True if the user is signed in, false otherwise.
|
|
223
|
+
*/
|
|
224
|
+
async signIn(tenantId, account) {
|
|
225
|
+
this.logger?.debug(`auth: Signing in (account="${account?.label ?? 'none'}") (tenantId="${tenantId ?? 'none'}")`);
|
|
226
|
+
try {
|
|
227
|
+
this.suppressSignInEvents = true;
|
|
228
|
+
const session = await getSessionFromVSCode([], tenantId, {
|
|
229
|
+
createIfNone: true,
|
|
230
|
+
// If no account is provided, then clear the session preference which tells VS Code to show the account picker
|
|
231
|
+
clearSessionPreference: !account,
|
|
232
|
+
account,
|
|
233
|
+
});
|
|
234
|
+
return !!session;
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
this.suppressSignInEvents = false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Signs the user out
|
|
242
|
+
*
|
|
243
|
+
* @deprecated Not currently supported by VS Code auth providers
|
|
244
|
+
*/
|
|
245
|
+
signOut() {
|
|
246
|
+
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.'));
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Gets the tenant filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
|
|
250
|
+
* override the settings with a custom filter, implement a child class with `getSubscriptionFilters()`
|
|
251
|
+
* and/or `getTenantFilters()` overridden.
|
|
252
|
+
*
|
|
253
|
+
* If no values are returned by `getTenantFilters()`, then all tenants will be scanned for subscriptions.
|
|
254
|
+
*
|
|
255
|
+
* @returns A list of tenant IDs that are configured in `azureResourceGroups.selectedSubscriptions`.
|
|
256
|
+
*/
|
|
257
|
+
async getTenantFilters() {
|
|
258
|
+
const config = vscode.workspace.getConfiguration('azureResourceGroups');
|
|
259
|
+
const fullSubscriptionIds = config.get('selectedSubscriptions', []);
|
|
260
|
+
return fullSubscriptionIds.map(id => id.split('/')[0]);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Gets the subscription filters that are configured in `azureResourceGroups.selectedSubscriptions`. To
|
|
264
|
+
* override the settings with a custom filter, implement a child class with `getSubscriptionFilters()`
|
|
265
|
+
* and/or `getTenantFilters()` overridden.
|
|
266
|
+
*
|
|
267
|
+
* If no values are returned by `getSubscriptionFilters()`, then all subscriptions will be returned.
|
|
268
|
+
*
|
|
269
|
+
* @returns A list of subscription IDs that are configured in `azureResourceGroups.selectedSubscriptions`.
|
|
270
|
+
*/
|
|
271
|
+
async getSubscriptionFilters() {
|
|
272
|
+
const config = vscode.workspace.getConfiguration('azureResourceGroups');
|
|
273
|
+
const fullSubscriptionIds = config.get('selectedSubscriptions', []);
|
|
274
|
+
return fullSubscriptionIds.map(id => id.split('/')[1]);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Gets the subscriptions for a given tenant.
|
|
278
|
+
*
|
|
279
|
+
* @param tenantId The tenant ID to get subscriptions for.
|
|
280
|
+
* @param account The account to get the subscriptions for.
|
|
281
|
+
*
|
|
282
|
+
* @returns The list of subscriptions for the tenant.
|
|
283
|
+
*/
|
|
284
|
+
async getSubscriptionsForTenant(account, tenantId) {
|
|
285
|
+
// If the user is not signed in to this tenant or account, then return an empty list
|
|
286
|
+
// This is to prevent the NotSignedInError from being thrown in getSubscriptionClient
|
|
287
|
+
if (!await this.isSignedIn(tenantId, account)) {
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
const { client, credential, authentication } = await this.getSubscriptionClient(account, tenantId, undefined);
|
|
291
|
+
const environment = getConfiguredAzureEnv();
|
|
292
|
+
const subscriptions = [];
|
|
293
|
+
for await (const subscription of client.subscriptions.list()) {
|
|
294
|
+
subscriptions.push({
|
|
295
|
+
authentication: authentication,
|
|
296
|
+
environment: environment,
|
|
297
|
+
credential: credential,
|
|
298
|
+
isCustomCloud: environment.isCustomCloud,
|
|
299
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
300
|
+
name: subscription.displayName,
|
|
301
|
+
subscriptionId: subscription.subscriptionId,
|
|
302
|
+
tenantId: tenantId ?? subscription.tenantId,
|
|
303
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
304
|
+
account: account
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return subscriptions;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Gets a fully-configured subscription client for a given tenant ID
|
|
311
|
+
*
|
|
312
|
+
* @param tenantId (Optional) The tenant ID to get a client for
|
|
313
|
+
* @param account The account that you would like to get the session for
|
|
314
|
+
*
|
|
315
|
+
* @returns A client, the credential used by the client, and the authentication function
|
|
316
|
+
*/
|
|
317
|
+
async getSubscriptionClient(account, tenantId, scopes) {
|
|
318
|
+
armSubs ||= await import('@azure/arm-resources-subscriptions');
|
|
319
|
+
const session = await getSessionFromVSCode(scopes, tenantId, { createIfNone: false, silent: true, account });
|
|
320
|
+
if (!session) {
|
|
321
|
+
throw new NotSignedInError();
|
|
322
|
+
}
|
|
323
|
+
const credential = {
|
|
324
|
+
getToken: async () => {
|
|
325
|
+
return {
|
|
326
|
+
token: session.accessToken,
|
|
327
|
+
expiresOnTimestamp: 0
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const configuredAzureEnv = getConfiguredAzureEnv();
|
|
332
|
+
const endpoint = configuredAzureEnv.resourceManagerEndpointUrl;
|
|
333
|
+
return {
|
|
334
|
+
client: new armSubs.SubscriptionClient(credential, { endpoint }),
|
|
335
|
+
credential: credential,
|
|
336
|
+
authentication: {
|
|
337
|
+
getSession: () => session,
|
|
338
|
+
getSessionWithScopes: (scopeListOrRequest) => {
|
|
339
|
+
// in order to handle a challenge, we must enable createIfNone so
|
|
340
|
+
// that we can prompt the user to step-up their session with MFA
|
|
341
|
+
// otherwise, never prompt the user
|
|
342
|
+
return getSessionFromVSCode(scopeListOrRequest, tenantId, { ...(isAuthenticationWwwAuthenticateRequest(scopeListOrRequest) ? { createIfNone: true } : { silent: true }), account });
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
//# sourceMappingURL=VSCodeAzureSubscriptionProvider.js.map
|
|
@@ -4,10 +4,10 @@ import * as vscode from "vscode";
|
|
|
4
4
|
* * Passing the configured auth provider id
|
|
5
5
|
* * Getting the list of scopes, adding the tenant id to the scope list if needed
|
|
6
6
|
*
|
|
7
|
-
* @param
|
|
8
|
-
* Use `vscode.AuthenticationWwwAuthenticateRequest` if you need to pass in a challenge (WWW-Authenticate header). Note: Use of `vscode.AuthenticationWwwAuthenticateRequest` requires VS Code 1.
|
|
7
|
+
* @param scopeOrListOrRequest - top-level resource scopes (e.g. http://management.azure.com, http://storage.azure.com) or .default scopes. All resources/scopes will be normalized to the `.default` scope for each resource.
|
|
8
|
+
* Use `vscode.AuthenticationWwwAuthenticateRequest` if you need to pass in a challenge (WWW-Authenticate header). Note: Use of `vscode.AuthenticationWwwAuthenticateRequest` requires VS Code 1.105.0 or newer.
|
|
9
9
|
* @param tenantId - (Optional) The tenant ID, will be added to the scopes
|
|
10
10
|
* @param options - see {@link vscode.AuthenticationGetSessionOptions}
|
|
11
11
|
* @returns An authentication session if available, or undefined if there are no sessions
|
|
12
12
|
*/
|
|
13
|
-
export declare function getSessionFromVSCode(
|
|
13
|
+
export declare function getSessionFromVSCode(scopeOrListOrRequest?: string | string[] | vscode.AuthenticationWwwAuthenticateRequest, tenantId?: string, options?: vscode.AuthenticationGetSessionOptions): Promise<vscode.AuthenticationSession | undefined>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import * as vscode from "vscode";
|
|
6
|
+
import { getConfiguredAuthProviderId, getConfiguredAzureEnv } from "./utils/configuredAzureEnv";
|
|
7
|
+
import { isAuthenticationWwwAuthenticateRequest } from "./utils/isAuthenticationWwwAuthenticateRequest";
|
|
8
|
+
function ensureEndingSlash(value) {
|
|
9
|
+
return value.endsWith('/') ? value : `${value}/`;
|
|
10
|
+
}
|
|
11
|
+
function getResourceScopes(scopes) {
|
|
12
|
+
if (scopes === undefined || scopes === "" || scopes.length === 0) {
|
|
13
|
+
scopes = ensureEndingSlash(getConfiguredAzureEnv().managementEndpointUrl);
|
|
14
|
+
}
|
|
15
|
+
const arrScopes = (Array.isArray(scopes) ? scopes : [scopes])
|
|
16
|
+
.map((scope) => {
|
|
17
|
+
if (scope.endsWith('.default')) {
|
|
18
|
+
return scope;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return `${scope}.default`;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return Array.from(new Set(arrScopes));
|
|
25
|
+
}
|
|
26
|
+
function addTenantIdScope(scopes, tenantId) {
|
|
27
|
+
const scopeSet = new Set(scopes);
|
|
28
|
+
scopeSet.add(`VSCODE_TENANT:${tenantId}`);
|
|
29
|
+
return Array.from(scopeSet);
|
|
30
|
+
}
|
|
31
|
+
function getModifiedScopes(scopes, tenantId) {
|
|
32
|
+
let scopeArr = getResourceScopes(scopes);
|
|
33
|
+
if (tenantId) {
|
|
34
|
+
scopeArr = addTenantIdScope(scopeArr, tenantId);
|
|
35
|
+
}
|
|
36
|
+
return scopeArr;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Deconstructs and rebuilds the scopes arg in order to use the above utils to modify the scopes array.
|
|
40
|
+
* And then returns the proper type to pass directly to vscode.authentication.getSession
|
|
41
|
+
*/
|
|
42
|
+
function formScopesArg(scopeOrListOrRequest, tenantId) {
|
|
43
|
+
const isChallenge = isAuthenticationWwwAuthenticateRequest(scopeOrListOrRequest);
|
|
44
|
+
let initialScopeList = undefined;
|
|
45
|
+
if (typeof scopeOrListOrRequest === 'string' && !!scopeOrListOrRequest) {
|
|
46
|
+
initialScopeList = [scopeOrListOrRequest];
|
|
47
|
+
}
|
|
48
|
+
else if (Array.isArray(scopeOrListOrRequest)) {
|
|
49
|
+
initialScopeList = scopeOrListOrRequest;
|
|
50
|
+
}
|
|
51
|
+
else if (isChallenge) {
|
|
52
|
+
// `scopeOrListOrRequest.fallbackScopes` being readonly forces us to rebuild the array
|
|
53
|
+
initialScopeList = scopeOrListOrRequest.fallbackScopes ? Array.from(scopeOrListOrRequest.fallbackScopes) : undefined;
|
|
54
|
+
}
|
|
55
|
+
const modifiedScopeList = getModifiedScopes(initialScopeList, tenantId);
|
|
56
|
+
return isChallenge ? { fallbackScopes: modifiedScopeList, wwwAuthenticate: scopeOrListOrRequest.wwwAuthenticate } : modifiedScopeList;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wraps {@link vscode.authentication.getSession} and handles:
|
|
60
|
+
* * Passing the configured auth provider id
|
|
61
|
+
* * Getting the list of scopes, adding the tenant id to the scope list if needed
|
|
62
|
+
*
|
|
63
|
+
* @param scopeOrListOrRequest - top-level resource scopes (e.g. http://management.azure.com, http://storage.azure.com) or .default scopes. All resources/scopes will be normalized to the `.default` scope for each resource.
|
|
64
|
+
* Use `vscode.AuthenticationWwwAuthenticateRequest` if you need to pass in a challenge (WWW-Authenticate header). Note: Use of `vscode.AuthenticationWwwAuthenticateRequest` requires VS Code 1.105.0 or newer.
|
|
65
|
+
* @param tenantId - (Optional) The tenant ID, will be added to the scopes
|
|
66
|
+
* @param options - see {@link vscode.AuthenticationGetSessionOptions}
|
|
67
|
+
* @returns An authentication session if available, or undefined if there are no sessions
|
|
68
|
+
*/
|
|
69
|
+
export async function getSessionFromVSCode(scopeOrListOrRequest, tenantId, options) {
|
|
70
|
+
return await vscode.authentication.getSession(getConfiguredAuthProviderId(), formScopesArg(scopeOrListOrRequest, tenantId), options);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=getSessionFromVSCode.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from './AzureAuthentication';
|
|
2
2
|
export * from './AzureDevOpsSubscriptionProvider';
|
|
3
|
-
export * from './AzureTenant';
|
|
4
3
|
export * from './AzureSubscription';
|
|
5
4
|
export * from './AzureSubscriptionProvider';
|
|
5
|
+
export * from './AzureTenant';
|
|
6
|
+
export * from './getSessionFromVSCode';
|
|
6
7
|
export * from './NotSignedInError';
|
|
7
8
|
export * from './signInToTenant';
|
|
8
9
|
export * from './utils/configuredAzureEnv';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
export * from './AzureAuthentication';
|
|
6
|
+
export * from './AzureDevOpsSubscriptionProvider';
|
|
7
|
+
export * from './AzureSubscription';
|
|
8
|
+
export * from './AzureSubscriptionProvider';
|
|
9
|
+
export * from './AzureTenant';
|
|
10
|
+
export * from './getSessionFromVSCode';
|
|
11
|
+
export * from './NotSignedInError';
|
|
12
|
+
export * from './signInToTenant';
|
|
13
|
+
export * from './utils/configuredAzureEnv';
|
|
14
|
+
export * from './utils/getUnauthenticatedTenants';
|
|
15
|
+
export * from './VSCodeAzureSubscriptionProvider';
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import * as vscode from "vscode";
|
|
6
|
+
import { getUnauthenticatedTenants } from "./utils/getUnauthenticatedTenants";
|
|
7
|
+
/**
|
|
8
|
+
* Prompts user to select from a list of unauthenticated tenants.
|
|
9
|
+
* Once selected, requests a new session from VS Code specifially for this tenant.
|
|
10
|
+
*/
|
|
11
|
+
export async function signInToTenant(subscriptionProvider) {
|
|
12
|
+
const tenantId = await pickTenant(subscriptionProvider);
|
|
13
|
+
if (tenantId) {
|
|
14
|
+
await subscriptionProvider.signIn(tenantId);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function pickTenant(subscriptionProvider) {
|
|
18
|
+
const pick = await vscode.window.showQuickPick(getPicks(subscriptionProvider), {
|
|
19
|
+
placeHolder: 'Select a Tenant (Directory) to Sign In To', // TODO: localize
|
|
20
|
+
matchOnDescription: true, // allow searching by tenantId
|
|
21
|
+
ignoreFocusOut: true,
|
|
22
|
+
});
|
|
23
|
+
return pick?.tenant.tenantId;
|
|
24
|
+
}
|
|
25
|
+
async function getPicks(subscriptionProvider) {
|
|
26
|
+
const unauthenticatedTenants = await getUnauthenticatedTenants(subscriptionProvider);
|
|
27
|
+
const duplicateTenants = new Set(unauthenticatedTenants
|
|
28
|
+
.filter((tenant, index, self) => index !== self.findIndex(t => t.tenantId === tenant.tenantId))
|
|
29
|
+
.map(tenant => tenant.tenantId));
|
|
30
|
+
const isDuplicate = (tenantId) => duplicateTenants.has(tenantId);
|
|
31
|
+
const picks = unauthenticatedTenants
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
33
|
+
.sort((a, b) => (a.displayName).localeCompare(b.displayName))
|
|
34
|
+
.map(tenant => ({
|
|
35
|
+
label: tenant.displayName ?? '',
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
37
|
+
description: `${tenant.tenantId}${isDuplicate(tenant.tenantId) ? ` (${tenant.account.label})` : ''}`,
|
|
38
|
+
detail: tenant.defaultDomain ?? '',
|
|
39
|
+
tenant,
|
|
40
|
+
}));
|
|
41
|
+
return picks;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=signInToTenant.js.map
|