From b0dd9eb75f6a526d254767ca52df6cf746f8c83b Mon Sep 17 00:00:00 2001 From: Vincent Croquette Date: Thu, 18 Apr 2024 13:05:30 +0200 Subject: [PATCH 1/9] Added default proxy support --- Snowflake.Data.Tests/SFBaseTest.cs | 40 ++++---- .../UnitTests/HttpUtilTest.cs | 45 +++++++-- Snowflake.Data/Core/HttpUtil.cs | 95 ++++++++++--------- .../Session/SFSessionHttpClientProperties.cs | 1 + .../SFSessionHttpClientProxyProperties.cs | 9 +- 5 files changed, 117 insertions(+), 73 deletions(-) diff --git a/Snowflake.Data.Tests/SFBaseTest.cs b/Snowflake.Data.Tests/SFBaseTest.cs index 6aacb94f9..12530a7c7 100755 --- a/Snowflake.Data.Tests/SFBaseTest.cs +++ b/Snowflake.Data.Tests/SFBaseTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ @@ -25,9 +25,9 @@ namespace Snowflake.Data.Tests using Newtonsoft.Json.Serialization; /* - * This is the base class for all tests that call blocking methods in the library - it uses MockSynchronizationContext to verify that + * This is the base class for all tests that call blocking methods in the library - it uses MockSynchronizationContext to verify that * there are no async deadlocks in the library - * + * */ [TestFixture] public class SFBaseTest : SFBaseTestAsync @@ -47,14 +47,14 @@ public static void TearDownContext() /* * This is the base class for all tests that call async methods in the library - it does not use a special SynchronizationContext - * + * */ [TestFixture] [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] [SetCulture("en-US")] - #if !SEQUENTIAL_TEST_RUN +#if !SEQUENTIAL_TEST_RUN [Parallelizable(ParallelScope.All)] - #endif +#endif public class SFBaseTestAsync { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); @@ -65,12 +65,12 @@ public class SFBaseTestAsync protected virtual string TestName => TestContext.CurrentContext.Test.MethodName; protected string TestNameWithWorker => TestName + TestContext.CurrentContext.WorkerId?.Replace("#", "_"); protected string TableName => TestNameWithWorker; - + private Stopwatch _stopwatch; private List _tablesToRemove; - + [SetUp] public void BeforeTest() { @@ -93,7 +93,7 @@ private void RemoveTables() { if (_tablesToRemove.Count == 0) return; - + using (var conn = new SnowflakeDbConnection(ConnectionString)) { conn.Open(); @@ -148,26 +148,26 @@ public SFBaseTestAsync() string.Format(ConnectionStringSnowflakeAuthFmt, testConfig.user, testConfig.password); - + protected string ConnectionStringWithInvalidUserName => ConnectionStringWithoutAuth + string.Format(ConnectionStringSnowflakeAuthFmt, "unknown", testConfig.password); protected TestConfig testConfig { get; } - + protected string ResolveHost() { return testConfig.host ?? $"{testConfig.account}.snowflakecomputing.com"; } } - + [SetUpFixture] public class TestEnvironment { - private const string ConnectionStringFmt = "scheme={0};host={1};port={2};" + + private const string ConnectionStringFmt = "scheme={0};host={1};port={2};" + "account={3};role={4};db={5};warehouse={6};user={7};password={8};"; - + public static TestConfig TestConfig { get; private set; } private static Dictionary s_testPerformance; @@ -201,7 +201,7 @@ public void Setup() var testConfigString = reader.ReadToEnd(); - // Local JSON settings to avoid using system wide settings which could be different + // Local JSON settings to avoid using system wide settings which could be different // than the default ones var jsonSettings = new JsonSerializerSettings { @@ -221,16 +221,16 @@ public void Setup() { Assert.Fail("Failed to load test configuration"); } - + ModifySchema(TestConfig.schema, SchemaAction.CREATE); } - + [OneTimeTearDown] public void Cleanup() { ModifySchema(TestConfig.schema, SchemaAction.DROP); } - + [OneTimeSetUp] public void SetupTestPerformance() { @@ -243,12 +243,12 @@ public void CreateTestTimeArtifact() var resultText = "test;time_in_ms\n"; resultText += string.Join("\n", s_testPerformance.Select(test => $"{test.Key};{Math.Round(test.Value.TotalMilliseconds,0)}")); - + var dotnetVersion = Environment.GetEnvironmentVariable("net_version"); var cloudEnv = Environment.GetEnvironmentVariable("snowflake_cloud_env"); var separator = Path.DirectorySeparatorChar; - + // We have to go up 3 times as the working directory path looks as follows: // Snowflake.Data.Tests/bin/debug/{.net_version}/ File.WriteAllText($"..{separator}..{separator}..{separator}{GetOs()}_{dotnetVersion}_{cloudEnv}_performance.csv", resultText); diff --git a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs index 668db5f91..911716a91 100644 --- a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs +++ b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2022 Snowflake Computing Inc. All rights reserved. */ @@ -102,48 +102,75 @@ public void TestGetJitter(int seconds) } [Test] - public void ShouldCreateHttpClientHandlerWithProxy() + public void ShouldCreateHttpClientHandlerWithExplicitProxy() { // given var config = new HttpClientConfig( + true, true, "snowflake.com", "123", "testUser", "proxyPassword", - "localhost", + "localhost", false, false, 7 ); - + // when var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); - + // then Assert.IsTrue(handler.UseProxy); Assert.IsNotNull(handler.Proxy); } [Test] - public void ShouldCreateHttpClientHandlerWithoutProxy() + public void ShouldCreateHttpClientHandlerWithImplicitProxy() { // given var config = new HttpClientConfig( + true, true, null, null, null, null, - null, + null, + false, + false, + 7 + ); + + // when + var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); + + // then + Assert.IsTrue(handler.UseProxy); + Assert.IsNull(handler.Proxy); + } + + [Test] + public void ShouldCreateHttpClientHandlerWithoutProxy() + { + // given + var config = new HttpClientConfig( + false, + false, + null, + null, + null, + null, + null, false, false, 0 ); - + // when var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); - + // then Assert.IsFalse(handler.UseProxy); Assert.IsNull(handler.Proxy); diff --git a/Snowflake.Data/Core/HttpUtil.cs b/Snowflake.Data/Core/HttpUtil.cs index 531e76fd7..f7a2cd2f9 100755 --- a/Snowflake.Data/Core/HttpUtil.cs +++ b/Snowflake.Data/Core/HttpUtil.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ @@ -21,6 +21,7 @@ public class HttpClientConfig { public HttpClientConfig( bool crlCheckEnabled, + bool useProxy, string proxyHost, string proxyPort, string proxyUser, @@ -32,6 +33,7 @@ public HttpClientConfig( bool includeRetryReason = true) { CrlCheckEnabled = crlCheckEnabled; + UseProxy = useProxy; ProxyHost = proxyHost; ProxyPort = proxyPort; ProxyUser = proxyUser; @@ -45,6 +47,7 @@ public HttpClientConfig( ConfKey = string.Join(";", new string[] { crlCheckEnabled.ToString(), + useProxy.ToString(), proxyHost, proxyPort, proxyUser, @@ -57,6 +60,7 @@ public HttpClientConfig( } public readonly bool CrlCheckEnabled; + public readonly bool UseProxy; public readonly string ProxyHost; public readonly string ProxyPort; public readonly string ProxyUser; @@ -87,7 +91,7 @@ public sealed class HttpUtil private HttpUtil() { - // This value is used by AWS SDK and can cause deadlock, + // This value is used by AWS SDK and can cause deadlock, // so we need to increase the default value of 2 // See: https://github.com/aws/aws-sdk-net/issues/152 ServicePointManager.DefaultConnectionLimit = 50; @@ -130,7 +134,7 @@ private HttpClient RegisterNewHttpClientIfNecessary(HttpClientConfig config) internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) { - HttpMessageHandler httpHandler; + HttpClientHandler httpHandler; try { httpHandler = new HttpClientHandler() @@ -140,8 +144,7 @@ internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) // Enforce tls v1.2 SslProtocols = SslProtocols.Tls12, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - UseCookies = false, // Disable cookies - UseProxy = false + UseCookies = false // Disable cookies }; } // special logic for .NET framework 4.7.1 that @@ -151,56 +154,62 @@ internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) httpHandler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - UseCookies = false, // Disable cookies - UseProxy = false + UseCookies = false // Disable cookies }; } // Add a proxy if necessary - if (null != config.ProxyHost) + if (!config.UseProxy) + { + httpHandler.UseProxy = false; + } + else { // Proxy needed - WebProxy proxy = new WebProxy(config.ProxyHost, int.Parse(config.ProxyPort)); + httpHandler.UseProxy = true; - // Add credential if provided - if (!String.IsNullOrEmpty(config.ProxyUser)) + if (!String.IsNullOrEmpty(config.ProxyHost)) { - ICredentials credentials = new NetworkCredential(config.ProxyUser, config.ProxyPassword); - proxy.Credentials = credentials; - } + // Host explicitly specified, do not use default proxy + WebProxy proxy = new WebProxy(config.ProxyHost, int.Parse(config.ProxyPort)); - // Add bypasslist if provided - if (!String.IsNullOrEmpty(config.NoProxyList)) - { - string[] bypassList = config.NoProxyList.Split( - new char[] { '|' }, - StringSplitOptions.RemoveEmptyEntries); - // Convert simplified syntax to standard regular expression syntax - string entry = null; - for (int i = 0; i < bypassList.Length; i++) + // Add credential if provided + if (!String.IsNullOrEmpty(config.ProxyUser)) + { + ICredentials credentials = new NetworkCredential(config.ProxyUser, config.ProxyPassword); + proxy.Credentials = credentials; + } + + // Add bypasslist if provided + if (!String.IsNullOrEmpty(config.NoProxyList)) { - // Get the original entry - entry = bypassList[i].Trim(); - // . -> [.] because . means any char - entry = entry.Replace(".", "[.]"); - // * -> .* because * is a quantifier and need a char or group to apply to - entry = entry.Replace("*", ".*"); - - entry = entry.StartsWith("^") ? entry : $"^{entry}"; - - entry = entry.EndsWith("$") ? entry : $"{entry}$"; - - // Replace with the valid entry syntax - bypassList[i] = entry; + string[] bypassList = config.NoProxyList.Split( + new char[] { '|' }, + StringSplitOptions.RemoveEmptyEntries); + // Convert simplified syntax to standard regular expression syntax + string entry = null; + for (int i = 0; i < bypassList.Length; i++) + { + // Get the original entry + entry = bypassList[i].Trim(); + // . -> [.] because . means any char + entry = entry.Replace(".", "[.]"); + // * -> .* because * is a quantifier and need a char or group to apply to + entry = entry.Replace("*", ".*"); + + entry = entry.StartsWith("^") ? entry : $"^{entry}"; + + entry = entry.EndsWith("$") ? entry : $"{entry}$"; + + // Replace with the valid entry syntax + bypassList[i] = entry; + } + proxy.BypassList = bypassList; } - proxy.BypassList = bypassList; - } - HttpClientHandler httpHandlerWithProxy = (HttpClientHandler)httpHandler; - httpHandlerWithProxy.UseProxy = true; - httpHandlerWithProxy.Proxy = proxy; - return httpHandlerWithProxy; + httpHandler.Proxy = proxy; + } } return httpHandler; } @@ -384,7 +393,7 @@ protected override async Task SendAsync(HttpRequestMessage if (httpTimeout.Ticks == 0) childCts.Cancel(); else - childCts.CancelAfter(httpTimeout); + childCts.CancelAfter(httpTimeout); } response = await base.SendAsync(requestMessage, childCts == null ? cancellationToken : childCts.Token).ConfigureAwait(false); diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs index f129de25a..abe08554c 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs @@ -76,6 +76,7 @@ internal HttpClientConfig BuildHttpClientConfig() { return new HttpClientConfig( !insecureMode, + proxyProperties.useProxy, proxyProperties.proxyHost, proxyProperties.proxyPort, proxyProperties.proxyUser, diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs index 4266c585d..b1d23efeb 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs @@ -6,6 +6,7 @@ namespace Snowflake.Data.Core internal class SFSessionHttpClientProxyProperties { + internal bool useProxy = false; internal string proxyHost = null; internal string proxyPort = null; internal string nonProxyHosts = null; @@ -22,7 +23,13 @@ internal class Extractor : IExtractor public SFSessionHttpClientProxyProperties ExtractProperties(SFSessionProperties propertiesDictionary) { var properties = new SFSessionHttpClientProxyProperties(); - if (Boolean.Parse(propertiesDictionary[SFSessionProperty.USEPROXY])) + + if (propertiesDictionary.TryGetValue(SFSessionProperty.USEPROXY, out string stringUseProxy)) + { + properties.useProxy = Boolean.Parse(stringUseProxy); + } + + if (properties.useProxy) { // Let's try to get the associated RestRequester propertiesDictionary.TryGetValue(SFSessionProperty.PROXYHOST, out properties.proxyHost); From 25507747d52bb060ec25212d00e520279395b77f Mon Sep 17 00:00:00 2001 From: Vincent Croquette Date: Thu, 18 Apr 2024 13:52:13 +0200 Subject: [PATCH 2/9] Updated readme --- README.md | 4 ++-- Snowflake.Data/Snowflake.Data.csproj | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7c7e65356..55a659f1b 100644 --- a/README.md +++ b/README.md @@ -159,8 +159,8 @@ The following table lists all valid connection properties: | TOKEN | Depends | The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth. | | INSECUREMODE | No | Set to true to disable the certificate revocation list check. Default is false. | | USEPROXY | No | Set to true if you need to use a proxy server. The default value is false.

This parameter was introduced in v2.0.4. | -| PROXYHOST | Depends | The hostname of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | -| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | +| PROXYHOST | No | The hostname of the proxy server.

If USEPROXY is set to `true`, you can set this parameter to define the proxy explicitly. If not set, the default proxy will be used (HTTPS_PROXY, WinINet...).

This parameter was introduced in v2.0.4. | +| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true` and PROXYHOST is specfied, you must set this parameter.

This parameter was introduced in v2.0.4. | | PROXYUSER | No | The username for authenticating to the proxy server.

This parameter was introduced in v2.0.4. | | PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYUSER is set, you must set this parameter.

This parameter was introduced in v2.0.4. | | NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index 0621c5fb0..890bedc1f 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -13,11 +13,11 @@ Snowflake Connector for .NET howryu, tchen Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. - 3.1.0 + 3.2.0 Full 7.3 - + @@ -31,7 +31,7 @@ - + @@ -45,7 +45,7 @@ - + full True @@ -55,13 +55,13 @@ full True - + true true $(Version) - + From 80e8ef7cd8d7e9b20f1844169b0b832346365183 Mon Sep 17 00:00:00 2001 From: Vincent Croquette Date: Thu, 18 Apr 2024 14:07:52 +0200 Subject: [PATCH 3/9] Rolled back formatting changes --- Snowflake.Data.Tests/SFBaseTest.cs | 40 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Snowflake.Data.Tests/SFBaseTest.cs b/Snowflake.Data.Tests/SFBaseTest.cs index 12530a7c7..84f83dc4f 100755 --- a/Snowflake.Data.Tests/SFBaseTest.cs +++ b/Snowflake.Data.Tests/SFBaseTest.cs @@ -25,9 +25,9 @@ namespace Snowflake.Data.Tests using Newtonsoft.Json.Serialization; /* - * This is the base class for all tests that call blocking methods in the library - it uses MockSynchronizationContext to verify that + * This is the base class for all tests that call blocking methods in the library - it uses MockSynchronizationContext to verify that * there are no async deadlocks in the library - * + * */ [TestFixture] public class SFBaseTest : SFBaseTestAsync @@ -47,14 +47,14 @@ public static void TearDownContext() /* * This is the base class for all tests that call async methods in the library - it does not use a special SynchronizationContext - * + * */ [TestFixture] [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] [SetCulture("en-US")] -#if !SEQUENTIAL_TEST_RUN + #if !SEQUENTIAL_TEST_RUN [Parallelizable(ParallelScope.All)] -#endif + #endif public class SFBaseTestAsync { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); @@ -65,12 +65,12 @@ public class SFBaseTestAsync protected virtual string TestName => TestContext.CurrentContext.Test.MethodName; protected string TestNameWithWorker => TestName + TestContext.CurrentContext.WorkerId?.Replace("#", "_"); protected string TableName => TestNameWithWorker; - + private Stopwatch _stopwatch; private List _tablesToRemove; - + [SetUp] public void BeforeTest() { @@ -93,7 +93,7 @@ private void RemoveTables() { if (_tablesToRemove.Count == 0) return; - + using (var conn = new SnowflakeDbConnection(ConnectionString)) { conn.Open(); @@ -148,26 +148,26 @@ public SFBaseTestAsync() string.Format(ConnectionStringSnowflakeAuthFmt, testConfig.user, testConfig.password); - + protected string ConnectionStringWithInvalidUserName => ConnectionStringWithoutAuth + string.Format(ConnectionStringSnowflakeAuthFmt, "unknown", testConfig.password); protected TestConfig testConfig { get; } - + protected string ResolveHost() { return testConfig.host ?? $"{testConfig.account}.snowflakecomputing.com"; } } - + [SetUpFixture] public class TestEnvironment { - private const string ConnectionStringFmt = "scheme={0};host={1};port={2};" + + private const string ConnectionStringFmt = "scheme={0};host={1};port={2};" + "account={3};role={4};db={5};warehouse={6};user={7};password={8};"; - + public static TestConfig TestConfig { get; private set; } private static Dictionary s_testPerformance; @@ -201,7 +201,7 @@ public void Setup() var testConfigString = reader.ReadToEnd(); - // Local JSON settings to avoid using system wide settings which could be different + // Local JSON settings to avoid using system wide settings which could be different // than the default ones var jsonSettings = new JsonSerializerSettings { @@ -221,16 +221,16 @@ public void Setup() { Assert.Fail("Failed to load test configuration"); } - + ModifySchema(TestConfig.schema, SchemaAction.CREATE); } - + [OneTimeTearDown] public void Cleanup() { ModifySchema(TestConfig.schema, SchemaAction.DROP); } - + [OneTimeSetUp] public void SetupTestPerformance() { @@ -243,12 +243,12 @@ public void CreateTestTimeArtifact() var resultText = "test;time_in_ms\n"; resultText += string.Join("\n", s_testPerformance.Select(test => $"{test.Key};{Math.Round(test.Value.TotalMilliseconds,0)}")); - + var dotnetVersion = Environment.GetEnvironmentVariable("net_version"); var cloudEnv = Environment.GetEnvironmentVariable("snowflake_cloud_env"); var separator = Path.DirectorySeparatorChar; - + // We have to go up 3 times as the working directory path looks as follows: // Snowflake.Data.Tests/bin/debug/{.net_version}/ File.WriteAllText($"..{separator}..{separator}..{separator}{GetOs()}_{dotnetVersion}_{cloudEnv}_performance.csv", resultText); @@ -444,4 +444,4 @@ public void AfterTest(ITest test) public ActionTargets Targets => ActionTargets.Test | ActionTargets.Suite; } -} +} \ No newline at end of file From cbf6afd679ab160c977aa771fd539ab6fb450152 Mon Sep 17 00:00:00 2001 From: Vincent Croquette Date: Sun, 28 Jul 2024 08:10:47 +0200 Subject: [PATCH 4/9] Make changes requested in PR --- Snowflake.Data.Tests/SFBaseTest.cs | 2 +- .../SFSessionHttpClientProxyProperties.cs | 5 +- Snowflake.Data/Snowflake.Data.csproj | 118 +++++++++--------- 3 files changed, 61 insertions(+), 64 deletions(-) diff --git a/Snowflake.Data.Tests/SFBaseTest.cs b/Snowflake.Data.Tests/SFBaseTest.cs index 84f83dc4f..711960007 100755 --- a/Snowflake.Data.Tests/SFBaseTest.cs +++ b/Snowflake.Data.Tests/SFBaseTest.cs @@ -444,4 +444,4 @@ public void AfterTest(ITest test) public ActionTargets Targets => ActionTargets.Test | ActionTargets.Suite; } -} \ No newline at end of file +} diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs index b1d23efeb..5f5ee7a28 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs @@ -24,10 +24,7 @@ public SFSessionHttpClientProxyProperties ExtractProperties(SFSessionProperties { var properties = new SFSessionHttpClientProxyProperties(); - if (propertiesDictionary.TryGetValue(SFSessionProperty.USEPROXY, out string stringUseProxy)) - { - properties.useProxy = Boolean.Parse(stringUseProxy); - } + properties.useProxy = Boolean.Parse(propertiesDictionary[SFSessionProperty.USEPROXY]); if (properties.useProxy) { diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index 890bedc1f..73d48e249 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -1,68 +1,68 @@ - - - net6.0;net471;net472 - net6.0 - Snowflake.Data - Snowflake.Data - https://github.com/snowflakedb/snowflake-connector-net/blob/master/LICENSE - https://github.com/snowflakedb/snowflake-connector-net - true - https://raw.githubusercontent.com/snowflakedb/snowflake-connector-net/master/Snowflake.Data/snowflake.ico - Snowflake Connector for .NET - Snowflake Computing, Inc - Snowflake Connector for .NET - howryu, tchen - Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. - 3.2.0 - Full - 7.3 - + + + net6.0;net471;net472 + net6.0 + Snowflake.Data + Snowflake.Data + https://github.com/snowflakedb/snowflake-connector-net/blob/master/LICENSE + https://github.com/snowflakedb/snowflake-connector-net + true + https://raw.githubusercontent.com/snowflakedb/snowflake-connector-net/master/Snowflake.Data/snowflake.ico + Snowflake Connector for .NET + Snowflake Computing, Inc + Snowflake Connector for .NET + howryu, tchen + Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. + 3.1.0 + Full + 7.3 + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + - - - + + + - - - - - + + + + + - - full - True - + + full + True + - - full - True - + + full + True + - - true - true - $(Version) - + + true + true + $(Version) + - - - + + + From 5a216c7c72251f4338b4a8dd205a8260a173a857 Mon Sep 17 00:00:00 2001 From: Vincent Croquette Date: Sun, 28 Jul 2024 08:13:46 +0200 Subject: [PATCH 5/9] Fixed project formatting --- Snowflake.Data/Snowflake.Data.csproj | 116 +++++++++++++-------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index 73d48e249..2c37269be 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -1,68 +1,68 @@ - - net6.0;net471;net472 - net6.0 - Snowflake.Data - Snowflake.Data - https://github.com/snowflakedb/snowflake-connector-net/blob/master/LICENSE - https://github.com/snowflakedb/snowflake-connector-net - true - https://raw.githubusercontent.com/snowflakedb/snowflake-connector-net/master/Snowflake.Data/snowflake.ico - Snowflake Connector for .NET - Snowflake Computing, Inc - Snowflake Connector for .NET - howryu, tchen - Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. - 3.1.0 - Full - 7.3 - + + net6.0;net471;net472 + net6.0 + Snowflake.Data + Snowflake.Data + https://github.com/snowflakedb/snowflake-connector-net/blob/master/LICENSE + https://github.com/snowflakedb/snowflake-connector-net + true + https://raw.githubusercontent.com/snowflakedb/snowflake-connector-net/master/Snowflake.Data/snowflake.ico + Snowflake Connector for .NET + Snowflake Computing, Inc + Snowflake Connector for .NET + howryu, tchen + Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. + 3.1.0 + Full + 7.3 + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + - - - + + + - - - - - + + + + + - - full - True - + + full + True + - - full - True - + + full + True + - - true - true - $(Version) - + + true + true + $(Version) + - - - + + + From 568810ca5e4d23fc64278e8a88e028f82fc9d981 Mon Sep 17 00:00:00 2001 From: Krzysztof Nozderko Date: Mon, 29 Jul 2024 11:58:13 +0000 Subject: [PATCH 6/9] add allowEmptyProxy parameter --- .../UnitTests/HttpUtilTest.cs | 50 ++++++++--- .../UnitTests/SFSessionPropertyTest.cs | 49 ++++++++--- Snowflake.Data/Core/HttpUtil.cs | 88 ++++++++++--------- .../Session/SFSessionHttpClientProperties.cs | 1 + .../SFSessionHttpClientProxyProperties.cs | 4 +- .../Core/Session/SFSessionProperty.cs | 4 +- 6 files changed, 130 insertions(+), 66 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs index 911716a91..1bc8bdd26 100644 --- a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs +++ b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs @@ -1,18 +1,17 @@ -/* +/* * Copyright (c) 2022 Snowflake Computing Inc. All rights reserved. */ using System.Net.Http; +using NUnit.Framework; +using Snowflake.Data.Core; +using RichardSzalay.MockHttp; +using System.Threading.Tasks; +using System.Net; +using System; namespace Snowflake.Data.Tests.UnitTests { - using NUnit.Framework; - using Snowflake.Data.Core; - using RichardSzalay.MockHttp; - using System.Threading.Tasks; - using System.Net; - using System; - [TestFixture] class HttpUtilTest { @@ -102,11 +101,12 @@ public void TestGetJitter(int seconds) } [Test] - public void ShouldCreateHttpClientHandlerWithExplicitProxy() + public void TestCreateHttpClientHandlerWithExplicitProxy([Values] bool allowEmptyProxy) { // given var config = new HttpClientConfig( true, + allowEmptyProxy, true, "snowflake.com", "123", @@ -127,10 +127,11 @@ public void ShouldCreateHttpClientHandlerWithExplicitProxy() } [Test] - public void ShouldCreateHttpClientHandlerWithImplicitProxy() + public void TestCreateHttpClientHandlerWithImplicitProxy() { // given var config = new HttpClientConfig( + true, true, true, null, @@ -152,11 +153,38 @@ public void ShouldCreateHttpClientHandlerWithImplicitProxy() } [Test] - public void ShouldCreateHttpClientHandlerWithoutProxy() + public void TestDoNotCreateHttpClientHandlerWithImplicitProxyWhenEmptyProxyNotAllowed() + { + // given + var config = new HttpClientConfig( + true, + false, + true, + null, + null, + null, + null, + null, + false, + false, + 7 + ); + + // when + var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); + + // then + Assert.IsFalse(handler.UseProxy); + Assert.IsNull(handler.Proxy); + } + + [Test] + public void ShouldCreateHttpClientHandlerWithoutProxy([Values] bool allowEmptyProxy) { // given var config = new HttpClientConfig( false, + allowEmptyProxy, false, null, null, diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index a57a9fb74..2b5fcb2fa 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -166,6 +166,19 @@ public void TestResolveConnectionArea(string host, string expectedMessage) Assert.AreEqual(expectedMessage, message); } + [Test] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;ALLOWEMPTYPROXY=false", "false")] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;ALLOWEMPTYPROXY=true", "true")] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test", "false")] + public void TestParseAllowEmptyProxy(string connectionString, string expectedAllowEmptyProxy) + { + // act + var properties = SFSessionProperties.ParseConnectionString(connectionString, null); + + // assert + Assert.AreEqual(expectedAllowEmptyProxy, properties[SFSessionProperty.ALLOWEMPTYPROXY]); + } + public static IEnumerable ConnectionStringTestCases() { string defAccount = "testaccount"; @@ -222,7 +235,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } } }; @@ -258,7 +272,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } } }; var testCaseWithProxySettings = new TestCase() @@ -296,7 +311,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};useProxy=true;proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -336,7 +352,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -375,7 +392,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } } }; var testCaseWithIncludeRetryReason = new TestCase() @@ -411,7 +429,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } } }; var testCaseWithDisableQueryContextCache = new TestCase() @@ -446,7 +465,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLEQUERYCONTEXTCACHE=true" @@ -483,7 +503,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLE_CONSOLE_LOGIN=false" @@ -522,7 +543,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } } }; var testCaseUnderscoredAccountName = new TestCase() @@ -558,7 +580,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } } }; var testCaseUnderscoredAccountNameWithEnabledAllowUnderscores = new TestCase() @@ -594,7 +617,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } } }; var testQueryTag = "Test QUERY_TAG 12345"; @@ -632,7 +656,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, + { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } } }; diff --git a/Snowflake.Data/Core/HttpUtil.cs b/Snowflake.Data/Core/HttpUtil.cs index f7a2cd2f9..41cd5d0dc 100755 --- a/Snowflake.Data/Core/HttpUtil.cs +++ b/Snowflake.Data/Core/HttpUtil.cs @@ -21,6 +21,7 @@ public class HttpClientConfig { public HttpClientConfig( bool crlCheckEnabled, + bool allowEmptyProxy, bool useProxy, string proxyHost, string proxyPort, @@ -33,6 +34,7 @@ public HttpClientConfig( bool includeRetryReason = true) { CrlCheckEnabled = crlCheckEnabled; + AllowEmptyProxy = allowEmptyProxy; UseProxy = useProxy; ProxyHost = proxyHost; ProxyPort = proxyPort; @@ -47,6 +49,7 @@ public HttpClientConfig( ConfKey = string.Join(";", new string[] { crlCheckEnabled.ToString(), + allowEmptyProxy.ToString(), useProxy.ToString(), proxyHost, proxyPort, @@ -60,6 +63,7 @@ public HttpClientConfig( } public readonly bool CrlCheckEnabled; + public readonly bool AllowEmptyProxy; public readonly bool UseProxy; public readonly string ProxyHost; public readonly string ProxyPort; @@ -158,60 +162,62 @@ internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) }; } - // Add a proxy if necessary - if (!config.UseProxy) + httpHandler.UseProxy = config.UseProxy && (config.AllowEmptyProxy || !string.IsNullOrEmpty(config.ProxyHost)); + + if (httpHandler.UseProxy && !string.IsNullOrEmpty(config.ProxyHost)) { - httpHandler.UseProxy = false; + logger.Info("Configuring proxy based on connection string properties"); + var proxy = ConfigureWebProxy(config); + httpHandler.Proxy = proxy; } - else + else if (httpHandler.UseProxy) { - // Proxy needed - httpHandler.UseProxy = true; + logger.Info("Proxy enabled, but not configured due to allowEmptyProxy property set to true"); + } - if (!String.IsNullOrEmpty(config.ProxyHost)) - { - // Host explicitly specified, do not use default proxy - WebProxy proxy = new WebProxy(config.ProxyHost, int.Parse(config.ProxyPort)); + return httpHandler; + } - // Add credential if provided - if (!String.IsNullOrEmpty(config.ProxyUser)) - { - ICredentials credentials = new NetworkCredential(config.ProxyUser, config.ProxyPassword); - proxy.Credentials = credentials; - } + private WebProxy ConfigureWebProxy(HttpClientConfig config) + { + WebProxy proxy = new WebProxy(config.ProxyHost, int.Parse(config.ProxyPort)); - // Add bypasslist if provided - if (!String.IsNullOrEmpty(config.NoProxyList)) - { - string[] bypassList = config.NoProxyList.Split( - new char[] { '|' }, - StringSplitOptions.RemoveEmptyEntries); - // Convert simplified syntax to standard regular expression syntax - string entry = null; - for (int i = 0; i < bypassList.Length; i++) - { - // Get the original entry - entry = bypassList[i].Trim(); - // . -> [.] because . means any char - entry = entry.Replace(".", "[.]"); - // * -> .* because * is a quantifier and need a char or group to apply to - entry = entry.Replace("*", ".*"); + // Add credential if provided + if (!String.IsNullOrEmpty(config.ProxyUser)) + { + ICredentials credentials = new NetworkCredential(config.ProxyUser, config.ProxyPassword); + proxy.Credentials = credentials; + } - entry = entry.StartsWith("^") ? entry : $"^{entry}"; + // Add bypasslist if provided + if (!String.IsNullOrEmpty(config.NoProxyList)) + { + string[] bypassList = config.NoProxyList.Split( + new char[] { '|' }, + StringSplitOptions.RemoveEmptyEntries); + // Convert simplified syntax to standard regular expression syntax + string entry = null; + for (int i = 0; i < bypassList.Length; i++) + { + // Get the original entry + entry = bypassList[i].Trim(); + // . -> [.] because . means any char + entry = entry.Replace(".", "[.]"); + // * -> .* because * is a quantifier and need a char or group to apply to + entry = entry.Replace("*", ".*"); - entry = entry.EndsWith("$") ? entry : $"{entry}$"; + entry = entry.StartsWith("^") ? entry : $"^{entry}"; - // Replace with the valid entry syntax - bypassList[i] = entry; + entry = entry.EndsWith("$") ? entry : $"{entry}$"; - } - proxy.BypassList = bypassList; - } + // Replace with the valid entry syntax + bypassList[i] = entry; - httpHandler.Proxy = proxy; } + proxy.BypassList = bypassList; } - return httpHandler; + + return proxy; } /// diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs index 6af6f64aa..5e0d1f85b 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs @@ -179,6 +179,7 @@ public HttpClientConfig BuildHttpClientConfig() { return new HttpClientConfig( !insecureMode, + proxyProperties.allowEmptyProxy, proxyProperties.useProxy, proxyProperties.proxyHost, proxyProperties.proxyPort, diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs index 5f5ee7a28..d6e83abf1 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs @@ -6,6 +6,7 @@ namespace Snowflake.Data.Core internal class SFSessionHttpClientProxyProperties { + internal bool allowEmptyProxy = false; internal bool useProxy = false; internal string proxyHost = null; internal string proxyPort = null; @@ -29,6 +30,7 @@ public SFSessionHttpClientProxyProperties ExtractProperties(SFSessionProperties if (properties.useProxy) { // Let's try to get the associated RestRequester + properties.allowEmptyProxy = Boolean.Parse(propertiesDictionary[SFSessionProperty.ALLOWEMPTYPROXY]); propertiesDictionary.TryGetValue(SFSessionProperty.PROXYHOST, out properties.proxyHost); propertiesDictionary.TryGetValue(SFSessionProperty.PROXYPORT, out properties.proxyPort); propertiesDictionary.TryGetValue(SFSessionProperty.NONPROXYHOSTS, out properties.nonProxyHosts); @@ -38,7 +40,7 @@ public SFSessionHttpClientProxyProperties ExtractProperties(SFSessionProperties if (!String.IsNullOrEmpty(properties.nonProxyHosts)) { // The list is url-encoded - // Host names are separated with a URL-escaped pipe symbol (%7C). + // Host names are separated with a URL-escaped pipe symbol (%7C). properties.nonProxyHosts = HttpUtility.UrlDecode(properties.nonProxyHosts); } } diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index bfbe71a2a..338e62539 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -112,7 +112,9 @@ internal enum SFSessionProperty [SFSessionPropertyAttr(required = false, defaultValue = "true")] POOLINGENABLED, [SFSessionPropertyAttr(required = false, defaultValue = "false")] - DISABLE_SAML_URL_CHECK + DISABLE_SAML_URL_CHECK, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] + ALLOWEMPTYPROXY } class SFSessionPropertyAttr : Attribute From c02b82a06a18046675f81177ff95607268bf27e5 Mon Sep 17 00:00:00 2001 From: Krzysztof Nozderko Date: Mon, 29 Jul 2024 12:35:13 +0000 Subject: [PATCH 7/9] document allowEmptyConfig --- doc/Connecting.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/Connecting.md b/doc/Connecting.md index 576120f79..b9c71f77b 100644 --- a/doc/Connecting.md +++ b/doc/Connecting.md @@ -34,10 +34,10 @@ The following table lists all valid connection properties: | TOKEN | Depends | The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth. | | INSECUREMODE | No | Set to true to disable the certificate revocation list check. Default is false. | | USEPROXY | No | Set to true if you need to use a proxy server. The default value is false.

This parameter was introduced in v2.0.4. | -| PROXYHOST | Depends | The hostname of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | -| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | +| PROXYHOST | Depends | The hostname of the proxy server.

Proxy parameters are used only if you enable the proxy by setting USEPROXY to `true`. In such case you should either
(1) specify the value of PROXYHOST property
or (2), if you want to enable the proxy, but you don't want the driver to configure it, you should not provide the value but instead set ALLOWEMPTYPROXY to `true`

This parameter was introduced in v2.0.4. | +| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true` and provided PROXYHOST, you must set this parameter.

This parameter was introduced in v2.0.4. | | PROXYUSER | No | The username for authenticating to the proxy server.

This parameter was introduced in v2.0.4. | -| PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYUSER is set, you must set this parameter.

This parameter was introduced in v2.0.4. | +| PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYHOST and PROXYUSER are set, you must set this parameter.

This parameter was introduced in v2.0.4. | | NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | | FILE_TRANSFER_MEMORY_THRESHOLD | No | The maximum number of bytes to store in memory used in order to provide a file encryption. If encrypting/decrypting file size exceeds provided value a temporary file will be created and the work will be continued in the temporary file instead of memory.
If no value provided 1MB will be used as a default value (that is 1048576 bytes).
It is possible to configure any integer value bigger than zero representing maximal number of bytes to reside in memory. | | CLIENT_CONFIG_FILE | No | The location of the client configuration json file. In this file you can configure easy logging feature. | @@ -50,6 +50,7 @@ The following table lists all valid connection properties: | EXPIRATIONTIMEOUT | No | Timeout for using each connection. Connections which last more than specified timeout are considered to be expired and are being removed from the pool. The default is 1 hour. Usage of units possible and allowed are: e. g. `360000ms` (milliseconds), `3600s` (seconds), `60m` (minutes) where seconds are default for a skipped postfix. Special values: `0` - immediate expiration of the connection just after its creation. Expiration timeout cannot be set to infinity. | | POOLINGENABLED | No | Boolean flag indicating if the connection should be a part of a pool. The default value is `true`. | | DISABLE_SAML_URL_CHECK | No | Specifies whether to check if the saml postback url matches the host url from the connection string. The default value is `false`. | +| ALLOWEMPTYPROXY | No | Set this property to `true` to allow to create an http client with proxy enabled but whitout configuring it. The default value is `false`. If you would specify `ALLOWEMPTYPROXY=true` and `USEPROXY=true` but without specifying `PROXYHOST` the http client will be created with proxy enabled but without being configured. It may be useful if your operating system provides you a default proxy which you would like to use. |
From dd4d836cf4b781574610b5286d5443178baa3ebe Mon Sep 17 00:00:00 2001 From: Krzysztof Nozderko Date: Thu, 1 Aug 2024 12:03:29 +0000 Subject: [PATCH 8/9] Fix validation for proxy --- .../UnitTests/SFSessionPropertyTest.cs | 53 ++++++++++++ .../SFHttpClientProxyPropertiesTest.cs | 41 ++++++++- .../Core/Session/SFSessionProperty.cs | 86 ++++++++++++------- 3 files changed, 145 insertions(+), 35 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index 2b5fcb2fa..6b83a87ad 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -9,6 +9,7 @@ using Snowflake.Data.Client; using Snowflake.Data.Core.Authenticator; using Snowflake.Data.Core.Tools; +using Snowflake.Data.Tests.Util; namespace Snowflake.Data.Tests.UnitTests { @@ -179,6 +180,58 @@ public void TestParseAllowEmptyProxy(string connectionString, string expectedAll Assert.AreEqual(expectedAllowEmptyProxy, properties[SFSessionProperty.ALLOWEMPTYPROXY]); } + [Test] + public void TestFailWhenProxyConfiguredWithoutPort() + { + // arrange + var connectionString = "ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyHost=localhost"; + + // act + var thrown = Assert.Throws(() => SFSessionProperties.ParseConnectionString(connectionString, null)); + + // assert + SnowflakeDbExceptionAssert.HasErrorCode(thrown, SFError.MISSING_CONNECTION_PROPERTY); + Assert.That(thrown.Message, Does.Contain("Required property PROXYPORT is not provided")); + } + + [Test] + public void TestFailWhenProxyUserProvidedWithoutProxyPassword() + { + // arrange + var connectionString = "ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyHost=localhost;proxyPort=1234;proxyUser=testUser"; + + // act + var thrown = Assert.Throws(() => SFSessionProperties.ParseConnectionString(connectionString, null)); + + // assert + SnowflakeDbExceptionAssert.HasErrorCode(thrown, SFError.MISSING_CONNECTION_PROPERTY); + Assert.That(thrown.Message, Does.Contain("Required property PROXYPASSWORD is not provided")); + } + + [Test] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyPort=1234;proxyPassword=xyz", SFSessionProperty.PROXYPORT)] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyUser=testUser;proxyPassword=xyz", SFSessionProperty.PROXYUSER)] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;proxyPassword=xyz", SFSessionProperty.PROXYPASSWORD)] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=true;nonProxyHosts=xyz", SFSessionProperty.NONPROXYHOSTS)] + public void TestFailWhenConfiguringProxyDetailsWithoutProxyHost(string connectionString, SFSessionProperty unwantedProperty) + { + // act + var thrown = Assert.Throws(() => SFSessionProperties.ParseConnectionString(connectionString, null)); + + // assert + SnowflakeDbExceptionAssert.HasErrorCode(thrown, SFError.INVALID_CONNECTION_STRING); + Assert.That(thrown.Message, Does.Contain($"Proxy property {unwantedProperty.ToString()} provided while PROXYHOST is missing")); + } + + [Test] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;proxyHost=localhost")] + [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;useProxy=false;proxyHost=localhost;proxyPort=1234;proxyUser=testUser")] + public void TestProxyValidationsOnlyWhenProxyEnabledAndProxyHostConfigured(string connectionString) + { + // act + Assert.DoesNotThrow(() => SFSessionProperties.ParseConnectionString(connectionString, null)); + } + public static IEnumerable ConnectionStringTestCases() { string defAccount = "testaccount"; diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs index 53941cc27..394f57f63 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs @@ -13,7 +13,7 @@ namespace Snowflake.Data.Tests.UnitTests.Session public class SFHttpClientProxyPropertiesTest { [Test, TestCaseSource(nameof(ProxyPropertiesProvider))] - public void ShouldExtractProxyProperties(ProxyPropertiesTestCase testCase) + public void TestExtractProxyProperties(ProxyPropertiesTestCase testCase) { // given var extractor = new SFSessionHttpClientProxyProperties.Extractor(); @@ -23,6 +23,7 @@ public void ShouldExtractProxyProperties(ProxyPropertiesTestCase testCase) var proxyProperties = extractor.ExtractProperties(properties); // then + Assert.AreEqual(testCase.expectedProperties.useProxy, proxyProperties.useProxy); Assert.AreEqual(testCase.expectedProperties.proxyHost, proxyProperties.proxyHost); Assert.AreEqual(testCase.expectedProperties.proxyPort, proxyProperties.proxyPort); Assert.AreEqual(testCase.expectedProperties.nonProxyHosts, proxyProperties.nonProxyHosts); @@ -37,6 +38,7 @@ public static IEnumerable ProxyPropertiesProvider() conectionString = "account=test;user=test;password=test", expectedProperties = new SFSessionHttpClientProxyProperties() { + useProxy = false, proxyHost = null, proxyPort = null, nonProxyHosts = null, @@ -49,6 +51,20 @@ public static IEnumerable ProxyPropertiesProvider() conectionString = "account=test;user=test;password=test;useProxy=false;proxyHost=snowflake.com;proxyPort=123;nonProxyHosts=localhost;proxyPassword=proxyPassword;proxyUser=Chris", expectedProperties = new SFSessionHttpClientProxyProperties() { + useProxy = false, + proxyHost = null, + proxyPort = null, + nonProxyHosts = null, + proxyPassword = null, + proxyUser = null + } + }; + var proxyPropertiesConfiguredButDisabledCase2 = new ProxyPropertiesTestCase() + { + conectionString = "account=test;user=test;password=test;proxyHost=snowflake.com;proxyPort=123;nonProxyHosts=localhost;proxyPassword=proxyPassword;proxyUser=Chris", + expectedProperties = new SFSessionHttpClientProxyProperties() + { + useProxy = false, proxyHost = null, proxyPort = null, nonProxyHosts = null, @@ -58,11 +74,12 @@ public static IEnumerable ProxyPropertiesProvider() }; var proxyPropertiesConfiguredAndEnabledCase = new ProxyPropertiesTestCase() { - conectionString = "account=test;user=test;password=test;useProxy=true;proxyHost=snowflake.com", + conectionString = "account=test;user=test;password=test;useProxy=true;proxyHost=snowflake.com;proxyPort=1234", expectedProperties = new SFSessionHttpClientProxyProperties() { + useProxy = true, proxyHost = "snowflake.com", - proxyPort = null, + proxyPort = "1234", nonProxyHosts = null, proxyPassword = null, proxyUser = null @@ -74,6 +91,7 @@ public static IEnumerable ProxyPropertiesProvider() "account=test;user=test;password=test;useProxy=true;proxyHost=snowflake.com;proxyPort=123;nonProxyHosts=localhost;proxyPassword=proxyPassword;proxyUser=Chris", expectedProperties = new SFSessionHttpClientProxyProperties() { + useProxy = true, proxyHost = "snowflake.com", proxyPort = "123", nonProxyHosts = "localhost", @@ -81,12 +99,27 @@ public static IEnumerable ProxyPropertiesProvider() proxyUser = "Chris" } }; + var defaultProxyEnabled = new ProxyPropertiesTestCase() + { + conectionString = "account=test;user=test;password=test;useProxy=true;", + expectedProperties = new SFSessionHttpClientProxyProperties() + { + useProxy = true, + proxyHost = null, + proxyPort = null, + nonProxyHosts = null, + proxyPassword = null, + proxyUser = null + } + }; return new [] { noProxyPropertiesCase, proxyPropertiesConfiguredButDisabledCase, + proxyPropertiesConfiguredButDisabledCase2, proxyPropertiesConfiguredAndEnabledCase, - proxyPropertiesAllConfiguredAndEnabled + proxyPropertiesAllConfiguredAndEnabled, + defaultProxyEnabled }; } diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index 338e62539..289b855e6 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -222,37 +222,7 @@ internal static SFSessionProperties ParseConnectionString(string connectionStrin } UpdatePropertiesForSpecialCases(properties, connectionString); - - var useProxy = false; - if (properties.ContainsKey(SFSessionProperty.USEPROXY)) - { - try - { - useProxy = Boolean.Parse(properties[SFSessionProperty.USEPROXY]); - } - catch (Exception e) - { - // The useProxy setting is not a valid boolean value - logger.Error("Unable to connect", e); - throw new SnowflakeDbException(e, - SFError.INVALID_CONNECTION_STRING, - e.Message); - } - } - - // Based on which proxy settings have been provided, update the required settings list - if (useProxy) - { - // If useProxy is true, then proxyhost and proxy port are mandatory - SFSessionProperty.PROXYHOST.GetAttribute().required = true; - SFSessionProperty.PROXYPORT.GetAttribute().required = true; - - // If a username is provided, then a password is required - if (properties.ContainsKey(SFSessionProperty.PROXYUSER)) - { - SFSessionProperty.PROXYPASSWORD.GetAttribute().required = true; - } - } + ValidateProxy(properties); if (password != null && password.Length > 0) { @@ -298,6 +268,60 @@ internal static string ResolveConnectionAreaMessage(string host) => ? "Connecting to CHINA Snowflake domain" : "Connecting to GLOBAL Snowflake domain"; + + private static void ValidateProxy(SFSessionProperties properties) + { + var useProxy = false; + if (properties.ContainsKey(SFSessionProperty.USEPROXY)) + { + try + { + useProxy = Boolean.Parse(properties[SFSessionProperty.USEPROXY]); + } + catch (Exception e) + { + // The useProxy setting is not a valid boolean value + logger.Error("Unable to connect", e); + throw new SnowflakeDbException(e, + SFError.INVALID_CONNECTION_STRING, + e.Message); + } + } + + var isProxyHostProvided = properties.IsNonEmptyValueProvided(SFSessionProperty.PROXYHOST); + if (useProxy && isProxyHostProvided) + { + if (!properties.IsNonEmptyValueProvided(SFSessionProperty.PROXYPORT)) + { + throw new SnowflakeDbException(SFError.MISSING_CONNECTION_PROPERTY, SFSessionProperty.PROXYPORT.ToString()); + } + if (properties.IsNonEmptyValueProvided(SFSessionProperty.PROXYUSER) && + !properties.IsNonEmptyValueProvided(SFSessionProperty.PROXYPASSWORD)) + { + throw new SnowflakeDbException(SFError.MISSING_CONNECTION_PROPERTY, SFSessionProperty.PROXYPASSWORD.ToString()); + } + } + + if (useProxy && !isProxyHostProvided) + { + var property = FindProxyDetailOtherThanHost(properties); + if (property != null) + { + var exception = new Exception($"Proxy property {property.ToString()} provided while {SFSessionProperty.PROXYHOST.ToString()} is missing"); + logger.Error("Unable to connect", exception); + throw new SnowflakeDbException(exception, SFError.INVALID_CONNECTION_STRING, exception.Message); + } + } + } + + private static SFSessionProperty? FindProxyDetailOtherThanHost(SFSessionProperties properties) + { + var proxyProperties = new[] { SFSessionProperty.PROXYPORT, SFSessionProperty.PROXYUSER, SFSessionProperty.PROXYPASSWORD, SFSessionProperty.NONPROXYHOSTS } + .Where(properties.IsNonEmptyValueProvided) + .ToArray(); + return proxyProperties.Length == 0 ? (SFSessionProperty?) null : proxyProperties[0]; + } + private static void ValidateAuthenticator(SFSessionProperties properties) { var knownAuthenticators = new[] { From 737d695ad83f9c69d2e07165418449cdc9b52683 Mon Sep 17 00:00:00 2001 From: Krzysztof Nozderko Date: Thu, 1 Aug 2024 12:37:50 +0000 Subject: [PATCH 9/9] remove allowEmptyProxyConfig parameter --- .../UnitTests/HttpUtilTest.cs | 30 +++++------- .../UnitTests/SFSessionPropertyTest.cs | 49 +++++-------------- Snowflake.Data/Core/HttpUtil.cs | 14 ++---- .../Session/SFSessionHttpClientProperties.cs | 1 - .../SFSessionHttpClientProxyProperties.cs | 2 - .../Core/Session/SFSessionProperty.cs | 4 +- doc/Connecting.md | 11 ++--- 7 files changed, 36 insertions(+), 75 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs index 1bc8bdd26..58be6fdf7 100644 --- a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs +++ b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs @@ -101,12 +101,11 @@ public void TestGetJitter(int seconds) } [Test] - public void TestCreateHttpClientHandlerWithExplicitProxy([Values] bool allowEmptyProxy) + public void TestCreateHttpClientHandlerWithExplicitProxy() { // given var config = new HttpClientConfig( true, - allowEmptyProxy, true, "snowflake.com", "123", @@ -127,11 +126,10 @@ public void TestCreateHttpClientHandlerWithExplicitProxy([Values] bool allowEmpt } [Test] - public void TestCreateHttpClientHandlerWithImplicitProxy() + public void TestCreateHttpClientHandlerWithDefaultProxy() { // given var config = new HttpClientConfig( - true, true, true, null, @@ -153,13 +151,12 @@ public void TestCreateHttpClientHandlerWithImplicitProxy() } [Test] - public void TestDoNotCreateHttpClientHandlerWithImplicitProxyWhenEmptyProxyNotAllowed() + public void TestCreateHttpClientHandlerWithoutProxy() { // given var config = new HttpClientConfig( - true, false, - true, + false, null, null, null, @@ -167,7 +164,7 @@ public void TestDoNotCreateHttpClientHandlerWithImplicitProxyWhenEmptyProxyNotAl null, false, false, - 7 + 0 ); // when @@ -179,21 +176,20 @@ public void TestDoNotCreateHttpClientHandlerWithImplicitProxyWhenEmptyProxyNotAl } [Test] - public void ShouldCreateHttpClientHandlerWithoutProxy([Values] bool allowEmptyProxy) + public void TestIgnoreProxyDetailsIfProxyDisabled() { // given var config = new HttpClientConfig( + true, false, - allowEmptyProxy, - false, - null, - null, - null, - null, - null, + "snowflake.com", + "123", + "testUser", + "proxyPassword", + "localhost", false, false, - 0 + 7 ); // when diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index 6b83a87ad..c4e5e7642 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -167,19 +167,6 @@ public void TestResolveConnectionArea(string host, string expectedMessage) Assert.AreEqual(expectedMessage, message); } - [Test] - [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;ALLOWEMPTYPROXY=false", "false")] - [TestCase("ACCOUNT=account;USER=test;PASSWORD=test;ALLOWEMPTYPROXY=true", "true")] - [TestCase("ACCOUNT=account;USER=test;PASSWORD=test", "false")] - public void TestParseAllowEmptyProxy(string connectionString, string expectedAllowEmptyProxy) - { - // act - var properties = SFSessionProperties.ParseConnectionString(connectionString, null); - - // assert - Assert.AreEqual(expectedAllowEmptyProxy, properties[SFSessionProperty.ALLOWEMPTYPROXY]); - } - [Test] public void TestFailWhenProxyConfiguredWithoutPort() { @@ -288,8 +275,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } } }; @@ -325,8 +311,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } } }; var testCaseWithProxySettings = new TestCase() @@ -364,8 +349,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};useProxy=true;proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -405,8 +389,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -445,8 +428,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } } }; var testCaseWithIncludeRetryReason = new TestCase() @@ -482,8 +464,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } } }; var testCaseWithDisableQueryContextCache = new TestCase() @@ -518,8 +499,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLEQUERYCONTEXTCACHE=true" @@ -556,8 +536,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLE_CONSOLE_LOGIN=false" @@ -596,8 +575,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } } }; var testCaseUnderscoredAccountName = new TestCase() @@ -633,8 +611,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } } }; var testCaseUnderscoredAccountNameWithEnabledAllowUnderscores = new TestCase() @@ -670,8 +647,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } } }; var testQueryTag = "Test QUERY_TAG 12345"; @@ -709,8 +685,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) }, - { SFSessionProperty.ALLOWEMPTYPROXY, DefaultValue(SFSessionProperty.ALLOWEMPTYPROXY) } + { SFSessionProperty.DISABLE_SAML_URL_CHECK, DefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) } } }; diff --git a/Snowflake.Data/Core/HttpUtil.cs b/Snowflake.Data/Core/HttpUtil.cs index 41cd5d0dc..cad58061d 100755 --- a/Snowflake.Data/Core/HttpUtil.cs +++ b/Snowflake.Data/Core/HttpUtil.cs @@ -21,7 +21,6 @@ public class HttpClientConfig { public HttpClientConfig( bool crlCheckEnabled, - bool allowEmptyProxy, bool useProxy, string proxyHost, string proxyPort, @@ -34,7 +33,6 @@ public HttpClientConfig( bool includeRetryReason = true) { CrlCheckEnabled = crlCheckEnabled; - AllowEmptyProxy = allowEmptyProxy; UseProxy = useProxy; ProxyHost = proxyHost; ProxyPort = proxyPort; @@ -49,7 +47,6 @@ public HttpClientConfig( ConfKey = string.Join(";", new string[] { crlCheckEnabled.ToString(), - allowEmptyProxy.ToString(), useProxy.ToString(), proxyHost, proxyPort, @@ -63,7 +60,6 @@ public HttpClientConfig( } public readonly bool CrlCheckEnabled; - public readonly bool AllowEmptyProxy; public readonly bool UseProxy; public readonly string ProxyHost; public readonly string ProxyPort; @@ -162,17 +158,17 @@ internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) }; } - httpHandler.UseProxy = config.UseProxy && (config.AllowEmptyProxy || !string.IsNullOrEmpty(config.ProxyHost)); + httpHandler.UseProxy = config.UseProxy; - if (httpHandler.UseProxy && !string.IsNullOrEmpty(config.ProxyHost)) + if (config.UseProxy && !string.IsNullOrEmpty(config.ProxyHost)) { - logger.Info("Configuring proxy based on connection string properties"); + logger.Info("Configuring proxy based on connection properties"); var proxy = ConfigureWebProxy(config); httpHandler.Proxy = proxy; } - else if (httpHandler.UseProxy) + else if (config.UseProxy) { - logger.Info("Proxy enabled, but not configured due to allowEmptyProxy property set to true"); + logger.Info("Using a default proxy"); } return httpHandler; diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs index 5e0d1f85b..6af6f64aa 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs @@ -179,7 +179,6 @@ public HttpClientConfig BuildHttpClientConfig() { return new HttpClientConfig( !insecureMode, - proxyProperties.allowEmptyProxy, proxyProperties.useProxy, proxyProperties.proxyHost, proxyProperties.proxyPort, diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs index d6e83abf1..ce63b098d 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs @@ -6,7 +6,6 @@ namespace Snowflake.Data.Core internal class SFSessionHttpClientProxyProperties { - internal bool allowEmptyProxy = false; internal bool useProxy = false; internal string proxyHost = null; internal string proxyPort = null; @@ -30,7 +29,6 @@ public SFSessionHttpClientProxyProperties ExtractProperties(SFSessionProperties if (properties.useProxy) { // Let's try to get the associated RestRequester - properties.allowEmptyProxy = Boolean.Parse(propertiesDictionary[SFSessionProperty.ALLOWEMPTYPROXY]); propertiesDictionary.TryGetValue(SFSessionProperty.PROXYHOST, out properties.proxyHost); propertiesDictionary.TryGetValue(SFSessionProperty.PROXYPORT, out properties.proxyPort); propertiesDictionary.TryGetValue(SFSessionProperty.NONPROXYHOSTS, out properties.nonProxyHosts); diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index 289b855e6..3d850a561 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -112,9 +112,7 @@ internal enum SFSessionProperty [SFSessionPropertyAttr(required = false, defaultValue = "true")] POOLINGENABLED, [SFSessionPropertyAttr(required = false, defaultValue = "false")] - DISABLE_SAML_URL_CHECK, - [SFSessionPropertyAttr(required = false, defaultValue = "false")] - ALLOWEMPTYPROXY + DISABLE_SAML_URL_CHECK } class SFSessionPropertyAttr : Attribute diff --git a/doc/Connecting.md b/doc/Connecting.md index b9c71f77b..9982da2e6 100644 --- a/doc/Connecting.md +++ b/doc/Connecting.md @@ -34,11 +34,11 @@ The following table lists all valid connection properties: | TOKEN | Depends | The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth. | | INSECUREMODE | No | Set to true to disable the certificate revocation list check. Default is false. | | USEPROXY | No | Set to true if you need to use a proxy server. The default value is false.

This parameter was introduced in v2.0.4. | -| PROXYHOST | Depends | The hostname of the proxy server.

Proxy parameters are used only if you enable the proxy by setting USEPROXY to `true`. In such case you should either
(1) specify the value of PROXYHOST property
or (2), if you want to enable the proxy, but you don't want the driver to configure it, you should not provide the value but instead set ALLOWEMPTYPROXY to `true`

This parameter was introduced in v2.0.4. | -| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true` and provided PROXYHOST, you must set this parameter.

This parameter was introduced in v2.0.4. | -| PROXYUSER | No | The username for authenticating to the proxy server.

This parameter was introduced in v2.0.4. | -| PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYHOST and PROXYUSER are set, you must set this parameter.

This parameter was introduced in v2.0.4. | -| NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | +| PROXYHOST | Depends | The hostname of the proxy server.

Proxy parameters are used only if you enable the proxy by setting USEPROXY to `true`. It is required if you provide your own proxy configuration. Do not provide this value if you want to use a default proxy.

This parameter was introduced in v2.0.4. | +| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true` and PROXYHOST is provided, you must set this parameter.

This parameter was introduced in v2.0.4. | +| PROXYUSER | No | The username for authenticating to the proxy server. Parameter is used only if USEPROXY is set to `true` and PROXYHOST is provided.

This parameter was introduced in v2.0.4. | +| PROXYPASSWORD | Depends | The password for authenticating to the proxy server. Parameter is used only if USEPROXY is set to `true` and PROXYHOST is provided.

If USEPROXY is `true` and PROXYHOST and PROXYUSER are set, you must set this parameter.

This parameter was introduced in v2.0.4. | +| NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server.
The parameter is used only if USEPROXY is set to `true` and PROXYHOST is provided.
Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | | FILE_TRANSFER_MEMORY_THRESHOLD | No | The maximum number of bytes to store in memory used in order to provide a file encryption. If encrypting/decrypting file size exceeds provided value a temporary file will be created and the work will be continued in the temporary file instead of memory.
If no value provided 1MB will be used as a default value (that is 1048576 bytes).
It is possible to configure any integer value bigger than zero representing maximal number of bytes to reside in memory. | | CLIENT_CONFIG_FILE | No | The location of the client configuration json file. In this file you can configure easy logging feature. | | ALLOWUNDERSCORESINHOST | No | Specifies whether to allow underscores in account names. This impacts PrivateLink customers whose account names contain underscores. In this situation, you must override the default value by setting allowUnderscoresInHost to true. | @@ -50,7 +50,6 @@ The following table lists all valid connection properties: | EXPIRATIONTIMEOUT | No | Timeout for using each connection. Connections which last more than specified timeout are considered to be expired and are being removed from the pool. The default is 1 hour. Usage of units possible and allowed are: e. g. `360000ms` (milliseconds), `3600s` (seconds), `60m` (minutes) where seconds are default for a skipped postfix. Special values: `0` - immediate expiration of the connection just after its creation. Expiration timeout cannot be set to infinity. | | POOLINGENABLED | No | Boolean flag indicating if the connection should be a part of a pool. The default value is `true`. | | DISABLE_SAML_URL_CHECK | No | Specifies whether to check if the saml postback url matches the host url from the connection string. The default value is `false`. | -| ALLOWEMPTYPROXY | No | Set this property to `true` to allow to create an http client with proxy enabled but whitout configuring it. The default value is `false`. If you would specify `ALLOWEMPTYPROXY=true` and `USEPROXY=true` but without specifying `PROXYHOST` the http client will be created with proxy enabled but without being configured. It may be useful if your operating system provides you a default proxy which you would like to use. |