Add support for custom endpoints (#23390)

* initial commit, add support for custom endpoints

* add template file with public azure endpoints

* add throw error, fix compile

* update description

* move custom provider to settings.json

* cleanup

* test

* wip

* try fix build error

* wip fix build issue

* add typings for package.json

* test remove default

* fix typings

* fix typing

* add object definitions

* add additional checks

* test define object

* add default

* remove default and extra checks

* Organize code well to fix integration tests (#23697)

* pr review updates

* fix custom setting checker

* fix default behavior

* add more required, change name for default cloud

* add one more required

* modify required properties to match with api

* Update extensions/azurecore/package.json

Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>

* Update extensions/azurecore/package.json

Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>

* pr review changes

* remove default value from display name

* make akv optional

* remove default name

* remove default

* update descriptions, function names

* add client ID

* small fix

* Update extensions/azurecore/package.nls.json

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* Update extensions/azurecore/package.json

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* updated names

* add custom for configkey

* providerSettingsJson -> customProviderSettings

---------

Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>
Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
This commit is contained in:
Christopher Suh
2023-07-18 14:16:53 -07:00
committed by GitHub
parent 04bce3f3ac
commit a752c84216
12 changed files with 375 additions and 31 deletions

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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;

View File

@@ -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"
}
}
}
}
]
}

View File

@@ -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

View File

@@ -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';

View File

@@ -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');
}
}

View File

@@ -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");

View File

@@ -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<boolean> {
const result = await vscode.window.showInformationMessage(message, loc.reloadChoice);
export async function displayReloadAds(sectionName: string): Promise<boolean> {
const result = await vscode.window.showInformationMessage(loc.reloadPrompt(sectionName), loc.reloadChoice);
if (result === loc.reloadChoice) {
await vscode.commands.executeCommand('workbench.action.reloadWindow');
return true;