Merge pull request #9512 from umbraco/netcore/feature/plugin_static_files

Netcore: Read static files from app_plugins folder
This commit is contained in:
Mole
2020-12-09 10:30:15 +01:00
committed by GitHub
6 changed files with 119 additions and 6 deletions

View File

@@ -0,0 +1,27 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
namespace Umbraco.Core.Configuration.Models
{
/// <summary>
/// Typed configuration options for the plugins.
/// </summary>
public class UmbracoPluginSettings
{
/// <summary>
/// Gets or sets the allowed file extensions (including the period ".") that should be accessible from the browser.
/// </summary>
public ISet<string> BrowsableFileExtensions { get; set; } = new HashSet<string>(new[]
{
".html", // markup
".css", // styles
".js", // scripts
".jpg", ".jpeg", ".gif", ".png", ".svg", // images
".eot", ".ttf", ".woff", // fonts
".xml", ".json", ".config", // configurations
".lic" // license
});
}
}

View File

@@ -39,6 +39,7 @@
public const string ConfigMemberPassword = ConfigPrefix + "Security:MemberPassword";
public const string ConfigModelsBuilder = ConfigPrefix + "ModelsBuilder";
public const string ConfigNuCache = ConfigPrefix + "NuCache";
public const string ConfigPlugins = ConfigPrefix + "Plugins";
public const string ConfigRequestHandler = ConfigPrefix + "RequestHandler";
public const string ConfigRuntime = ConfigPrefix + "Runtime";
public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification";

View File

@@ -1,8 +1,14 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp.Web.DependencyInjection;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Web.BackOffice.Middleware;
using Umbraco.Web.BackOffice.Plugins;
using Umbraco.Web.BackOffice.Routing;
using Umbraco.Web.Common.Security;
@@ -21,6 +27,7 @@ namespace Umbraco.Extensions
app.UseRequestLocalization();
app.UseUmbracoRequestLogging();
app.UseUmbracoBackOffice();
app.UseUmbracoPlugins();
app.UseUmbracoPreview();
app.UseUmbracoInstaller();
@@ -57,6 +64,30 @@ namespace Umbraco.Extensions
return app;
}
public static IApplicationBuilder UseUmbracoPlugins(this IApplicationBuilder app)
{
var hostingEnvironment = app.ApplicationServices.GetRequiredService<IHostingEnvironment>();
var umbracoPluginSettings = app.ApplicationServices.GetRequiredService<IOptions<UmbracoPluginSettings>>();
var pluginFolder = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins);
// Ensure the plugin folder exists
Directory.CreateDirectory(pluginFolder);
var fileProvider = new UmbracoPluginPhysicalFileProvider(
pluginFolder,
umbracoPluginSettings);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = Constants.SystemDirectories.AppPlugins
});
return app;
}
public static IApplicationBuilder UseUmbracoPreview(this IApplicationBuilder app)
{
app.UseEndpoints(endpoints =>
@@ -67,7 +98,6 @@ namespace Umbraco.Extensions
return app;
}
private static IApplicationBuilder UseBackOfficeUserManagerAuditing(this IApplicationBuilder app)
{
var auditer = app.ApplicationServices.GetRequiredService<BackOfficeUserManagerAuditer>();

View File

@@ -0,0 +1,53 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.IO;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
namespace Umbraco.Web.BackOffice.Plugins
{
/// <summary>
/// Looks up files using the on-disk file system and check file extensions are on a allow list
/// </summary>
/// <remarks>
/// When the environment variable "DOTNET_USE_POLLING_FILE_WATCHER" is set to "1" or "true", calls to
/// <see cref="PhysicalFileProvider.Watch" /> will use <see cref="PollingFileChangeToken" />.
/// </remarks>
public class UmbracoPluginPhysicalFileProvider : PhysicalFileProvider, IFileProvider
{
private readonly IOptions<UmbracoPluginSettings> _options;
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoPluginPhysicalFileProvider"/> class, at the given root directory.
/// </summary>
/// <param name="root">The root directory. This should be an absolute path.</param>
/// <param name="options">The configuration options.</param>
/// <param name="filters">Specifies which files or directories are excluded.</param>
public UmbracoPluginPhysicalFileProvider(string root, IOptions<UmbracoPluginSettings> options, ExclusionFilters filters = ExclusionFilters.Sensitive)
: base(root, filters) => _options = options;
/// <summary>
/// Locate a file at the given path by directly mapping path segments to physical directories.
/// </summary>
/// <remarks>
/// The path needs to pass the <see cref="ExclusionFilters"/> and the <see cref="UmbracoPluginSettings.BrowsableFileExtensions"/> to be found.
/// </remarks>
/// <param name="subpath">A path under the root directory</param>
/// <returns>The file information. Caller must check <see cref="IFileInfo.Exists"/> property. </returns>
public new IFileInfo GetFileInfo(string subpath)
{
var extension = Path.GetExtension(subpath);
var subPathInclAppPluginsFolder = Path.Combine(Constants.SystemDirectories.AppPlugins, subpath);
if (!_options.Value.BrowsableFileExtensions.Contains(extension))
{
return new NotFoundFileInfo(subPathInclAppPluginsFolder);
}
return base.GetFileInfo(subPathInclAppPluginsFolder);
}
}
}

View File

@@ -18,13 +18,11 @@ using Microsoft.Extensions.Options;
using Serilog;
using Smidge;
using Smidge.Nuglify;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Configuration.Models.Validation;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
@@ -195,6 +193,7 @@ namespace Umbraco.Core.DependencyInjection
builder.Services.Configure<TypeFinderSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigTypeFinder));
builder.Services.Configure<UserPasswordConfigurationSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigUserPassword));
builder.Services.Configure<WebRoutingSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigWebRouting));
builder.Services.Configure<UmbracoPluginSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigPlugins));
return builder;
}

View File

@@ -4,8 +4,8 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Umbraco.Extensions;
using Umbraco.Core.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Web.UI.NetCore
{
@@ -28,11 +28,14 @@ namespace Umbraco.Web.UI.NetCore
_config = config ?? throw new ArgumentNullException(nameof(config));
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
/// <summary>
/// Configures the services
/// </summary>
/// <remarks>
/// This method gets called by the runtime. Use this method to add services to the container.
/// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
/// </remarks>
public void ConfigureServices(IServiceCollection services)
{
#pragma warning disable IDE0022 // Use expression body for methods