Netcore: Handle tree authentication (#8866)
* Added helper methods to invoke the authorization filters of the other controller action Signed-off-by: Bjarke Berg <mail@bergmania.dk> * Implemented Tree Auth Signed-off-by: Bjarke Berg <mail@bergmania.dk> * cleanup Signed-off-by: Bjarke Berg <mail@bergmania.dk> * Throw forbidden if user has no access instead of InternalServerError Signed-off-by: Bjarke Berg <mail@bergmania.dk> * EnsureBackofficeSecurity for background jobs Signed-off-by: Bjarke Berg <mail@bergmania.dk> Co-authored-by: Elitsa Marinovska <elm@umbraco.dk>
This commit is contained in:
@@ -14,13 +14,14 @@ namespace Umbraco.Web.Scheduling
|
||||
private readonly IMainDom _mainDom;
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly IServerMessenger _serverMessenger;
|
||||
private readonly IBackofficeSecurityFactory _backofficeSecurityFactory;
|
||||
private readonly IServerRegistrar _serverRegistrar;
|
||||
private readonly IUmbracoContextFactory _umbracoContextFactory;
|
||||
|
||||
public ScheduledPublishing(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds,
|
||||
int periodMilliseconds,
|
||||
IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar, IContentService contentService,
|
||||
IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger)
|
||||
IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger, IBackofficeSecurityFactory backofficeSecurityFactory)
|
||||
: base(runner, delayMilliseconds, periodMilliseconds)
|
||||
{
|
||||
_runtime = runtime;
|
||||
@@ -30,6 +31,7 @@ namespace Umbraco.Web.Scheduling
|
||||
_umbracoContextFactory = umbracoContextFactory;
|
||||
_logger = logger;
|
||||
_serverMessenger = serverMessenger;
|
||||
_backofficeSecurityFactory = backofficeSecurityFactory;
|
||||
}
|
||||
|
||||
public override bool IsAsync => false;
|
||||
@@ -76,6 +78,7 @@ namespace Umbraco.Web.Scheduling
|
||||
// but then what should be its "scope"? could we attach it to scopes?
|
||||
// - and we should definitively *not* have to flush it here (should be auto)
|
||||
//
|
||||
_backofficeSecurityFactory.EnsureBackofficeSecurity();
|
||||
using (var contextReference = _umbracoContextFactory.EnsureUmbracoContext())
|
||||
{
|
||||
try
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Umbraco.Web.Scheduling
|
||||
private readonly HealthChecksSettings _healthChecksSettings;
|
||||
private readonly IServerMessenger _serverMessenger;
|
||||
private readonly IRequestAccessor _requestAccessor;
|
||||
private readonly IBackofficeSecurityFactory _backofficeSecurityFactory;
|
||||
private readonly LoggingSettings _loggingSettings;
|
||||
private readonly KeepAliveSettings _keepAliveSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
@@ -60,7 +61,8 @@ namespace Umbraco.Web.Scheduling
|
||||
IApplicationShutdownRegistry applicationShutdownRegistry, IOptions<HealthChecksSettings> healthChecksSettings,
|
||||
IServerMessenger serverMessenger, IRequestAccessor requestAccessor,
|
||||
IOptions<LoggingSettings> loggingSettings, IOptions<KeepAliveSettings> keepAliveSettings,
|
||||
IHostingEnvironment hostingEnvironment)
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IBackofficeSecurityFactory backofficeSecurityFactory)
|
||||
{
|
||||
_runtime = runtime;
|
||||
_mainDom = mainDom;
|
||||
@@ -77,6 +79,7 @@ namespace Umbraco.Web.Scheduling
|
||||
_healthChecksSettings = healthChecksSettings.Value ?? throw new ArgumentNullException(nameof(healthChecksSettings));
|
||||
_serverMessenger = serverMessenger;
|
||||
_requestAccessor = requestAccessor;
|
||||
_backofficeSecurityFactory = backofficeSecurityFactory;
|
||||
_loggingSettings = loggingSettings.Value;
|
||||
_keepAliveSettings = keepAliveSettings.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
@@ -150,7 +153,7 @@ namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
// scheduled publishing/unpublishing
|
||||
// install on all, will only run on non-replica servers
|
||||
var task = new ScheduledPublishing(_publishingRunner, DefaultDelayMilliseconds, OneMinuteMilliseconds, _runtime, _mainDom, _serverRegistrar, _contentService, _umbracoContextFactory, _logger, _serverMessenger);
|
||||
var task = new ScheduledPublishing(_publishingRunner, DefaultDelayMilliseconds, OneMinuteMilliseconds, _runtime, _mainDom, _serverRegistrar, _contentService, _umbracoContextFactory, _logger, _serverMessenger, _backofficeSecurityFactory);
|
||||
_publishingRunner.TryAdd(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -23,6 +24,7 @@ namespace Umbraco.Web.Editors
|
||||
public class SectionController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IControllerFactory _controllerFactory;
|
||||
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
|
||||
private readonly IDashboardService _dashboardService;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly ISectionService _sectionService;
|
||||
@@ -34,7 +36,8 @@ namespace Umbraco.Web.Editors
|
||||
IBackofficeSecurityAccessor backofficeSecurityAccessor,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IDashboardService dashboardService, ISectionService sectionService, ITreeService treeService,
|
||||
UmbracoMapper umbracoMapper, IControllerFactory controllerFactory)
|
||||
UmbracoMapper umbracoMapper, IControllerFactory controllerFactory,
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
|
||||
{
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_localizedTextService = localizedTextService;
|
||||
@@ -43,6 +46,7 @@ namespace Umbraco.Web.Editors
|
||||
_treeService = treeService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_controllerFactory = controllerFactory;
|
||||
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
|
||||
}
|
||||
|
||||
public IEnumerable<Section> GetSections()
|
||||
@@ -54,7 +58,7 @@ namespace Umbraco.Web.Editors
|
||||
// this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that
|
||||
// since tree's by nature are controllers and require request contextual data
|
||||
var appTreeController =
|
||||
new ApplicationTreeController(_treeService, _sectionService, _localizedTextService, _controllerFactory)
|
||||
new ApplicationTreeController(_treeService, _sectionService, _localizedTextService, _controllerFactory, _actionDescriptorCollectionProvider)
|
||||
{
|
||||
ControllerContext = ControllerContext
|
||||
};
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
internal static class ControllerContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the authorization filters for the controller action.
|
||||
/// </summary>
|
||||
/// <returns>Whether the user is authenticated or not.</returns>
|
||||
internal static async Task<bool> InvokeAuthorizationFiltersForRequest(this ControllerContext controllerContext, ActionContext actionContext)
|
||||
{
|
||||
var actionDescriptor = controllerContext.ActionDescriptor;
|
||||
|
||||
var filters = actionDescriptor.FilterDescriptors;
|
||||
var filterGrouping = new FilterGrouping(filters, controllerContext.HttpContext.RequestServices);
|
||||
|
||||
// because the continuation gets built from the inside out we need to reverse the filter list
|
||||
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
|
||||
var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToList();
|
||||
var asyncAuthorizationFilters = filterGrouping.AsyncAuthorizationFilters.Reverse().ToList();
|
||||
|
||||
if (authorizationFilters.Count == 0 && asyncAuthorizationFilters.Count == 0)
|
||||
return true;
|
||||
|
||||
// if the authorization filter returns a result, it means it failed to authorize
|
||||
var authorizationFilterContext = new AuthorizationFilterContext(actionContext, filters.Select(x=>x.Filter).ToArray());
|
||||
return await ExecuteAuthorizationFiltersAsync(authorizationFilterContext, authorizationFilters, asyncAuthorizationFilters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a chain of filters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Recursively calls in to itself as its continuation for the next filter in the chain.
|
||||
/// </remarks>
|
||||
private static async Task<bool> ExecuteAuthorizationFiltersAsync(
|
||||
AuthorizationFilterContext authorizationFilterContext,
|
||||
IList<IAuthorizationFilter> authorizationFilters,
|
||||
IList<IAsyncAuthorizationFilter> asyncAuthorizationFilters)
|
||||
{
|
||||
|
||||
foreach (var authorizationFilter in authorizationFilters)
|
||||
{
|
||||
authorizationFilter.OnAuthorization(authorizationFilterContext);
|
||||
if (!(authorizationFilterContext.Result is null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach (var asyncAuthorizationFilter in asyncAuthorizationFilters)
|
||||
{
|
||||
await asyncAuthorizationFilter.OnAuthorizationAsync(authorizationFilterContext);
|
||||
if (!(authorizationFilterContext.Result is null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quickly split filters into different types
|
||||
/// </summary>
|
||||
private class FilterGrouping
|
||||
{
|
||||
private readonly List<IActionFilter> _actionFilters = new List<IActionFilter>();
|
||||
private readonly List<IAsyncActionFilter> _asyncActionFilters = new List<IAsyncActionFilter>();
|
||||
private readonly List<IAuthorizationFilter> _authorizationFilters = new List<IAuthorizationFilter>();
|
||||
private readonly List<IAsyncAuthorizationFilter> _asyncAuthorizationFilters = new List<IAsyncAuthorizationFilter>();
|
||||
private readonly List<IExceptionFilter> _exceptionFilters = new List<IExceptionFilter>();
|
||||
private readonly List<IAsyncExceptionFilter> _asyncExceptionFilters = new List<IAsyncExceptionFilter>();
|
||||
|
||||
public FilterGrouping(IEnumerable<FilterDescriptor> filters, IServiceProvider serviceProvider)
|
||||
{
|
||||
if (filters == null) throw new ArgumentNullException("filters");
|
||||
|
||||
foreach (FilterDescriptor f in filters)
|
||||
{
|
||||
var filter = f.Filter;
|
||||
Categorize(filter, _actionFilters, serviceProvider);
|
||||
Categorize(filter, _authorizationFilters, serviceProvider);
|
||||
Categorize(filter, _exceptionFilters, serviceProvider);
|
||||
Categorize(filter, _asyncActionFilters, serviceProvider);
|
||||
Categorize(filter, _asyncAuthorizationFilters, serviceProvider);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IActionFilter> ActionFilters => _actionFilters;
|
||||
public IEnumerable<IAsyncActionFilter> AsyncActionFilters => _asyncActionFilters;
|
||||
public IEnumerable<IAuthorizationFilter> AuthorizationFilters => _authorizationFilters;
|
||||
|
||||
public IEnumerable<IAsyncAuthorizationFilter> AsyncAuthorizationFilters => _asyncAuthorizationFilters;
|
||||
|
||||
public IEnumerable<IExceptionFilter> ExceptionFilters => _exceptionFilters;
|
||||
|
||||
public IEnumerable<IAsyncExceptionFilter> AsyncExceptionFilters => _asyncExceptionFilters;
|
||||
|
||||
private static void Categorize<T>(IFilterMetadata filter, List<T> list, IServiceProvider serviceProvider) where T : class
|
||||
{
|
||||
if(filter is TypeFilterAttribute typeFilterAttribute)
|
||||
{
|
||||
filter = typeFilterAttribute.CreateInstance(serviceProvider);
|
||||
}
|
||||
|
||||
T match = filter as T;
|
||||
if (match != null)
|
||||
{
|
||||
list.Add(match);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,12 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.BackOffice.Trees;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
@@ -35,18 +37,22 @@ namespace Umbraco.Web.Trees
|
||||
private readonly ISectionService _sectionService;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IControllerFactory _controllerFactory;
|
||||
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
|
||||
|
||||
|
||||
public ApplicationTreeController(
|
||||
ITreeService treeService,
|
||||
ISectionService sectionService,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IControllerFactory controllerFactory
|
||||
IControllerFactory controllerFactory,
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider
|
||||
)
|
||||
{
|
||||
_treeService = treeService;
|
||||
_sectionService = sectionService;
|
||||
_localizedTextService = localizedTextService;
|
||||
_controllerFactory = controllerFactory;
|
||||
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -251,67 +257,36 @@ namespace Umbraco.Web.Trees
|
||||
private async Task<object> GetApiControllerProxy(Type controllerType, string action, FormCollection querystring)
|
||||
{
|
||||
// note: this is all required in order to execute the auth-filters for the sub request, we
|
||||
// need to "trick" web-api into thinking that it is actually executing the proxied controller.
|
||||
|
||||
// need to "trick" mvc into thinking that it is actually executing the proxied controller.
|
||||
|
||||
var controllerName = controllerType.Name.Substring(0, controllerType.Name.Length - 10); // remove controller part of name;
|
||||
// create proxy route data specifying the action & controller to execute
|
||||
var routeData = new RouteData(new RouteValueDictionary()
|
||||
{
|
||||
["action"] = action,
|
||||
["controller"] = controllerType.Name.Substring(0,controllerType.Name.Length-10) // remove controller part of name;
|
||||
|
||||
["controller"] = controllerName
|
||||
});
|
||||
if (!(querystring is null))
|
||||
{
|
||||
foreach (var (key,value) in querystring)
|
||||
{
|
||||
routeData.Values[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
var actionDescriptor = _actionDescriptorCollectionProvider.ActionDescriptors.Items
|
||||
.Cast<ControllerActionDescriptor>()
|
||||
.First(x =>
|
||||
x.ControllerName.Equals(controllerName) &&
|
||||
x.ActionName == action);
|
||||
|
||||
var controllerContext = new ControllerContext(
|
||||
new ActionContext(
|
||||
HttpContext,
|
||||
routeData,
|
||||
new ControllerActionDescriptor()
|
||||
{
|
||||
ControllerTypeInfo = controllerType.GetTypeInfo()
|
||||
}
|
||||
));
|
||||
var actionContext = new ActionContext(HttpContext, routeData, actionDescriptor);
|
||||
var proxyControllerContext = new ControllerContext(actionContext);
|
||||
var controller = (TreeController) _controllerFactory.CreateController(proxyControllerContext);
|
||||
|
||||
var controller = (TreeController) _controllerFactory.CreateController(controllerContext);
|
||||
|
||||
|
||||
//TODO Refactor trees or reimplement this hacks to check authentication.
|
||||
//https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/3694
|
||||
|
||||
// var context = ControllerContext;
|
||||
//
|
||||
// // get the controller
|
||||
// var controller = (TreeController) DependencyResolver.Current.GetService(controllerType)
|
||||
// ?? throw new Exception($"Failed to create controller of type {controllerType.FullName}.");
|
||||
//
|
||||
// // create the proxy URL for the controller action
|
||||
// var proxyUrl = HttpContext.Request.RequestUri.GetLeftPart(UriPartial.Authority)
|
||||
// + HttpContext.Request.GetUrlHelper().GetUmbracoApiService(action, controllerType)
|
||||
// + "?" + querystring.ToQueryString();
|
||||
//
|
||||
//
|
||||
//
|
||||
// // create a proxy request
|
||||
// var proxyRequest = new HttpRequestMessage(HttpMethod.Get, proxyUrl);
|
||||
//
|
||||
// // create a proxy controller context
|
||||
// var proxyContext = new HttpControllerContext(context.Configuration, proxyRoute, proxyRequest)
|
||||
// {
|
||||
// ControllerDescriptor = new HttpControllerDescriptor(context.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(controllerType), controllerType),
|
||||
// RequestContext = context.RequestContext,
|
||||
// Controller = controller
|
||||
// };
|
||||
//
|
||||
// // wire everything
|
||||
// controller.ControllerContext = proxyContext;
|
||||
// controller.Request = proxyContext.Request;
|
||||
// controller.RequestContext.RouteData = proxyRoute;
|
||||
//
|
||||
// // auth
|
||||
// var authResult = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest();
|
||||
// if (authResult != null)
|
||||
// throw new HttpResponseException(authResult);
|
||||
var isAllowed = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(actionContext);
|
||||
if (!isAllowed)
|
||||
throw new HttpResponseException(HttpStatusCode.Forbidden);
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -40,16 +40,16 @@ namespace Umbraco.Web
|
||||
IRequestAccessor requestAccessor)
|
||||
{
|
||||
if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService));
|
||||
if (backofficeSecurity == null) throw new ArgumentNullException(nameof(backofficeSecurity));
|
||||
VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
|
||||
_globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings));
|
||||
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_cookieManager = cookieManager;
|
||||
_requestAccessor = requestAccessor;
|
||||
|
||||
ObjectCreated = DateTime.Now;
|
||||
UmbracoRequestId = Guid.NewGuid();
|
||||
Security = backofficeSecurity;
|
||||
Security = backofficeSecurity ?? throw new ArgumentNullException(nameof(backofficeSecurity));
|
||||
|
||||
// beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing
|
||||
_publishedSnapshot = new Lazy<IPublishedSnapshot>(() => publishedSnapshotService.CreatePublishedSnapshot(PreviewToken));
|
||||
|
||||
@@ -296,7 +296,6 @@
|
||||
<Compile Include="WebApi\Filters\DisableBrowserCacheAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\FilterGrouping.cs" />
|
||||
<Compile Include="WebApi\Filters\ValidateAngularAntiForgeryTokenAttribute.cs" />
|
||||
<Compile Include="WebApi\HttpControllerContextExtensions.cs" />
|
||||
<Compile Include="Controllers\UmbRegisterController.cs" />
|
||||
<Compile Include="Models\ProfileModel.cs" />
|
||||
<Compile Include="Models\LoginStatusModel.cs" />
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
internal static class HttpControllerContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the authorization filters for the controller action.
|
||||
/// </summary>
|
||||
/// <returns>The response of the first filter returning a result, if any, otherwise null (authorized).</returns>
|
||||
internal static async Task<HttpResponseMessage> InvokeAuthorizationFiltersForRequest(this HttpControllerContext controllerContext)
|
||||
{
|
||||
var controllerDescriptor = controllerContext.ControllerDescriptor;
|
||||
var controllerServices = controllerDescriptor.Configuration.Services;
|
||||
var actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
|
||||
|
||||
var filters = actionDescriptor.GetFilterPipeline();
|
||||
var filterGrouping = new FilterGrouping(filters);
|
||||
|
||||
// because the continuation gets built from the inside out we need to reverse the filter list
|
||||
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
|
||||
var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToList();
|
||||
|
||||
if (authorizationFilters.Count == 0)
|
||||
return null;
|
||||
|
||||
// if the authorization filter returns a result, it means it failed to authorize
|
||||
var actionContext = new HttpActionContext(controllerContext, actionDescriptor);
|
||||
return await ExecuteAuthorizationFiltersAsync(actionContext, CancellationToken.None, authorizationFilters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a chain of filters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Recursively calls in to itself as its continuation for the next filter in the chain.
|
||||
/// </remarks>
|
||||
private static async Task<HttpResponseMessage> ExecuteAuthorizationFiltersAsync(HttpActionContext actionContext, CancellationToken token, IList<IAuthorizationFilter> filters, int index = 0)
|
||||
{
|
||||
return await filters[index].ExecuteAuthorizationFilterAsync(actionContext, token,
|
||||
() => ++index == filters.Count
|
||||
? Task.FromResult<HttpResponseMessage>(null)
|
||||
: ExecuteAuthorizationFiltersAsync(actionContext, token, filters, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user