diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index 9fd0a57be18..c07985cf830 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -128,6 +128,133 @@ "Microsoft Authentication Library" ], "deprecationMessage": "Warning: ADAL has been deprecated, and is scheduled to be removed in a future release. Please use MSAL (default option) instead." + }, + "azure.customProviderSettings": { + "type": "array", + "description": "%config.customProviderSettings%", + "scope": "resource", + "items": { + "type": "object", + "description": "%config.providerSettingsTitle%", + "required": ["name", "settings"], + "properties":{ + "name": { + "type": "string", + "default": "Azure Public", + "description": "%config.providerSettingsName%" + }, + "settings": { + "type": "object", + "description": "%config.providerSettingsDescription%", + "required": ["metadata"], + "properties": { + "metadata": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "default": "Azure Public Cloud", + "description": "%config.providerSettingsMetadata%" + }, + "id": { + "type": "string", + "default": "azure_publicCloud", + "description": "%config.providerSettingsId%" + }, + "endpoints": { + "type": "object", + "required": ["host", "clientId", "scopes", "sqlResource", "microsoftResource", "armResource", "graphResource", "azureStorageResource"], + "properties": { + "type": "object", + "host": { + "type": "string", + "default": "https://login.microsoftonline.com/", + "description": "%config.providerSettings.endpoints.host%" + }, + "clientId": { + "type": "string", + "default": "a69788c6-1d43-44ed-9ca3-b83e194da255", + "description": "%config.providerSettings.endpoints.clientId%" + }, + "microsoftResource": { + "type": "string", + "default": "https://management.core.windows.net/", + "description": "%config.providerSettings.endpoints.microsoftResource%" + }, + "graphResource": { + "type": "string", + "default": "https://graph.windows.net/", + "description": "%config.providerSettings.endpoints.graphResource%" + }, + "msGraphResource": { + "type": "string", + "default": "https://graph.microsoft.com/", + "description": "%config.providerSettings.endpoints.msGraphResource%" + }, + "armResource": { + "type": "string", + "default": "https://management.azure.com/", + "description": "%config.providerSettings.endpoints.armResource%" + }, + "sqlResource": { + "type": "string", + "default": "https://database.windows.net/", + "description": "%config.providerSettings.endpoints.sqlResource%" + }, + "azureKeyVaultResource": { + "type": "string", + "default": "https://vault.azure.net/", + "description": "%config.providerSettings.endpoints.azureKeyVaultResource%" + }, + "azureLogAnalyticsResource": { + "type": "string", + "default": "https://api.loganalytics.io/", + "description": "%config.providerSettings.endpoints.logAnalytics%" + }, + "azureStorageResource": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "default": "", + "description": "%config.providerSettings.endpoints.azureStorageResource%" + }, + "endpointSuffix": { + "type": "string", + "default": ".core.windows.net/", + "description": "%config.providerSettings.endpoints.azureStorageResourceSuffix%" + } + } + }, + "azureKustoResource": { + "type": "string", + "default": "https://kusto.kusto.windows.net/", + "description": "%config.providerSettings.endpoints.azureKustoResource%" + }, + "powerBiResource": { + "type": "string", + "default": "https://analysis.windows.net/powerbi/api/", + "description": "%config.providerSettings.endpoints.powerBiResource%" + }, + "scopes": { + "type": "string", + "default": "https://management.azure.com/user_impersonation", + "description": "%config.providerSettings.endpoints.scopes%" + }, + "portalEndpoint": { + "type": "string", + "default": "https://portal.azure.com", + "description": "%config.providerSettings.endpoints.portal%" + } + } + } + } + } + } + } + } + } } } } diff --git a/extensions/azurecore/package.nls.json b/extensions/azurecore/package.nls.json index 8c0c71ffa10..00658009af2 100644 --- a/extensions/azurecore/package.nls.json +++ b/extensions/azurecore/package.nls.json @@ -20,14 +20,33 @@ "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", - "config.enableUsNatCloudDescription": "Should US National Azure cloud integration be enabled", "config.enableChinaCloudDescription": "Should Azure China integration be enabled", - "config.enableGermanyCloudDescription": "Should Azure Germany integration be enabled", "config.azureAuthMethodConfigurationSection": "Azure Authentication Method", "config.azureCodeGrantMethod": "Code Grant Method", "config.azureDeviceCodeMethod": "Device Code Method", "config.noSystemKeychain": "Disable system keychain integration. Credentials will be stored in a flat file in the user's home directory.", "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." + "config.authenticationLibrary": "The library used for the AAD auth flow. Please restart ADS after changing this option.", + "config.customProviderSettings": "Setting containing custom Azure authentication endpoints. Changes to this setting require a restart to take effect.", + "config.providerSettingsTitle": "Provider Settings", + "config.providerSettingsName": "Cloud Name", + "config.providerSettingsDescription": "Cloud Settings", + "config.providerSettingsMetadata": "Cloud Display Name", + "config.providerSettingsId": "Cloud ID", + "config.providerSettings.endpoints.host": "Host Endpoint", + "config.providerSettings.endpoints.clientId": "Client ID for Azure Data Studio", + "config.providerSettings.endpoints.microsoftResource": "Microsoft Resource Endpoint", + "config.providerSettings.endpoints.graphResource": "Graph Resource Endpoint", + "config.providerSettings.endpoints.msGraphResource": "Microsoft Graph Resource Endpoint", + "config.providerSettings.endpoints.armResource": "ARM Resource Endpoint", + "config.providerSettings.endpoints.sqlResource": "SQL Resource Endpoint", + "config.providerSettings.endpoints.azureKeyVaultResource": "Azure KeyVault Resource Endpoint", + "config.providerSettings.endpoints.logAnalytics": "Azure Log Analytics Endpoint", + "config.providerSettings.endpoints.azureStorageResource": "Azure Storage Resource Endpoint", + "config.providerSettings.endpoints.azureStorageResourceSuffix": "Azure Storage Resource Endpoint Suffix", + "config.providerSettings.endpoints.azureKustoResource": "Azure Kusto Resource Endpoint", + "config.providerSettings.endpoints.powerBiResource": "Power BI Resource Endpoint", + "config.providerSettings.endpoints.scopes": "Scopes Endpoint", + "config.providerSettings.endpoints.portal": "Azure Portal Endpoint" } diff --git a/extensions/azurecore/src/account-provider/auths/azureAuth.ts b/extensions/azurecore/src/account-provider/auths/azureAuth.ts index 6c9a25dca18..7fab8537144 100644 --- a/extensions/azurecore/src/account-provider/auths/azureAuth.ts +++ b/extensions/azurecore/src/account-provider/auths/azureAuth.ts @@ -72,9 +72,7 @@ export abstract class AzureAuth implements vscode.Disposable { this.resources = [ this.metadata.settings.armResource, this.metadata.settings.graphResource, - this.metadata.settings.azureKeyVaultResource ]; - if (this.metadata.settings.sqlResource) { this.resources.push(this.metadata.settings.sqlResource); } @@ -90,6 +88,9 @@ export abstract class AzureAuth implements vscode.Disposable { if (this.metadata.settings.azureLogAnalyticsResource) { this.resources.push(this.metadata.settings.azureLogAnalyticsResource); } + if (this.metadata.settings.azureKeyVaultResource) { + this.resources.push(this.metadata.settings.azureKeyVaultResource); + } if (this.metadata.settings.azureKustoResource) { this.resources.push(this.metadata.settings.azureKustoResource); } diff --git a/extensions/azurecore/src/account-provider/azureAccountProviderService.ts b/extensions/azurecore/src/account-provider/azureAccountProviderService.ts index 428215e09c3..26e6eb72f5d 100644 --- a/extensions/azurecore/src/account-provider/azureAccountProviderService.ts +++ b/extensions/azurecore/src/account-provider/azureAccountProviderService.ts @@ -150,6 +150,11 @@ export class AzureAccountProviderService implements vscode.Disposable { if (!oldConfigValue && newConfigValue) { providerChanges.push(this.registerAccountProvider(provider)); } + + // Case 4: Provider was added from JSON - register provider + if (provider.configKey !== 'enablePublicCloud' && provider.configKey !== 'enableUsGovCloud' && provider.configKey !== 'enableChinaCloud') { + providerChanges.push(this.registerAccountProvider(provider)); + } } // Process all the changes before continuing diff --git a/extensions/azurecore/src/account-provider/interfaces.ts b/extensions/azurecore/src/account-provider/interfaces.ts index 3ec215429f1..b028a7dc59f 100644 --- a/extensions/azurecore/src/account-provider/interfaces.ts +++ b/extensions/azurecore/src/account-provider/interfaces.ts @@ -5,6 +5,21 @@ import * as azurecore from 'azurecore'; +export const enum SettingIds { + marm = 'marm', + graph = 'graph', + msgraph = 'msgraph', + arm = 'arm', + sql = 'sql', + ossrdbms = 'ossrdbms', + vault = 'vault', + ado = 'ado', + ala = 'ala', + storage = 'storage', + kusto = 'kusto', + powerbi = 'powerbi' +} + /** * Mapping of configuration key with the metadata to instantiate the account provider */ @@ -20,6 +35,39 @@ export interface ProviderSettings { metadata: azurecore.AzureAccountProviderMetadata; } +/** + * Custom Provider settings mapping + */ +export type ProviderSettingsJson = { + name: string, + settings: { + configKey: string, + metadata: { + displayName: string, + id: string, + endpoints: { + host: string, + clientId: string, + microsoftResource: string, + graphResource: string, + msGraphResource?: string, + armResource: string, + sqlResource: string, + azureKeyVaultResource: string, + azureLogAnalyticsResource?: string, + azureStorageResource: { + endpoint: string, + endpointSuffix: string + } + azureKustoResource?: string, + powerBiResource?: string, + scopes: string, + portalEndpoint?: string + } + } + } +} + export interface Subscription { id: string, tenantId: string, diff --git a/extensions/azurecore/src/account-provider/providerSettings.ts b/extensions/azurecore/src/account-provider/providerSettings.ts index 7cb3719b12d..e7dca9adc48 100644 --- a/extensions/azurecore/src/account-provider/providerSettings.ts +++ b/extensions/azurecore/src/account-provider/providerSettings.ts @@ -4,26 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vscode-nls'; -import { ProviderSettings } from './interfaces'; +import { ProviderSettings, SettingIds } from './interfaces'; import { AzureResource } from 'azdata'; +import { updateCustomCloudProviderSettings } from '../utils'; const localize = nls.loadMessageBundle(); -const enum SettingIds { - marm = 'marm', - graph = 'graph', - msgraph = 'msgraph', - arm = 'arm', - sql = 'sql', - ossrdbms = 'ossrdbms', - vault = 'vault', - ado = 'ado', - ala = 'ala', - storage = 'storage', - kusto = 'kusto', - powerbi = 'powerbi' -} - const publicAzureSettings: ProviderSettings = { configKey: 'enablePublicCloud', metadata: { @@ -246,5 +232,7 @@ const chinaAzureSettings: ProviderSettings = { } } }; -const allSettings = [publicAzureSettings, usGovAzureSettings, chinaAzureSettings]; + +let allSettings = [publicAzureSettings, usGovAzureSettings, chinaAzureSettings]; +allSettings = updateCustomCloudProviderSettings(allSettings); export default allSettings; diff --git a/extensions/azurecore/src/account-provider/providerSettingsTemplate.json b/extensions/azurecore/src/account-provider/providerSettingsTemplate.json new file mode 100644 index 00000000000..6f5984623de --- /dev/null +++ b/extensions/azurecore/src/account-provider/providerSettingsTemplate.json @@ -0,0 +1,32 @@ +{ + "clouds": [ + { + "name": "Azure Public", + "settings": { + "configKey": "enablePublicCloud", + "metadata": { + "displayName": "Azure Public Cloud", + "id": "azure_publicCloud", + "endpoints": { + "host": "https://login.microsoftonline.com/", + "microsoftResource": "https://management.core.windows.net/", + "graphResource": "https://graph.windows.net/", + "msGraphResource": "https://graph.microsoft.com/", + "armResource": "https://management.azure.com/", + "sqlResource": "https://database.windows.net/", + "azureKeyVaultResource": "https://vault.azure.net/", + "azureLogAnalyticsResource": "https://api.loganalytics.io/", + "azureStorageResource": { + "endpoint": "", + "endpointSuffix": ".core.windows.net/" + }, + "azureKustoResource": "https://kusto.kusto.windows.net/", + "powerBiResource": "https://analysis.windows.net/powerbi/api/", + "scopes": "https://management.azure.com/user_impersonation", + "portalEndpoint": "https://portal.azure.com" + } + } + } + } + ] +} diff --git a/extensions/azurecore/src/azurecore.d.ts b/extensions/azurecore/src/azurecore.d.ts index fdf2e00f5d2..81a464609d9 100644 --- a/extensions/azurecore/src/azurecore.d.ts +++ b/extensions/azurecore/src/azurecore.d.ts @@ -91,7 +91,7 @@ declare module 'azurecore' { /** * Information that describes the Microsoft resource management resource */ - microsoftResource?: Resource + microsoftResource: Resource /** * Information that describes the AAD graph resource @@ -121,7 +121,7 @@ declare module 'azurecore' { /** * Information that describes the Azure Key Vault resource */ - azureKeyVaultResource: Resource; + azureKeyVaultResource?: Resource; /** * Information that describes the Azure Dev Ops resource diff --git a/extensions/azurecore/src/constants.ts b/extensions/azurecore/src/constants.ts index 3ecd10b8720..13d371ef8d0 100644 --- a/extensions/azurecore/src/constants.ts +++ b/extensions/azurecore/src/constants.ts @@ -47,6 +47,10 @@ export const oldMsalCacheFileName = 'azureTokenCacheMsal-azure_publicCloud'; export const piiLogging = 'piiLogging'; +export const CustomProviderSettings = 'customProviderSettings'; + +export const CustomProviderSettingsSection = AzureSection + '.' + CustomProviderSettings; + /** MSAL Account version */ export const AccountVersion = '2.0'; diff --git a/extensions/azurecore/src/extension.ts b/extensions/azurecore/src/extension.ts index 71689e2a9c7..2c7019fff59 100644 --- a/extensions/azurecore/src/extension.ts +++ b/extensions/azurecore/src/extension.ts @@ -293,7 +293,7 @@ async function onDidChangeConfiguration(e: vscode.ConfigurationChangeEvent): Pro if (vscode.workspace.getConfiguration(Constants.AzureSection).get('authenticationLibrary') === 'ADAL') { void vscode.window.showInformationMessage(loc.deprecatedOption); } - await utils.displayReloadAds(loc.reloadPrompt); + await utils.displayReloadAds('authenticationLibrary'); } } diff --git a/extensions/azurecore/src/localizedConstants.ts b/extensions/azurecore/src/localizedConstants.ts index 59fc2a53209..be2b14b836a 100644 --- a/extensions/azurecore/src/localizedConstants.ts +++ b/extensions/azurecore/src/localizedConstants.ts @@ -63,7 +63,9 @@ export const location = localize('azurecore.location', "Location"); export const subscription = localize('azurecore.subscription', "Subscription"); export const typeIcon = localize('azurecore.typeIcon', "Type Icon"); -export const reloadPrompt = localize('azurecore.reloadPrompt', "Authentication Library has changed, please reload Azure Data Studio."); +export function reloadPrompt(sectionName: string): string { + return localize('azurecore.reloadPrompt', "{0} setting changed, please reload Azure Data Studio.", sectionName); +} export const reloadPromptCacheClear = localize('azurecore.reloadPromptCacheClear', "Token cache has been cleared successfully, please reload Azure Data Studio."); export const reloadChoice = localize('azurecore.reloadChoice', "Reload Azure Data Studio"); diff --git a/extensions/azurecore/src/utils.ts b/extensions/azurecore/src/utils.ts index 0d4d1ffa88e..b723a055883 100644 --- a/extensions/azurecore/src/utils.ts +++ b/extensions/azurecore/src/utils.ts @@ -5,6 +5,7 @@ import * as loc from './localizedConstants'; import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; import * as constants from './constants'; import { AzureRegion, azureResource } from 'azurecore'; @@ -13,7 +14,11 @@ import { HttpClient } from './account-provider/auths/httpClient'; import { parse } from 'url'; import { getProxyAgentOptions } from './proxy'; import { HttpsProxyAgentOptions } from 'https-proxy-agent'; +import { ProviderSettings, ProviderSettingsJson, SettingIds } from './account-provider/interfaces'; +import { AzureResource } from 'azdata'; +import { Logger } from './utils/Logger'; +const localize = nls.loadMessageBundle(); const configProxy = 'proxy'; const configProxyStrictSSL = 'proxyStrictSSL'; const configProxyAuthorization = 'proxyAuthorization'; @@ -161,6 +166,118 @@ export async function updateTenantIgnoreList(tenantIgnoreList: string[]): Promis await configuration.update(constants.Filter, tenantIgnoreList, vscode.ConfigurationTarget.Global); } +export function updateCustomCloudProviderSettings(defaultSettings: ProviderSettings[]): ProviderSettings[] { + let providerSettingsJson: ProviderSettingsJson[] | undefined = vscode.workspace.getConfiguration(constants.AzureSection).get(constants.CustomProviderSettings) as ProviderSettingsJson[]; + vscode.workspace.onDidChangeConfiguration(async (changeEvent) => { + const impactProvider = changeEvent.affectsConfiguration(constants.CustomProviderSettingsSection); + if (impactProvider === true) { + await displayReloadAds(constants.CustomProviderSettingsSection); + } + }); + if (providerSettingsJson && providerSettingsJson.length > 0) { + try { + for (let cloudProvider of providerSettingsJson) { + // build provider setting + let newSettings = buildCustomCloudProviderSettings(cloudProvider); + defaultSettings.push(newSettings) + Logger.info(`Custom provider settings loaded for ${cloudProvider.settings.metadata.displayName}`); + } + void vscode.window.showInformationMessage(localize('providerSettings.success', 'Successfully loaded custom endpoints from settings')); + + } catch (error) { + void vscode.window.showErrorMessage(localize('providerSettings.error', 'Could not load endpoints from settings, please check the logs for more details.')); + console.error(error.message); + throw Error(error.message); + } + } + return defaultSettings; +} + +function buildCustomCloudProviderSettings(customProvider: ProviderSettingsJson): ProviderSettings { + // build provider setting + let newSettings: ProviderSettings = { + configKey: 'enableCustom' + customProvider.settings.metadata.id, + metadata: { + displayName: customProvider.settings.metadata.displayName, + id: customProvider.settings.metadata.id, + settings: { + host: customProvider.settings.metadata.endpoints.host, + clientId: customProvider.settings.metadata.endpoints.clientId, + microsoftResource: { + id: SettingIds.marm, + endpoint: customProvider.settings.metadata.endpoints.microsoftResource, + azureResourceId: AzureResource.MicrosoftResourceManagement + }, + armResource: { + id: SettingIds.arm, + endpoint: customProvider.settings.metadata.endpoints.armResource, + azureResourceId: AzureResource.ResourceManagement + }, + graphResource: { + id: SettingIds.graph, + endpoint: customProvider.settings.metadata.endpoints.graphResource, + azureResourceId: AzureResource.Graph + }, + azureStorageResource: { + id: SettingIds.storage, + endpoint: customProvider.settings.metadata.endpoints.azureStorageResource.endpoint, + endpointSuffix: customProvider.settings.metadata.endpoints.azureStorageResource.endpointSuffix, + azureResourceId: AzureResource.AzureStorage + }, + sqlResource: { + id: SettingIds.sql, + endpoint: customProvider.settings.metadata.endpoints.sqlResource, + azureResourceId: AzureResource.Sql + }, + redirectUri: 'http://localhost', + scopes: [ + 'openid', 'email', 'profile', 'offline_access', + customProvider.settings.metadata.endpoints.scopes + ], + } + } + }; + if (customProvider.settings.metadata.endpoints.msGraphResource) { + newSettings.metadata.settings.msGraphResource = { + id: SettingIds.msgraph, + endpoint: customProvider.settings.metadata.endpoints.msGraphResource, + azureResourceId: AzureResource.MsGraph + }; + } + if (customProvider.settings.metadata.endpoints.azureLogAnalyticsResource) { + newSettings.metadata.settings.azureLogAnalyticsResource = { + id: SettingIds.ala, + endpoint: customProvider.settings.metadata.endpoints.azureLogAnalyticsResource, + azureResourceId: AzureResource.AzureLogAnalytics + }; + } + if (customProvider.settings.metadata.endpoints.azureKustoResource) { + newSettings.metadata.settings.azureKustoResource = { + id: SettingIds.kusto, + endpoint: customProvider.settings.metadata.endpoints.azureKustoResource, + azureResourceId: AzureResource.AzureKusto + }; + } + if (customProvider.settings.metadata.endpoints.azureKeyVaultResource) { + newSettings.metadata.settings.azureKeyVaultResource = { + id: SettingIds.vault, + endpoint: customProvider.settings.metadata.endpoints.azureKeyVaultResource, + azureResourceId: AzureResource.AzureKeyVault + }; + } + if (customProvider.settings.metadata.endpoints.powerBiResource) { + newSettings.metadata.settings.powerBiResource = { + id: SettingIds.powerbi, + endpoint: customProvider.settings.metadata.endpoints.powerBiResource, + azureResourceId: AzureResource.PowerBi + }; + } + if (customProvider.settings.metadata.endpoints.portalEndpoint) { + newSettings.metadata.settings.portalEndpoint = customProvider.settings.metadata.endpoints.portalEndpoint; + } + return newSettings; +} + export function getResourceTypeIcon(appContext: AppContext, type: string): string { switch (type) { case azureResource.AzureResourceType.sqlServer: @@ -201,12 +318,13 @@ export function getProxyEnabledHttpClient(): HttpClient { return new HttpClient(proxy, agentOptions); } -/* Display notification with button to reload - * return true if button clicked - * return false if button not clicked +/** + * Display notification with button to reload + * @param sectionName Name of section to reload + * @returns true if reload clicked, false otherwise. */ -export async function displayReloadAds(message: string): Promise { - const result = await vscode.window.showInformationMessage(message, loc.reloadChoice); +export async function displayReloadAds(sectionName: string): Promise { + const result = await vscode.window.showInformationMessage(loc.reloadPrompt(sectionName), loc.reloadChoice); if (result === loc.reloadChoice) { await vscode.commands.executeCommand('workbench.action.reloadWindow'); return true;