Add Fail fast option for AuthZ

This commit is contained in:
Hao Kung 2016-09-15 16:12:54 -07:00
parent ce0ed3d237
commit e8f55bdb13
3 changed files with 90 additions and 7 deletions

View File

@ -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>

View File

@ -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))

View File

@ -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);
}