V14: Reintroduce umbraco api controller as obsolete (#16263)
* Revert "v14: Remove mentions of UmbracoApiController (#15863)"
This reverts commit 30e2dea57a.
* Obsolete UmbracoApiController
* Added a few more obsoletion messages
* Removed some of the reintroduced stuff again
* Add obsoletion to FrontEndRoutes controller
---------
Co-authored-by: kjac <kja@umbraco.dk>
This commit is contained in:
@@ -19,8 +19,6 @@ namespace Umbraco.Cms.Api.Management.Routing;
|
||||
/// </summary>
|
||||
public sealed class BackOfficeAreaRoutes : IAreaRoutes
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly string _umbracoPathSegment;
|
||||
|
||||
@@ -32,10 +30,18 @@ public sealed class BackOfficeAreaRoutes : IAreaRoutes
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState)
|
||||
{
|
||||
_globalSettings = globalSettings.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_runtimeState = runtimeState;
|
||||
_umbracoPathSegment = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment);
|
||||
_umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
|
||||
public BackOfficeAreaRoutes(
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState,
|
||||
UmbracoApiControllerTypeCollection apiControllers) : this(globalSettings, hostingEnvironment, runtimeState)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
12
src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs
Normal file
12
src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
|
||||
namespace Umbraco.Cms.Core;
|
||||
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public class UmbracoApiControllerTypeCollection : BuilderCollectionBase<Type>
|
||||
{
|
||||
public UmbracoApiControllerTypeCollection(Func<IEnumerable<Type>> items)
|
||||
: base(items)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Umbraco.Cms.Web.Common.ApplicationModels;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Attributes;
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
[Obsolete("No-op attribute. Will be removed in Umbraco 15.")]
|
||||
public sealed class UmbracoApiControllerAttribute : Attribute
|
||||
{
|
||||
}
|
||||
26
src/Umbraco.Web.Common/Controllers/UmbracoApiController.cs
Normal file
26
src/Umbraco.Web.Common/Controllers/UmbracoApiController.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for auto-routed Umbraco API controllers.
|
||||
/// </summary>
|
||||
[Obsolete("""
|
||||
WARNING
|
||||
The UmbracoAPIController does not work exactly as in previous versions of Umbraco because serialization is now done using System.Text.Json.
|
||||
Please verify your API responses still work as expect.
|
||||
|
||||
We recommend using regular ASP.NET Core ApiControllers for your APIs so that OpenAPI specifications are generated.
|
||||
Read more about this here: https://learn.microsoft.com/en-us/aspnet/core/web-api/
|
||||
|
||||
UmbracoAPIController will be removed in Umbraco 15.
|
||||
""")]
|
||||
public abstract class UmbracoApiController : UmbracoApiControllerBase, IDiscoverable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoApiController" /> class.
|
||||
/// </summary>
|
||||
protected UmbracoApiController()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Features;
|
||||
using Umbraco.Cms.Web.Common.Attributes;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for Umbraco API controllers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>These controllers are NOT auto-routed.</para>
|
||||
/// <para>The base class is <see cref="ControllerBase" /> which are netcore API controllers without any view support</para>
|
||||
/// </remarks>
|
||||
[Authorize(Policy = AuthorizationPolicies.UmbracoFeatureEnabled)]
|
||||
[UmbracoApiController]
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public abstract class UmbracoApiControllerBase : ControllerBase, IUmbracoFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoApiControllerBase" /> class.
|
||||
/// </summary>
|
||||
protected UmbracoApiControllerBase()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Controllers;
|
||||
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public class UmbracoApiControllerTypeCollectionBuilder : TypeCollectionBuilderBase<
|
||||
UmbracoApiControllerTypeCollectionBuilder, UmbracoApiControllerTypeCollection, UmbracoApiController>
|
||||
{
|
||||
protected override UmbracoApiControllerTypeCollectionBuilder This => this;
|
||||
}
|
||||
@@ -44,6 +44,7 @@ using Umbraco.Cms.Web.Common.ApplicationModels;
|
||||
using Umbraco.Cms.Web.Common.AspNetCore;
|
||||
using Umbraco.Cms.Web.Common.Blocks;
|
||||
using Umbraco.Cms.Web.Common.Configuration;
|
||||
using Umbraco.Cms.Web.Common.Controllers;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Cms.Web.Common.FileProviders;
|
||||
using Umbraco.Cms.Web.Common.Helpers;
|
||||
@@ -287,6 +288,10 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder.Services.AddUnique<IUmbracoContextFactory, UmbracoContextFactory>();
|
||||
builder.Services.AddUnique<IBackOfficeSecurityAccessor, BackOfficeSecurityAccessor>();
|
||||
|
||||
var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList();
|
||||
builder.WithCollectionBuilder<UmbracoApiControllerTypeCollectionBuilder>()
|
||||
.Add(umbracoApiControllerTypes);
|
||||
|
||||
builder.Services.AddSingleton<UmbracoRequestLoggingMiddleware>();
|
||||
builder.Services.AddSingleton<PreviewAuthenticationMiddleware>();
|
||||
builder.Services.AddSingleton<UmbracoRequestMiddleware>();
|
||||
|
||||
@@ -12,6 +12,36 @@ namespace Umbraco.Extensions;
|
||||
|
||||
public static class LinkGeneratorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the Url for a Web Api service
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="UmbracoApiControllerBase" /></typeparam>
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, object? id = null)
|
||||
where T : UmbracoApiControllerBase => linkGenerator.GetUmbracoControllerUrl(
|
||||
actionName,
|
||||
typeof(T),
|
||||
new Dictionary<string, object?> { ["id"] = id });
|
||||
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, IDictionary<string, object?>? values)
|
||||
where T : UmbracoApiControllerBase => linkGenerator.GetUmbracoControllerUrl(actionName, typeof(T), values);
|
||||
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiServiceBaseUrl<T>(
|
||||
this LinkGenerator linkGenerator,
|
||||
Expression<Func<T, object?>> methodSelector)
|
||||
where T : UmbracoApiControllerBase
|
||||
{
|
||||
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
|
||||
if (method == null)
|
||||
{
|
||||
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) +
|
||||
" or the result ");
|
||||
}
|
||||
|
||||
return linkGenerator.GetUmbracoApiService<T>(method.Name)?.TrimEnd(method.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for an Umbraco controller
|
||||
@@ -101,4 +131,26 @@ public static class LinkGeneratorExtensions
|
||||
|
||||
return linkGenerator.GetUmbracoControllerUrl(actionName, ControllerExtensions.GetControllerName(controllerType), area, values);
|
||||
}
|
||||
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiService<T>(
|
||||
this LinkGenerator linkGenerator,
|
||||
Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiController
|
||||
{
|
||||
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
|
||||
IDictionary<string, object?>? methodParams = ExpressionHelper.GetMethodParams(methodSelector);
|
||||
if (method == null)
|
||||
{
|
||||
throw new MissingMethodException(
|
||||
$"Could not find the method {methodSelector} on type {typeof(T)} or the result ");
|
||||
}
|
||||
|
||||
if (methodParams?.Any() == false)
|
||||
{
|
||||
return linkGenerator.GetUmbracoApiService<T>(method.Name);
|
||||
}
|
||||
|
||||
return linkGenerator.GetUmbracoApiService<T>(method.Name, methodParams);
|
||||
}
|
||||
}
|
||||
|
||||
13
src/Umbraco.Web.Common/Extensions/TypeLoaderExtensions.cs
Normal file
13
src/Umbraco.Web.Common/Extensions/TypeLoaderExtensions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Web.Common.Controllers;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class TypeLoaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all types implementing <see cref="UmbracoApiController" />.
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> GetUmbracoApiControllers(this TypeLoader typeLoader)
|
||||
=> typeLoader.GetTypes<UmbracoApiController>();
|
||||
}
|
||||
@@ -21,6 +21,219 @@ namespace Umbraco.Extensions;
|
||||
|
||||
public static class UrlHelperExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the Url for a Web Api service
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="umbracoApiControllerTypeCollection"></param>
|
||||
/// <param name="actionName"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiService<T>(
|
||||
this IUrlHelper url,
|
||||
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
|
||||
string actionName,
|
||||
object? id = null)
|
||||
where T : UmbracoApiController =>
|
||||
url.GetUmbracoApiService(umbracoApiControllerTypeCollection, actionName, typeof(T), id);
|
||||
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiService<T>(
|
||||
this IUrlHelper url,
|
||||
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
|
||||
Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiController
|
||||
{
|
||||
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
|
||||
IDictionary<string, object?>? methodParams = ExpressionHelper.GetMethodParams(methodSelector);
|
||||
if (method == null)
|
||||
{
|
||||
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) +
|
||||
" or the result ");
|
||||
}
|
||||
|
||||
if (methodParams?.Any() == false)
|
||||
{
|
||||
return url.GetUmbracoApiService<T>(umbracoApiControllerTypeCollection, method.Name);
|
||||
}
|
||||
|
||||
return url.GetUmbracoApiService<T>(umbracoApiControllerTypeCollection, method.Name, methodParams?.Values.First());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for a Web Api service
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="umbracoApiControllerTypeCollection"></param>
|
||||
/// <param name="actionName"></param>
|
||||
/// <param name="apiControllerType"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiService(
|
||||
this IUrlHelper url,
|
||||
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
|
||||
string actionName,
|
||||
Type apiControllerType,
|
||||
object? id = null)
|
||||
{
|
||||
if (actionName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(actionName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(actionName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Value can't be empty or consist only of white-space characters.",
|
||||
nameof(actionName));
|
||||
}
|
||||
|
||||
if (apiControllerType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(apiControllerType));
|
||||
}
|
||||
|
||||
var area = string.Empty;
|
||||
|
||||
Type? apiController = umbracoApiControllerTypeCollection.SingleOrDefault(x => x == apiControllerType);
|
||||
if (apiController == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find the umbraco api controller of type " +
|
||||
apiControllerType.FullName);
|
||||
}
|
||||
|
||||
PluginControllerMetadata metaData = PluginController.GetMetadata(apiController);
|
||||
if (metaData.AreaName.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
// set the area to the plugin area
|
||||
area = metaData.AreaName;
|
||||
}
|
||||
|
||||
return url.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area!, id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for a Web Api service
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="actionName"></param>
|
||||
/// <param name="controllerName"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public static string? GetUmbracoApiService(this IUrlHelper url, string actionName, string controllerName, object? id = null) => url.GetUmbracoApiService(actionName, controllerName, string.Empty, id);
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for a Web Api service
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="actionName"></param>
|
||||
/// <param name="controllerName"></param>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public static string? GetUmbracoApiService(
|
||||
this IUrlHelper url,
|
||||
string actionName,
|
||||
string controllerName,
|
||||
string area,
|
||||
object? id = null)
|
||||
{
|
||||
if (actionName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(actionName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(actionName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Value can't be empty or consist only of white-space characters.",
|
||||
nameof(actionName));
|
||||
}
|
||||
|
||||
if (controllerName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(controllerName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(controllerName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Value can't be empty or consist only of white-space characters.",
|
||||
nameof(controllerName));
|
||||
}
|
||||
|
||||
if (area.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
return url.Action(actionName, controllerName);
|
||||
}
|
||||
|
||||
return url.Action(actionName, controllerName, new { id });
|
||||
}
|
||||
|
||||
if (id == null)
|
||||
{
|
||||
return url.Action(actionName, controllerName, new { area });
|
||||
}
|
||||
|
||||
return url.Action(actionName, controllerName, new { area, id });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Base Url (not including the action) for a Web Api service
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="umbracoApiControllerTypeCollection"></param>
|
||||
/// <param name="actionName"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiServiceBaseUrl<T>(
|
||||
this IUrlHelper url,
|
||||
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
|
||||
string actionName)
|
||||
where T : UmbracoApiController =>
|
||||
url.GetUmbracoApiService<T>(umbracoApiControllerTypeCollection, actionName)?.TrimEnd(actionName);
|
||||
|
||||
[Obsolete("This will be removed in Umbraco 15.")]
|
||||
public static string? GetUmbracoApiServiceBaseUrl<T>(
|
||||
this IUrlHelper url,
|
||||
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
|
||||
Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiController
|
||||
{
|
||||
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
|
||||
if (method == null)
|
||||
{
|
||||
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) +
|
||||
" or the result ");
|
||||
}
|
||||
|
||||
return url.GetUmbracoApiService<T>(umbracoApiControllerTypeCollection, method.Name)?.TrimEnd(method.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for an action with a cache-busting hash appended
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetUrlWithCacheBust(
|
||||
this IUrlHelper url,
|
||||
string actionName,
|
||||
string controllerName,
|
||||
RouteValueDictionary routeVals,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IUmbracoVersion umbracoVersion)
|
||||
{
|
||||
var applicationJs = url.Action(actionName, controllerName, routeVals);
|
||||
applicationJs = applicationJs + "?umb__rnd=" +
|
||||
GetCacheBustHash(hostingEnvironment, umbracoVersion);
|
||||
return applicationJs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -60,7 +60,17 @@ public sealed class ConfigureMemberCookieOptions : IConfigureNamedOptions<Cookie
|
||||
},
|
||||
OnRedirectToAccessDenied = ctx =>
|
||||
{
|
||||
new CookieAuthenticationEvents().OnRedirectToAccessDenied(ctx);
|
||||
// When the controller is an UmbracoAPIController, we want to return a StatusCode instead of a redirect.
|
||||
// All other cases should use the default Redirect of the CookieAuthenticationEvent.
|
||||
var controllerDescriptor = ctx.HttpContext.GetEndpoint()?.Metadata
|
||||
.OfType<ControllerActionDescriptor>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!controllerDescriptor?.ControllerTypeInfo.IsSubclassOf(typeof(UmbracoApiController)) ?? false)
|
||||
{
|
||||
new CookieAuthenticationEvents().OnRedirectToAccessDenied(ctx);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,4 +15,10 @@ public static class TypeLoaderExtensions
|
||||
/// </summary>
|
||||
internal static IEnumerable<Type> GetSurfaceControllers(this TypeLoader typeLoader)
|
||||
=> typeLoader.GetTypes<SurfaceController>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all types implementing <see cref="UmbracoApiController" />.
|
||||
/// </summary>
|
||||
internal static IEnumerable<Type> GetUmbracoApiControllers(this TypeLoader typeLoader)
|
||||
=> typeLoader.GetTypes<UmbracoApiController>();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web.Mvc;
|
||||
@@ -17,10 +19,25 @@ namespace Umbraco.Cms.Web.Website.Routing;
|
||||
/// </summary>
|
||||
public sealed class FrontEndRoutes : IAreaRoutes
|
||||
{
|
||||
private readonly UmbracoApiControllerTypeCollection _apiControllers;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly SurfaceControllerTypeCollection _surfaceControllerTypeCollection;
|
||||
private readonly string _umbracoPathSegment;
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
|
||||
public FrontEndRoutes(
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState,
|
||||
SurfaceControllerTypeCollection surfaceControllerTypeCollection,
|
||||
UmbracoApiControllerTypeCollection apiControllers)
|
||||
{
|
||||
_runtimeState = runtimeState;
|
||||
_surfaceControllerTypeCollection = surfaceControllerTypeCollection;
|
||||
_apiControllers = apiControllers;
|
||||
_umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FrontEndRoutes" /> class.
|
||||
/// </summary>
|
||||
@@ -29,10 +46,13 @@ public sealed class FrontEndRoutes : IAreaRoutes
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState,
|
||||
SurfaceControllerTypeCollection surfaceControllerTypeCollection)
|
||||
: this(
|
||||
globalSettings,
|
||||
hostingEnvironment,
|
||||
runtimeState,
|
||||
surfaceControllerTypeCollection,
|
||||
StaticServiceProvider.Instance.GetRequiredService<UmbracoApiControllerTypeCollection>())
|
||||
{
|
||||
_runtimeState = runtimeState;
|
||||
_surfaceControllerTypeCollection = surfaceControllerTypeCollection;
|
||||
_umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -45,6 +65,7 @@ public sealed class FrontEndRoutes : IAreaRoutes
|
||||
case RuntimeLevel.Run:
|
||||
|
||||
AutoRouteSurfaceControllers(endpoints);
|
||||
AutoRouteFrontEndApiControllers(endpoints);
|
||||
break;
|
||||
case RuntimeLevel.BootFailed:
|
||||
case RuntimeLevel.Unknown:
|
||||
@@ -71,4 +92,28 @@ public sealed class FrontEndRoutes : IAreaRoutes
|
||||
meta.AreaName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-routes all front-end api controllers
|
||||
/// </summary>
|
||||
private void AutoRouteFrontEndApiControllers(IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
foreach (Type controller in _apiControllers)
|
||||
{
|
||||
PluginControllerMetadata meta = PluginController.GetMetadata(controller);
|
||||
|
||||
// exclude back-end api controllers
|
||||
if (meta.IsBackOffice)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
endpoints.MapUmbracoApiRoute(
|
||||
meta.ControllerType,
|
||||
_umbracoPathSegment,
|
||||
meta.AreaName,
|
||||
meta.IsBackOffice,
|
||||
string.Empty); // no default action (this is what we had before)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,18 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare a url before using <see cref="Client"/>.
|
||||
/// This returns the url but also sets the HttpContext.request into to use this url.
|
||||
/// </summary>
|
||||
/// <returns>The string URL of the controller action.</returns>
|
||||
protected string PrepareApiControllerUrl<T>(Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiController
|
||||
{
|
||||
var url = LinkGenerator.GetUmbracoApiService(methodSelector);
|
||||
return PrepareUrl(url);
|
||||
}
|
||||
|
||||
protected string GetManagementApiUrl<T>(Expression<Func<T, object>> methodSelector)
|
||||
where T : ManagementApiControllerBase
|
||||
{
|
||||
|
||||
@@ -67,6 +67,36 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Security
|
||||
Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode);
|
||||
Assert.AreEqual(cookieAuthenticationOptions.Value.AccessDeniedPath.ToString(), response.Headers.Location?.AbsolutePath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[LongRunning]
|
||||
public async Task Secure_ApiController_Should_Return_Unauthorized_WhenNotLoggedIn()
|
||||
{
|
||||
_memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(false);
|
||||
var url = PrepareApiControllerUrl<TestApiController>(x => x.Secure());
|
||||
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[LongRunning]
|
||||
public async Task Secure_ApiController_Should_Return_Forbidden_WhenNotAuthorized()
|
||||
{
|
||||
_memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(true);
|
||||
_memberManagerMock.Setup(x => x.IsMemberAuthorizedAsync(
|
||||
It.IsAny<IEnumerable<string>>(),
|
||||
It.IsAny<IEnumerable<string>>(),
|
||||
It.IsAny<IEnumerable<int>>()))
|
||||
.ReturnsAsync(false);
|
||||
|
||||
var url = PrepareApiControllerUrl<TestApiController>(x => x.Secure());
|
||||
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestSurfaceController : SurfaceController
|
||||
@@ -91,4 +121,10 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Security
|
||||
[UmbracoMemberAuthorize]
|
||||
public IActionResult Secure() => NoContent();
|
||||
}
|
||||
|
||||
public class TestApiController : UmbracoApiController
|
||||
{
|
||||
[UmbracoMemberAuthorize]
|
||||
public IActionResult Secure() => NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,14 @@ internal class UmbracoCustomizations : ICustomization
|
||||
fixture.Customize<HostingSettings>(x =>
|
||||
x.With(settings => settings.ApplicationVirtualPath, string.Empty));
|
||||
|
||||
fixture.Customize<BackOfficeAreaRoutes>(u => u.FromFactory(
|
||||
() => new BackOfficeAreaRoutes(
|
||||
Options.Create(new GlobalSettings()),
|
||||
Mock.Of<IHostingEnvironment>(x =>
|
||||
x.ToAbsolute(It.IsAny<string>()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty),
|
||||
Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run),
|
||||
new UmbracoApiControllerTypeCollection(Enumerable.Empty<Type>))));
|
||||
|
||||
fixture.Customize<PreviewRoutes>(u => u.FromFactory(
|
||||
() => new PreviewRoutes(
|
||||
Options.Create(new GlobalSettings()),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Management.Controllers.Security;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Attributes;
|
||||
using Umbraco.Cms.Web.Common.Controllers;
|
||||
using Umbraco.Extensions;
|
||||
using static Umbraco.Cms.Core.Constants.Web.Routing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Routing;
|
||||
|
||||
[TestFixture]
|
||||
public class BackOfficeAreaRoutesTests
|
||||
{
|
||||
[TestCase(RuntimeLevel.BootFailed)]
|
||||
[TestCase(RuntimeLevel.Unknown)]
|
||||
[TestCase(RuntimeLevel.Boot)]
|
||||
public void RuntimeState_No_Routes(RuntimeLevel level)
|
||||
{
|
||||
var routes = GetBackOfficeAreaRoutes(level);
|
||||
var endpoints = new TestRouteBuilder();
|
||||
routes.CreateRoutes(endpoints);
|
||||
|
||||
Assert.AreEqual(0, endpoints.DataSources.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(RuntimeLevel.Run)]
|
||||
[TestCase(RuntimeLevel.Upgrade)]
|
||||
[TestCase(RuntimeLevel.Install)]
|
||||
public void RuntimeState_All_Routes(RuntimeLevel level)
|
||||
{
|
||||
var routes = GetBackOfficeAreaRoutes(level);
|
||||
var endpoints = new TestRouteBuilder();
|
||||
routes.CreateRoutes(endpoints);
|
||||
|
||||
Assert.AreEqual(1, endpoints.DataSources.Count);
|
||||
var route = endpoints.DataSources.First();
|
||||
Assert.AreEqual(2, route.Endpoints.Count);
|
||||
|
||||
AssertMinimalBackOfficeRoutes(route);
|
||||
}
|
||||
|
||||
private void AssertMinimalBackOfficeRoutes(EndpointDataSource route)
|
||||
{
|
||||
var endpoint1 = (RouteEndpoint)route.Endpoints[0];
|
||||
Assert.AreEqual("umbraco/{action}/{id?}", endpoint1.RoutePattern.RawText);
|
||||
Assert.AreEqual("Index", endpoint1.RoutePattern.Defaults[ActionToken]);
|
||||
Assert.AreEqual(ControllerExtensions.GetControllerName<BackOfficeDefaultController>(), endpoint1.RoutePattern.Defaults[ControllerToken]);
|
||||
}
|
||||
|
||||
private BackOfficeAreaRoutes GetBackOfficeAreaRoutes(RuntimeLevel level)
|
||||
{
|
||||
var globalSettings = new GlobalSettings();
|
||||
var routes = new BackOfficeAreaRoutes(
|
||||
Options.Create(globalSettings),
|
||||
Mock.Of<IHostingEnvironment>(x =>
|
||||
x.ToAbsolute(It.IsAny<string>()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty),
|
||||
Mock.Of<IRuntimeState>(x => x.Level == level),
|
||||
new UmbracoApiControllerTypeCollection(() => new[] { typeof(Testing1Controller) }));
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
[IsBackOffice]
|
||||
private class Testing1Controller : UmbracoApiController
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user