128 lines
4.1 KiB
C#
128 lines
4.1 KiB
C#
using System.Security.AccessControl;
|
|
using System.Security.Principal;
|
|
using System.Runtime.Versioning;
|
|
using MBN_STOCK_WEBVIEW.Infrastructure;
|
|
|
|
namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests;
|
|
|
|
public sealed class TrustedManualDirectoryTests
|
|
{
|
|
[Fact]
|
|
public void Prepare_creates_nested_private_directory_with_protected_acl()
|
|
{
|
|
using var parent = new TemporaryDirectory();
|
|
var target = Path.Combine(parent.Path, "operator", "private");
|
|
|
|
var prepared = TrustedManualDirectory.PreparePrivateWritableDirectory(target);
|
|
|
|
Assert.Equal(Path.GetFullPath(target), prepared);
|
|
Assert.True(Directory.Exists(prepared));
|
|
if (OperatingSystem.IsWindows())
|
|
{
|
|
AssertPrivateAcl(prepared);
|
|
}
|
|
}
|
|
|
|
[SupportedOSPlatform("windows")]
|
|
private static void AssertPrivateAcl(string directory)
|
|
{
|
|
var security = new DirectoryInfo(directory).GetAccessControl(
|
|
AccessControlSections.Access);
|
|
Assert.True(security.AreAccessRulesProtected);
|
|
Assert.DoesNotContain(
|
|
security.GetAccessRules(true, true, typeof(SecurityIdentifier))
|
|
.Cast<FileSystemAccessRule>(),
|
|
rule => rule.IsInherited);
|
|
}
|
|
|
|
[Fact]
|
|
public void Writable_store_rejects_acl_expanded_to_world_readable()
|
|
{
|
|
if (!OperatingSystem.IsWindows())
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var directory = new TemporaryDirectory();
|
|
TrustedManualDirectory.PreparePrivateWritableDirectory(directory.Path);
|
|
var info = new DirectoryInfo(directory.Path);
|
|
var security = info.GetAccessControl();
|
|
security.AddAccessRule(new FileSystemAccessRule(
|
|
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
|
|
FileSystemRights.Read,
|
|
AccessControlType.Allow));
|
|
info.SetAccessControl(security);
|
|
|
|
Assert.Throws<S5025TrustedManualDataException>(() =>
|
|
new S5025TrustedManualFileStore(directory.Path));
|
|
}
|
|
|
|
[Fact]
|
|
public void Writable_lease_pins_directory_against_rename_race()
|
|
{
|
|
if (!OperatingSystem.IsWindows())
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var parent = new TemporaryDirectory();
|
|
var directory = Path.Combine(parent.Path, "operator");
|
|
TrustedManualDirectory.PreparePrivateWritableDirectory(directory);
|
|
using (var store = new S5025TrustedManualFileStore(directory))
|
|
{
|
|
Assert.ThrowsAny<IOException>(() =>
|
|
Directory.Move(directory, Path.Combine(parent.Path, "redirected")));
|
|
}
|
|
|
|
Directory.Move(directory, Path.Combine(parent.Path, "released"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Prepare_rejects_reparse_ancestor_before_creating_target()
|
|
{
|
|
using var parent = new TemporaryDirectory();
|
|
using var outside = new TemporaryDirectory();
|
|
var link = Path.Combine(parent.Path, "linked");
|
|
try
|
|
{
|
|
Directory.CreateSymbolicLink(link, outside.Path);
|
|
}
|
|
catch (Exception exception) when (exception is
|
|
UnauthorizedAccessException or IOException or PlatformNotSupportedException)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Assert.Throws<S5025TrustedManualDataException>(() =>
|
|
TrustedManualDirectory.PreparePrivateWritableDirectory(
|
|
Path.Combine(link, "must-not-be-created")));
|
|
Assert.False(Directory.Exists(Path.Combine(outside.Path, "must-not-be-created")));
|
|
}
|
|
|
|
private sealed class TemporaryDirectory : IDisposable
|
|
{
|
|
public TemporaryDirectory()
|
|
{
|
|
Path = System.IO.Path.Combine(
|
|
System.IO.Path.GetTempPath(),
|
|
Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(Path);
|
|
}
|
|
|
|
public string Path { get; }
|
|
|
|
public void Dispose()
|
|
{
|
|
try
|
|
{
|
|
Directory.Delete(Path, recursive: true);
|
|
}
|
|
catch (Exception exception) when (exception is
|
|
IOException or UnauthorizedAccessException)
|
|
{
|
|
// Cleanup must not hide the test result.
|
|
}
|
|
}
|
|
}
|
|
}
|