Fix regression with Authorize + IPolicyProvider (#8068)
This commit is contained in:
parent
46189abda7
commit
a5083d525b
|
|
@ -108,6 +108,24 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
|
||||
bool IFilterFactory.IsReusable => true;
|
||||
|
||||
// Computes the actual policy for this filter using either Policy or PolicyProvider + AuthorizeData
|
||||
private Task<AuthorizationPolicy> ComputePolicyAsync()
|
||||
{
|
||||
if (Policy != null)
|
||||
{
|
||||
return Task.FromResult(Policy);
|
||||
}
|
||||
if (PolicyProvider == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatAuthorizeFilter_AuthorizationPolicyCannotBeCreated(
|
||||
nameof(AuthorizationPolicy),
|
||||
nameof(IAuthorizationPolicyProvider)));
|
||||
}
|
||||
|
||||
return AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData);
|
||||
}
|
||||
|
||||
private async Task<AuthorizationPolicy> GetEffectivePolicyAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (_effectivePolicy != null)
|
||||
|
|
@ -115,7 +133,8 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
return _effectivePolicy;
|
||||
}
|
||||
|
||||
var effectivePolicy = Policy;
|
||||
var effectivePolicy = await ComputePolicyAsync();
|
||||
var canCache = PolicyProvider == null;
|
||||
|
||||
if (_mvcOptions == null)
|
||||
{
|
||||
|
|
@ -124,13 +143,13 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
|
||||
if (_mvcOptions.AllowCombiningAuthorizeFilters)
|
||||
{
|
||||
if (!context.IsEffectivePolicy<AuthorizeFilter>(this))
|
||||
if (!context.IsEffectivePolicy(this))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Combine all authorize filters into single effective policy that's only run on the closest filter
|
||||
AuthorizationPolicyBuilder builder = null;
|
||||
var builder = new AuthorizationPolicyBuilder(effectivePolicy);
|
||||
for (var i = 0; i < context.Filters.Count; i++)
|
||||
{
|
||||
if (ReferenceEquals(this, context.Filters[i]))
|
||||
|
|
@ -140,29 +159,17 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
|
||||
if (context.Filters[i] is AuthorizeFilter authorizeFilter)
|
||||
{
|
||||
builder = builder ?? new AuthorizationPolicyBuilder(effectivePolicy);
|
||||
builder.Combine(authorizeFilter.Policy);
|
||||
// Combine using the explicit policy, or the dynamic policy provider
|
||||
builder.Combine(await authorizeFilter.ComputePolicyAsync());
|
||||
canCache = canCache && authorizeFilter.PolicyProvider == null;
|
||||
}
|
||||
}
|
||||
|
||||
effectivePolicy = builder?.Build() ?? effectivePolicy;
|
||||
}
|
||||
|
||||
if (effectivePolicy == null)
|
||||
{
|
||||
if (PolicyProvider == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatAuthorizeFilter_AuthorizationPolicyCannotBeCreated(
|
||||
nameof(AuthorizationPolicy),
|
||||
nameof(IAuthorizationPolicyProvider)));
|
||||
}
|
||||
|
||||
effectivePolicy = await AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData);
|
||||
}
|
||||
|
||||
// We can cache the effective policy when there is no custom policy provider
|
||||
if (PolicyProvider == null)
|
||||
// We can cache the effective policy when there is no custom policy provider
|
||||
if (canCache)
|
||||
{
|
||||
_effectivePolicy = effectivePolicy;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,6 +221,81 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
Assert.Null(authorizationContext.Result);
|
||||
}
|
||||
|
||||
private class TestPolicyProvider : IAuthorizationPolicyProvider
|
||||
{
|
||||
private AuthorizationPolicy _true = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build();
|
||||
private AuthorizationPolicy _false = new AuthorizationPolicyBuilder().RequireAssertion(_ => false).Build();
|
||||
|
||||
public int GetPolicyCalls = 0;
|
||||
|
||||
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
|
||||
=> Task.FromResult(_true);
|
||||
|
||||
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
|
||||
{
|
||||
GetPolicyCalls++;
|
||||
return Task.FromResult(policyName == "true" ? _true : _false);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthorizationFilterCombinesMultipleFiltersWithPolicyProvider()
|
||||
{
|
||||
// Arrange
|
||||
var authorizeFilter = new AuthorizeFilter(new TestPolicyProvider(), new IAuthorizeData[]
|
||||
{
|
||||
new AuthorizeAttribute { Policy = "true"},
|
||||
new AuthorizeAttribute { Policy = "false"}
|
||||
});
|
||||
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
|
||||
// Effective policy should fail, if both are combined
|
||||
authorizationContext.Filters.Add(authorizeFilter);
|
||||
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
|
||||
authorizationContext.Filters.Add(secondFilter);
|
||||
|
||||
// Act
|
||||
await secondFilter.OnAuthorizationAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ForbidResult>(authorizationContext.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthorizationFilterCombinesMultipleFiltersWithDifferentPolicyProvider()
|
||||
{
|
||||
// Arrange
|
||||
var testProvider1 = new TestPolicyProvider();
|
||||
var testProvider2 = new TestPolicyProvider();
|
||||
var authorizeFilter = new AuthorizeFilter(testProvider1, new IAuthorizeData[]
|
||||
{
|
||||
new AuthorizeAttribute { Policy = "true"},
|
||||
new AuthorizeAttribute { Policy = "false"}
|
||||
});
|
||||
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
|
||||
// Effective policy should fail, if both are combined
|
||||
authorizationContext.Filters.Add(authorizeFilter);
|
||||
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
|
||||
authorizationContext.Filters.Add(secondFilter);
|
||||
var thirdFilter = new AuthorizeFilter(testProvider2, new IAuthorizeData[] { new AuthorizeAttribute(policy: "something") });
|
||||
authorizationContext.Filters.Add(thirdFilter);
|
||||
|
||||
// Act
|
||||
await thirdFilter.OnAuthorizationAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ForbidResult>(authorizationContext.Result);
|
||||
Assert.Equal(2, testProvider1.GetPolicyCalls);
|
||||
Assert.Equal(1, testProvider2.GetPolicyCalls);
|
||||
|
||||
// Make sure the policy calls are not cached
|
||||
await thirdFilter.OnAuthorizationAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ForbidResult>(authorizationContext.Result);
|
||||
Assert.Equal(4, testProvider1.GetPolicyCalls);
|
||||
Assert.Equal(2, testProvider2.GetPolicyCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthorizationFilterCombinesMultipleFilters()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -54,11 +54,47 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Equal(2, policyProvider.GetPolicyCount);
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext()
|
||||
// This is a test for security, because we can't assume that any IAuthorizationPolicyProvider other than
|
||||
// DefaultAuthorizationPolicyProvider will return the same result for the same input. So a cache could cause
|
||||
// undesired access.
|
||||
[Fact]
|
||||
public async Task CombinedAuthorizeFilter_AlwaysCalledWithNonDefaultProvider()
|
||||
{
|
||||
// Arrange
|
||||
var applicationModelProviderContext = GetProviderContext(typeof(AuthorizeController));
|
||||
|
||||
var policyProvider = new TestAuthorizationPolicyProvider();
|
||||
|
||||
var controller = Assert.Single(applicationModelProviderContext.Result.Controllers);
|
||||
var action = Assert.Single(controller.Actions);
|
||||
var authorizeData = action.Attributes.OfType<AuthorizeAttribute>();
|
||||
var authorizeFilter = new AuthorizeFilter(policyProvider, authorizeData);
|
||||
|
||||
var actionContext = new ActionContext(GetHttpContext(combineAuthorize: true), new RouteData(), new ControllerActionDescriptor());
|
||||
|
||||
var authorizationFilterContext = new AuthorizationFilterContext(actionContext, action.Filters);
|
||||
|
||||
authorizationFilterContext.Filters.Add(authorizeFilter);
|
||||
|
||||
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
|
||||
authorizationFilterContext.Filters.Add(secondFilter);
|
||||
|
||||
var thirdFilter = new AuthorizeFilter(policyProvider, authorizeData);
|
||||
authorizationFilterContext.Filters.Add(thirdFilter);
|
||||
|
||||
// Act
|
||||
await thirdFilter.OnAuthorizationAsync(authorizationFilterContext);
|
||||
await thirdFilter.OnAuthorizationAsync(authorizationFilterContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, policyProvider.GetPolicyCount);
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext(bool combineAuthorize = false)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
||||
httpContext.RequestServices = GetServices();
|
||||
httpContext.RequestServices = GetServices(combineAuthorize);
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
|
|
@ -73,11 +109,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
return context;
|
||||
}
|
||||
|
||||
private static IServiceProvider GetServices()
|
||||
private static IServiceProvider GetServices(bool combineAuthorize)
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddAuthorization();
|
||||
serviceCollection.AddMvc();
|
||||
serviceCollection.AddMvc(o => o.AllowCombiningAuthorizeFilters = combineAuthorize);
|
||||
serviceCollection
|
||||
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
|
||||
.AddTransient<ILogger<DefaultAuthorizationService>, Logger<DefaultAuthorizationService>>()
|
||||
|
|
|
|||
Loading…
Reference in New Issue