Skip to content

Commit

Permalink
Support for xdg_cache_home env variable, enforce secure dir permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-knozderko committed Dec 19, 2024
1 parent f5e1862 commit 47b6333
Show file tree
Hide file tree
Showing 9 changed files with 572 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ public class SFCredentialManagerFileImplTest : SFBaseCredentialManagerTest

private const string CustomJsonDir = "testdirectory";

private static readonly string s_customJsonPath = Path.Combine(CustomJsonDir, SFCredentialManagerFileImpl.CredentialCacheFileName);
private static readonly string s_customJsonPath = Path.Combine(CustomJsonDir, SFCredentialManagerFileStorage.CredentialCacheFileName);

private static readonly string s_customLockPath = Path.Combine(CustomJsonDir, SFCredentialManagerFileImpl.CredentialCacheLockName);
private static readonly string s_customLockPath = Path.Combine(CustomJsonDir, SFCredentialManagerFileStorage.CredentialCacheLockName);

private const int UserId = 1;

[SetUp]
public void SetUp()
Expand All @@ -49,7 +51,10 @@ public void SetUp()
[TearDown]
public void CleanAll()
{
File.Delete(SFCredentialManagerFileImpl.Instance._jsonCacheFilePath);
if (SFCredentialManagerFileImpl.Instance._fileStorage != null)
{
File.Delete(SFCredentialManagerFileImpl.Instance._fileStorage.JsonCacheFilePath);
}
}

[Test]
Expand All @@ -64,8 +69,17 @@ public void TestThatThrowsErrorWhenCacheFailToCreateCacheFile()
FilePermissions.S_IRUSR | FilePermissions.S_IWUSR))
.Returns(-1);
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName))
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CredentialCacheDirectoryEnvironmentName))
.Returns(CustomJsonDir);
t_directoryOperations
.Setup(d => d.GetParentDirectoryInfo(CustomJsonDir))
.Returns(new DirectoryInformation(true, DateTime.UtcNow));
t_unixOperations
.Setup(u => u.GetDirectoryInfo(CustomJsonDir))
.Returns(new DirectoryUnixInformation(CustomJsonDir, true, FileAccessPermissions.UserReadWriteExecute, UserId));
t_unixOperations
.Setup(u => u.GetCurrentUserId())
.Returns(UserId);
t_directoryOperations
.Setup(d => d.GetDirectoryInfo(s_customLockPath))
.Returns(new DirectoryInformation(false, null));
Expand All @@ -84,13 +98,13 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers()
// arrange
var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName))
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CredentialCacheDirectoryEnvironmentName))
.Returns(tempDirectory);
_credentialManager = CreateFileCredentialManagerWithMockedEnvironmentalVariables();
try
{
DirectoryOperations.Instance.CreateDirectory(tempDirectory);
UnixOperations.Instance.CreateFileWithPermissions(Path.Combine(tempDirectory, SFCredentialManagerFileImpl.CredentialCacheFileName), FilePermissions.ALLPERMS);
UnixOperations.Instance.CreateFileWithPermissions(Path.Combine(tempDirectory, SFCredentialManagerFileStorage.CredentialCacheFileName), FilePermissions.ALLPERMS);

// act
var thrown = Assert.Throws<SecurityException>(() => _credentialManager.SaveCredentials("key", "token"));
Expand All @@ -116,12 +130,21 @@ public void TestThatJsonFileIsCheckedIfAlreadyExists()
.Setup(u => u.GetFilePermissions(s_customJsonPath))
.Returns(FileAccessPermissions.UserRead | FileAccessPermissions.UserWrite);
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName))
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CredentialCacheDirectoryEnvironmentName))
.Returns(CustomJsonDir);
t_fileOperations
.SetupSequence(f => f.Exists(s_customJsonPath))
.Returns(false)
.Returns(true);
t_directoryOperations
.Setup(d => d.GetParentDirectoryInfo(CustomJsonDir))
.Returns(new DirectoryInformation(true, DateTime.UtcNow));
t_unixOperations
.Setup(u => u.GetDirectoryInfo(CustomJsonDir))
.Returns(new DirectoryUnixInformation(CustomJsonDir, true, FileAccessPermissions.UserReadWriteExecute, UserId));
t_unixOperations
.Setup(u => u.GetCurrentUserId())
.Returns(UserId);
t_directoryOperations
.Setup(d => d.GetDirectoryInfo(s_customLockPath))
.Returns(new DirectoryInformation(false, null));
Expand All @@ -142,7 +165,7 @@ public void TestWritingIsUnavailableIfFailedToCreateDirLock()
.Setup(u => u.GetFilePermissions(s_customJsonPath))
.Returns(FileAccessPermissions.UserRead | FileAccessPermissions.UserWrite);
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName))
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CredentialCacheDirectoryEnvironmentName))
.Returns(CustomJsonDir);
t_fileOperations
.SetupSequence(f => f.Exists(s_customJsonPath))
Expand All @@ -151,6 +174,15 @@ public void TestWritingIsUnavailableIfFailedToCreateDirLock()
t_directoryOperations
.Setup(d => d.GetDirectoryInfo(s_customLockPath))
.Returns(new DirectoryInformation(false, null));
t_directoryOperations
.Setup(d => d.GetParentDirectoryInfo(CustomJsonDir))
.Returns(new DirectoryInformation(true, DateTime.UtcNow));
t_unixOperations
.Setup(u => u.GetDirectoryInfo(CustomJsonDir))
.Returns(new DirectoryUnixInformation(CustomJsonDir, true, FileAccessPermissions.UserReadWriteExecute, UserId));
t_unixOperations
.Setup(u => u.GetCurrentUserId())
.Returns(UserId);
t_unixOperations
.Setup(u => u.CreateDirectoryWithPermissions(s_customLockPath, SFCredentialManagerFileImpl.CredentialCacheLockDirPermissions))
.Returns(-1);
Expand All @@ -171,7 +203,7 @@ public void TestReadingIsUnavailableIfFailedToCreateDirLock()
.Setup(u => u.GetFilePermissions(s_customJsonPath))
.Returns(FileAccessPermissions.UserRead | FileAccessPermissions.UserWrite);
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName))
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CredentialCacheDirectoryEnvironmentName))
.Returns(CustomJsonDir);
t_fileOperations
.SetupSequence(f => f.Exists(s_customJsonPath))
Expand All @@ -180,6 +212,15 @@ public void TestReadingIsUnavailableIfFailedToCreateDirLock()
t_unixOperations
.Setup(u => u.CreateDirectoryWithPermissions(s_customLockPath, SFCredentialManagerFileImpl.CredentialCacheLockDirPermissions))
.Returns(-1);
t_directoryOperations
.Setup(d => d.GetParentDirectoryInfo(CustomJsonDir))
.Returns(new DirectoryInformation(true, DateTime.UtcNow));
t_unixOperations
.Setup(u => u.GetDirectoryInfo(CustomJsonDir))
.Returns(new DirectoryUnixInformation(CustomJsonDir, true, FileAccessPermissions.UserReadWriteExecute, UserId));
t_unixOperations
.Setup(u => u.GetCurrentUserId())
.Returns(UserId);
t_directoryOperations
.Setup(d => d.GetDirectoryInfo(s_customLockPath))
.Returns(new DirectoryInformation(false, null));
Expand All @@ -198,13 +239,13 @@ public void TestReadingAndWritingAreUnavailableIfDirLockExists()
// arrange
var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName))
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CredentialCacheDirectoryEnvironmentName))
.Returns(tempDirectory);
_credentialManager = CreateFileCredentialManagerWithMockedEnvironmentalVariables();
try
{
DirectoryOperations.Instance.CreateDirectory(tempDirectory);
DirectoryOperations.Instance.CreateDirectory(Path.Combine(tempDirectory, SFCredentialManagerFileImpl.CredentialCacheLockName));
DirectoryOperations.Instance.CreateDirectory(Path.Combine(tempDirectory, SFCredentialManagerFileStorage.CredentialCacheLockName));

// act
_credentialManager.SaveCredentials("key", "token");
Expand All @@ -219,6 +260,34 @@ public void TestReadingAndWritingAreUnavailableIfDirLockExists()
}
}

[Test]
public void TestChangeCacheDirPermissionsWhenInsecure()
{
// arrange
var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CredentialCacheDirectoryEnvironmentName))
.Returns(tempDirectory);
_credentialManager = CreateFileCredentialManagerWithMockedEnvironmentalVariables();
try
{
DirectoryOperations.Instance.CreateDirectory(tempDirectory);
UnixOperations.Instance.ChangePermissions(tempDirectory, FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.GroupRead);

// act
_credentialManager.SaveCredentials("key", "token");
var result = _credentialManager.GetCredentials("key");

// assert
Assert.AreEqual("token", result);
Assert.AreEqual(FileAccessPermissions.UserReadWriteExecute, UnixOperations.Instance.GetDirectoryInfo(tempDirectory).Permissions);
}
finally
{
DirectoryOperations.Instance.Delete(tempDirectory, true);
}
}

private SFCredentialManagerFileImpl CreateFileCredentialManagerWithMockedEnvironmentalVariables() =>
new (FileOperations.Instance, DirectoryOperations.Instance, UnixOperations.Instance, t_environmentOperations.Object);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
*/

using System;
using System.IO;
using NUnit.Framework;
using Moq;
using Snowflake.Data.Core.CredentialManager.Infrastructure;
using Snowflake.Data.Core.Tools;


namespace Snowflake.Data.Tests.UnitTests.CredentialManager
{
[TestFixture]
public class SFCredentialManagerFileStorageTest
{
private const string SnowflakeCacheLocation = "/Users/snowflake/cache";
private const string CommonCacheLocation = "/Users/snowflake/.cache";
private const string HomeLocation = "/Users/snowflake";

[ThreadStatic]
private static Mock<EnvironmentOperations> t_environmentOperations;

[SetUp]
public void SetUp()
{
t_environmentOperations = new Mock<EnvironmentOperations>();
}

[Test]
public void TestChooseLocationFromSnowflakeCacheEnvironmentVariable()
{
// arrange
MockSnowflakeCacheEnvironmentVariable();
MockCommonCacheEnvironmentVariable();
MockHomeLocation();

// act
var fileStorage = new SFCredentialManagerFileStorage(t_environmentOperations.Object);

// assert
AssertFileStorageForLocation(SnowflakeCacheLocation, fileStorage);
}

[Test]
public void TestChooseLocationFromCommonCacheEnvironmentVariable()
{
// arrange
MockCommonCacheEnvironmentVariable();
MockHomeLocation();
var expectedLocation = Path.Combine(CommonCacheLocation, SFCredentialManagerFileStorage.CredentialCacheDirName);

// act
var fileStorage = new SFCredentialManagerFileStorage(t_environmentOperations.Object);

// assert
AssertFileStorageForLocation(expectedLocation, fileStorage);
}

[Test]
public void TestChooseLocationFromHomeFolder()
{
// arrange
MockHomeLocation();
var expectedLocation = Path.Combine(HomeLocation, SFCredentialManagerFileStorage.CommonCacheDirectoryName, SFCredentialManagerFileStorage.CredentialCacheDirName);

// act
var fileStorage = new SFCredentialManagerFileStorage(t_environmentOperations.Object);

// assert
AssertFileStorageForLocation(expectedLocation, fileStorage);
}

[Test]
public void TestFailWhenLocationCannotBeIdentified()
{
// act
var thrown = Assert.Throws<Exception>(() => new SFCredentialManagerFileStorage(t_environmentOperations.Object));

// assert
Assert.That(thrown.Message, Contains.Substring("Unable to identify credential cache directory"));
}

private void AssertFileStorageForLocation(string directory, SFCredentialManagerFileStorage fileStorage)
{
Assert.NotNull(fileStorage);
Assert.AreEqual(directory, fileStorage.JsonCacheDirectory);
Assert.AreEqual(Path.Combine(directory, SFCredentialManagerFileStorage.CredentialCacheFileName), fileStorage.JsonCacheFilePath);
Assert.AreEqual(Path.Combine(directory, SFCredentialManagerFileStorage.CredentialCacheLockName), fileStorage.JsonCacheLockPath);
}

private void MockSnowflakeCacheEnvironmentVariable()
{
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CredentialCacheDirectoryEnvironmentName))
.Returns(SnowflakeCacheLocation);
}

private void MockCommonCacheEnvironmentVariable()
{
t_environmentOperations
.Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileStorage.CommonCacheDirectoryEnvironmentName))
.Returns(CommonCacheLocation);
}

private void MockHomeLocation()
{
t_environmentOperations
.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile))
.Returns(HomeLocation);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
*/

using System.IO;
using Mono.Unix;
using NUnit.Framework;
using Snowflake.Data.Core.Tools;

namespace Snowflake.Data.Tests.UnitTests.Tools
{
[TestFixture]
public class DirectoryUnixInformationTest
{
private const long UserId = 5;
private const long AnotherUserId = 6;
static readonly string s_directoryFullName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

[Test]
[TestCase(FileAccessPermissions.UserWrite)]
[TestCase(FileAccessPermissions.UserRead)]
[TestCase(FileAccessPermissions.UserExecute)]
[TestCase(FileAccessPermissions.UserReadWriteExecute)]
public void TestSafeDirectory(FileAccessPermissions securePermissions)
{
// arrange
var dirInfo = new DirectoryUnixInformation(s_directoryFullName, true, securePermissions, UserId);

// act
var isSafe = dirInfo.IsSafe(UserId);

// assert
Assert.True(isSafe);
}

[Test]
[TestCase(FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.GroupRead)]
[TestCase(FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.OtherRead)]
public void TestUnsafePermissions(FileAccessPermissions unsecurePermissions)
{
// arrange
var dirInfo = new DirectoryUnixInformation(s_directoryFullName, true, unsecurePermissions, UserId);

// act
var isSafe = dirInfo.IsSafe(UserId);

// assert
Assert.False(isSafe);
}

[Test]
public void TestSafeExactlyDirectory()
{
// arrange
var dirInfo = new DirectoryUnixInformation(s_directoryFullName, true, FileAccessPermissions.UserReadWriteExecute, UserId);

// act
var isSafe = dirInfo.IsSafeExactly(UserId);

// assert
Assert.True(isSafe);
}

[Test]
[TestCase(FileAccessPermissions.UserRead)]
[TestCase(FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.GroupRead)]
[TestCase(FileAccessPermissions.UserReadWriteExecute | FileAccessPermissions.OtherRead)]
public void TestUnsafeExactlyPermissions(FileAccessPermissions unsecurePermissions)
{
// arrange
var dirInfo = new DirectoryUnixInformation(s_directoryFullName, true, unsecurePermissions, UserId);

// act
var isSafe = dirInfo.IsSafeExactly(UserId);

// assert
Assert.False(isSafe);
}

[Test]
public void TestOwnedByOthers()
{
// arrange
var dirInfo = new DirectoryUnixInformation(s_directoryFullName, true, FileAccessPermissions.UserReadWriteExecute, UserId);

// act
var isSafe = dirInfo.IsSafe(AnotherUserId);
var isSafeExactly = dirInfo.IsSafeExactly(AnotherUserId);

// assert
Assert.False(isSafe);
Assert.False(isSafeExactly);
}
}
}
Loading

0 comments on commit 47b6333

Please sign in to comment.