diff --git a/src/Microsoft.SqlTools.Authentication/Authenticator.cs b/src/Microsoft.SqlTools.Authentication/Authenticator.cs index aef00541..ae43b5f8 100644 --- a/src/Microsoft.SqlTools.Authentication/Authenticator.cs +++ b/src/Microsoft.SqlTools.Authentication/Authenticator.cs @@ -162,6 +162,7 @@ namespace Microsoft.SqlTools.Authentication private IPublicClientApplication CreatePublicClientAppInstance(string authority, string audience) => PublicClientApplicationBuilder.Create(this.configuration.AppClientId) .WithAuthority(authority, audience) + .WithHttpClientFactory(new HttpClientProxyFactory(this.configuration.HttpProxyUrl, this.configuration.HttpProxyStrictSSL)) .WithClientName(this.configuration.AppName) .WithLogging(Utils.MSALLogCallback) .WithDefaultRedirectUri() diff --git a/src/Microsoft.SqlTools.Authentication/Utility/AuthenticatorConfiguration.cs b/src/Microsoft.SqlTools.Authentication/Utility/AuthenticatorConfiguration.cs index 3e73ed62..02ca9709 100644 --- a/src/Microsoft.SqlTools.Authentication/Utility/AuthenticatorConfiguration.cs +++ b/src/Microsoft.SqlTools.Authentication/Utility/AuthenticatorConfiguration.cs @@ -31,11 +31,23 @@ namespace Microsoft.SqlTools.Authentication.Utility /// public string CacheFileName { get; set; } - public AuthenticatorConfiguration(string appClientId, string appName, string cacheFolderPath, string cacheFileName) { + /// + /// Proxy URL defined by end user. + /// + public string? HttpProxyUrl { get; set; } + + /// + /// Whether the proxy server certificate must be verified against list of configured CAs. + /// + public bool HttpProxyStrictSSL { get; set; } + + public AuthenticatorConfiguration(string appClientId, string appName, string cacheFolderPath, string cacheFileName, string? httpProxyUrl = null, bool httpProxyStrictSSL = true) { AppClientId = appClientId; AppName = appName; CacheFolderPath = cacheFolderPath; CacheFileName = cacheFileName; + HttpProxyUrl = httpProxyUrl; + HttpProxyStrictSSL = httpProxyStrictSSL; } } } diff --git a/src/Microsoft.SqlTools.Authentication/Utility/ProxyHttpClientFactory.cs b/src/Microsoft.SqlTools.Authentication/Utility/ProxyHttpClientFactory.cs new file mode 100644 index 00000000..4e279ae4 --- /dev/null +++ b/src/Microsoft.SqlTools.Authentication/Utility/ProxyHttpClientFactory.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Net; +using System.Net.Security; +using Microsoft.Identity.Client; +using SqlToolsLogger = Microsoft.SqlTools.Utility.Logger; + +namespace Microsoft.SqlTools.Authentication.Utility +{ + /// + /// Implements Http Client Factory for Microsoft Identity Client to support proxy authentication. + /// + public class HttpClientProxyFactory : IMsalHttpClientFactory + { + private readonly HttpClient s_httpClient; + + /// + /// Default constructor to construct instance with proxy URL provided. + /// + /// Proxy URL to be used for network access. + /// Whether or not proxy server certficate must be strictly validated. + public HttpClientProxyFactory(string? proxyUrl, bool proxyStrictSSL) + { + if (proxyUrl != null) + { + var webProxy = new WebProxy( + new Uri(proxyUrl), + BypassOnLocal: false); + + var proxyHttpClientHandler = new HttpClientHandler + { + Proxy = webProxy, + UseProxy = true, + ServerCertificateCustomValidationCallback = (request, certs, chain, policyErrors) => + { + if (policyErrors != SslPolicyErrors.None) + { + SqlToolsLogger.Error("Proxy Server Certificate Validation failed with error: " + policyErrors.ToString()); + } + + if (proxyStrictSSL) + { + return policyErrors != SslPolicyErrors.None; + } + // Bypass Proxy server certificate validation. + else + { + SqlToolsLogger.Information("Proxy Server Certificate Validation bypassed as requested."); + return true; + } + } + }; + + s_httpClient = new HttpClient(proxyHttpClientHandler); + SqlToolsLogger.Verbose($"Received Http Proxy URL is used to instantiate HttpClientFactory"); + } + else + { + s_httpClient = new HttpClient(); + } + } + + /// + /// Gets Http Client instance + /// + /// + public HttpClient GetHttpClient() + { + return s_httpClient; + } + } +} diff --git a/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs b/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs index 6229815f..df1ecbb6 100644 --- a/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs +++ b/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs @@ -70,6 +70,12 @@ namespace Microsoft.SqlTools.Utility case "-help": ShouldExit = true; return; + case "-http-proxy-url": + HttpProxyUrl = args[++i]; + break; + case "-http-proxy-strict-ssl": + HttpProxyStrictSSL = true; + break; case "-service-name": ServiceName = args[++i]; break; @@ -136,6 +142,16 @@ namespace Microsoft.SqlTools.Utility /// public string Locale { get; private set; } + /// + /// Custom Http Proxy URL as specified in Azure Data Studio + /// + public string? HttpProxyUrl { get; private set; } + + /// + /// Specifies whether the proxy server certificate should be verified against the list of supplied CAs. + /// + public bool HttpProxyStrictSSL { get; private set; } + /// /// Name of service that is receiving command options /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 0a0b6609..4f0bef28 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -1190,7 +1190,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } var cachePath = Path.Combine(applicationPath, applicationName, AzureTokenFolder); - return new Authenticator(new(ApplicationClientId, applicationName, cachePath, MsalCacheName), () => this.encryptionKeys); + return new Authenticator(new(ApplicationClientId, applicationName, cachePath, MsalCacheName, commandOptions.HttpProxyUrl, commandOptions.HttpProxyStrictSSL), () => this.encryptionKeys); } private Task HandleEncryptionKeysNotificationEvent(EncryptionKeysChangeParams @params, EventContext context) diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs index 818abe1e..3dd47ffd 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Database/DatabaseHandler.cs @@ -302,11 +302,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement ((DatabaseInfo)databaseViewInfo.ObjectInfo).IsFilesTabSupported = true; ((DatabaseInfo)databaseViewInfo.ObjectInfo).Filegroups = GetFileGroups(smoDatabase, databaseViewInfo); } + } - if (prototype is DatabasePrototype160) - { - ((DatabaseInfo)databaseViewInfo.ObjectInfo).IsLedgerDatabase = smoDatabase.IsLedger; - } + if (prototype is DatabasePrototype160) + { + ((DatabaseInfo)databaseViewInfo.ObjectInfo).IsLedgerDatabase = smoDatabase.IsLedger; } databaseScopedConfigurationsCollection = smoDatabase.IsSupportedObject() ? smoDatabase.DatabaseScopedConfigurations : null; databaseViewInfo.FileTypesOptions = displayFileTypes.Values.ToArray();