Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit e8f55bd

Browse files
committed
Add Fail fast option for AuthZ
1 parent ce0ed3d commit e8f55bd

File tree

3 files changed

+90
-7
lines changed

3 files changed

+90
-7
lines changed

src/Microsoft.AspNetCore.Authorization/AuthorizationOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ public class AuthorizationOptions
1313
{
1414
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
1515

16+
/// <summary>
17+
/// Determines whether authentication handlers should be invoked after a failure.
18+
/// Defaults to true.
19+
/// </summary>
20+
public bool InvokeHandlersAfterFailure { get; set; } = true;
21+
1622
/// <summary>
1723
/// Gets or sets the default authorization policy.
1824
/// </summary>

src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Security.Principal;
99
using System.Threading.Tasks;
1010
using Microsoft.Extensions.Logging;
11+
using Microsoft.Extensions.Options;
1112

1213
namespace Microsoft.AspNetCore.Authorization
1314
{
@@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Authorization
1617
/// </summary>
1718
public class DefaultAuthorizationService : IAuthorizationService
1819
{
20+
private readonly AuthorizationOptions _options;
1921
private readonly IAuthorizationHandlerContextFactory _contextFactory;
2022
private readonly IAuthorizationEvaluator _evaluator;
2123
private readonly IAuthorizationPolicyProvider _policyProvider;
@@ -28,7 +30,7 @@ public class DefaultAuthorizationService : IAuthorizationService
2830
/// <param name="policyProvider">The <see cref="IAuthorizationPolicyProvider"/> used to provide policies.</param>
2931
/// <param name="handlers">The handlers used to fulfill <see cref="IAuthorizationRequirement"/>s.</param>
3032
/// <param name="logger">The logger used to log messages, warnings and errors.</param>
31-
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizationHandler> handlers, ILogger<DefaultAuthorizationService> logger) : this(policyProvider, handlers, logger, new DefaultAuthorizationHandlerContextFactory(), new DefaultAuthorizationEvaluator()) { }
33+
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizationHandler> handlers, ILogger<DefaultAuthorizationService> logger) : this(policyProvider, handlers, logger, new DefaultAuthorizationHandlerContextFactory(), new DefaultAuthorizationEvaluator(), Options.Create(new AuthorizationOptions())) { }
3234

3335
/// <summary>
3436
/// Creates a new instance of <see cref="DefaultAuthorizationService"/>.
@@ -38,8 +40,13 @@ public class DefaultAuthorizationService : IAuthorizationService
3840
/// <param name="logger">The logger used to log messages, warnings and errors.</param>
3941
/// <param name="contextFactory">The <see cref="IAuthorizationHandlerContextFactory"/> used to create the context to handle the authorization.</param>
4042
/// <param name="evaluator">The <see cref="IAuthorizationEvaluator"/> used to determine if authorzation was successful.</param>
41-
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizationHandler> handlers, ILogger<DefaultAuthorizationService> logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator)
43+
/// <param name="options">The <see cref="AuthorizationOptions"/> used.</param>
44+
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizationHandler> handlers, ILogger<DefaultAuthorizationService> logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator, IOptions<AuthorizationOptions> options)
4245
{
46+
if (options == null)
47+
{
48+
throw new ArgumentNullException(nameof(options));
49+
}
4350
if (policyProvider == null)
4451
{
4552
throw new ArgumentNullException(nameof(policyProvider));
@@ -61,6 +68,7 @@ public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider,
6168
throw new ArgumentNullException(nameof(evaluator));
6269
}
6370

71+
_options = options.Value;
6472
_handlers = handlers.ToArray();
6573
_policyProvider = policyProvider;
6674
_logger = logger;
@@ -89,6 +97,10 @@ public async Task<bool> AuthorizeAsync(ClaimsPrincipal user, object resource, IE
8997
foreach (var handler in _handlers)
9098
{
9199
await handler.HandleAsync(authContext);
100+
if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
101+
{
102+
break;
103+
}
92104
}
93105

94106
if (_evaluator.HasSucceeded(authContext))

test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@ private IAuthorizationService BuildAuthorizationService(Action<IServiceCollectio
2020
var services = new ServiceCollection();
2121
services.AddAuthorization();
2222
services.AddLogging();
23-
services.AddOptions();
24-
if (setupServices != null)
25-
{
26-
setupServices(services);
27-
}
23+
setupServices?.Invoke(services);
2824
return services.BuildServiceProvider().GetRequiredService<IAuthorizationService>();
2925
}
3026

@@ -109,6 +105,72 @@ public async Task Authorize_ShouldAllowIfClaimIsAmongValues()
109105
Assert.True(allowed);
110106
}
111107

108+
public async Task Authorize_ShouldInvokeAllHandlersByDefault()
109+
{
110+
// Arrange
111+
var handler1 = new FailHandler();
112+
var handler2 = new FailHandler();
113+
var authorizationService = BuildAuthorizationService(services =>
114+
{
115+
services.AddSingleton<IAuthorizationHandler>(handler1);
116+
services.AddSingleton<IAuthorizationHandler>(handler2);
117+
services.AddAuthorization(options =>
118+
{
119+
options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement()));
120+
});
121+
});
122+
123+
// Act
124+
var allowed = await authorizationService.AuthorizeAsync(new ClaimsPrincipal(), "Custom");
125+
126+
// Assert
127+
Assert.False(allowed);
128+
Assert.True(handler1.Invoked);
129+
Assert.True(handler2.Invoked);
130+
}
131+
132+
[Theory]
133+
[InlineData(true)]
134+
[InlineData(false)]
135+
public async Task Authorize_ShouldInvokeAllHandlersDependingOnSetting(bool invokeAllHandlers)
136+
{
137+
// Arrange
138+
var handler1 = new FailHandler();
139+
var handler2 = new FailHandler();
140+
var authorizationService = BuildAuthorizationService(services =>
141+
{
142+
services.AddSingleton<IAuthorizationHandler>(handler1);
143+
services.AddSingleton<IAuthorizationHandler>(handler2);
144+
services.AddAuthorization(options =>
145+
{
146+
options.InvokeHandlersAfterFailure = invokeAllHandlers;
147+
options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement()));
148+
});
149+
});
150+
151+
// Act
152+
var allowed = await authorizationService.AuthorizeAsync(new ClaimsPrincipal(), "Custom");
153+
154+
// Assert
155+
Assert.False(allowed);
156+
Assert.True(handler1.Invoked);
157+
Assert.Equal(invokeAllHandlers, handler2.Invoked);
158+
}
159+
160+
private class FailHandler : IAuthorizationHandler
161+
{
162+
public bool Invoked { get; set; }
163+
164+
public Task HandleAsync(AuthorizationHandlerContext context)
165+
{
166+
Invoked = true;
167+
context.Fail();
168+
return Task.FromResult(0);
169+
}
170+
}
171+
172+
173+
112174
[Fact]
113175
public async Task Authorize_ShouldFailWhenAllRequirementsNotHandled()
114176
{
@@ -584,8 +646,11 @@ public async Task CanBlockNonAuthenticatedUser()
584646
public class CustomRequirement : IAuthorizationRequirement { }
585647
public class CustomHandler : AuthorizationHandler<CustomRequirement>
586648
{
649+
public bool Invoked { get; set; }
650+
587651
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRequirement requirement)
588652
{
653+
Invoked = true;
589654
context.Succeed(requirement);
590655
return Task.FromResult(0);
591656
}

0 commit comments

Comments
 (0)