From a3b42e43bd348928611adc1e14e788e28e5b8927 Mon Sep 17 00:00:00 2001
From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>
Date: Wed, 11 Oct 2023 10:49:47 -0700
Subject: [PATCH] Support passing Http proxy URL and Strict SSL flag to
Authenticator (#2256)
---
.../Authenticator.cs | 1 +
.../Utility/AuthenticatorConfiguration.cs | 14 +++-
.../Utility/ProxyHttpClientFactory.cs | 75 +++++++++++++++++++
.../Utility/CommandOptions.cs | 16 ++++
.../Connection/ConnectionService.cs | 2 +-
.../ObjectTypes/Database/DatabaseHandler.cs | 8 +-
6 files changed, 110 insertions(+), 6 deletions(-)
create mode 100644 src/Microsoft.SqlTools.Authentication/Utility/ProxyHttpClientFactory.cs
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();