diff --git a/Snowflake.Data.Tests/IntegrationTests/EasyLoggingIT.cs b/Snowflake.Data.Tests/IntegrationTests/EasyLoggingIT.cs new file mode 100644 index 000000000..d120abcfd --- /dev/null +++ b/Snowflake.Data.Tests/IntegrationTests/EasyLoggingIT.cs @@ -0,0 +1,99 @@ +using System.Data; +using System.IO; +using System.Runtime.InteropServices; +using Mono.Unix.Native; +using NUnit.Framework; +using Snowflake.Data.Client; +using Snowflake.Data.Configuration; +using Snowflake.Data.Core; +using Snowflake.Data.Log; +using static Snowflake.Data.Tests.UnitTests.Configuration.EasyLoggingConfigGenerator; + +namespace Snowflake.Data.Tests.IntegrationTests +{ + [TestFixture, NonParallelizable] + public class EasyLoggingIT: SFBaseTest + { + private static readonly string s_workingDirectory = Path.Combine(Path.GetTempPath(), "easy_logging_test_configs_", Path.GetRandomFileName()); + + [OneTimeSetUp] + public static void BeforeAll() + { + if (!Directory.Exists(s_workingDirectory)) + { + Directory.CreateDirectory(s_workingDirectory); + } + } + + [OneTimeTearDown] + public static void AfterAll() + { + Directory.Delete(s_workingDirectory, true); + } + + [TearDown] + public static void AfterEach() + { + EasyLoggingStarter.Instance.Reset(EasyLoggingLogLevel.Warn); + } + + [Test] + public void TestEnableEasyLogging() + { + // arrange + var configFilePath = CreateConfigTempFile(s_workingDirectory, Config("WARN", s_workingDirectory)); + using (IDbConnection conn = new SnowflakeDbConnection()) + { + conn.ConnectionString = ConnectionString + $"CLIENT_CONFIG_FILE={configFilePath}"; + + // act + conn.Open(); + + // assert + Assert.IsTrue(EasyLoggerManager.HasEasyLoggingAppender()); + } + } + + [Test] + public void TestFailToEnableEasyLoggingForWrongConfiguration() + { + // arrange + var configFilePath = CreateConfigTempFile(s_workingDirectory, "random config content"); + using (IDbConnection conn = new SnowflakeDbConnection()) + { + conn.ConnectionString = ConnectionString + $"CLIENT_CONFIG_FILE={configFilePath}"; + + // act + var thrown = Assert.Throws(() => conn.Open()); + + // assert + Assert.That(thrown.Message, Does.Contain("Connection string is invalid: Unable to connect")); + Assert.IsFalse(EasyLoggerManager.HasEasyLoggingAppender()); + } + } + + [Test] + public void TestFailToEnableEasyLoggingWhenConfigHasWrongPermissions() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + var configFilePath = CreateConfigTempFile(s_workingDirectory, Config("WARN", s_workingDirectory)); + Syscall.chmod(configFilePath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IWGRP); + using (IDbConnection conn = new SnowflakeDbConnection()) + { + conn.ConnectionString = ConnectionString + $"CLIENT_CONFIG_FILE={configFilePath}"; + + // act + var thrown = Assert.Throws(() => conn.Open()); + + // assert + Assert.That(thrown.Message, Does.Contain("Connection string is invalid: Unable to connect")); + Assert.IsFalse(EasyLoggerManager.HasEasyLoggingAppender()); + } + } + } +} \ No newline at end of file diff --git a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs index bea1e14d0..ba4460531 100644 --- a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigFinderTest.cs @@ -194,7 +194,7 @@ public void TestThatDoesNotFailWhenHomeDirectoryDoesNotExist() private static void MockHasFlagReturnsTrue() { t_unixOperations - .Setup(f => f.CheckFileHasPermissions(s_homeConfigFilePath, + .Setup(f => f.CheckFileHasAnyOfPermissions(s_homeConfigFilePath, It.Is(p => p.Equals(FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite)))) .Returns(true); } diff --git a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigGenerator.cs b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigGenerator.cs index a1fa1f06d..15c4ea8bb 100644 --- a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigGenerator.cs +++ b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigGenerator.cs @@ -12,12 +12,10 @@ public class EasyLoggingConfigGenerator public const string EmptyCommonConfig = @"{ ""common"": {} }"; - - public static readonly string WorkingDirectory = Path.Combine(Path.GetTempPath(), "easy_logging_test_configs_", Path.GetRandomFileName()); - - public static string CreateConfigTempFile(string fileContent) + + public static string CreateConfigTempFile(string workingDirectory, string fileContent) { - var filePath = NewConfigFilePath(); + var filePath = NewConfigFilePath(workingDirectory); using (var writer = File.CreateText(filePath)) { writer.Write(fileContent); @@ -26,9 +24,9 @@ public static string CreateConfigTempFile(string fileContent) return filePath; } - private static string NewConfigFilePath() + private static string NewConfigFilePath(string workingDirectory) { - return Path.Combine(WorkingDirectory, Path.GetRandomFileName()); + return Path.Combine(workingDirectory, Path.GetRandomFileName()); } public static string Config(string logLevel, string logPath) @@ -40,7 +38,8 @@ public static string Config(string logLevel, string logPath) } }" .Replace("{logLevel}", SerializeParameter(logLevel)) - .Replace("{logPath}", SerializeParameter(logPath)); + .Replace("{logPath}", SerializeParameter(logPath)) + .Replace("\\", "\\\\"); } private static string SerializeParameter(string parameter) diff --git a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigParserTest.cs b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigParserTest.cs index e5d1f3b90..847d57c51 100644 --- a/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigParserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Configuration/EasyLoggingConfigParserTest.cs @@ -17,20 +17,21 @@ public class EasyLoggingConfigParserTest private const string NotExistingFilePath = "../../../Resources/EasyLogging/not_existing_config.json"; private const string LogLevel = "info"; private const string LogPath = "./test-logs/log_file.log"; + private static readonly string s_workingDirectory = Path.Combine(Path.GetTempPath(), "easy_logging_test_configs_", Path.GetRandomFileName()); [OneTimeSetUp] public static void BeforeAll() { - if (!Directory.Exists(WorkingDirectory)) + if (!Directory.Exists(s_workingDirectory)) { - Directory.CreateDirectory(WorkingDirectory); + Directory.CreateDirectory(s_workingDirectory); } } [OneTimeTearDown] public static void AfterAll() { - Directory.Delete(WorkingDirectory, true); + Directory.Delete(s_workingDirectory, true); } [Test] @@ -38,7 +39,7 @@ public void TestThatParsesConfigFile() { // arrange var parser = new EasyLoggingConfigParser(); - var configFilePath = CreateConfigTempFile(Config(LogLevel, LogPath)); + var configFilePath = CreateConfigTempFile(s_workingDirectory, Config(LogLevel, LogPath)); // act var config = parser.Parse(configFilePath); @@ -113,8 +114,8 @@ public static IEnumerable ConfigFilesWithoutValues() BeforeAll(); return new[] { - CreateConfigTempFile(EmptyCommonConfig), - CreateConfigTempFile(Config(null, null)) + CreateConfigTempFile(s_workingDirectory, EmptyCommonConfig), + CreateConfigTempFile(s_workingDirectory, Config(null, null)) }; } @@ -123,8 +124,8 @@ public static IEnumerable WrongConfigFiles() BeforeAll(); return new[] { - CreateConfigTempFile(EmptyConfig), - CreateConfigTempFile(Config("unknown", LogPath)), + CreateConfigTempFile(s_workingDirectory, EmptyConfig), + CreateConfigTempFile(s_workingDirectory, Config("unknown", LogPath)), }; } } diff --git a/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs new file mode 100644 index 000000000..fde51602c --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using Mono.Unix; +using Mono.Unix.Native; +using NUnit.Framework; +using Snowflake.Data.Core.Tools; +using static Snowflake.Data.Tests.UnitTests.Configuration.EasyLoggingConfigGenerator; + +namespace Snowflake.Data.Tests.Tools +{ + [TestFixture, NonParallelizable] + public class UnixOperationsTest + { + private static UnixOperations s_unixOperations; + private static readonly string s_workingDirectory = Path.Combine(Path.GetTempPath(), "easy_logging_test_configs_", Path.GetRandomFileName()); + + [OneTimeSetUp] + public static void BeforeAll() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; + if (!Directory.Exists(s_workingDirectory)) + { + Directory.CreateDirectory(s_workingDirectory); + } + s_unixOperations = new UnixOperations(); + } + + [OneTimeTearDown] + public static void AfterAll() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; + Directory.Delete(s_workingDirectory, true); + } + + [Test] + public void TestDetectGroupOrOthersWritablePermissions( + [ValueSource(nameof(GroupOrOthersWritablePermissions))] FilePermissions groupOrOthersWritablePermissions, + [ValueSource(nameof(GroupNotWritablePermissions))] FilePermissions groupNotWritablePermissions, + [ValueSource(nameof(OtherNotWritablePermissions))] FilePermissions otherNotWritablePermissions) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + var filePath = CreateConfigTempFile(s_workingDirectory, "random text"); + var readWriteUserPermissions = FilePermissions.S_IRUSR | FilePermissions.S_IWUSR; + var filePermissions = readWriteUserPermissions | groupOrOthersWritablePermissions | groupNotWritablePermissions | otherNotWritablePermissions; + Syscall.chmod(filePath, filePermissions); + + // act + var result = s_unixOperations.CheckFileHasAnyOfPermissions(filePath, FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite); + + // assert + Assert.IsTrue(result); + } + + [Test] + public void TestDetectGroupOrOthersNotWritablePermissions( + [ValueSource(nameof(UserPermissions))] FilePermissions userPermissions, + [ValueSource(nameof(GroupNotWritablePermissions))] FilePermissions groupNotWritablePermissions, + [ValueSource(nameof(OtherNotWritablePermissions))] FilePermissions otherNotWritablePermissions) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + var filePath = CreateConfigTempFile(s_workingDirectory, "random text"); + var filePermissions = userPermissions | groupNotWritablePermissions | otherNotWritablePermissions; + Syscall.chmod(filePath, filePermissions); + + // act + var result = s_unixOperations.CheckFileHasAnyOfPermissions(filePath, FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite); + + // assert + Assert.IsFalse(result); + } + + + public static IEnumerable UserPermissions() + { + yield return FilePermissions.S_IRUSR; + yield return FilePermissions.S_IWUSR; + yield return FilePermissions.S_IXUSR; + yield return FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR; + } + + public static IEnumerable GroupOrOthersWritablePermissions() + { + yield return FilePermissions.S_IWGRP; + yield return FilePermissions.S_IWOTH; + yield return FilePermissions.S_IWGRP | FilePermissions.S_IWOTH; + } + + public static IEnumerable GroupNotWritablePermissions() + { + yield return 0; + yield return FilePermissions.S_IRGRP; + yield return FilePermissions.S_IXGRP; + yield return FilePermissions.S_IRGRP | FilePermissions.S_IXGRP; + } + + public static IEnumerable OtherNotWritablePermissions() + { + yield return 0; + yield return FilePermissions.S_IROTH; + yield return FilePermissions.S_IXOTH; + yield return FilePermissions.S_IROTH | FilePermissions.S_IXOTH; + } + } +} diff --git a/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs b/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs index 3fed8850e..fab097027 100644 --- a/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs +++ b/Snowflake.Data/Configuration/EasyLoggingConfigFinder.cs @@ -107,7 +107,7 @@ private void CheckIfValidPermissions(string filePath) return; // Check if others have permissions to modify the file and fail if so - if (_unixOperations.CheckFileHasPermissions(filePath, FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite)) + if (_unixOperations.CheckFileHasAnyOfPermissions(filePath, FileAccessPermissions.GroupWrite | FileAccessPermissions.OtherWrite)) { var errorMessage = $"Error due to other users having permission to modify the config file: {filePath}"; s_logger.Error(errorMessage); diff --git a/Snowflake.Data/Core/Session/EasyLoggingStarter.cs b/Snowflake.Data/Core/Session/EasyLoggingStarter.cs index cd82d3904..894e9308a 100644 --- a/Snowflake.Data/Core/Session/EasyLoggingStarter.cs +++ b/Snowflake.Data/Core/Session/EasyLoggingStarter.cs @@ -83,6 +83,15 @@ public virtual void Init(string configFilePathFromConnectionString) } } + internal void Reset(EasyLoggingLogLevel logLevel) + { + lock (_lockForExclusiveInit) + { + _initTrialParameters = null; + _easyLoggerManager.ResetEasyLogging(logLevel); + } + } + private bool AllowedToInitialize(string configFilePathFromConnectionString) { var everTriedToInitialize = _initTrialParameters != null; diff --git a/Snowflake.Data/Core/Tools/UnixOperations.cs b/Snowflake.Data/Core/Tools/UnixOperations.cs index d0e1fdf36..2437e7b74 100644 --- a/Snowflake.Data/Core/Tools/UnixOperations.cs +++ b/Snowflake.Data/Core/Tools/UnixOperations.cs @@ -22,10 +22,10 @@ public virtual FileAccessPermissions GetDirPermissions(string path) return dirInfo.FileAccessPermissions; } - public virtual bool CheckFileHasPermissions(string path, FileAccessPermissions permissions) + public virtual bool CheckFileHasAnyOfPermissions(string path, FileAccessPermissions permissions) { var fileInfo = new UnixFileInfo(path); - return fileInfo.FileAccessPermissions.HasFlag(permissions); + return (permissions & fileInfo.FileAccessPermissions) != 0; } } } diff --git a/Snowflake.Data/Logger/EasyLoggerManager.cs b/Snowflake.Data/Logger/EasyLoggerManager.cs index da1748912..ca7800f0e 100644 --- a/Snowflake.Data/Logger/EasyLoggerManager.cs +++ b/Snowflake.Data/Logger/EasyLoggerManager.cs @@ -4,6 +4,7 @@ using System; using System.IO; +using System.Linq; using log4net; using log4net.Appender; using log4net.Layout; @@ -36,6 +37,26 @@ public virtual void ReconfigureEasyLogging(EasyLoggingLogLevel easyLoggingLogLev repository.RaiseConfigurationChanged(EventArgs.Empty); } } + + internal void ResetEasyLogging(EasyLoggingLogLevel easyLoggingLogLevel) + { + var log4netLevel = _levelMapper.ToLog4NetLevel(easyLoggingLogLevel); + lock (_lockForExclusiveConfigure) + { + var repository = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository(); + var rootLogger = (log4net.Repository.Hierarchy.Logger)repository.GetLogger("Snowflake.Data"); + rootLogger.Level = log4netLevel; + RemoveOtherEasyLoggingAppenders(rootLogger, null); + repository.RaiseConfigurationChanged(EventArgs.Empty); + } + } + + internal static bool HasEasyLoggingAppender() + { + var repository = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository(); + var rootLogger = (log4net.Repository.Hierarchy.Logger)repository.GetLogger("Snowflake.Data"); + return rootLogger.Appenders.ToArray().Any(IsEasyLoggingAppender); + } private static void RemoveOtherEasyLoggingAppenders(log4net.Repository.Hierarchy.Logger logger, IAppender appender) {