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);
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the default authorization policy.
|
/// Gets or sets the default authorization policy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Security.Claims;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Authorization
|
namespace Microsoft.AspNetCore.Authorization
|
||||||
{
|
{
|
||||||
|
|
@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Authorization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultAuthorizationService : IAuthorizationService
|
public class DefaultAuthorizationService : IAuthorizationService
|
||||||
{
|
{
|
||||||
|
private readonly AuthorizationOptions _options;
|
||||||
private readonly IAuthorizationHandlerContextFactory _contextFactory;
|
private readonly IAuthorizationHandlerContextFactory _contextFactory;
|
||||||
private readonly IAuthorizationEvaluator _evaluator;
|
private readonly IAuthorizationEvaluator _evaluator;
|
||||||
private readonly IAuthorizationPolicyProvider _policyProvider;
|
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="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="handlers">The handlers used to fulfill <see cref="IAuthorizationRequirement"/>s.</param>
|
||||||
/// <param name="logger">The logger used to log messages, warnings and errors.</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>
|
/// <summary>
|
||||||
/// Creates a new instance of <see cref="DefaultAuthorizationService"/>.
|
/// 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="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="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>
|
/// <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)
|
if (policyProvider == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(policyProvider));
|
throw new ArgumentNullException(nameof(policyProvider));
|
||||||
|
|
@ -61,6 +68,7 @@ namespace Microsoft.AspNetCore.Authorization
|
||||||
throw new ArgumentNullException(nameof(evaluator));
|
throw new ArgumentNullException(nameof(evaluator));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_options = options.Value;
|
||||||
_handlers = handlers.ToArray();
|
_handlers = handlers.ToArray();
|
||||||
_policyProvider = policyProvider;
|
_policyProvider = policyProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
@ -89,6 +97,10 @@ namespace Microsoft.AspNetCore.Authorization
|
||||||
foreach (var handler in _handlers)
|
foreach (var handler in _handlers)
|
||||||
{
|
{
|
||||||
await handler.HandleAsync(authContext);
|
await handler.HandleAsync(authContext);
|
||||||
|
if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_evaluator.HasSucceeded(authContext))
|
if (_evaluator.HasSucceeded(authContext))
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,7 @@ namespace Microsoft.AspNetCore.Authorization.Test
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddAuthorization();
|
services.AddAuthorization();
|
||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
services.AddOptions();
|
setupServices?.Invoke(services);
|
||||||
if (setupServices != null)
|
|
||||||
{
|
|
||||||
setupServices(services);
|
|
||||||
}
|
|
||||||
return services.BuildServiceProvider().GetRequiredService<IAuthorizationService>();
|
return services.BuildServiceProvider().GetRequiredService<IAuthorizationService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,6 +105,72 @@ namespace Microsoft.AspNetCore.Authorization.Test
|
||||||
Assert.True(allowed);
|
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]
|
[Fact]
|
||||||
public async Task Authorize_ShouldFailWhenAllRequirementsNotHandled()
|
public async Task Authorize_ShouldFailWhenAllRequirementsNotHandled()
|
||||||
{
|
{
|
||||||
|
|
@ -584,8 +646,11 @@ namespace Microsoft.AspNetCore.Authorization.Test
|
||||||
public class CustomRequirement : IAuthorizationRequirement { }
|
public class CustomRequirement : IAuthorizationRequirement { }
|
||||||
public class CustomHandler : AuthorizationHandler<CustomRequirement>
|
public class CustomHandler : AuthorizationHandler<CustomRequirement>
|
||||||
{
|
{
|
||||||
|
public bool Invoked { get; set; }
|
||||||
|
|
||||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRequirement requirement)
|
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRequirement requirement)
|
||||||
{
|
{
|
||||||
|
Invoked = true;
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue