Media: Add protection to restrict access to media in recycle bin (closes #2931) (#20378)

* Add MoveFile it IFileSystem and implement on file systems.

* Rename media file on move to recycle bin.

* Rename file on restore from recycle bin.

* Add configuration to enabled recycle bin media protection.

* Expose backoffice authentication as cookie for non-backoffice usage.
Protected requests for media in recycle bin.

* Display protected image when viewing image cropper in the backoffice media recycle bin.

* Code tidy and comments.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Introduced helper class to DRY up repeated code between image cropper and file upload notification handlers.

* Reverted client-side and management API updates.

* Moved update of path to media file in recycle bin with deleted suffix to the server.

* Separate integration tests for add and remove.

* Use interpolated strings.

* Renamed variable.

* Move EnableMediaRecycleBinProtection to ContentSettings.

* Tidied up comments.

* Added TODO for 18.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Andy Butland
2025-11-04 08:39:44 +01:00
committed by GitHub
parent b502e29d51
commit 2b8146f72d
24 changed files with 757 additions and 34 deletions

View File

@@ -428,13 +428,9 @@ namespace Umbraco.Cms.Core.IO
WithRetry(() => File.Delete(fullPath));
}
var directory = Path.GetDirectoryName(fullPath);
if (directory == null)
{
throw new InvalidOperationException("Could not get directory.");
}
Directory.CreateDirectory(directory); // ensure it exists
// Ensure the directory exists.
var directory = Path.GetDirectoryName(fullPath) ?? throw new InvalidOperationException("Could not get directory.");
Directory.CreateDirectory(directory);
if (copy)
{
@@ -446,6 +442,35 @@ namespace Umbraco.Cms.Core.IO
}
}
/// <inheritdoc/>
public void MoveFile(string source, string target, bool overrideIfExists = true)
{
var fullSourcePath = GetFullPath(source);
if (File.Exists(fullSourcePath) is false)
{
throw new FileNotFoundException($"File at path '{source}' could not be found.");
}
var fullTargetPath = GetFullPath(target);
if (File.Exists(fullTargetPath))
{
if (overrideIfExists)
{
DeleteFile(target);
}
else
{
throw new IOException($"A file at path '{target}' already exists.");
}
}
// Ensure the directory exists.
var directory = Path.GetDirectoryName(fullTargetPath) ?? throw new InvalidOperationException("Could not get directory.");
Directory.CreateDirectory(directory);
WithRetry(() => File.Move(fullSourcePath, fullTargetPath));
}
#region Helper Methods
protected virtual void EnsureDirectory(string path)