Skip to content

Commit

Permalink
Merge remote-tracking branch 'official/main' into issue/OCORE-161
Browse files Browse the repository at this point in the history
# Conflicts:
#	mkdocs.yml
  • Loading branch information
Piedone committed Apr 28, 2024
2 parents ada1b47 + bcf2150 commit f2cc0b1
Show file tree
Hide file tree
Showing 62 changed files with 1,446 additions and 267 deletions.
3 changes: 1 addition & 2 deletions OrchardCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{E33D95DF-E864-4D14-A561-D59E8AC06811}"
ProjectSection(SolutionItems) = preProject
mkdocs.yml = mkdocs.yml
src\README.md = src\README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Docs", "src\docs\OrchardCore.Docs.csproj", "{A37CD7CB-6B26-483A-9D5A-3E80B6CB115F}"
Expand Down Expand Up @@ -500,7 +499,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Notifications.C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Notifications", "src\OrchardCore.Modules\OrchardCore.Notifications\OrchardCore.Notifications.csproj", "{19594A96-A033-4820-820B-C6186D00D507}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.HealthChecks.Abstractions", "src\OrchardCore\OrchardCore.HealthChecks.Abstractions\OrchardCore.HealthChecks.Abstractions.csproj", "{91CED599-45B1-474D-A7F2-231BF857A5F2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.HealthChecks.Abstractions", "src\OrchardCore\OrchardCore.HealthChecks.Abstractions\OrchardCore.HealthChecks.Abstractions.csproj", "{91CED599-45B1-474D-A7F2-231BF857A5F2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Seo.Abstractions", "src\OrchardCore\OrchardCore.Seo.Abstractions\OrchardCore.Seo.Abstractions.csproj", "{C61CC748-39BD-4900-9FEE-A2483259573D}"
EndProject
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Orchard Core consists of two distinct projects:

## Build Status

Stable (`release/1.8.2`):
Stable (`release/1.8.3`):

[![Build status](https://github.com/OrchardCMS/OrchardCore/actions/workflows/release_ci.yml/badge.svg)](https://github.com/OrchardCMS/OrchardCore/actions?query=workflow%3A%22Release+-+CI%22)
[![NuGet](https://img.shields.io/nuget/v/OrchardCore.Application.Cms.Targets.svg)](https://www.nuget.org/packages/OrchardCore.Application.Cms.Targets)
Expand All @@ -23,7 +23,7 @@ Nightly (`main`):
[![Build status](https://github.com/OrchardCMS/OrchardCore/actions/workflows/preview_ci.yml/badge.svg)](https://github.com/OrchardCMS/OrchardCore/actions?query=workflow%3A%22Preview+-+CI%22)
[![Cloudsmith](https://api-prd.cloudsmith.io/badges/version/orchardcore/preview/nuget/OrchardCore.Application.Cms.Targets/latest/x/?render=true&badge_token=gAAAAABey9hKFD_C-ZIpLvayS3HDsIjIorQluDs53KjIdlxoDz6Ntt1TzvMNJp7a_UWvQbsfN5nS7_0IbxCyqHZsjhmZP6cBkKforo-NqwrH5-E6QCrJ3D8%3D)](https://cloudsmith.io/~orchardcore/repos/preview/packages/detail/nuget/OrchardCore.Application.Cms.Targets/latest/)

## Project Status: v1.8.2
## Project Status: v1.8.3

The software is production-ready, and capable of serving large mission-critical applications as well, and we're not aware of any fundamental bugs or missing features we deem crucial. Orchard Core continues to evolve, with each version bringing new improvements, and keeping up with the cutting-edge of .NET.

Expand Down
372 changes: 191 additions & 181 deletions mkdocs.yml

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/OrchardCore.Build/Dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<PackageManagement Include="MessagePack" Version="2.2.60" />
<PackageManagement Include="Microsoft.Extensions.Azure" Version="1.7.3" />
<PackageManagement Include="Microsoft.Extensions.Http.Resilience" Version="8.4.0" />
<PackageManagement Include="Microsoft.Identity.Web" Version="2.17.5" />
<PackageManagement Include="Microsoft.Identity.Web" Version="2.18.0" />
<PackageManagement Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageManagement Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageManagement Include="MimeKit" Version="4.5.0" />
Expand All @@ -63,8 +63,8 @@
<PackageManagement Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageManagement Include="Shortcodes" Version="1.3.3" />
<PackageManagement Include="SixLabors.ImageSharp.Web" Version="3.1.2" />
<PackageManagement Include="SixLabors.ImageSharp.Web.Providers.Azure" Version="3.1.1" />
<PackageManagement Include="SixLabors.ImageSharp.Web.Providers.AWS" Version="3.1.1" />
<PackageManagement Include="SixLabors.ImageSharp.Web.Providers.Azure" Version="3.1.2" />
<PackageManagement Include="SixLabors.ImageSharp.Web.Providers.AWS" Version="3.1.2" />
<PackageManagement Include="StackExchange.Redis" Version="2.7.33" />
<PackageManagement Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageManagement Include="System.Linq.Async" Version="6.0.1" />
Expand Down
1 change: 1 addition & 0 deletions src/OrchardCore.Cms.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
//"OrchardCore_Media": {
// "SupportedSizes": [ 16, 32, 50, 100, 160, 240, 480, 600, 1024, 2048 ],
// "MaxBrowserCacheDays": 30,
// "MaxSecureFilesBrowserCacheDays": 0,
// "MaxCacheDays": 365,
// "ResizedCacheMaxStale": "01:00:00", // The time before a stale item is removed from the resized media cache, if not provided there is no cleanup.
// "RemoteCacheMaxStale": "01:00:00", // The time before a stale item is removed from the remote media cache, if not provided there is no cleanup.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function initializeMediaApplication(displayMediaApplication, mediaApplicationUrl
name: $('#t-mediaLibrary').text(),
path: '',
folder: '',
isDirectory: true
isDirectory: true,
canCreateFolder: $('#allowNewRootFolders').val() === 'true'
};

mediaApp = new Vue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ Vue.component('folder', {
<span v-on:click.stop="toggle" class="expand" :class="{opened: open, closed: !open, empty: empty}"><i v-if="open" class="fa-solid fa-chevron-${document.dir == "ltr" ? "right" : "left"}"></i></span>
<div class="folder-name ms-2">{{model.name}}</div>
<div class="btn-group folder-actions" >
<a v-cloak href="javascript:;" class="btn btn-sm" v-on:click="createFolder" v-if="isSelected || isRoot"><i class="fa-solid fa-plus" aria-hidden="true"></i></a>
<a v-cloak href="javascript:;" class="btn btn-sm" v-on:click="deleteFolder" v-if="isSelected && !isRoot"><i class="fa-solid fa-trash" aria-hidden="true"></i></a>
<a v-cloak href="javascript:;" class="btn btn-sm" v-on:click="createFolder" v-if="canCreateFolder && (isSelected || isRoot)"><i class="fa-solid fa-plus" aria-hidden="true"></i></a>
<a v-cloak href="javascript:;" class="btn btn-sm" v-on:click="deleteFolder" v-if="canDeleteFolder && isSelected && !isRoot"><i class="fa-solid fa-trash" aria-hidden="true"></i></a>
</div>
</a>
</div>
Expand Down Expand Up @@ -48,6 +48,12 @@ Vue.component('folder', {
},
isRoot: function () {
return this.model.path === '';
},
canCreateFolder: function () {
return this.model.canCreateFolder !== undefined ? this.model.canCreateFolder : true;
},
canDeleteFolder: function () {
return this.model.canDeleteFolder !== undefined ? this.model.canDeleteFolder : true;
}
},
mounted: function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class AdminController : Controller
private readonly IChunkFileUploadService _chunkFileUploadService;
private readonly IFileVersionProvider _fileVersionProvider;
private readonly IServiceProvider _serviceProvider;
private readonly AttachedMediaFieldFileService _attachedMediaFieldFileService;

public AdminController(
IMediaFileStore mediaFileStore,
Expand All @@ -48,7 +49,8 @@ public class AdminController : Controller
IUserAssetFolderNameProvider userAssetFolderNameProvider,
IChunkFileUploadService chunkFileUploadService,
IFileVersionProvider fileVersionProvider,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
AttachedMediaFieldFileService attachedMediaFieldFileService)
{
_mediaFileStore = mediaFileStore;
_mediaNameNormalizerService = mediaNameNormalizerService;
Expand All @@ -61,6 +63,7 @@ public class AdminController : Controller
_chunkFileUploadService = chunkFileUploadService;
_fileVersionProvider = fileVersionProvider;
_serviceProvider = serviceProvider;
_attachedMediaFieldFileService = attachedMediaFieldFileService;
}

[Admin("Media", "Media.Index")]
Expand All @@ -74,7 +77,7 @@ public async Task<IActionResult> Index()
return View();
}

public async Task<ActionResult<IEnumerable<IFileStoreEntry>>> GetFolders(string path)
public async Task<ActionResult<IEnumerable<MediaFolderViewModel>>> GetFolders(string path)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageMedia))
{
Expand All @@ -101,7 +104,21 @@ public async Task<ActionResult<IEnumerable<IFileStoreEntry>>> GetFolders(string
var allowed = _mediaFileStore.GetDirectoryContentAsync(path)
.WhereAwait(async e => e.IsDirectory && await _authorizationService.AuthorizeAsync(User, Permissions.ManageMediaFolder, (object)e.Path));

return Ok(await allowed.ToListAsync());
return Ok(await allowed.Select(folder =>
{
var isSpecial = IsSpecialFolder(folder.Path);
return new MediaFolderViewModel()
{
Name = folder.Name,
Path = folder.Path,
DirectoryPath = folder.DirectoryPath,
IsDirectory = true,
LastModifiedUtc = folder.LastModifiedUtc,
Length = folder.Length,
CanCreateFolder = !isSpecial,
CanDeleteFolder = !isSpecial
};
}).ToListAsync());
}

public async Task<ActionResult<IEnumerable<object>>> GetMediaItems(string path, string extensions)
Expand Down Expand Up @@ -136,7 +153,8 @@ public async Task<ActionResult<IEnumerable<object>>> GetMediaItems(string path,

public async Task<ActionResult<object>> GetMediaItem(string path)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageMedia))
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageMedia)
|| (HttpContext.IsSecureMediaEnabled() && !await _authorizationService.AuthorizeAsync(User, SecureMediaPermissions.ViewMedia, (object)(path ?? string.Empty))))
{
return Forbid();
}
Expand All @@ -160,7 +178,8 @@ public async Task<ActionResult<object>> GetMediaItem(string path)
[MediaSizeLimit]
public async Task<IActionResult> Upload(string path, string extensions)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageMedia))
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageMedia)
|| (HttpContext.IsSecureMediaEnabled() && !await _authorizationService.AuthorizeAsync(User, SecureMediaPermissions.ViewMedia, (object)(path ?? string.Empty))))
{
return Forbid();
}
Expand Down Expand Up @@ -308,7 +327,8 @@ public async Task<IActionResult> DeleteMedia(string path)
public async Task<IActionResult> MoveMedia(string oldPath, string newPath)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageMedia)
|| !await _authorizationService.AuthorizeAsync(User, Permissions.ManageMediaFolder, (object)oldPath))
|| !await _authorizationService.AuthorizeAsync(User, Permissions.ManageMediaFolder, (object)oldPath)
|| !await _authorizationService.AuthorizeAsync(User, Permissions.ManageMediaFolder, (object)newPath))
{
return Forbid();
}
Expand Down Expand Up @@ -482,8 +502,11 @@ public object CreateFileResult(IFileStoreEntry mediaFile)
};
}

public IActionResult MediaApplication(MediaApplicationViewModel model)
public async Task<IActionResult> MediaApplication(MediaApplicationViewModel model)
{
// Check if the user has access to new folders. If not, we hide the "create folder" button from the root folder.
model.AllowNewRootFolders = !HttpContext.IsSecureMediaEnabled() || await _authorizationService.AuthorizeAsync(User, SecureMediaPermissions.ViewMedia, (object)"_non-existent-path-87FD1922-8F88-4A33-9766-DA03E6E6F7BA");

return View(model);
}

Expand Down Expand Up @@ -553,5 +576,8 @@ private async Task PreCacheRemoteMedia(IFileStoreEntry mediaFile, Stream stream
localStream?.Dispose();
}
}

private bool IsSpecialFolder(string path)
=> string.Equals(path, _mediaOptions.AssetsUsersFolder, StringComparison.OrdinalIgnoreCase) || string.Equals(path, _attachedMediaFieldFileService.MediaFieldsFolder, StringComparison.OrdinalIgnoreCase);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public override IDisplayResult Edit(MediaField field, BuildFieldEditorContext co
}
model.Paths = JConvert.SerializeObject(itemPaths, JOptions.CamelCase);
model.TempUploadFolder = _attachedMediaFieldFileService.MediaFieldsTempSubFolder;
model.TempUploadFolder = _attachedMediaFieldFileService.GetMediaFieldsTempSubFolder();
model.Field = field;
model.Part = context.ContentPart;
model.PartFieldDefinition = context.PartFieldDefinition;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Threading.Tasks;
using OrchardCore.Environment.Cache;
using OrchardCore.Media.Core.Events;

namespace OrchardCore.Media.Events;

internal sealed class SecureMediaFileStoreEventHandler : MediaEventHandlerBase
{
private readonly ISignal _signal;

public SecureMediaFileStoreEventHandler(ISignal signal)
{
_signal = signal;
}

public override Task MediaCreatedDirectoryAsync(MediaCreatedContext context)
{
if (context.Result)
{
SignalDirectoryChange();
}

return Task.CompletedTask;
}

public override Task MediaDeletedDirectoryAsync(MediaDeletedContext context)
{
if (context.Result)
{
SignalDirectoryChange();
}

return Task.CompletedTask;
}

private void SignalDirectoryChange() => _signal.DeferredSignalToken(nameof(SecureMediaPermissions));
}
11 changes: 11 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Media/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,14 @@
],
Category = "Content Management"
)]

[assembly: Feature(
Id = "OrchardCore.Media.Security",
Name = "Secure Media",
Description = "Adds permissions to restrict access to media folders.",
Dependencies =
[
"OrchardCore.Media"
],
Category = "Content Management"
)]
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OrchardCore.Media.Services;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Web.Middleware;
using SixLabors.ImageSharp.Web.Processors;
Expand Down Expand Up @@ -83,6 +84,26 @@ public void Configure(ImageSharpMiddlewareOptions options)
return Task.CompletedTask;
};

var onPrepareResponse = options.OnPrepareResponseAsync;
options.OnPrepareResponseAsync = async context =>
{
if (onPrepareResponse is not null)
{
await onPrepareResponse(context);
}
// Override cache control for secure files
if (context.IsSecureMediaRequested())
{
var mediaOptions = context.RequestServices.GetRequiredService<IOptions<MediaOptions>>().Value;
var secureCacheControl = mediaOptions.MaxSecureFilesBrowserCacheDays == 0
? "no-store"
: "public, must-revalidate, max-age=" + TimeSpan.FromDays(mediaOptions.MaxSecureFilesBrowserCacheDays).TotalSeconds.ToString();
context.Response.Headers.CacheControl = secureCacheControl;
}
};
}

private static void ValidateTokenlessCommands(ImageCommandContext context, MediaOptions mediaOptions)
Expand Down

0 comments on commit f2cc0b1

Please sign in to comment.