Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle unauthorized files / dirs for cleanup #1328

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,80 +56,80 @@
// determining the files that exist as there will be no subsequent cleanup process.
if (this.FileUtilityService == null
|| this.DirectoryUtilityService == null
|| !this.TryGetCleanupFileDirectory(processRequest, out var fileParentDirectory))
|| !this.TryGetCleanupFileDirectory(processRequest, out var fileParentDirectory)
|| !cleanupCreatedFiles)
pauld-msft marked this conversation as resolved.
Show resolved Hide resolved
{
await process(processRequest, detectorArgs, cancellationToken).ConfigureAwait(false);
return;
}

// Get the files and directories that match the cleanup pattern and exist before the process runs.
var (preExistingFiles, preExistingDirs) = this.DirectoryUtilityService.GetFilesAndDirectories(fileParentDirectory, this.CleanupPatterns, DefaultCleanDepth);
try
var (preSuccess, preExistingFiles, preExistingDirs) = this.TryGetFilesAndDirectories(fileParentDirectory, this.CleanupPatterns, DefaultCleanDepth);

await process(processRequest, detectorArgs, cancellationToken).ConfigureAwait(false);
if (!preSuccess)
{
await process(processRequest, detectorArgs, cancellationToken).ConfigureAwait(false);
// return early if we failed to get the pre-existing files and directories, since no need for cleanup
return;
}
finally

// Ensure that only one cleanup process is running at a time, helping to prevent conflicts
await this.cleanupSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

try
{
// Ensure that only one cleanup process is running at a time, helping to prevent conflicts
await this.cleanupSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
// Clean up any new files or directories created during the scan that match the clean up patterns.
var (postSuccess, latestFiles, latestDirs) = this.TryGetFilesAndDirectories(fileParentDirectory, this.CleanupPatterns, DefaultCleanDepth);
if (!postSuccess)
{

Check warning on line 84 in src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs

View check run for this annotation

Codecov / codecov/patch

src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs#L84

Added line #L84 was not covered by tests
// return early if we failed to get the latest files and directories, since we will not be able
// to determine what to clean up
return;

Check warning on line 87 in src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs

View check run for this annotation

Codecov / codecov/patch

src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs#L87

Added line #L87 was not covered by tests
}

try
var createdFiles = latestFiles.Except(preExistingFiles).ToList();
var createdDirs = latestDirs.Except(preExistingDirs).ToList();

foreach (var createdDir in createdDirs)
{
// Clean up any new files or directories created during the scan that match the clean up patterns.
// If the cleanupCreatedFiles flag is set to false, this will be a dry run and will just log the files that it would clean.
var dryRun = !cleanupCreatedFiles;
var dryRunStr = dryRun ? "[DRYRUN] " : string.Empty;
var (latestFiles, latestDirs) = this.DirectoryUtilityService.GetFilesAndDirectories(fileParentDirectory, this.CleanupPatterns, DefaultCleanDepth);
var createdFiles = latestFiles.Except(preExistingFiles).ToList();
var createdDirs = latestDirs.Except(preExistingDirs).ToList();

foreach (var createdDir in createdDirs)
if (createdDir is null || !this.DirectoryUtilityService.Exists(createdDir))
{
if (createdDir is null || !this.DirectoryUtilityService.Exists(createdDir))
{
continue;
}

try
{
this.Logger.LogDebug("{DryRun}Cleaning up directory {Dir}", dryRunStr, createdDir);
if (!dryRun)
{
this.DirectoryUtilityService.Delete(createdDir, true);
}
}
catch (Exception e)
{
this.Logger.LogDebug(e, "{DryRun}Failed to delete directory {Dir}", dryRunStr, createdDir);
}
continue;

Check warning on line 97 in src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs

View check run for this annotation

Codecov / codecov/patch

src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs#L97

Added line #L97 was not covered by tests
}

foreach (var createdFile in createdFiles)
try
{
this.Logger.LogDebug("Cleaning up directory {Dir}", createdDir);
this.DirectoryUtilityService.Delete(createdDir, true);
}
catch (Exception e)

Check warning on line 105 in src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs

View check run for this annotation

Codecov / codecov/patch

src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs#L105

Added line #L105 was not covered by tests
{
if (createdFile is null || !this.FileUtilityService.Exists(createdFile))
{
continue;
}

try
{
this.Logger.LogDebug("{DryRun}Cleaning up file {File}", dryRunStr, createdFile);
if (!dryRun)
{
this.FileUtilityService.Delete(createdFile);
}
}
catch (Exception e)
{
this.Logger.LogDebug(e, "{DryRun}Failed to delete file {File}", dryRunStr, createdFile);
}
this.Logger.LogDebug(e, "Failed to delete directory {Dir}", createdDir);

Check warning on line 107 in src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs

View check run for this annotation

Codecov / codecov/patch

src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs#L107

Added line #L107 was not covered by tests
}
}
finally

foreach (var createdFile in createdFiles)
{
_ = this.cleanupSemaphore.Release();
if (createdFile is null || !this.FileUtilityService.Exists(createdFile))
{
continue;

Check warning on line 115 in src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs

View check run for this annotation

Codecov / codecov/patch

src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs#L114-L115

Added lines #L114 - L115 were not covered by tests
}

try
{
this.Logger.LogDebug("Cleaning up file {File}", createdFile);
this.FileUtilityService.Delete(createdFile);
}
catch (Exception e)
{
this.Logger.LogDebug(e, "Failed to delete file {File}", createdFile);
}

Check warning on line 126 in src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs

View check run for this annotation

Codecov / codecov/patch

src/Microsoft.ComponentDetection.Contracts/FileComponentDetectorWithCleanup.cs#L123-L126

Added lines #L123 - L126 were not covered by tests
}
}
finally
{
_ = this.cleanupSemaphore.Release();
}
}

protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs, bool cleanupCreatedFiles, CancellationToken cancellationToken = default) =>
Expand All @@ -151,4 +151,19 @@

return false;
}

private (bool Success, HashSet<string> Files, HashSet<string> Directories) TryGetFilesAndDirectories(string root, IList<string> patterns, int depth)
{
try
{
var (files, directories) = this.DirectoryUtilityService.GetFilesAndDirectories(root, patterns, depth);
return (true, files, directories);
}
catch (UnauthorizedAccessException e)
{
// log and return false if we are unauthorized to get files and directories
this.Logger.LogDebug(e, "Unauthorized to get files and directories for {Root}", root);
return (false, new HashSet<string>(), new HashSet<string>());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ public async Task WithCleanupAsync_CleansUpCreatedFile()
var fileComponentDetector = new TestFileComponentDetector(["*.pyc"], this.loggerMock.Object, fileUtilityService, directoryUtilityService);
var createdFilePath = Path.Combine(this.testDirectory, "todelete.pyc");

// Add files/dirs to the mock file system

// Act
await fileComponentDetector.TestCleanupAsync(
(process, args, token) =>
Expand Down Expand Up @@ -198,6 +196,11 @@ await fileComponentDetector.TestCleanupAsync(
fileUtilityService.Exists(this.existingFilePath).Should().BeTrue();
directoryUtilityService.Exists(createdDirectory).Should().BeTrue();
fileUtilityService.Exists(createdFilePath).Should().BeTrue();

// Assert we don't even try to read items
this.directoryUtilityServiceMock.Verify(
d => d.GetFilesAndDirectories(It.IsAny<string>(), It.IsAny<List<string>>(), It.IsAny<int>()),
Times.Never);
}

[TestMethod]
Expand Down Expand Up @@ -292,6 +295,40 @@ await fileComponentDetector.TestCleanupAsync(
fileUtilityService.Exists(createdFilePath).Should().BeTrue();
}

[TestMethod]
public async Task WithCleanupAsync_NoCleanup_WhenUnauthorized()
{
// Arrange
var fileUtilityService = this.fileUtilityServiceMock.Object;
var directoryUtilityService = this.directoryUtilityServiceMock.Object;
CancellationToken cancellationToken = default;
var detectorArgs = new Dictionary<string, string>();
var cleanupCreatedFiles = true;
var fileComponentDetector = new TestFileComponentDetector(["*.pyc"], this.loggerMock.Object, fileUtilityService, directoryUtilityService);
var createdFilePath = Path.Combine(this.testDirectory, "todelete.pyc");
this.directoryUtilityServiceMock
.Setup(d => d.GetFilesAndDirectories(It.IsAny<string>(), It.IsAny<List<string>>(), It.IsAny<int>()))
.Throws(new UnauthorizedAccessException("Unauthorized"));

// Act
await fileComponentDetector.TestCleanupAsync(
(process, args, token) =>
{
// creates a single file
this.fileSystemMockFiles.Add(createdFilePath);
return Task.CompletedTask;
},
this.processRequest,
detectorArgs,
cleanupCreatedFiles,
cancellationToken).ConfigureAwait(false);

// Assert
directoryUtilityService.Exists(this.testDirectory).Should().BeTrue();
fileUtilityService.Exists(this.existingFilePath).Should().BeTrue();
fileUtilityService.Exists(createdFilePath).Should().BeTrue();
}

private bool IsPatternMatch(string path, string pattern)
{
return Regex.IsMatch(path, "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$");
Expand Down
Loading