RequiredPolicy reborn and less demanding as FallbackPolicy (#9759)

This commit is contained in:
Hao Kung 2019-05-14 12:42:34 -07:00 committed by GitHub
parent dd2721f5ca
commit 0425808b70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 118 additions and 31 deletions

View File

@ -251,7 +251,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
return Task.FromResult(policyName == "true" ? _true : _false); return Task.FromResult(policyName == "true" ? _true : _false);
} }
public Task<AuthorizationPolicy> GetRequiredPolicyAsync() public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
=> Task.FromResult<AuthorizationPolicy>(null); => Task.FromResult<AuthorizationPolicy>(null);
} }

View File

@ -209,7 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
return Task.FromResult(new AuthorizationPolicy(requirements, new string[] { })); return Task.FromResult(new AuthorizationPolicy(requirements, new string[] { }));
} }
public Task<AuthorizationPolicy> GetRequiredPolicyAsync() public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
{ {
return Task.FromResult<AuthorizationPolicy>(null); return Task.FromResult<AuthorizationPolicy>(null);
} }

View File

@ -45,12 +45,14 @@ namespace Microsoft.AspNetCore.Authorization
public partial class AuthorizationMiddleware public partial class AuthorizationMiddleware
{ {
public AuthorizationMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider policyProvider) { } public AuthorizationMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider policyProvider) { }
[System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; }
} }
public partial class AuthorizationOptions public partial class AuthorizationOptions
{ {
public AuthorizationOptions() { } public AuthorizationOptions() { }
public Microsoft.AspNetCore.Authorization.AuthorizationPolicy DefaultPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Authorization.AuthorizationPolicy DefaultPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Authorization.AuthorizationPolicy FallbackPolicy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public bool InvokeHandlersAfterFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool InvokeHandlersAfterFailure { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public void AddPolicy(string name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy) { } public void AddPolicy(string name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy) { }
public void AddPolicy(string name, System.Action<Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder> configurePolicy) { } public void AddPolicy(string name, System.Action<Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder> configurePolicy) { }
@ -130,6 +132,7 @@ namespace Microsoft.AspNetCore.Authorization
{ {
public DefaultAuthorizationPolicyProvider(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Authorization.AuthorizationOptions> options) { } public DefaultAuthorizationPolicyProvider(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Authorization.AuthorizationOptions> options) { }
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetDefaultPolicyAsync() { throw null; } public System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetDefaultPolicyAsync() { throw null; }
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetFallbackPolicyAsync() { throw null; }
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetPolicyAsync(string policyName) { throw null; } public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetPolicyAsync(string policyName) { throw null; }
} }
public partial class DefaultAuthorizationService : Microsoft.AspNetCore.Authorization.IAuthorizationService public partial class DefaultAuthorizationService : Microsoft.AspNetCore.Authorization.IAuthorizationService
@ -159,6 +162,7 @@ namespace Microsoft.AspNetCore.Authorization
public partial interface IAuthorizationPolicyProvider public partial interface IAuthorizationPolicyProvider
{ {
System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetDefaultPolicyAsync(); System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetDefaultPolicyAsync();
System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetFallbackPolicyAsync();
System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetPolicyAsync(string policyName); System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy> GetPolicyAsync(string policyName);
} }
public partial interface IAuthorizationRequirement public partial interface IAuthorizationRequirement

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
@ -23,21 +22,11 @@ namespace Microsoft.AspNetCore.Authorization
public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider) public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider)
{ {
if (next == null) _next = next ?? throw new ArgumentNullException(nameof(next));
{ _policyProvider = policyProvider ?? throw new ArgumentNullException(nameof(policyProvider));
throw new ArgumentNullException(nameof(next));
}
if (policyProvider == null)
{
throw new ArgumentNullException(nameof(policyProvider));
}
_next = next;
_policyProvider = policyProvider;
} }
public Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
if (context == null) if (context == null)
{ {
@ -49,18 +38,8 @@ namespace Microsoft.AspNetCore.Authorization
// Flag to indicate to other systems, e.g. MVC, that authorization middleware was run for this request // Flag to indicate to other systems, e.g. MVC, that authorization middleware was run for this request
context.Items[AuthorizationMiddlewareInvokedKey] = AuthorizationMiddlewareInvokedValue; context.Items[AuthorizationMiddlewareInvokedKey] = AuthorizationMiddlewareInvokedValue;
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>();
if (authorizeData == null || authorizeData.Count() == 0)
{
return _next(context);
}
return EvaluatePolicy(context, endpoint, authorizeData);
}
private async Task EvaluatePolicy(HttpContext context, Endpoint endpoint, IEnumerable<IAuthorizeData> authorizeData)
{
// IMPORTANT: Changes to authorization logic should be mirrored in MVC's AuthorizeFilter // IMPORTANT: Changes to authorization logic should be mirrored in MVC's AuthorizeFilter
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData); var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
if (policy == null) if (policy == null)
{ {

View File

@ -27,6 +27,16 @@ namespace Microsoft.AspNetCore.Authorization
/// </remarks> /// </remarks>
public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
/// <summary>
/// Gets or sets the fallback authorization policy used by <see cref="AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider, IEnumerable{IAuthorizeData})"/>
/// when no IAuthorizeData have been provided. As a result, the <see cref="AuthorizationMiddleware"/> uses the fallback policy
/// if there are no <see cref="IAuthorizeData"/> instances for a resource. If a resource has any <see cref="IAuthorizeData"/>
/// then they are evaluated instead of the fallback policy. By default the fallback policy is null, and usually will have no
/// effect unless you have the <see cref="AuthorizationMiddleware"/> middleware in your pipeline. It is not used in any way by the
/// default <see cref="IAuthorizationService"/>.
/// </summary>
public AuthorizationPolicy FallbackPolicy { get; set; }
/// <summary> /// <summary>
/// Add an authorization policy with the provided name. /// Add an authorization policy with the provided name.
/// </summary> /// </summary>

View File

@ -176,6 +176,16 @@ namespace Microsoft.AspNetCore.Authorization
} }
} }
// If we have no policy by now, use the fallback policy if we have one
if (policyBuilder == null)
{
var fallbackPolicy = await policyProvider.GetFallbackPolicyAsync();
if (fallbackPolicy != null)
{
return fallbackPolicy;
}
}
return policyBuilder?.Build(); return policyBuilder?.Build();
} }
} }

View File

@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Authorization
{ {
private readonly AuthorizationOptions _options; private readonly AuthorizationOptions _options;
private Task<AuthorizationPolicy> _cachedDefaultPolicy; private Task<AuthorizationPolicy> _cachedDefaultPolicy;
private Task<AuthorizationPolicy> _cachedFallbackPolicy;
/// <summary> /// <summary>
/// Creates a new instance of <see cref="DefaultAuthorizationPolicyProvider"/>. /// Creates a new instance of <see cref="DefaultAuthorizationPolicyProvider"/>.
@ -39,6 +40,15 @@ namespace Microsoft.AspNetCore.Authorization
return GetCachedPolicy(ref _cachedDefaultPolicy, _options.DefaultPolicy); return GetCachedPolicy(ref _cachedDefaultPolicy, _options.DefaultPolicy);
} }
/// <summary>
/// Gets the fallback authorization policy.
/// </summary>
/// <returns>The fallback authorization policy.</returns>
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
{
return GetCachedPolicy(ref _cachedFallbackPolicy, _options.FallbackPolicy);
}
private Task<AuthorizationPolicy> GetCachedPolicy(ref Task<AuthorizationPolicy> cachedPolicy, AuthorizationPolicy currentPolicy) private Task<AuthorizationPolicy> GetCachedPolicy(ref Task<AuthorizationPolicy> cachedPolicy, AuthorizationPolicy currentPolicy)
{ {
var local = cachedPolicy; var local = cachedPolicy;

View File

@ -22,5 +22,11 @@ namespace Microsoft.AspNetCore.Authorization
/// </summary> /// </summary>
/// <returns>The default authorization policy.</returns> /// <returns>The default authorization policy.</returns>
Task<AuthorizationPolicy> GetDefaultPolicyAsync(); Task<AuthorizationPolicy> GetDefaultPolicyAsync();
/// <summary>
/// Gets the fallback authorization policy.
/// </summary>
/// <returns>The fallback authorization policy.</returns>
Task<AuthorizationPolicy> GetFallbackPolicyAsync();
} }
} }

View File

@ -40,6 +40,25 @@ namespace Microsoft.AspNetCore.Authorization.Test
Assert.True(next.Called); Assert.True(next.Called);
} }
[Fact]
public async Task NoEndpointWithFallback_AnonymousUser_Challenges()
{
// Arrange
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
var policyProvider = new Mock<IAuthorizationPolicyProvider>();
policyProvider.Setup(p => p.GetFallbackPolicyAsync()).ReturnsAsync(policy);
var next = new TestRequestDelegate();
var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
var context = GetHttpContext(anonymous: true);
// Act
await middleware.Invoke(context);
// Assert
Assert.False(next.Called);
}
[Fact] [Fact]
public async Task HasEndpointWithoutAuth_AnonymousUser_Allows() public async Task HasEndpointWithoutAuth_AnonymousUser_Allows()
{ {
@ -59,6 +78,47 @@ namespace Microsoft.AspNetCore.Authorization.Test
Assert.True(next.Called); Assert.True(next.Called);
} }
[Fact]
public async Task HasEndpointWithFallbackWithoutAuth_AnonymousUser_Challenges()
{
// Arrange
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
var policyProvider = new Mock<IAuthorizationPolicyProvider>();
policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy);
policyProvider.Setup(p => p.GetFallbackPolicyAsync()).ReturnsAsync(policy);
var next = new TestRequestDelegate();
var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint());
// Act
await middleware.Invoke(context);
// Assert
Assert.False(next.Called);
}
[Fact]
public async Task HasEndpointWithOnlyFallbackAuth_AnonymousUser_Allows()
{
// Arrange
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
var policyProvider = new Mock<IAuthorizationPolicyProvider>();
policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
policyProvider.Setup(p => p.GetFallbackPolicyAsync()).ReturnsAsync(policy);
var next = new TestRequestDelegate();
var authenticationService = new TestAuthenticationService();
var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService);
// Act
await middleware.Invoke(context);
// Assert
Assert.True(next.Called);
}
[Fact] [Fact]
public async Task HasEndpointWithAuth_AnonymousUser_Challenges() public async Task HasEndpointWithAuth_AnonymousUser_Challenges()
{ {
@ -108,8 +168,11 @@ namespace Microsoft.AspNetCore.Authorization.Test
var policy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build(); var policy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build();
var policyProvider = new Mock<IAuthorizationPolicyProvider>(); var policyProvider = new Mock<IAuthorizationPolicyProvider>();
var getPolicyCount = 0; var getPolicyCount = 0;
var getFallbackPolicyCount = 0;
policyProvider.Setup(p => p.GetPolicyAsync(It.IsAny<string>())).ReturnsAsync(policy) policyProvider.Setup(p => p.GetPolicyAsync(It.IsAny<string>())).ReturnsAsync(policy)
.Callback(() => getPolicyCount++); .Callback(() => getPolicyCount++);
policyProvider.Setup(p => p.GetFallbackPolicyAsync()).ReturnsAsync(policy)
.Callback(() => getFallbackPolicyCount++);
var next = new TestRequestDelegate(); var next = new TestRequestDelegate();
var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); var middleware = CreateMiddleware(next.Invoke, policyProvider.Object);
var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute("whatever"))); var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute("whatever")));
@ -117,14 +180,17 @@ namespace Microsoft.AspNetCore.Authorization.Test
// Act & Assert // Act & Assert
await middleware.Invoke(context); await middleware.Invoke(context);
Assert.Equal(1, getPolicyCount); Assert.Equal(1, getPolicyCount);
Assert.Equal(0, getFallbackPolicyCount);
Assert.Equal(1, next.CalledCount); Assert.Equal(1, next.CalledCount);
await middleware.Invoke(context); await middleware.Invoke(context);
Assert.Equal(2, getPolicyCount); Assert.Equal(2, getPolicyCount);
Assert.Equal(0, getFallbackPolicyCount);
Assert.Equal(2, next.CalledCount); Assert.Equal(2, next.CalledCount);
await middleware.Invoke(context); await middleware.Invoke(context);
Assert.Equal(3, getPolicyCount); Assert.Equal(3, getPolicyCount);
Assert.Equal(0, getFallbackPolicyCount);
Assert.Equal(3, next.CalledCount); Assert.Equal(3, next.CalledCount);
} }

View File

@ -1025,7 +1025,7 @@ namespace Microsoft.AspNetCore.Authorization.Test
return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
} }
public Task<AuthorizationPolicy> GetRequiredPolicyAsync() public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
{ {
return Task.FromResult<AuthorizationPolicy>(null); return Task.FromResult<AuthorizationPolicy>(null);
} }
@ -1064,7 +1064,7 @@ namespace Microsoft.AspNetCore.Authorization.Test
return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
} }
public Task<AuthorizationPolicy> GetRequiredPolicyAsync() public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
{ {
return Task.FromResult<AuthorizationPolicy>(null); return Task.FromResult<AuthorizationPolicy>(null);
} }

View File

@ -27,6 +27,8 @@ namespace CustomPolicyProvider
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync(); public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy> GetFallbackPolicyAsync() => FallbackPolicyProvider.GetFallbackPolicyAsync();
// Policies are looked up by string name, so expect 'parameters' (like age) // Policies are looked up by string name, so expect 'parameters' (like age)
// to be embedded in the policy names. This is abstracted away from developers // to be embedded in the policy names. This is abstracted away from developers
// by the more strongly-typed attributes derived from AuthorizeAttribute // by the more strongly-typed attributes derived from AuthorizeAttribute