Add Fail fast option for AuthZ
This commit is contained in:
parent
ce0ed3d237
commit
e8f55bdb13
|
|
@ -13,6 +13,12 @@ namespace Microsoft.AspNetCore.Authorization
|
|||
{
|
||||
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether authentication handlers should be invoked after a failure.
|
||||
/// Defaults to true.
|
||||
/// </summary>
|
||||
public bool InvokeHandlersAfterFailure { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default authorization policy.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Security.Claims;
|
|||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authorization
|
||||
{
|
||||
|
|
@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Authorization
|
|||
/// </summary>
|
||||
public class DefaultAuthorizationService : IAuthorizationService
|
||||
{
|
||||
private readonly AuthorizationOptions _options;
|
||||
private readonly IAuthorizationHandlerContextFactory _contextFactory;
|
||||
private readonly IAuthorizationEvaluator _evaluator;
|
||||
private readonly IAuthorizationPolicyProvider _policyProvider;
|
||||
|
|
@ -28,7 +30,7 @@ namespace Microsoft.AspNetCore.Authorization
|
|||
/// <param name="policyProvider">The <see cref="IAuthorizationPolicyProvider"/> used to provide policies.</param>
|
||||
/// <param name="handlers">The handlers used to fulfill <see cref="IAuthorizationRequirement"/>s.</param>
|
||||
/// <param name="logger">The logger used to log messages, warnings and errors.</param>
|
||||
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizationHandler> handlers, ILogger<DefaultAuthorizationService> logger) : this(policyProvider, handlers, logger, new DefaultAuthorizationHandlerContextFactory(), new DefaultAuthorizationEvaluator()) { }
|
||||
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizationHandler> handlers, ILogger<DefaultAuthorizationService> logger) : this(policyProvider, handlers, logger, new DefaultAuthorizationHandlerContextFactory(), new DefaultAuthorizationEvaluator(), Options.Create(new AuthorizationOptions())) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="DefaultAuthorizationService"/>.
|
||||
|
|
@ -38,8 +40,13 @@ namespace Microsoft.AspNetCore.Authorization
|
|||
/// <param name="logger">The logger used to log messages, warnings and errors.</param>
|
||||
/// <param name="contextFactory">The <see cref="IAuthorizationHandlerContextFactory"/> used to create the context to handle the authorization.</param>
|
||||
/// <param name="evaluator">The <see cref="IAuthorizationEvaluator"/> used to determine if authorzation was successful.</param>
|
||||
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizationHandler> handlers, ILogger<DefaultAuthorizationService> logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator)
|
||||
/// <param name="options">The <see cref="AuthorizationOptions"/> used.</param>
|
||||
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizationHandler> handlers, ILogger<DefaultAuthorizationService> logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator, IOptions<AuthorizationOptions> options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
if (policyProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policyProvider));
|
||||
|
|
@ -61,6 +68,7 @@ namespace Microsoft.AspNetCore.Authorization
|
|||
throw new ArgumentNullException(nameof(evaluator));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
_handlers = handlers.ToArray();
|
||||
_policyProvider = policyProvider;
|
||||
_logger = logger;
|
||||
|
|
@ -89,6 +97,10 @@ namespace Microsoft.AspNetCore.Authorization
|
|||
foreach (var handler in _handlers)
|
||||
{
|
||||
await handler.HandleAsync(authContext);
|
||||
if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_evaluator.HasSucceeded(authContext))
|
||||
|
|
|
|||
|
|
@ -20,11 +20,7 @@ namespace Microsoft.AspNetCore.Authorization.Test
|
|||
var services = new ServiceCollection();
|
||||
services.AddAuthorization();
|
||||
services.AddLogging();
|
||||
services.AddOptions();
|
||||
if (setupServices != null)
|
||||
{
|
||||
setupServices(services);
|
||||
}
|
||||
setupServices?.Invoke(services);
|
||||
return services.BuildServiceProvider().GetRequiredService<IAuthorizationService>();
|
||||
}
|
||||
|
||||
|
|
@ -109,6 +105,72 @@ namespace Microsoft.AspNetCore.Authorization.Test
|
|||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
public async Task Authorize_ShouldInvokeAllHandlersByDefault()
|
||||
{
|
||||
// Arrange
|
||||
var handler1 = new FailHandler();
|
||||
var handler2 = new FailHandler();
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.AddSingleton<IAuthorizationHandler>(handler1);
|
||||
services.AddSingleton<IAuthorizationHandler>(handler2);
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement()));
|
||||
});
|
||||
});
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync(new ClaimsPrincipal(), "Custom");
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
Assert.True(handler1.Invoked);
|
||||
Assert.True(handler2.Invoked);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task Authorize_ShouldInvokeAllHandlersDependingOnSetting(bool invokeAllHandlers)
|
||||
{
|
||||
// Arrange
|
||||
var handler1 = new FailHandler();
|
||||
var handler2 = new FailHandler();
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.AddSingleton<IAuthorizationHandler>(handler1);
|
||||
services.AddSingleton<IAuthorizationHandler>(handler2);
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.InvokeHandlersAfterFailure = invokeAllHandlers;
|
||||
options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement()));
|
||||
});
|
||||
});
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync(new ClaimsPrincipal(), "Custom");
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
Assert.True(handler1.Invoked);
|
||||
Assert.Equal(invokeAllHandlers, handler2.Invoked);
|
||||
}
|
||||
|
||||
private class FailHandler : IAuthorizationHandler
|
||||
{
|
||||
public bool Invoked { get; set; }
|
||||
|
||||
public Task HandleAsync(AuthorizationHandlerContext context)
|
||||
{
|
||||
Invoked = true;
|
||||
context.Fail();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task Authorize_ShouldFailWhenAllRequirementsNotHandled()
|
||||
{
|
||||
|
|
@ -584,8 +646,11 @@ namespace Microsoft.AspNetCore.Authorization.Test
|
|||
public class CustomRequirement : IAuthorizationRequirement { }
|
||||
public class CustomHandler : AuthorizationHandler<CustomRequirement>
|
||||
{
|
||||
public bool Invoked { get; set; }
|
||||
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRequirement requirement)
|
||||
{
|
||||
Invoked = true;
|
||||
context.Succeed(requirement);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue