Skip to content

Commit

Permalink
Applying additional PR suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-jmartinezramirez committed Oct 3, 2024
1 parent 974e679 commit 797c124
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 47 deletions.
35 changes: 11 additions & 24 deletions Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,24 @@
* Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
*/

using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Snowflake.Data.Client;
using Snowflake.Data.Core;
using Snowflake.Data.Core.Session;
using Snowflake.Data.Log;
using Snowflake.Data.Tests.Mock;
using Snowflake.Data.Tests.Util;

namespace Snowflake.Data.Tests.IntegrationTests
{
using NUnit.Framework;
using Snowflake.Data.Client;
using System.Data;
using System;
using Snowflake.Data.Core;
using System.Threading.Tasks;
using System.Threading;
using Snowflake.Data.Log;
using System.Diagnostics;
using Snowflake.Data.Tests.Mock;
using System.Runtime.InteropServices;
using System.Net.Http;

[TestFixture]
class SFConnectionIT : SFBaseTest
Expand Down Expand Up @@ -2262,18 +2261,6 @@ public void TestConnectStringWithQueryTag()
}
}


[Test]
[IgnoreOnCI("This test requires a valid connection string in the configuration file.")]
public void TestLocalDefaultConnectStringReadFromToml()
{
using (var conn = new SnowflakeDbConnection())
{
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);
}
}

[Test]
public void TestUseMultiplePoolsConnectionPoolByDefault()
{
Expand Down
134 changes: 134 additions & 0 deletions Snowflake.Data.Tests/IntegrationTests/SFConnectionWithTomlIT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
*/

using System;
using System.Data;
using System.IO;
using System.Runtime.InteropServices;
using Mono.Unix.Native;
using NUnit.Framework;
using Snowflake.Data.Client;
using Snowflake.Data.Core;
using Snowflake.Data.Log;
using Tomlyn;
using Tomlyn.Model;

namespace Snowflake.Data.Tests.IntegrationTests
{

[TestFixture, NonParallelizable]
class SFConnectionWithTomlIT : SFBaseTest
{
private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<SFConnectionIT>();

private static readonly string s_workingDirectory = Path.Combine(Path.GetTempPath(), "tomlconfig", Path.GetRandomFileName());


[SetUp]
public new void BeforeTest()
{
if (!Directory.Exists(s_workingDirectory))
{
Directory.CreateDirectory(s_workingDirectory);
}
var snowflakeHome = Environment.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome);
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome, s_workingDirectory);
}

[TearDown]
public new void AfterTest()
{
Directory.Delete(s_workingDirectory, true);
}

public static void CreateTomlConfigBaseOnConnectionString(string connectionString)
{
var tomlModel = new TomlTable();
var properties = SFSessionProperties.ParseConnectionString(connectionString, null);

var defaultTomlTable = new TomlTable();
tomlModel.Add("default", defaultTomlTable);

foreach (var property in properties)
{
defaultTomlTable.Add(property.Key.ToString(), property.Value);
}

var filePath = Path.Combine(s_workingDirectory, "connections.toml");
using (var writer = File.CreateText(filePath))
{
writer.Write(Toml.FromModel(tomlModel));
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;

Syscall.chmod(filePath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR);

}

[Test]
public void TestLocalDefaultConnectStringReadFromToml()
{
var snowflakeHome = Environment.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome);
CreateTomlConfigBaseOnConnectionString(ConnectionString);
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome, s_workingDirectory);
try
{
using (var conn = new SnowflakeDbConnection())
{
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);
}
}
finally
{
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome, snowflakeHome);
}
}

[Test]
public void TestThrowExceptionIfTomlNotFoundWithOtherConnectionString()
{
var snowflakeHome = Environment.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome);
var connectionName = Environment.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName);
CreateTomlConfigBaseOnConnectionString(ConnectionString);
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome, s_workingDirectory);
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName, "notfoundconnection");
try
{
using (var conn = new SnowflakeDbConnection())
{
Assert.Throws<SnowflakeDbException>(() => conn.Open(), "Unable to connect. Specified connection name does not exist in connections.toml");
}
}
finally
{
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome, snowflakeHome);
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeDefaultConnectionName, connectionName);
}
}

[Test]
public void TestThrowExceptionIfTomlFromNotFoundFromDbConnection()
{
var snowflakeHome = Environment.GetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome);
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome, s_workingDirectory);
try
{
using (var conn = new SnowflakeDbConnection())
{
Assert.Throws<SnowflakeDbException>(() => conn.Open(), "Error: Required property ACCOUNT is not provided");
}
}
finally
{
Environment.SetEnvironmentVariable(TomlConnectionBuilder.SnowflakeHome, snowflakeHome);
}
}
}

}


1 change: 1 addition & 0 deletions Snowflake.Data.Tests/Snowflake.Data.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="Tomlyn.Signed" Version="0.17.0" />
<ProjectReference Include="..\Snowflake.Data\Snowflake.Data.csproj" />
</ItemGroup>
<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,40 @@ public void TestConnectionWithOauthAuthenticatorShouldNotIncludeTokenIfNullOrEmp
// Assert
Assert.AreEqual($"account=testaccountname;authenticator=oauth;", connectionString);
}

[Test]
[TestCase("\\\"password;default\\\"", "password;default")]
[TestCase("\\\"\\\"\\\"password;default\\\"", "\"password;default")]
[TestCase("p\\\"assworddefault", "p\"assworddefault")]
[TestCase("password\\\"default", "password\"default")]
[TestCase("password\'default", "password\'default")]
[TestCase("password=default", "password=default")]
[TestCase("\\\"pa=ss\\\"\\\"word;def\'ault\\\"", "pa=ss\"word;def\'ault")]
public void TestConnectionMapPropertiesWithSpecialCharacters(string passwordValueWithSpecialCharacter, string expectedValue)
{
// Arrange
var mockFileOperations = new Mock<FileOperations>();
var mockEnvironmentOperations = new Mock<EnvironmentOperations>();
mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile))
.Returns($"{Path.DirectorySeparatorChar}home");
mockFileOperations.Setup(f => f.Exists(It.IsAny<string>())).Returns(true);
mockFileOperations.Setup(f => f.ReadAllText(It.Is<string>(p => p.Contains(".snowflake")), It.IsAny<Action<UnixStream>>()))
.Returns($@"
[default]
account = ""defaultaccountname""
user = ""defaultusername""
password = ""{passwordValueWithSpecialCharacter}""
");

var reader = new TomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object);

// Act
var connectionString = reader.GetConnectionStringFromToml();
var properties = SFSessionProperties.ParseConnectionString(connectionString, null);

// Assert
Assert.AreEqual(expectedValue, properties[SFSessionProperty.PASSWORD]);
}
}

}
21 changes: 4 additions & 17 deletions Snowflake.Data.Tests/UnitTests/Tools/FileOperationsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


using System;
using Snowflake.Data.Core;

namespace Snowflake.Data.Tests.Tools
{
Expand Down Expand Up @@ -51,7 +52,7 @@ public void TestReadAllTextOnWindows()
var filePath = CreateConfigTempFile(s_workingDirectory, content);

// act
var result = s_fileOperations.ReadAllText(filePath, GetTestFileValidation());
var result = s_fileOperations.ReadAllText(filePath, TomlConnectionBuilder.GetFileValidations());

// assert
Assert.AreEqual(content, result);
Expand All @@ -71,7 +72,7 @@ public void TestReadAllTextCheckingPermissions()
Syscall.chmod(filePath, (FilePermissions)filePermissions);

// act
var result = s_fileOperations.ReadAllText(filePath, GetTestFileValidation());
var result = s_fileOperations.ReadAllText(filePath, TomlConnectionBuilder.GetFileValidations());

// assert
Assert.AreEqual(content, result);
Expand All @@ -91,22 +92,8 @@ public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadConfiguration
Syscall.chmod(filePath, (FilePermissions)filePermissions);

// act and assert
Assert.Throws<SecurityException>(() => s_fileOperations.ReadAllText(filePath, GetTestFileValidation()),
Assert.Throws<SecurityException>(() => s_fileOperations.ReadAllText(filePath, TomlConnectionBuilder.GetFileValidations()),
"Attempting to read a file with too broad permissions assigned");
}

private Action<UnixStream> 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");
};
}
}
}
23 changes: 19 additions & 4 deletions Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,19 @@ public void TestReadAllTextCheckingPermissions()
}

[Test]
[TestCase(FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.OtherReadWriteExecute)]
[TestCase(FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.GroupReadWriteExecute)]
public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadAllText(FileAccessPermissions filePermissions)
public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadAllText([ValueSource(nameof(UserReadWritePermissions))] FilePermissions userPermissions,
[ValueSource(nameof(GroupOrOthersWritablePermissions))] FilePermissions groupOrOthersWritablePermissions,
[ValueSource(nameof(GroupOrOthersReadablePermissions))] FilePermissions groupOrOthersReadablePermissions)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Assert.Ignore("skip test on Windows");
}
var content = "random text";
var filePath = CreateConfigTempFile(s_workingDirectory, content);
Syscall.chmod(filePath, (FilePermissions)filePermissions);

var filePermissions = userPermissions | groupOrOthersWritablePermissions | groupOrOthersReadablePermissions;
Syscall.chmod(filePath, filePermissions);

// act and assert
Assert.Throws<SecurityException>(() => s_unixOperations.ReadAllText(filePath, TomlConnectionBuilder.GetFileValidations()), "Attempting to read a file with too broad permissions assigned");
Expand Down Expand Up @@ -149,5 +151,18 @@ public static IEnumerable<FilePermissions> OtherNotWritablePermissions()
yield return FilePermissions.S_IXOTH;
yield return FilePermissions.S_IROTH | FilePermissions.S_IXOTH;
}

public static IEnumerable<FilePermissions> UserReadWritePermissions()
{
yield return FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR;
}

public static IEnumerable<FilePermissions> GroupOrOthersReadablePermissions()
{
yield return 0;
yield return FilePermissions.S_IRGRP;
yield return FilePermissions.S_IROTH;
yield return FilePermissions.S_IRGRP | FilePermissions.S_IROTH;
}
}
}
5 changes: 3 additions & 2 deletions Snowflake.Data/Core/TomlConnectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal class TomlConnectionBuilder
private readonly FileOperations _fileOperations;
private readonly EnvironmentOperations _environmentOperations;

internal static readonly TomlConnectionBuilder Instance = new TomlConnectionBuilder();
public static readonly TomlConnectionBuilder Instance = new TomlConnectionBuilder();

private TomlConnectionBuilder() : this(FileOperations.Instance, EnvironmentOperations.Instance)
{
Expand Down Expand Up @@ -118,7 +118,8 @@ private TomlTable GetTomlTableFromConfig(string tomlPath, string connectionName)
}

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.
// Avoid handling error when default connection does not exist, user could not want to use toml configuration and forgot to provide the
// connection string, this error should be thrown later when the undefined connection string is used.
if (!connectionExists && connectionName != DefaultConnectionName)
{
throw new Exception("Specified connection name does not exist in connections.toml");
Expand Down
8 changes: 8 additions & 0 deletions doc/Connecting.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,14 @@ The following examples show how you can include different types of special chara
user = "fakeuser"
password = "fake\"password"
```
- In case that double quote is use with other character that requires be wrap with double quoted it shoud use \\"\\" for a ":

```toml
[default]
host = "fakeaccount.snowflakecomputing.com"
user = "fakeuser"
password = "\";fake\"\"password\""
```

- To include a semicolon (;):

Expand Down

0 comments on commit 797c124

Please sign in to comment.