From 22f85ad4ff55fc2bd76b0c35d25593b29c80d910 Mon Sep 17 00:00:00 2001 From: Amir Omidi Date: Mon, 13 Jul 2020 14:52:41 -0700 Subject: [PATCH] Handle don't ask again button (#11236) * Handle don't ask again button * Change to ignore * Change language * Pass in tenant name and ID * Fix message * Don't allow common tenant to get interaction required prompt * Move things around --- extensions/azurecore/package.json | 5 ++ extensions/azurecore/package.nls.json | 1 + .../src/account-provider/auths/azureAuth.ts | 68 +++++++++++++++---- .../auths/azureAuthCodeGrant.ts | 6 +- .../account-provider/auths/azureDeviceCode.ts | 4 +- .../src/account-provider/interfaces.ts | 4 +- 6 files changed, 68 insertions(+), 20 deletions(-) diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index 1a6b101ec0..17197017bf 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -28,6 +28,11 @@ "type": "array", "default": null, "description": "%azure.resource.config.filter.description%" + }, + "azure.tenant.config.filter": { + "type":"array", + "default": [], + "description": "%azure.tenant.config.filter.description%" } } }, diff --git a/extensions/azurecore/package.nls.json b/extensions/azurecore/package.nls.json index bcf12a4286..14818ed349 100644 --- a/extensions/azurecore/package.nls.json +++ b/extensions/azurecore/package.nls.json @@ -13,6 +13,7 @@ "azure.resource.startterminal.title": "Start Cloud Shell", "azure.resource.connectsqlserver.title": "Connect", "azure.resource.connectsqldb.title": "Add to Servers", + "azure.tenant.config.filter.description": "The list of tenant IDs to ignore when querying azure resources. Each element is a tenant id.", "accounts.clearTokenCache": "Clear Azure Account Token Cache", diff --git a/extensions/azurecore/src/account-provider/auths/azureAuth.ts b/extensions/azurecore/src/account-provider/auths/azureAuth.ts index 22bfd555d2..0e1ff65614 100644 --- a/extensions/azurecore/src/account-provider/auths/azureAuth.ts +++ b/extensions/azurecore/src/account-provider/auths/azureAuth.ts @@ -100,7 +100,7 @@ export abstract class AzureAuth implements vscode.Disposable { protected readonly MicrosoftAccountType: string = 'microsoft'; protected readonly loginEndpointUrl: string; - protected readonly commonTenant: string; + protected readonly commonTenant: Tenant; protected readonly redirectUri: string; protected readonly scopes: string[]; protected readonly scopesString: string; @@ -117,7 +117,10 @@ export abstract class AzureAuth implements vscode.Disposable { public readonly userFriendlyName: string ) { this.loginEndpointUrl = this.metadata.settings.host; - this.commonTenant = 'common'; + this.commonTenant = { + id: 'common', + displayName: 'common', + }; this.redirectUri = this.metadata.settings.redirectUri; this.clientId = this.metadata.settings.clientId; @@ -390,7 +393,7 @@ export abstract class AzureAuth implements vscode.Disposable { let refreshResponse: TokenRefreshResponse; try { - const tokenUrl = `${this.loginEndpointUrl}${tenant}/oauth2/token`; + const tokenUrl = `${this.loginEndpointUrl}${tenant.id}/oauth2/token`; const tokenResponse = await this.makePostRequest(tokenUrl, postData); Logger.pii(JSON.stringify(tokenResponse.data)); const tokenClaims = this.getTokenClaims(tokenResponse.data.access_token); @@ -412,7 +415,7 @@ export abstract class AzureAuth implements vscode.Disposable { if (ex?.response?.data?.error === 'interaction_required') { const shouldOpenLink = await this.openConsentDialog(tenant, resourceId); if (shouldOpenLink === true) { - const { tokenRefreshResponse, authCompleteDeferred } = await this.promptForConsent(resourceEndpoint, tenant); + const { tokenRefreshResponse, authCompleteDeferred } = await this.promptForConsent(resourceEndpoint, tenant.id); refreshResponse = tokenRefreshResponse; authCompleteDeferred.resolve(); } else { @@ -423,7 +426,7 @@ export abstract class AzureAuth implements vscode.Disposable { } } - this.memdb.set(this.createMemdbString(refreshResponse.accessToken.key, tenant, resourceId), refreshResponse.expiresOn); + this.memdb.set(this.createMemdbString(refreshResponse.accessToken.key, tenant.id, resourceId), refreshResponse.expiresOn); return refreshResponse; } catch (err) { const msg = localize('azure.noToken', "Retrieving the Azure token failed. Please sign in again."); @@ -432,24 +435,63 @@ export abstract class AzureAuth implements vscode.Disposable { } } - private async openConsentDialog(tenantId: string, resourceId: string): Promise { + private async openConsentDialog(tenant: Tenant, resourceId: string): Promise { + if (!tenant.displayName && !tenant.id) { + throw new Error('Tenant did not have display name or id'); + } + + if (tenant.id === 'common') { + throw new Error('Common tenant should not need consent'); + } + + const getTenantConfigurationSet = (): Set => { + const configuration = vscode.workspace.getConfiguration('azure.tenant.config'); + let values: string[] = configuration.get('filter') ?? []; + return new Set(values); + }; + + // The user wants to ignore this tenant. + if (getTenantConfigurationSet().has(tenant?.displayName ?? tenant?.id)) { + return false; + } + + const updateTenantConfigurationSet = (set: Set): void => { + const configuration = vscode.workspace.getConfiguration('azure.tenant.config'); + configuration.update('filter', Array.from(set), vscode.ConfigurationTarget.Global); + }; + interface ConsentMessageItem extends vscode.MessageItem { booleanResult: boolean; + action?: (tenantId: string) => void; } const openItem: ConsentMessageItem = { - title: localize('open', "Open"), + title: localize('azurecore.consentDialog.open', "Open"), booleanResult: true }; const closeItem: ConsentMessageItem = { - title: localize('cancel', "Cancel"), + title: localize('azurecore.consentDialog.cancel', "Cancel"), isCloseAffordance: true, booleanResult: false }; - const messageBody = localize('azurecore.consentDialog.body', "Your tenant {0} requires you to re-authenticate again to access {1} resources. Press Open to start the authentication process.", tenantId, resourceId); - const result = await vscode.window.showInformationMessage(messageBody, { modal: true }, openItem, closeItem); + const dontAskAgainItem: ConsentMessageItem = { + title: localize('azurecore.consentDialog.ignore', "Ignore Tenant"), + booleanResult: false, + action: (tenantId: string) => { + let set = getTenantConfigurationSet(); + set.add(tenantId); + updateTenantConfigurationSet(set); + } + + }; + 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, resourceId); + const result = await vscode.window.showInformationMessage(messageBody, { modal: true }, openItem, closeItem, dontAskAgainItem); + + if (result.action) { + result.action(tenant.id); + } return result.booleanResult; } @@ -463,19 +505,19 @@ export abstract class AzureAuth implements vscode.Disposable { } } - private async refreshAccessToken(account: azdata.AccountKey, rt: RefreshToken, tenant?: Tenant, resource?: Resource): Promise { + private async refreshAccessToken(account: azdata.AccountKey, rt: RefreshToken, tenant: Tenant = this.commonTenant, resource?: Resource): Promise { const postData: { [key: string]: string } = { grant_type: 'refresh_token', refresh_token: rt.token, client_id: this.clientId, - tenant: this.commonTenant, + tenant: tenant.id, }; if (resource) { postData.resource = resource.endpoint; } - const getTokenResponse = await this.getToken(postData, tenant?.id, resource?.id, resource?.endpoint); + const getTokenResponse = await this.getToken(postData, tenant, resource?.id, resource?.endpoint); const accessToken = getTokenResponse?.accessToken; const refreshToken = getTokenResponse?.refreshToken; diff --git a/extensions/azurecore/src/account-provider/auths/azureAuthCodeGrant.ts b/extensions/azurecore/src/account-provider/auths/azureAuthCodeGrant.ts index 5180fa92c5..90e106cca8 100644 --- a/extensions/azurecore/src/account-provider/auths/azureAuthCodeGrant.ts +++ b/extensions/azurecore/src/account-provider/auths/azureAuthCodeGrant.ts @@ -57,7 +57,7 @@ export class AzureAuthCodeGrant extends AzureAuth { super(metadata, tokenCache, context, uriEventEmitter, AzureAuthType.AuthCodeGrant, AzureAuthCodeGrant.USER_FRIENDLY_NAME); } - public async promptForConsent(resourceEndpoint: string, tenant: string = this.commonTenant): Promise<{ tokenRefreshResponse: TokenRefreshResponse, authCompleteDeferred: Deferred } | undefined> { + public async promptForConsent(resourceEndpoint: string, tenant: string = this.commonTenant.id): Promise<{ tokenRefreshResponse: TokenRefreshResponse, authCompleteDeferred: Deferred } | undefined> { let authCompleteDeferred: Deferred; let authCompletePromise = new Promise((resolve, reject) => authCompleteDeferred = { resolve, reject }); @@ -102,7 +102,7 @@ export class AzureAuthCodeGrant extends AzureAuth { return this.server.shutdown(); } - public async loginWithLocalServer(authCompletePromise: Promise, resourceId: string, tenant: string = this.commonTenant): Promise { + public async loginWithLocalServer(authCompletePromise: Promise, resourceId: string, tenant: string = this.commonTenant.id): Promise { this.server = new SimpleWebServer(); const nonce = crypto.randomBytes(16).toString('base64'); let serverPort: string; @@ -147,7 +147,7 @@ export class AzureAuthCodeGrant extends AzureAuth { }; } - public async loginWithoutLocalServer(resourceId: string, tenant: string = this.commonTenant): Promise { + public async loginWithoutLocalServer(resourceId: string, tenant: string = this.commonTenant.id): Promise { const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://microsoft.azurecore`)); const nonce = crypto.randomBytes(16).toString('base64'); const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' ? 443 : 80); diff --git a/extensions/azurecore/src/account-provider/auths/azureDeviceCode.ts b/extensions/azurecore/src/account-provider/auths/azureDeviceCode.ts index 18c5890d90..252e15de0b 100644 --- a/extensions/azurecore/src/account-provider/auths/azureDeviceCode.ts +++ b/extensions/azurecore/src/account-provider/auths/azureDeviceCode.ts @@ -57,7 +57,7 @@ export class AzureDeviceCode extends AzureAuth { } - public async promptForConsent(resourceId: string, tenant: string = this.commonTenant): Promise { + public async promptForConsent(resourceId: string, tenant: string = this.commonTenant.id): Promise { vscode.window.showErrorMessage(localize('azure.deviceCodeDoesNotSupportConsent', "Device code authentication does not support prompting for consent. Switch the authentication method in settings to code grant.")); return undefined; } @@ -144,7 +144,7 @@ export class AzureDeviceCode extends AzureAuth { const postData = { grant_type: 'urn:ietf:params:oauth:grant-type:device_code', client_id: this.clientId, - tenant: this.commonTenant, + tenant: this.commonTenant.id, code: info.device_code }; diff --git a/extensions/azurecore/src/account-provider/interfaces.ts b/extensions/azurecore/src/account-provider/interfaces.ts index 795c5e23e7..7e7915bc2b 100644 --- a/extensions/azurecore/src/account-provider/interfaces.ts +++ b/extensions/azurecore/src/account-provider/interfaces.ts @@ -22,12 +22,12 @@ export interface Tenant { /** * Identifier of the user in the tenant */ - userId: string; + userId?: string; /** * The category the user has set their tenant to (e.g. Home Tenant) */ - tenantCategory: string; + tenantCategory?: string; } /**