Skip to content

Commit 0425808

Browse files
authored
RequiredPolicy reborn and less demanding as FallbackPolicy (#9759)
1 parent dd2721f commit 0425808

File tree

11 files changed

+118
-31
lines changed

11 files changed

+118
-31
lines changed

src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
251251
return Task.FromResult(policyName == "true" ? _true : _false);
252252
}
253253

254-
public Task<AuthorizationPolicy> GetRequiredPolicyAsync()
254+
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
255255
=> Task.FromResult<AuthorizationPolicy>(null);
256256
}
257257

src/Mvc/test/Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
209209
return Task.FromResult(new AuthorizationPolicy(requirements, new string[] { }));
210210
}
211211

212-
public Task<AuthorizationPolicy> GetRequiredPolicyAsync()
212+
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
213213
{
214214
return Task.FromResult<AuthorizationPolicy>(null);
215215
}

src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ protected AuthorizationHandler() { }
4545
public partial class AuthorizationMiddleware
4646
{
4747
public AuthorizationMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider policyProvider) { }
48+
[System.Diagnostics.DebuggerStepThroughAttribute]
4849
public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; }
4950
}
5051
public partial class AuthorizationOptions
5152
{
5253
public AuthorizationOptions() { }
5354
public Microsoft.AspNetCore.Authorization.AuthorizationPolicy DefaultPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
55+
public Microsoft.AspNetCore.Authorization.AuthorizationPolicy FallbackPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
5456
public bool InvokeHandlersAfterFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
5557
public void AddPolicy(string name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy) { }
5658
public void AddPolicy(string name, System.Action<Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder> configurePolicy) { }
@@ -130,6 +132,7 @@ public partial class DefaultAuthorizationPolicyProvider : Microsoft.AspNetCore.A
130132
{
131133
public DefaultAuthorizationPolicyProvider(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Authorization.AuthorizationOptions> options) { }
132134
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetDefaultPolicyAsync() { throw null; }
135+
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetFallbackPolicyAsync() { throw null; }
133136
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetPolicyAsync(string policyName) { throw null; }
134137
}
135138
public partial class DefaultAuthorizationService : Microsoft.AspNetCore.Authorization.IAuthorizationService
@@ -159,6 +162,7 @@ public partial interface IAuthorizationHandlerProvider
159162
public partial interface IAuthorizationPolicyProvider
160163
{
161164
System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetDefaultPolicyAsync();
165+
System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetFallbackPolicyAsync();
162166
System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetPolicyAsync(string policyName);
163167
}
164168
public partial interface IAuthorizationRequirement

src/Security/Authorization/Core/src/Policy/AuthorizationMiddleware.cs renamed to src/Security/Authorization/Core/src/AuthorizationMiddleware.cs

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
65
using System.Linq;
76
using System.Threading.Tasks;
87
using Microsoft.AspNetCore.Authentication;
@@ -23,21 +22,11 @@ public class AuthorizationMiddleware
2322

2423
public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider)
2524
{
26-
if (next == null)
27-
{
28-
throw new ArgumentNullException(nameof(next));
29-
}
30-
31-
if (policyProvider == null)
32-
{
33-
throw new ArgumentNullException(nameof(policyProvider));
34-
}
35-
36-
_next = next;
37-
_policyProvider = policyProvider;
25+
_next = next ?? throw new ArgumentNullException(nameof(next));
26+
_policyProvider = policyProvider ?? throw new ArgumentNullException(nameof(policyProvider));
3827
}
3928

40-
public Task Invoke(HttpContext context)
29+
public async Task Invoke(HttpContext context)
4130
{
4231
if (context == null)
4332
{
@@ -49,18 +38,8 @@ public Task Invoke(HttpContext context)
4938
// Flag to indicate to other systems, e.g. MVC, that authorization middleware was run for this request
5039
context.Items[AuthorizationMiddlewareInvokedKey] = AuthorizationMiddlewareInvokedValue;
5140

52-
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>();
53-
if (authorizeData == null || authorizeData.Count() == 0)
54-
{
55-
return _next(context);
56-
}
57-
58-
return EvaluatePolicy(context, endpoint, authorizeData);
59-
}
60-
61-
private async Task EvaluatePolicy(HttpContext context, Endpoint endpoint, IEnumerable<IAuthorizeData> authorizeData)
62-
{
6341
// IMPORTANT: Changes to authorization logic should be mirrored in MVC's AuthorizeFilter
42+
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
6443
var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
6544
if (policy == null)
6645
{

src/Security/Authorization/Core/src/AuthorizationOptions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ public class AuthorizationOptions
2727
/// </remarks>
2828
public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
2929

30+
/// <summary>
31+
/// Gets or sets the fallback authorization policy used by <see cref="AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider, IEnumerable{IAuthorizeData})"/>
32+
/// when no IAuthorizeData have been provided. As a result, the <see cref="AuthorizationMiddleware"/> uses the fallback policy
33+
/// if there are no <see cref="IAuthorizeData"/> instances for a resource. If a resource has any <see cref="IAuthorizeData"/>
34+
/// then they are evaluated instead of the fallback policy. By default the fallback policy is null, and usually will have no
35+
/// effect unless you have the <see cref="AuthorizationMiddleware"/> middleware in your pipeline. It is not used in any way by the
36+
/// default <see cref="IAuthorizationService"/>.
37+
/// </summary>
38+
public AuthorizationPolicy FallbackPolicy { get; set; }
39+
3040
/// <summary>
3141
/// Add an authorization policy with the provided name.
3242
/// </summary>
@@ -84,4 +94,4 @@ public AuthorizationPolicy GetPolicy(string name)
8494
return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
8595
}
8696
}
87-
}
97+
}

src/Security/Authorization/Core/src/AuthorizationPolicy.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,17 @@ public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyP
176176
}
177177
}
178178

179+
// If we have no policy by now, use the fallback policy if we have one
180+
if (policyBuilder == null)
181+
{
182+
var fallbackPolicy = await policyProvider.GetFallbackPolicyAsync();
183+
if (fallbackPolicy != null)
184+
{
185+
return fallbackPolicy;
186+
}
187+
}
188+
179189
return policyBuilder?.Build();
180190
}
181191
}
182-
}
192+
}

src/Security/Authorization/Core/src/DefaultAuthorizationPolicyProvider.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider
1515
{
1616
private readonly AuthorizationOptions _options;
1717
private Task<AuthorizationPolicy> _cachedDefaultPolicy;
18+
private Task<AuthorizationPolicy> _cachedFallbackPolicy;
1819

1920
/// <summary>
2021
/// Creates a new instance of <see cref="DefaultAuthorizationPolicyProvider"/>.
@@ -39,6 +40,15 @@ public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
3940
return GetCachedPolicy(ref _cachedDefaultPolicy, _options.DefaultPolicy);
4041
}
4142

43+
/// <summary>
44+
/// Gets the fallback authorization policy.
45+
/// </summary>
46+
/// <returns>The fallback authorization policy.</returns>
47+
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
48+
{
49+
return GetCachedPolicy(ref _cachedFallbackPolicy, _options.FallbackPolicy);
50+
}
51+
4252
private Task<AuthorizationPolicy> GetCachedPolicy(ref Task<AuthorizationPolicy> cachedPolicy, AuthorizationPolicy currentPolicy)
4353
{
4454
var local = cachedPolicy;

src/Security/Authorization/Core/src/IAuthorizationPolicyProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,11 @@ public interface IAuthorizationPolicyProvider
2222
/// </summary>
2323
/// <returns>The default authorization policy.</returns>
2424
Task<AuthorizationPolicy> GetDefaultPolicyAsync();
25+
26+
/// <summary>
27+
/// Gets the fallback authorization policy.
28+
/// </summary>
29+
/// <returns>The fallback authorization policy.</returns>
30+
Task<AuthorizationPolicy> GetFallbackPolicyAsync();
2531
}
2632
}

src/Security/Authorization/test/AuthorizationMiddlewareTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ public async Task NoEndpoint_AnonymousUser_Allows()
4040
Assert.True(next.Called);
4141
}
4242

43+
[Fact]
44+
public async Task NoEndpointWithFallback_AnonymousUser_Challenges()
45+
{
46+
// Arrange
47+
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
48+
var policyProvider = new Mock<IAuthorizationPolicyProvider>();
49+
policyProvider.Setup(p => p.GetFallbackPolicyAsync()).ReturnsAsync(policy);
50+
var next = new TestRequestDelegate();
51+
52+
var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
53+
var context = GetHttpContext(anonymous: true);
54+
55+
// Act
56+
await middleware.Invoke(context);
57+
58+
// Assert
59+
Assert.False(next.Called);
60+
}
61+
4362
[Fact]
4463
public async Task HasEndpointWithoutAuth_AnonymousUser_Allows()
4564
{
@@ -59,6 +78,47 @@ public async Task HasEndpointWithoutAuth_AnonymousUser_Allows()
5978
Assert.True(next.Called);
6079
}
6180

81+
[Fact]
82+
public async Task HasEndpointWithFallbackWithoutAuth_AnonymousUser_Challenges()
83+
{
84+
// Arrange
85+
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
86+
var policyProvider = new Mock<IAuthorizationPolicyProvider>();
87+
policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
88+
policyProvider.Setup(p => p.GetFallbackPolicyAsync()).ReturnsAsync(policy);
89+
var next = new TestRequestDelegate();
90+
91+
var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
92+
var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint());
93+
94+
// Act
95+
await middleware.Invoke(context);
96+
97+
// Assert
98+
Assert.False(next.Called);
99+
}
100+
101+
[Fact]
102+
public async Task HasEndpointWithOnlyFallbackAuth_AnonymousUser_Allows()
103+
{
104+
// Arrange
105+
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
106+
var policyProvider = new Mock<IAuthorizationPolicyProvider>();
107+
policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
108+
policyProvider.Setup(p => p.GetFallbackPolicyAsync()).ReturnsAsync(policy);
109+
var next = new TestRequestDelegate();
110+
var authenticationService = new TestAuthenticationService();
111+
112+
var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
113+
var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
114+
115+
// Act
116+
await middleware.Invoke(context);
117+
118+
// Assert
119+
Assert.True(next.Called);
120+
}
121+
62122
[Fact]
63123
public async Task HasEndpointWithAuth_AnonymousUser_Challenges()
64124
{
@@ -108,23 +168,29 @@ public async Task OnAuthorizationAsync_WillCallPolicyProvider()
108168
var policy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build();
109169
var policyProvider = new Mock<IAuthorizationPolicyProvider>();
110170
var getPolicyCount = 0;
171+
var getFallbackPolicyCount = 0;
111172
policyProvider.Setup(p => p.GetPolicyAsync(It.IsAny<string>())).ReturnsAsync(policy)
112173
.Callback(() => getPolicyCount++);
174+
policyProvider.Setup(p => p.GetFallbackPolicyAsync()).ReturnsAsync(policy)
175+
.Callback(() => getFallbackPolicyCount++);
113176
var next = new TestRequestDelegate();
114177
var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
115178
var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute("whatever")));
116179

117180
// Act & Assert
118181
await middleware.Invoke(context);
119182
Assert.Equal(1, getPolicyCount);
183+
Assert.Equal(0, getFallbackPolicyCount);
120184
Assert.Equal(1, next.CalledCount);
121185

122186
await middleware.Invoke(context);
123187
Assert.Equal(2, getPolicyCount);
188+
Assert.Equal(0, getFallbackPolicyCount);
124189
Assert.Equal(2, next.CalledCount);
125190

126191
await middleware.Invoke(context);
127192
Assert.Equal(3, getPolicyCount);
193+
Assert.Equal(0, getFallbackPolicyCount);
128194
Assert.Equal(3, next.CalledCount);
129195
}
130196

src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,7 @@ public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
10251025
return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
10261026
}
10271027

1028-
public Task<AuthorizationPolicy> GetRequiredPolicyAsync()
1028+
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
10291029
{
10301030
return Task.FromResult<AuthorizationPolicy>(null);
10311031
}
@@ -1064,7 +1064,7 @@ public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
10641064
return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
10651065
}
10661066

1067-
public Task<AuthorizationPolicy> GetRequiredPolicyAsync()
1067+
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
10681068
{
10691069
return Task.FromResult<AuthorizationPolicy>(null);
10701070
}

0 commit comments

Comments
 (0)