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(), 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(() => 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(() => 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(() => 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. } } } }