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[] {