From 4c36d9c0ff491b2954c17c19a968aa34c186d595 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 | 16 +-- .../SnowflakeTomlConnectionBuilderTest.cs | 113 ++++++------------ .../UnitTests/Tools/FileOperationsTest.cs | 24 +++- .../UnitTests/Tools/UnixOperationsTest.cs | 8 +- .../Client/SnowflakeDbConnection.cs | 20 ++-- Snowflake.Data/Core/EnvironmentVariables.cs | 8 +- .../Core/SnowflakeTomlConnectionBuilder.cs | 81 +++++++++---- .../Core/Tools/EnvironmentOperations.cs | 4 +- Snowflake.Data/Core/Tools/FileOperations.cs | 8 +- Snowflake.Data/Core/Tools/UnixOperations.cs | 37 +++--- doc/Connecting.md | 59 +++++++-- snowflake-connector-net.sln.DotSettings | 2 + 13 files changed, 210 insertions(+), 172 deletions(-) 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..5ddbd1cc0 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,21 +20,19 @@ 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); // 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); } } @@ -41,8 +43,6 @@ public void TestFillConnectionStringFromTomlConfigShouldNotBeExecutedIfAlreadySe 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"); diff --git a/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs b/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs index e08f22a02..c214cad18 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; @@ -29,18 +31,15 @@ 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); @@ -59,15 +58,12 @@ public void TestConnectionFromCustomSnowflakeHome() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(SnowflakeTomlConnectionBuilder.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); @@ -86,15 +82,12 @@ 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(SnowflakeTomlConnectionBuilder.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); @@ -113,15 +106,12 @@ 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(SnowflakeTomlConnectionBuilder.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); @@ -140,15 +130,12 @@ 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(SnowflakeTomlConnectionBuilder.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); @@ -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"" @@ -199,7 +183,7 @@ public void TestConnectionConfigurationFileDoesNotExistsShouldReturnEmpty() var mockFileOperations = new Mock(); var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations - .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Setup(e => e.GetEnvironmentVariable(SnowflakeTomlConnectionBuilder.SnowflakeHome)) .Returns($"{Path.DirectorySeparatorChar}notexistenttestpath"); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); @@ -220,15 +204,12 @@ 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(SnowflakeTomlConnectionBuilder.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); @@ -243,13 +224,10 @@ 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); @@ -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(SnowflakeTomlConnectionBuilder.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"" @@ -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(SnowflakeTomlConnectionBuilder.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"" @@ -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(SnowflakeTomlConnectionBuilder.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,7 +333,7 @@ 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); @@ -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(SnowflakeTomlConnectionBuilder.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,7 +366,7 @@ 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); @@ -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(SnowflakeTomlConnectionBuilder.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"" @@ -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(SnowflakeTomlConnectionBuilder.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"" @@ -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(SnowflakeTomlConnectionBuilder.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,7 +466,7 @@ 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); 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..77ff2631c 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,7 +96,7 @@ public void TestReadAllTextCheckingPermissions() Syscall.chmod(filePath, (FilePermissions)filePermissions); // act - var result = s_unixOperations.ReadAllText(filePath); + var result = s_unixOperations.ReadAllText(filePath, SnowflakeTomlConnectionBuilder.GetFileValidations()); // assert Assert.AreEqual(content, result); @@ -115,7 +115,7 @@ public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadAllText() 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, SnowflakeTomlConnectionBuilder.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 9f6cb8084..3d11b9bab 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 { @@ -46,7 +46,7 @@ protected enum TransactionRollbackStatus Failure } - public SnowflakeDbConnection() : this(new SnowflakeTomlConnectionBuilder()) + public SnowflakeDbConnection() : this(SnowflakeTomlConnectionBuilder.Instance) { } diff --git a/Snowflake.Data/Core/EnvironmentVariables.cs b/Snowflake.Data/Core/EnvironmentVariables.cs index 37290b9f3..459aa6740 100644 --- a/Snowflake.Data/Core/EnvironmentVariables.cs +++ b/Snowflake.Data/Core/EnvironmentVariables.cs @@ -1,12 +1,10 @@ -// -// Copyright (c) 2019-2023 Snowflake Inc. All rights reserved. -// +/* + * Copyright (c) 2024 Snowflake Computing 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/SnowflakeTomlConnectionBuilder.cs index 6cee01582..67e39f4c1 100644 --- a/Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs +++ b/Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs @@ -1,25 +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.Runtime.InteropServices; +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 SnowflakeTomlConnectionBuilder { private const string DefaultConnectionName = "default"; private const string DefaultSnowflakeFolder = ".snowflake"; private const string DefaultTokenPath = "/snowflake/session/token"; + internal const string SnowflakeDefaultConnectionName = "SNOWFLAKE_DEFAULT_CONNECTION_NAME"; + internal const string SnowflakeHome = "SNOWFLAKE_HOME"; + private readonly SFLogger _logger = SFLoggerFactory.GetLogger(); private readonly Dictionary _tomlToNetPropertiesMapper = new Dictionary(StringComparer.InvariantCultureIgnoreCase) @@ -30,7 +37,9 @@ public class SnowflakeTomlConnectionBuilder private readonly FileOperations _fileOperations; private readonly EnvironmentOperations _environmentOperations; - public SnowflakeTomlConnectionBuilder() : this(FileOperations.Instance, EnvironmentOperations.Instance) + internal static readonly SnowflakeTomlConnectionBuilder Instance = new SnowflakeTomlConnectionBuilder(); + + internal SnowflakeTomlConnectionBuilder() : this(FileOperations.Instance, EnvironmentOperations.Instance) { } @@ -59,17 +68,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)) @@ -80,16 +100,13 @@ private string GetConnectionStringFromTomlTable(TomlTable connectionToml) { _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; + return _fileOperations.Exists(tokenFile) ? _fileOperations.ReadAllText(tokenFile, GetFileValidations()) : null; } private TomlTable GetTomlTableFromConfig(string tomlPath, string connectionName) @@ -99,11 +116,11 @@ 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); @@ -119,10 +136,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..3353d7e10 100644 --- a/Snowflake.Data/Core/Tools/FileOperations.cs +++ b/Snowflake.Data/Core/Tools/FileOperations.cs @@ -2,7 +2,9 @@ * Copyright (c) 2023 Snowflake Computing Inc. All rights reserved. */ +using System; using System.IO; +using System.Linq; namespace Snowflake.Data.Core.Tools { @@ -21,12 +23,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 validation) { - var contentFile = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? File.ReadAllText(path) : _unixOperations.ReadAllText(path, forbiddenPermissions); + var contentFile = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || validation == null ? File.ReadAllText(path) : _unixOperations.ReadAllText(path, validation); return contentFile; } } diff --git a/Snowflake.Data/Core/Tools/UnixOperations.cs b/Snowflake.Data/Core/Tools/UnixOperations.cs index dab753067..093fd6b8b 100644 --- a/Snowflake.Data/Core/Tools/UnixOperations.cs +++ b/Snowflake.Data/Core/Tools/UnixOperations.cs @@ -2,13 +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 { @@ -31,32 +33,23 @@ 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 ValidateFileAndReadText(string path, Action validation) { 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)) + validation?.Invoke(handle); + using (var streamReader = new StreamReader(handle, Encoding.UTF8)) { return streamReader.ReadToEnd(); } } } + + public string ReadAllText(string path, Action validation) + { + return ValidateFileAndReadText(path, validation); + } } } diff --git a/doc/Connecting.md b/doc/Connecting.md index 7a4baedb2..689922327 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 the one of the following locations, 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 994f01e04..af09ae25d 100644 --- a/snowflake-connector-net.sln.DotSettings +++ b/snowflake-connector-net.sln.DotSettings @@ -1,4 +1,6 @@  + False + True True CI False