mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 02:48:30 -05:00
Update error for ignored tenants (#22881)
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
"azure.displayName": "Azure (Core)",
|
||||
"azure.description": "Browse and work with Azure resources",
|
||||
"azure.title": "Azure",
|
||||
|
||||
"azure.resource.config.title": "Azure Resource Configuration",
|
||||
"azure.resource.config.filter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash",
|
||||
"azure.resource.explorer.title": "Azure",
|
||||
@@ -14,12 +13,9 @@
|
||||
"azure.resource.connectsqlserver.title": "Connect",
|
||||
"azure.resource.connectsqldb.title": "Add to Servers",
|
||||
"azure.resource.view.title": "Azure (Preview)",
|
||||
"azure.tenant.config.filter.description": "The list of tenant IDs to ignore when querying azure resources. Each element is a tenant id.",
|
||||
|
||||
"azure.tenant.config.filter.description": "The list of tenant IDs which will be skipped when querying Azure resources or requesting authentication tokens.",
|
||||
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
|
||||
|
||||
"azure.openInAzurePortal.title": "Open in Azure Portal",
|
||||
|
||||
"config.azureAccountConfigurationSection": "Azure Account Configuration",
|
||||
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
||||
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
||||
@@ -33,5 +29,4 @@
|
||||
"config.piiLogging": "Should Personally Identifiable Information (PII) be logged in the Azure Accounts output channel and the output channel log file.",
|
||||
"config.loggingLevel": "[Optional] The verbosity of logging for the Azure Accounts extension.",
|
||||
"config.authenticationLibrary": "The library used for the AAD auth flow. Please restart ADS after changing this option."
|
||||
|
||||
}
|
||||
|
||||
@@ -330,12 +330,6 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The user wants to ignore this tenant.
|
||||
if (getTenantIgnoreList().includes(tenantId)) {
|
||||
Logger.info(`Tenant ${tenantId} found in the ignore list, authentication will not be attempted.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Resource endpoint must end with '/' to form a valid scope for MSAL token request.
|
||||
const endpoint = resource.endpoint.endsWith('/') ? resource.endpoint : resource.endpoint + '/';
|
||||
|
||||
@@ -509,6 +503,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
tenantCategory: tenantInfo.tenantCategory
|
||||
} as Tenant;
|
||||
});
|
||||
|
||||
Logger.verbose(`Tenants: ${tenantList}`);
|
||||
const homeTenantIndex = tenants.findIndex(tenant => tenant.tenantCategory === Constants.HomeCategory);
|
||||
// remove home tenant from list of tenants
|
||||
@@ -683,7 +678,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
|
||||
interface ConsentMessageItem extends vscode.MessageItem {
|
||||
booleanResult: boolean;
|
||||
action?: (tenantId: string) => Promise<void>;
|
||||
action?: (tenantId: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
const openItem: ConsentMessageItem = {
|
||||
@@ -697,23 +692,50 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
booleanResult: false
|
||||
};
|
||||
|
||||
const cancelAndAuthenticate: ConsentMessageItem = {
|
||||
title: localize('azurecore.consentDialog.authenticate', "Cancel and Authenticate"),
|
||||
isCloseAffordance: true,
|
||||
booleanResult: true
|
||||
};
|
||||
|
||||
const dontAskAgainItem: ConsentMessageItem = {
|
||||
title: localize('azurecore.consentDialog.ignore', "Ignore Tenant"),
|
||||
booleanResult: false,
|
||||
action: async (tenantId: string) => {
|
||||
return await confirmIgnoreTenantDialog();
|
||||
}
|
||||
};
|
||||
|
||||
const confirmIgnoreTenantItem: ConsentMessageItem = {
|
||||
title: localize('azurecore.confirmIgnoreTenantDialog.confirm', "Confirm"),
|
||||
booleanResult: false,
|
||||
action: async (tenantId: string) => {
|
||||
tenantIgnoreList.push(tenantId);
|
||||
await updateTenantIgnoreList(tenantIgnoreList);
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
const messageBody = localize('azurecore.consentDialog.body', "Your tenant '{0} ({1})' requires you to re-authenticate again to access {2} resources. Press Open to start the authentication process.", tenant.displayName, tenant.id, resource.endpoint);
|
||||
const result = await vscode.window.showInformationMessage(messageBody, { modal: true }, openItem, closeItem, dontAskAgainItem);
|
||||
const confirmIgnoreTenantDialog = async () => {
|
||||
const confirmMessage = localize('azurecore.confirmIgnoreTenantDialog.body', "Azure Data Studio will no longer trigger authentication for this tenant {0} ({1}) and resources will not be accessible. \n\nTo allow access to resources for this tenant again, you will need to remove the tenant from the exclude list in the '{2}' setting.\n\nDo you wish to proceed?", tenant.displayName, tenant.id, Constants.AzureTenantConfigFilterSetting);
|
||||
let confirmation = await vscode.window.showInformationMessage(confirmMessage, { modal: true }, cancelAndAuthenticate, confirmIgnoreTenantItem);
|
||||
|
||||
if (result?.action) {
|
||||
await result.action(tenant.id);
|
||||
if (confirmation?.action) {
|
||||
await confirmation.action(tenant.id);
|
||||
}
|
||||
|
||||
return confirmation?.booleanResult || false;
|
||||
}
|
||||
|
||||
return result?.booleanResult || false;
|
||||
const messageBody = localize('azurecore.consentDialog.body', "Your tenant {0} ({1}) requires you to re-authenticate again to access {2} resources. Press Open to start the authentication process.", tenant.displayName, tenant.id, resource.endpoint);
|
||||
const result = await vscode.window.showInformationMessage(messageBody, { modal: true }, openItem, closeItem, dontAskAgainItem);
|
||||
|
||||
let response = false;
|
||||
if (result?.action) {
|
||||
response = await result.action(tenant.id);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import { AzureDeviceCode } from './auths/azureDeviceCode';
|
||||
import { filterAccounts } from '../azureResource/utils';
|
||||
import * as Constants from '../constants';
|
||||
import { MsalCachePluginProvider } from './utils/msalCachePlugin';
|
||||
import { getTenantIgnoreList } from '../utils';
|
||||
import { TenantIgnoredError } from '../utils/TenantIgnoredError';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -133,7 +135,6 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
|
||||
return accounts;
|
||||
}
|
||||
|
||||
|
||||
getSecurityToken(account: AzureAccount, resource: azdata.AzureResource): Thenable<MultiTenantTokenResponse | undefined> {
|
||||
return this._getSecurityToken(account, resource);
|
||||
}
|
||||
@@ -159,29 +160,35 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
|
||||
Logger.info(`Could not fetch access token from cache: ${e}, fetching new access token instead.`);
|
||||
}
|
||||
tenantId = tenantId || account.properties.owningTenant.id;
|
||||
let authResult = await azureAuth.getTokenMsal(account.key.accountId, resource, tenantId);
|
||||
if (this.isAuthenticationResult(authResult) && authResult.account && authResult.account.idTokenClaims) {
|
||||
const token: Token = {
|
||||
key: authResult.account.homeAccountId,
|
||||
token: authResult.accessToken,
|
||||
tokenType: authResult.tokenType,
|
||||
expiresOn: authResult.account.idTokenClaims.exp!,
|
||||
tenantId: tenantId,
|
||||
resource: resource
|
||||
};
|
||||
try {
|
||||
await this.msalCacheProvider.writeTokenToLocalCache(token);
|
||||
} catch (e) {
|
||||
Logger.error(`Could not save access token to local cache: ${e}, this might cause throttling of AAD requests.`);
|
||||
}
|
||||
return token;
|
||||
if (getTenantIgnoreList().includes(tenantId)) {
|
||||
// Tenant found in ignore list, don't fetch access token.
|
||||
Logger.info(`Tenant ${tenantId} found in the ignore list, authentication will not be attempted. Please remove tenant from setting: '${Constants.AzureTenantConfigFilterSetting}' if you want to re-enable tenant for authentication.`);
|
||||
throw new TenantIgnoredError(localize('tenantIgnoredError', 'Tenant found in ignore list, authentication not attempted. You can remove tenant {0} from ignore list in settings.json file: {1} if you wish to access resources from this tenant.', tenantId, Constants.AzureTenantConfigFilterSetting));
|
||||
} else {
|
||||
Logger.error(`MSAL: getToken call failed`);
|
||||
// Throw error with MSAL-specific code/message, else throw generic error message
|
||||
if (this.isProviderError(authResult)) {
|
||||
throw new Error(localize('msalTokenError', `{0} occurred when acquiring token. \n{1}`, authResult.errorCode, authResult.errorMessage));
|
||||
let authResult = await azureAuth.getTokenMsal(account.key.accountId, resource, tenantId);
|
||||
if (this.isAuthenticationResult(authResult) && authResult.account && authResult.account.idTokenClaims) {
|
||||
const token: Token = {
|
||||
key: authResult.account.homeAccountId,
|
||||
token: authResult.accessToken,
|
||||
tokenType: authResult.tokenType,
|
||||
expiresOn: authResult.account.idTokenClaims.exp!,
|
||||
tenantId: tenantId,
|
||||
resource: resource
|
||||
};
|
||||
try {
|
||||
await this.msalCacheProvider.writeTokenToLocalCache(token);
|
||||
} catch (e) {
|
||||
Logger.error(`Could not save access token to local cache: ${e}, this might cause throttling of AAD requests.`);
|
||||
}
|
||||
return token;
|
||||
} else {
|
||||
throw new Error(localize('genericTokenError', 'Failed to get token'));
|
||||
Logger.error(`MSAL: getToken call failed`);
|
||||
// Throw error with MSAL-specific code/message, else throw generic error message
|
||||
if (this.isProviderError(authResult)) {
|
||||
throw new Error(localize('msalTokenError', `{0} occurred when acquiring token. \n{1}`, authResult.errorCode, authResult.errorMessage));
|
||||
} else {
|
||||
throw new Error(localize('genericTokenError', 'Failed to get token'));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // fallback to ADAL as default
|
||||
@@ -226,7 +233,13 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
|
||||
const azureAccount = account as AzureAccount;
|
||||
const response: MultiTenantTokenResponse = {};
|
||||
for (const tenant of azureAccount.properties.tenants) {
|
||||
response[tenant.id] = await this._getAccountSecurityToken(account, tenant.id, resource);
|
||||
try {
|
||||
response[tenant.id] = await this._getAccountSecurityToken(account, tenant.id, resource);
|
||||
} catch (e) {
|
||||
if (!(e instanceof TenantIgnoredError)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { Logger } from '../../utils/Logger';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { TenantIgnoredError } from '../../utils/TenantIgnoredError';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
||||
@@ -50,7 +51,7 @@ export class AzureResourceSubscriptionService implements IAzureResourceSubscript
|
||||
void vscode.window.showWarningMessage(errorMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!account.isStale) {
|
||||
if (!account.isStale && !(error instanceof TenantIgnoredError)) {
|
||||
const errorMsg = localize('azure.resource.tenantSubscriptionsError', "Failed to get subscriptions for account {0} (tenant '{1}'). {2}", account.displayInfo.displayName, tenantId, AzureResourceErrorMessageUtil.getErrorMessage(error));
|
||||
Logger.error(`Failed to get subscriptions for account ${account.displayInfo.displayName} (tenant '${tenantId}'). ${AzureResourceErrorMessageUtil.getErrorMessage(error)}`);
|
||||
errors.push(error);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../../azureResource/interfaces';
|
||||
import { AzureAccount, azureResource } from 'azurecore';
|
||||
import { TenantIgnoredError } from '../../utils/TenantIgnoredError';
|
||||
|
||||
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
@@ -75,10 +76,14 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
try {
|
||||
token = await azdata.accounts.getAccountSecurityToken(this.account, s.tenant!, azdata.AzureResource.ResourceManagement);
|
||||
} catch (err) {
|
||||
errMsg = AzureResourceErrorMessageUtil.getErrorMessage(err);
|
||||
if (!(err instanceof TenantIgnoredError)) {
|
||||
errMsg = AzureResourceErrorMessageUtil.getErrorMessage(err);
|
||||
}
|
||||
}
|
||||
if (!token) {
|
||||
void vscode.window.showWarningMessage(localize('azure.unableToAccessSubscription', "Unable to access subscription {0} ({1}). Please [refresh the account](command:azure.resource.signin) to try again. {2}", s.name, s.id, errMsg));
|
||||
if (errMsg !== '') {
|
||||
void vscode.window.showWarningMessage(localize('azure.unableToAccessSubscription', "Unable to access subscription {0} ({1}). Please [refresh the account](command:azure.resource.signin) to try again. {2}", s.name, s.id, errMsg));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -187,8 +187,12 @@ class FlatAccountTreeNodeLoader {
|
||||
let tenants = this._account.properties.tenants;
|
||||
// Filter out tenants that we can't authenticate to.
|
||||
tenants = tenants.filter(async tenant => {
|
||||
const token = await azdata.accounts.getAccountSecurityToken(this._account, tenant.id, azdata.AzureResource.ResourceManagement);
|
||||
return token !== undefined;
|
||||
try {
|
||||
const token = await azdata.accounts.getAccountSecurityToken(this._account, tenant.id, azdata.AzureResource.ResourceManagement);
|
||||
return token !== undefined;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = (await getSubscriptionInfo(this._account, this._subscriptionService, this._subscriptionFilterService)).subscriptions;
|
||||
|
||||
@@ -21,6 +21,7 @@ import { getProxyEnabledHttpClient } from '../utils';
|
||||
import { HttpClient } from '../account-provider/auths/httpClient';
|
||||
import { NetworkRequestOptions } from '@azure/msal-common';
|
||||
import { ErrorResponseBody } from '@azure/arm-subscriptions/esm/models';
|
||||
import { TenantIgnoredError } from '../utils/TenantIgnoredError';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -178,18 +179,20 @@ export async function getResourceGroups(appContext: AppContext, account?: AzureA
|
||||
|
||||
result.resourceGroups.push(...await service.getResources([subscription], new TokenCredentials(token, tokenType), account));
|
||||
} catch (err) {
|
||||
const error = new Error(localize('azure.accounts.getResourceGroups.queryError', "Error fetching resource groups for account {0} ({1}) subscription {2} ({3}) tenant {4} : {5}",
|
||||
account.displayInfo.displayName,
|
||||
account.displayInfo.userId,
|
||||
subscription.id,
|
||||
subscription.name,
|
||||
tenant.id,
|
||||
err instanceof Error ? err.message : err));
|
||||
console.warn(error);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
if (!(err instanceof TenantIgnoredError)) {
|
||||
const error = new Error(localize('azure.accounts.getResourceGroups.queryError', "Error fetching resource groups for account {0} ({1}) subscription {2} ({3}) tenant {4} : {5}",
|
||||
account.displayInfo.displayName,
|
||||
account.displayInfo.userId,
|
||||
subscription.id,
|
||||
subscription.name,
|
||||
tenant.id,
|
||||
err instanceof Error ? err.message : err));
|
||||
console.warn(error);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
}));
|
||||
return result;
|
||||
@@ -282,8 +285,10 @@ export async function runResourceQuery<T extends azureResource.AzureGraphResourc
|
||||
resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = new Error(unableToFetchTokenError(tenant.id));
|
||||
result.errors.push(error);
|
||||
if (!(err instanceof TenantIgnoredError)) {
|
||||
const error = new Error(unableToFetchTokenError(tenant.id));
|
||||
result.errors.push(error);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ export const AzureTenantConfigSection = AzureSection + '.' + TenantSection + '.'
|
||||
|
||||
export const Filter = 'filter';
|
||||
|
||||
export const AzureTenantConfigFilterSetting = AzureTenantConfigSection + '.' + Filter;
|
||||
|
||||
export const NoSystemKeyChainSection = 'noSystemKeychain';
|
||||
|
||||
export const oldMsalCacheFileName = 'azureTokenCacheMsal-azure_publicCloud';
|
||||
|
||||
6
extensions/azurecore/src/utils/TenantIgnoredError.ts
Normal file
6
extensions/azurecore/src/utils/TenantIgnoredError.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export class TenantIgnoredError extends Error { }
|
||||
Reference in New Issue
Block a user