Skip to content

Commit d055a3a

Browse files
authored
fix: throw an UnauthorizedAccessException immediately when enumerating a directory without access (#860)
This PR fixes a bug where `UnauthorizedAccessException` was not thrown immediately when attempting to enumerate directories without proper access. The fix moves the access control check from inside the enumeration implementation to before enumeration begins, ensuring the exception is thrown synchronously rather than when the enumeration is actually consumed. ### Key Changes - Access control validation is now performed immediately when enumeration methods are called - Refactored enumeration implementation to separate access checking from the actual enumeration logic - Added comprehensive tests to verify the immediate exception behavior
1 parent b16c3ce commit d055a3a

File tree

2 files changed

+78
-10
lines changed

2 files changed

+78
-10
lines changed

Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,14 @@ public IEnumerable<IStorageLocation> EnumerateLocations(
199199
throw ExceptionFactory.DirectoryNotFound(location.FullPath);
200200
}
201201

202-
return EnumerateLocationsImpl(location, type, requestParentAccess, searchPattern,
203-
enumerationOptions, parentContainer);
202+
IDisposable parentAccess = new NoOpDisposable();
203+
if (requestParentAccess)
204+
{
205+
parentAccess = parentContainer.RequestAccess(FileAccess.Read, FileShare.ReadWrite);
206+
}
207+
208+
return EnumerateLocationsImpl(location, type, searchPattern,
209+
enumerationOptions, parentAccess);
204210
}
205211

206212
/// <summary>
@@ -210,17 +216,10 @@ public IEnumerable<IStorageLocation> EnumerateLocations(
210216
private IEnumerable<IStorageLocation> EnumerateLocationsImpl(
211217
IStorageLocation location,
212218
FileSystemTypes type,
213-
bool requestParentAccess,
214219
string searchPattern,
215220
EnumerationOptions? enumerationOptions,
216-
IStorageContainer parentContainer)
221+
IDisposable parentAccess)
217222
{
218-
IDisposable parentAccess = new NoOpDisposable();
219-
if (requestParentAccess)
220-
{
221-
parentAccess = parentContainer.RequestAccess(FileAccess.Read, FileShare.ReadWrite);
222-
}
223-
224223
using (parentAccess)
225224
{
226225
enumerationOptions ??= EnumerationOptionsHelper.Compatible;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Testably.Abstractions.Testing.FileSystem;
2+
3+
namespace Testably.Abstractions.Testing.Tests.FileSystem;
4+
5+
public class DirectoryMockTests
6+
{
7+
[Fact]
8+
public async Task
9+
EnumerateDirectories_UnauthorizedParentAccess_ShouldThrowUnauthorizedAccessExceptionImmediately()
10+
{
11+
Skip.IfNot(Test.RunsOnWindows);
12+
13+
string path = "foo";
14+
MockFileSystem fileSystem = new();
15+
IDirectoryInfo sut = fileSystem.Directory.CreateDirectory(path);
16+
fileSystem.WithAccessControlStrategy(
17+
new DefaultAccessControlStrategy((p, _)
18+
=> !p.EndsWith(path, StringComparison.Ordinal)));
19+
20+
void Act() =>
21+
_ = fileSystem.Directory.EnumerateDirectories(path);
22+
23+
await That(Act).Throws<UnauthorizedAccessException>()
24+
.WithMessageContaining($"'{sut.FullName}'").And
25+
.WithHResult(-2147024891);
26+
}
27+
28+
[Fact]
29+
public async Task
30+
EnumerateFiles_UnauthorizedParentAccess_ShouldThrowUnauthorizedAccessExceptionImmediately()
31+
{
32+
Skip.IfNot(Test.RunsOnWindows);
33+
34+
string path = "foo";
35+
MockFileSystem fileSystem = new();
36+
IDirectoryInfo sut = fileSystem.Directory.CreateDirectory(path);
37+
fileSystem.WithAccessControlStrategy(
38+
new DefaultAccessControlStrategy((p, _)
39+
=> !p.EndsWith(path, StringComparison.Ordinal)));
40+
41+
void Act() =>
42+
_ = fileSystem.Directory.EnumerateFiles(path);
43+
44+
await That(Act).Throws<UnauthorizedAccessException>()
45+
.WithMessageContaining($"'{sut.FullName}'").And
46+
.WithHResult(-2147024891);
47+
}
48+
49+
[Fact]
50+
public async Task
51+
EnumerateFileSystemEntries_UnauthorizedParentAccess_ShouldThrowUnauthorizedAccessExceptionImmediately()
52+
{
53+
Skip.IfNot(Test.RunsOnWindows);
54+
55+
string path = "foo";
56+
MockFileSystem fileSystem = new();
57+
IDirectoryInfo sut = fileSystem.Directory.CreateDirectory(path);
58+
fileSystem.WithAccessControlStrategy(
59+
new DefaultAccessControlStrategy((p, _)
60+
=> !p.EndsWith(path, StringComparison.Ordinal)));
61+
62+
void Act() =>
63+
_ = fileSystem.Directory.EnumerateFileSystemEntries(path);
64+
65+
await That(Act).Throws<UnauthorizedAccessException>()
66+
.WithMessageContaining($"'{sut.FullName}'").And
67+
.WithHResult(-2147024891);
68+
}
69+
}

0 commit comments

Comments
 (0)