Skip to content

Commit

Permalink
Merge branch 'UpdateExternalUser' of https://github.com/hyzx86/Orchar…
Browse files Browse the repository at this point in the history
…dCore into UpdateExternalUser
  • Loading branch information
hyzx86 committed Apr 28, 2024
2 parents 877da10 + fbd68e6 commit 02a6131
Show file tree
Hide file tree
Showing 51 changed files with 1,236 additions and 67 deletions.
4 changes: 2 additions & 2 deletions README.md
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
1 change: 1 addition & 0 deletions mkdocs.yml
Expand Up @@ -257,6 +257,7 @@ nav:
- Owners: docs/resources/owners/README.md
- Workshops: docs/resources/workshops/README.md
- Releases:
- 1.8.3: docs/releases/1.8.3.md
- 1.8.2: docs/releases/1.8.2.md
- 1.8.1: docs/releases/1.8.1.md
- 1.8.0: docs/releases/1.8.0.md
Expand Down
4 changes: 2 additions & 2 deletions src/OrchardCore.Build/Dependencies.props
Expand Up @@ -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
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
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
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
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);
}
}
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
@@ -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
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"
)]
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 02a6131

Please sign in to comment.