From 7123341c43c1909908759724156b9b2b96653f6a Mon Sep 17 00:00:00 2001 From: Juan Martinez Ramirez Date: Thu, 22 Aug 2024 15:19:37 -0600 Subject: [PATCH] Applying PR suggestions --- .../EasyLoggingConfigFinderTest.cs | 2 +- .../UnitTests/SnowflakeDbConnectionTest.cs | 22 +-- .../SnowflakeTomlConnectionBuilderTest.cs | 147 +++++++----------- .../UnitTests/Tools/FileOperationsTest.cs | 24 ++- .../UnitTests/Tools/UnixOperationsTest.cs | 13 +- .../Client/SnowflakeDbConnection.cs | 24 +-- Snowflake.Data/Core/EnvironmentVariables.cs | 12 -- ...ionBuilder.cs => TomlConnectionBuilder.cs} | 96 +++++++----- .../Core/Tools/EnvironmentOperations.cs | 4 +- Snowflake.Data/Core/Tools/FileOperations.cs | 11 +- Snowflake.Data/Core/Tools/UnixOperations.cs | 33 ++-- doc/Connecting.md | 59 +++++-- snowflake-connector-net.sln.DotSettings | 2 + 13 files changed, 233 insertions(+), 216 deletions(-) delete mode 100644 Snowflake.Data/Core/EnvironmentVariables.cs rename Snowflake.Data/Core/{SnowflakeTomlConnectionBuilder.cs => TomlConnectionBuilder.cs} (55%) diff --git a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs index c837824f1..4b9e36d47 100644 --- a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs @@ -238,7 +238,7 @@ private static void MockHomeDirectoryReturnsNull() private static void MockFileFromEnvironmentalVariable() { t_environmentOperations - .Setup(e => e.GetEnvironmentVariable(EasyLoggingConfigFinder.ClientConfigEnvironmentName, null)) + .Setup(e => e.GetEnvironmentVariable(EasyLoggingConfigFinder.ClientConfigEnvironmentName)) .Returns(EnvironmentalConfigFilePath); } diff --git a/Snowflake.Data.Tests/UnitTests/SnowflakeDbConnectionTest.cs b/Snowflake.Data.Tests/UnitTests/SnowflakeDbConnectionTest.cs index c023b6aaf..18ba3539d 100644 --- a/Snowflake.Data.Tests/UnitTests/SnowflakeDbConnectionTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SnowflakeDbConnectionTest.cs @@ -1,5 +1,9 @@ +using System; +using System.IO; +using Mono.Unix; + namespace Snowflake.Data.Tests.UnitTests { using Core; @@ -16,37 +20,33 @@ public void TestFillConnectionStringFromTomlConfig() // Arrange var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); - mockEnvironmentOperations.Setup(e => e.GetEnvironmentVariable(It.IsAny(), It.IsAny())) - .Returns((string v, string d) => d); + mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) + .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.IsAny())) + mockFileOperations.Setup(f => f.ReadAllText(It.IsAny(), It.IsAny>())) .Returns("[default]\naccount=\"testaccount\"\nuser=\"testuser\"\npassword=\"testpassword\"\n"); - var tomlConnectionBuilder = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var tomlConnectionBuilder = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act using (var conn = new SnowflakeDbConnection(tomlConnectionBuilder)) { - conn.ConnectionString = "account=user1account;user=user1;password=user1password;"; conn.FillConnectionStringFromTomlConfigIfNotSet(); // Assert - Assert.AreNotEqual("account=testaccount;user=testuser;password=testpassword;", conn.ConnectionString); - Assert.AreNotEqual("account=testaccount;user=testuser;password=testpassword;", conn.ConnectionString); + Assert.AreEqual("account=testaccount;user=testuser;password=testpassword;", conn.ConnectionString); } } [Test] - public void TestFillConnectionStringFromTomlConfigShouldNotBeExecutedIfAlreadySetConnectionString() + public void TestTomlConfigurationDoesNotOverrideExistingConnectionString() { // Arrange var connectionTest = "account=user1account;user=user1;password=user1password;"; var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); - mockEnvironmentOperations.Setup(e => e.GetEnvironmentVariable(It.IsAny(), It.IsAny())) - .Returns((string v, string d) => d); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); mockFileOperations.Setup(f => f.ReadAllText(It.IsAny())) .Returns("[default]\naccount=\"testaccount\"\nuser=\"testuser\"\npassword=\"testpassword\"\n"); - var tomlConnectionBuilder = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var tomlConnectionBuilder = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act using (var conn = new SnowflakeDbConnection(tomlConnectionBuilder)) diff --git a/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs b/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs index e08f22a02..c19863028 100644 --- a/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs @@ -2,6 +2,8 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ +using Mono.Unix; + namespace Snowflake.Data.Tests.UnitTests { using System; @@ -12,7 +14,7 @@ namespace Snowflake.Data.Tests.UnitTests using Snowflake.Data.Core; [TestFixture] - class SnowflakeTomlConnectionBuilderTest + class TomlConnectionBuilderTest { private const string BasicTomlConfig = @" [default] @@ -29,21 +31,18 @@ class SnowflakeTomlConnectionBuilderTest password = ""otherpassword"""; [Test] - public void TestConnectionWithReadFromDefaultValuesInEnvironmentVariables() + public void TestConnectionWithReadFromDefaultValuesInSnowflakeTomlConnectionBuilder() { // Arrange var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(It.IsAny(), It.IsAny())) - .Returns((string _, string s) => s); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(BasicTomlConfig); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -59,18 +58,15 @@ public void TestConnectionFromCustomSnowflakeHome() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome)) .Returns($"{Path.DirectorySeparatorChar}customsnowhome"); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) - .Returns((string _, string s) => s); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("customsnowhome")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("customsnowhome")), It.IsAny>())) .Returns(BasicTomlConfig); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -86,18 +82,15 @@ public void TestConnectionWithUserConnectionNameFromEnvVariable() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("testconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(BasicTomlConfig); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -113,18 +106,15 @@ public void TestConnectionWithUserConnectionNameFromEnvVariableWithMultipleConne var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("otherconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(BasicTomlConfig); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -140,18 +130,15 @@ public void TestConnectionWithUserConnectionName() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("otherconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(BasicTomlConfig); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml("testconnection"); @@ -168,13 +155,10 @@ public void TestConnectionMapPropertiesFromTomlKeyValues(string tomlKeyValue, st // Arrange var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(It.IsAny(), It.IsAny())) - .Returns((string _, string d) => d); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns($@" [default] account = ""defaultaccountname"" @@ -183,7 +167,7 @@ public void TestConnectionMapPropertiesFromTomlKeyValues(string tomlKeyValue, st {tomlKeyValue} "); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -199,12 +183,12 @@ public void TestConnectionConfigurationFileDoesNotExistsShouldReturnEmpty() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome)) .Returns($"{Path.DirectorySeparatorChar}notexistenttestpath"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(false); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -220,18 +204,15 @@ public void TestConnectionWithInvalidConnectionName() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("wrongconnectionname"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(BasicTomlConfig); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act and assert Assert.Throws(() => reader.GetConnectionStringFromToml(), "Specified connection name does not exist in connections.toml"); @@ -243,16 +224,13 @@ public void TestConnectionWithNonExistingDefaultConnection() // Arrange var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(It.IsAny(), It.IsAny())) - .Returns((string _, string s) => s); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns("[qa]\naccount = \"qaaccountname\"\nuser = \"qausername\"\npassword = \"qapassword\""); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -269,15 +247,12 @@ public void TestConnectionWithSpecifiedConnectionEmpty() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("testconnection1"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(@" [default] account = ""defaultaccountname"" @@ -289,7 +264,7 @@ public void TestConnectionWithSpecifiedConnectionEmpty() user = ""testusername"" password = ""testpassword"""); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -307,16 +282,13 @@ public void TestConnectionWithOauthAuthenticatorTokenFromFile() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("oauthconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(tokenFilePath)).Returns(testToken); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(tokenFilePath, It.IsAny>())).Returns(testToken); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(@$" [default] account = ""defaultaccountname"" @@ -327,7 +299,7 @@ public void TestConnectionWithOauthAuthenticatorTokenFromFile() authenticator = ""oauth"" token_file_path = ""{tokenFilePath}"""); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -345,16 +317,13 @@ public void TestConnectionWithOauthAuthenticatorFromDefaultIfTokenFilePathNotExi var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("oauthconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(tokenFilePath)).Returns(false); mockFileOperations.Setup(f => f.Exists(It.Is(p => !p.Equals(tokenFilePath)))).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(@$" [default] account = ""defaultaccountname"" @@ -364,9 +333,9 @@ public void TestConnectionWithOauthAuthenticatorFromDefaultIfTokenFilePathNotExi account = ""testaccountname"" authenticator = ""oauth"" token_file_path = ""{tokenFilePath}"""); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")))).Returns(defaultToken); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")), It.IsAny>())).Returns(defaultToken); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -383,15 +352,12 @@ public void TestConnectionWithOauthAuthenticatorFromDefaultPathShouldBeLoadedIfT var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("oauthconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(@$" [default] account = ""defaultaccountname"" @@ -400,9 +366,9 @@ public void TestConnectionWithOauthAuthenticatorFromDefaultPathShouldBeLoadedIfT [oauthconnection] account = ""testaccountname"" authenticator = ""oauth"""); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")))).Returns(defaultToken); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")), It.IsAny>())).Returns(defaultToken); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -418,16 +384,13 @@ public void TestConnectionWithOauthAuthenticatorShouldNotIncludeTokenIfNotStored var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("oauthconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.Is(p => p.Contains("/snowflake/session/token")))).Returns(false); mockFileOperations.Setup(f => f.Exists(It.Is(p => !string.IsNullOrEmpty(p) && !p.Contains("/snowflake/session/token")))).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(@$" [default] account = ""defaultaccountname"" @@ -437,7 +400,7 @@ public void TestConnectionWithOauthAuthenticatorShouldNotIncludeTokenIfNotStored account = ""testaccountname"" authenticator = ""oauth"""); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -456,15 +419,12 @@ public void TestConnectionWithOauthAuthenticatorShouldNotLoadFromFileIsSpecified var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("oauthconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(@$" [default] account = ""defaultaccountname"" @@ -476,7 +436,7 @@ public void TestConnectionWithOauthAuthenticatorShouldNotLoadFromFileIsSpecified token = ""{tokenFromToml}"" token_file_path = ""{tokenFilePath}"""); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); @@ -492,15 +452,12 @@ public void TestConnectionWithOauthAuthenticatorShouldNotIncludeTokenIfNullOrEmp var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string _, string d) => d); - mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName)) .Returns("oauthconnection"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")), It.IsAny>())) .Returns(@$" [default] account = ""defaultaccountname"" @@ -509,9 +466,9 @@ public void TestConnectionWithOauthAuthenticatorShouldNotIncludeTokenIfNullOrEmp [oauthconnection] account = ""testaccountname"" authenticator = ""oauth"""); - mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")))).Returns(string.Empty); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")), It.IsAny>())).Returns(string.Empty); - var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); // Act var connectionString = reader.GetConnectionStringFromToml(); diff --git a/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsTest.cs index b175dd079..d390908d9 100644 --- a/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsTest.cs @@ -3,6 +3,8 @@ */ +using System; + namespace Snowflake.Data.Tests.Tools { using System.IO; @@ -49,7 +51,7 @@ public void TestReadAllTextOnWindows() var filePath = CreateConfigTempFile(s_workingDirectory, content); // act - var result = s_fileOperations.ReadAllText(filePath); + var result = s_fileOperations.ReadAllText(filePath, GetTestFileValidation()); // assert Assert.AreEqual(content, result); @@ -69,14 +71,14 @@ public void TestReadAllTextCheckingPermissions() Syscall.chmod(filePath, (FilePermissions)filePermissions); // act - var result = s_fileOperations.ReadAllText(filePath); + var result = s_fileOperations.ReadAllText(filePath, GetTestFileValidation()); // assert Assert.AreEqual(content, result); } [Test] - public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadAllText() + public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadConfigurationFile() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -89,8 +91,22 @@ public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadAllText() Syscall.chmod(filePath, (FilePermissions)filePermissions); // act and assert - Assert.Throws(() => s_fileOperations.ReadAllText(filePath), + Assert.Throws(() => s_fileOperations.ReadAllText(filePath, GetTestFileValidation()), "Attempting to read a file with too broad permissions assigned"); } + + private Action GetTestFileValidation() + { + return stream => + { + const FileAccessPermissions forbiddenPermissions = FileAccessPermissions.OtherReadWriteExecute | FileAccessPermissions.GroupReadWriteExecute; + if (stream.OwnerUser.UserId != Syscall.geteuid()) + throw new SecurityException("Attempting to read a file not owned by the effective user of the current process"); + if (stream.OwnerGroup.GroupId != Syscall.getegid()) + throw new SecurityException("Attempting to read a file not owned by the effective group of the current process"); + if ((stream.FileAccessPermissions & forbiddenPermissions) != 0) + throw new SecurityException("Attempting to read a file with too broad permissions assigned"); + }; + } } } diff --git a/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs index c3931e30e..e47a12540 100644 --- a/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs @@ -1,16 +1,16 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; +using System.Security; using Mono.Unix; using Mono.Unix.Native; using NUnit.Framework; +using Snowflake.Data.Core; using Snowflake.Data.Core.Tools; using static Snowflake.Data.Tests.UnitTests.Configuration.EasyLoggingConfigGenerator; namespace Snowflake.Data.Tests.Tools { - using System.Security; - [TestFixture, NonParallelizable] public class UnixOperationsTest { @@ -96,14 +96,16 @@ public void TestReadAllTextCheckingPermissions() Syscall.chmod(filePath, (FilePermissions)filePermissions); // act - var result = s_unixOperations.ReadAllText(filePath); + var result = s_unixOperations.ReadAllText(filePath, TomlConnectionBuilder.GetFileValidations()); // assert Assert.AreEqual(content, result); } [Test] - public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadAllText() + [TestCase(FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.OtherReadWriteExecute)] + [TestCase(FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.GroupReadWriteExecute)] + public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadAllText(FileAccessPermissions filePermissions) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -111,11 +113,10 @@ public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadAllText() } var content = "random text"; var filePath = CreateConfigTempFile(s_workingDirectory, content); - var filePermissions = FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.OtherReadWriteExecute; Syscall.chmod(filePath, (FilePermissions)filePermissions); // act and assert - Assert.Throws(() => s_unixOperations.ReadAllText(filePath), "Attempting to read a file with too broad permissions assigned"); + Assert.Throws(() => s_unixOperations.ReadAllText(filePath, TomlConnectionBuilder.GetFileValidations()), "Attempting to read a file with too broad permissions assigned"); } public static IEnumerable UserPermissions() diff --git a/Snowflake.Data/Client/SnowflakeDbConnection.cs b/Snowflake.Data/Client/SnowflakeDbConnection.cs index 0bd9b0d94..70fa642ea 100755 --- a/Snowflake.Data/Client/SnowflakeDbConnection.cs +++ b/Snowflake.Data/Client/SnowflakeDbConnection.cs @@ -2,17 +2,17 @@ * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ +using System; +using System.Data; +using System.Data.Common; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Snowflake.Data.Core; +using Snowflake.Data.Log; + namespace Snowflake.Data.Client { - using System; - using System.Data.Common; - using Snowflake.Data.Core; - using System.Security; - using System.Threading.Tasks; - using System.Data; - using System.Threading; - using Snowflake.Data.Log; - [System.ComponentModel.DesignerCategory("Code")] public class SnowflakeDbConnection : DbConnection { @@ -37,7 +37,7 @@ public class SnowflakeDbConnection : DbConnection // Will fix that in a separated PR though as it's a different issue private static Boolean _isArrayBindStageCreated; - private readonly SnowflakeTomlConnectionBuilder _tomlConnectionBuilder; + private readonly TomlConnectionBuilder _tomlConnectionBuilder; protected enum TransactionRollbackStatus { @@ -46,7 +46,7 @@ protected enum TransactionRollbackStatus Failure } - public SnowflakeDbConnection() : this(new SnowflakeTomlConnectionBuilder()) + public SnowflakeDbConnection() : this(TomlConnectionBuilder.Instance) { } @@ -55,7 +55,7 @@ public SnowflakeDbConnection(string connectionString) : this() ConnectionString = connectionString; } - internal SnowflakeDbConnection(SnowflakeTomlConnectionBuilder tomlConnectionBuilder) + internal SnowflakeDbConnection(TomlConnectionBuilder tomlConnectionBuilder) { _tomlConnectionBuilder = tomlConnectionBuilder; _connectionState = ConnectionState.Closed; diff --git a/Snowflake.Data/Core/EnvironmentVariables.cs b/Snowflake.Data/Core/EnvironmentVariables.cs deleted file mode 100644 index 37290b9f3..000000000 --- a/Snowflake.Data/Core/EnvironmentVariables.cs +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2019-2023 Snowflake Inc. All rights reserved. -// - -namespace Snowflake.Data.Core -{ - public static class EnvironmentVariables - { - public static string SnowflakeDefaultConnectionName = "SNOWFLAKE_DEFAULT_CONNECTION_NAME"; - public static string SnowflakeHome = "SNOWFLAKE_HOME"; - } -} diff --git a/Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs b/Snowflake.Data/Core/TomlConnectionBuilder.cs similarity index 55% rename from Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs rename to Snowflake.Data/Core/TomlConnectionBuilder.cs index 6cee01582..2af5f8dae 100644 --- a/Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs +++ b/Snowflake.Data/Core/TomlConnectionBuilder.cs @@ -1,26 +1,32 @@ -// -// Copyright (c) 2024 Snowflake Inc. All rights reserved. -// +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Security; +using System.Text; +using Mono.Unix; +using Mono.Unix.Native; +using Snowflake.Data.Client; +using Snowflake.Data.Core.Tools; +using Snowflake.Data.Log; +using Tomlyn; +using Tomlyn.Model; namespace Snowflake.Data.Core { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using Client; - using Log; - using Tomlyn; - using Tomlyn.Model; - using Tools; - - public class SnowflakeTomlConnectionBuilder + internal class TomlConnectionBuilder { private const string DefaultConnectionName = "default"; private const string DefaultSnowflakeFolder = ".snowflake"; private const string DefaultTokenPath = "/snowflake/session/token"; - private readonly SFLogger _logger = SFLoggerFactory.GetLogger(); + internal const string SnowflakeDefaultConnectionName = "SNOWFLAKE_DEFAULT_CONNECTION_NAME"; + internal const string SnowflakeHome = "SNOWFLAKE_HOME"; + + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); private readonly Dictionary _tomlToNetPropertiesMapper = new Dictionary(StringComparer.InvariantCultureIgnoreCase) { @@ -30,11 +36,13 @@ public class SnowflakeTomlConnectionBuilder private readonly FileOperations _fileOperations; private readonly EnvironmentOperations _environmentOperations; - public SnowflakeTomlConnectionBuilder() : this(FileOperations.Instance, EnvironmentOperations.Instance) + internal static readonly TomlConnectionBuilder Instance = new TomlConnectionBuilder(); + + private TomlConnectionBuilder() : this(FileOperations.Instance, EnvironmentOperations.Instance) { } - internal SnowflakeTomlConnectionBuilder(FileOperations fileOperations, EnvironmentOperations environmentOperations) + internal TomlConnectionBuilder(FileOperations fileOperations, EnvironmentOperations environmentOperations) { _fileOperations = fileOperations; _environmentOperations = environmentOperations; @@ -42,14 +50,9 @@ internal SnowflakeTomlConnectionBuilder(FileOperations fileOperations, Environme public string GetConnectionStringFromToml(string connectionName = null) { - var connectionString = string.Empty; var tomlPath = ResolveConnectionTomlFile(); var connectionToml = GetTomlTableFromConfig(tomlPath, connectionName); - if (connectionToml != null) - { - connectionString = GetConnectionStringFromTomlTable(connectionToml); - } - return connectionString; + return connectionToml == null ? string.Empty : GetConnectionStringFromTomlTable(connectionToml); } private string GetConnectionStringFromTomlTable(TomlTable connectionToml) @@ -59,17 +62,28 @@ private string GetConnectionStringFromTomlTable(TomlTable connectionToml) var isOauth = connectionToml.TryGetValue("authenticator", out var authenticator) && authenticator.ToString().Equals("oauth"); foreach (var property in connectionToml.Keys) { + var propertyValue = (string)connectionToml[property]; if (isOauth && property.Equals("token_file_path", StringComparison.InvariantCultureIgnoreCase)) { - tokenFilePathValue = (string)connectionToml[property]; + tokenFilePathValue = propertyValue; continue; } var mappedProperty = _tomlToNetPropertiesMapper.TryGetValue(property, out var mapped) ? mapped : property; - connectionStringBuilder.Append($"{mappedProperty}={(string)connectionToml[property]};"); + connectionStringBuilder.Append($"{mappedProperty}={propertyValue};"); } + AppendTokenFromFileIfNotGivenExplicitly(connectionToml, isOauth, connectionStringBuilder, tokenFilePathValue); + + return connectionStringBuilder.ToString(); + } + + private void AppendTokenFromFileIfNotGivenExplicitly(TomlTable connectionToml, bool isOauth, + StringBuilder connectionStringBuilder, string tokenFilePathValue) + { if (!isOauth || connectionToml.ContainsKey("token")) - return connectionStringBuilder.ToString(); + { + return; + } var token = LoadTokenFromFile(tokenFilePathValue); if (!string.IsNullOrEmpty(token)) @@ -78,18 +92,15 @@ private string GetConnectionStringFromTomlTable(TomlTable connectionToml) } else { - _logger.Warn("The token has empty value"); + s_logger.Warn("The token has empty value"); } - - - return connectionStringBuilder.ToString(); } private string LoadTokenFromFile(string tokenFilePathValue) { var tokenFile = !string.IsNullOrEmpty(tokenFilePathValue) && _fileOperations.Exists(tokenFilePathValue) ? tokenFilePathValue : DefaultTokenPath; - _logger.Debug($"Read token from file path: {tokenFile}"); - return _fileOperations.Exists(tokenFile) ? _fileOperations.ReadAllText(tokenFile) : null; + s_logger.Debug($"Read token from file path: {tokenFile}"); + return _fileOperations.Exists(tokenFile) ? _fileOperations.ReadAllText(tokenFile, GetFileValidations()) : null; } private TomlTable GetTomlTableFromConfig(string tomlPath, string connectionName) @@ -99,14 +110,15 @@ private TomlTable GetTomlTableFromConfig(string tomlPath, string connectionName) return null; } - var tomlContent = _fileOperations.ReadAllText(tomlPath) ?? string.Empty; + var tomlContent = _fileOperations.ReadAllText(tomlPath, GetFileValidations()) ?? string.Empty; var toml = Toml.ToModel(tomlContent); if (string.IsNullOrEmpty(connectionName)) { - connectionName = _environmentOperations.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, DefaultConnectionName); + connectionName = _environmentOperations.GetEnvironmentVariable(SnowflakeDefaultConnectionName) ?? DefaultConnectionName; } var connectionExists = toml.TryGetValue(connectionName, out var connection); + // In the case where the connection name is the default connection name and does not exist, we will not use the toml builder feature. if (!connectionExists && connectionName != DefaultConnectionName) { throw new Exception("Specified connection name does not exist in connections.toml"); @@ -119,10 +131,24 @@ private TomlTable GetTomlTableFromConfig(string tomlPath, string connectionName) private string ResolveConnectionTomlFile() { var defaultDirectory = Path.Combine(HomeDirectoryProvider.HomeDirectory(_environmentOperations), DefaultSnowflakeFolder); - var tomlFolder = _environmentOperations.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, defaultDirectory); + var tomlFolder = _environmentOperations.GetEnvironmentVariable(SnowflakeHome) ?? defaultDirectory; var tomlPath = Path.Combine(tomlFolder, "connections.toml"); tomlPath = Path.GetFullPath(tomlPath); return tomlPath; } + + internal static Action GetFileValidations() + { + return stream => + { + const FileAccessPermissions forbiddenPermissions = FileAccessPermissions.OtherReadWriteExecute | FileAccessPermissions.GroupReadWriteExecute; + if (stream.OwnerUser.UserId != Syscall.geteuid()) + throw new SecurityException("Attempting to read a file not owned by the effective user of the current process"); + if (stream.OwnerGroup.GroupId != Syscall.getegid()) + throw new SecurityException("Attempting to read a file not owned by the effective group of the current process"); + if ((stream.FileAccessPermissions & forbiddenPermissions) != 0) + throw new SecurityException("Attempting to read a file with too broad permissions assigned"); + }; + } } } diff --git a/Snowflake.Data/Core/Tools/EnvironmentOperations.cs b/Snowflake.Data/Core/Tools/EnvironmentOperations.cs index 1f011a6c5..1f1959986 100644 --- a/Snowflake.Data/Core/Tools/EnvironmentOperations.cs +++ b/Snowflake.Data/Core/Tools/EnvironmentOperations.cs @@ -13,9 +13,9 @@ internal class EnvironmentOperations public static readonly EnvironmentOperations Instance = new EnvironmentOperations(); private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - public virtual string GetEnvironmentVariable(string variable, string defaultValue = null) + public virtual string GetEnvironmentVariable(string variable) { - return Environment.GetEnvironmentVariable(variable) ?? defaultValue; + return Environment.GetEnvironmentVariable(variable); } public virtual string GetFolderPath(Environment.SpecialFolder folder) diff --git a/Snowflake.Data/Core/Tools/FileOperations.cs b/Snowflake.Data/Core/Tools/FileOperations.cs index 8953a8d6e..577bd54ee 100644 --- a/Snowflake.Data/Core/Tools/FileOperations.cs +++ b/Snowflake.Data/Core/Tools/FileOperations.cs @@ -2,12 +2,13 @@ * Copyright (c) 2023 Snowflake Computing Inc. All rights reserved. */ +using System; using System.IO; +using System.Runtime.InteropServices; +using Mono.Unix; namespace Snowflake.Data.Core.Tools { - using System.Runtime.InteropServices; - using Mono.Unix; internal class FileOperations { @@ -21,12 +22,12 @@ public virtual bool Exists(string path) public virtual string ReadAllText(string path) { - return ReadAllText(path, FileAccessPermissions.OtherReadWriteExecute); + return ReadAllText(path, null); } - public virtual string ReadAllText(string path, FileAccessPermissions? forbiddenPermissions) + public virtual string ReadAllText(string path, Action validator) { - var contentFile = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? File.ReadAllText(path) : _unixOperations.ReadAllText(path, forbiddenPermissions); + var contentFile = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || validator == null ? File.ReadAllText(path) : _unixOperations.ReadAllText(path, validator); return contentFile; } } diff --git a/Snowflake.Data/Core/Tools/UnixOperations.cs b/Snowflake.Data/Core/Tools/UnixOperations.cs index dab753067..655b708ea 100644 --- a/Snowflake.Data/Core/Tools/UnixOperations.cs +++ b/Snowflake.Data/Core/Tools/UnixOperations.cs @@ -2,14 +2,15 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ +using System; +using System.IO; +using System.Security; +using System.Text; +using Mono.Unix; +using Mono.Unix.Native; + namespace Snowflake.Data.Core.Tools { - using Mono.Unix; - using Mono.Unix.Native; - using System.IO; - using System.Security; - using System.Text; - internal class UnixOperations { public static readonly UnixOperations Instance = new UnixOperations(); @@ -31,28 +32,14 @@ public virtual bool CheckFileHasAnyOfPermissions(string path, FileAccessPermissi return (permissions & fileInfo.FileAccessPermissions) != 0; } - /// - /// Reads all text from a file at the specified path, ensuring the file is owned by the effective user and group of the current process, - /// and does not have broader permissions than specified. - /// - /// The path to the file. - /// Permissions that are not allowed for the file. Defaults to OtherReadWriteExecute. - /// The content of the file as a string. - /// Thrown if the file is not owned by the effective user or group, or if it has forbidden permissions. - - public string ReadAllText(string path, FileAccessPermissions? forbiddenPermissions = FileAccessPermissions.OtherReadWriteExecute) + public string ReadAllText(string path, Action validator) { var fileInfo = new UnixFileInfo(path: path); using (var handle = fileInfo.OpenRead()) { - if (handle.OwnerUser.UserId != Syscall.geteuid()) - throw new SecurityException("Attempting to read a file not owned by the effective user of the current process"); - if (handle.OwnerGroup.GroupId != Syscall.getegid()) - throw new SecurityException("Attempting to read a file not owned by the effective group of the current process"); - if (forbiddenPermissions.HasValue && (handle.FileAccessPermissions & forbiddenPermissions.Value) != 0) - throw new SecurityException("Attempting to read a file with too broad permissions assigned"); - using (var streamReader = new StreamReader(handle, Encoding.Default)) + validator?.Invoke(handle); + using (var streamReader = new StreamReader(handle, Encoding.UTF8)) { return streamReader.ReadToEnd(); } diff --git a/doc/Connecting.md b/doc/Connecting.md index 7a4baedb2..ef9cd5874 100644 --- a/doc/Connecting.md +++ b/doc/Connecting.md @@ -297,15 +297,16 @@ Examples: ### Snowflake credentials using a configuration file -.NET Drivers allows to add connections definitions to a configuration file. For this connection all supported parameters in .NET could be defined and will be use to generate our connection string. +.NET Drivers allows to add connections definitions to a configuration file. For a connection defined in this way all supported parameters in .NET could be defined and will be used to generate our connection string. -.NET Driver looks for the `connection.toml` in the following locations, in order. +.NET Driver looks for the `connections.toml` in the following locations, in order. -* `$SNOWFLAKE_HOME` environment variable, You can modify the environment variable to use a different location. -* If the environment variable is not specified will use `~/.snowflake` directory if exists. -* Otherwise, for others OS Systems `${HOME_DIRECTORY}/.snowflake`. +* `SNOWFLAKE_HOME` environment variable, You can modify the environment variable to use a different location. +* Otherwise, it uses the `connections.toml` file in `.snowflake` subfolder of the home directory, that is, based on your operating system: + * MacOS/Linux: `~/.snowflake/connections.toml` + * Windows: `%USERPROFILE%\.snowflake\connections.toml` -For MacOS and Linux systems, .NET Driver requires the connections.toml file to limit its file permissions to read and write for the file owner only. To set the file required file permissions execute the following commands: +For MacOS and Linux systems, .NET Driver demands the connections.toml file to have limited file permissions to read and write for the file owner only. To set the file required file permissions execute the following commands: ``` BASH chown $USER connections.toml @@ -330,8 +331,46 @@ using (IDbConnection conn = new SnowflakeDbConnection()) } ``` -By default the name of the connection will be `default`. You can also change the default connection by setting the SNOWFLAKE_DEFAULT_CONNECTION_NAME environment variable, as shown: +By default the name of the connection will be `default`. You can also change the default connection name by setting the SNOWFLAKE_DEFAULT_CONNECTION_NAME environment variable, as shown: -``` bash -export SNOWFLAKE_DEFAULT_CONNECTION_NAME="my_prod_connection" -``` \ No newline at end of file +```bash +set SNOWFLAKE_DEFAULT_CONNECTION_NAME=my_prod_connection +``` + +The following examples show how you can include different types of special characters in a toml key value pair string: + +- To include a single quote (') character: + + ```toml + [default] + host = "fakeaccount.snowflakecomputing.com" + user = "fakeuser" + password = "fake\'password" + ``` + +- To include a double quote (") character: + + ```toml + [default] + host = "fakeaccount.snowflakecomputing.com" + user = "fakeuser" + password = "fake\"password" + ``` + +- To include a semicolon (;): + + ```toml + [default] + host = "fakeaccount.snowflakecomputing.com" + user = "fakeuser" + password = "\";fakepassword\"" + ``` + +- To include an equal sign (=): + + ```toml + [default] + host = "fakeaccount.snowflakecomputing.com" + user = "fakeuser" + password = "fake=password" + ``` diff --git a/snowflake-connector-net.sln.DotSettings b/snowflake-connector-net.sln.DotSettings index 115ae1b54..fd86da92d 100644 --- a/snowflake-connector-net.sln.DotSettings +++ b/snowflake-connector-net.sln.DotSettings @@ -1,4 +1,6 @@  + False + True True CI False