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

View File

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

View File

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