Use compatibility switch for auth filters

This commit is contained in:
Ryan Nowak 2018-01-11 14:36:46 -08:00
parent c922b0b90d
commit d342ebf8c8
10 changed files with 66 additions and 27 deletions

View File

@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
_mvcOptions = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value; _mvcOptions = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value;
} }
if (_mvcOptions.CombineAuthorizeFilters) if (_mvcOptions.AllowCombiningAuthorizeFilters)
{ {
if (!context.IsEffectivePolicy<AuthorizeFilter>(this)) if (!context.IsEffectivePolicy<AuthorizeFilter>(this))
{ {

View File

@ -25,8 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
if (Version >= CompatibilityVersion.Version_2_1) if (Version >= CompatibilityVersion.Version_2_1)
{ {
values[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true; values[nameof(MvcOptions.AllowCombiningAuthorizeFilters)] = true;
values[nameof(MvcOptions.InputFormatterExceptionPolicy)] = InputFormatterExceptionPolicy.MalformedInputExceptions; values[nameof(MvcOptions.InputFormatterExceptionPolicy)] = InputFormatterExceptionPolicy.MalformedInputExceptions;
values[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true;
} }
return values; return values;

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
@ -23,6 +24,7 @@ namespace Microsoft.AspNetCore.Mvc
private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors; private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors;
// See CompatibilitySwitch.cs for guide on how to implement these. // See CompatibilitySwitch.cs for guide on how to implement these.
private readonly CompatibilitySwitch<bool> _allowCombiningAuthorizeFilters;
private readonly CompatibilitySwitch<InputFormatterExceptionPolicy> _inputFormatterExceptionPolicy; private readonly CompatibilitySwitch<InputFormatterExceptionPolicy> _inputFormatterExceptionPolicy;
private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType; private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType;
private readonly ICompatibilitySwitch[] _switches; private readonly ICompatibilitySwitch[] _switches;
@ -44,11 +46,13 @@ namespace Microsoft.AspNetCore.Mvc
ModelValidatorProviders = new List<IModelValidatorProvider>(); ModelValidatorProviders = new List<IModelValidatorProvider>();
ValueProviderFactories = new List<IValueProviderFactory>(); ValueProviderFactories = new List<IValueProviderFactory>();
_allowCombiningAuthorizeFilters = new CompatibilitySwitch<bool>(nameof(AllowCombiningAuthorizeFilters));
_inputFormatterExceptionPolicy = new CompatibilitySwitch<InputFormatterExceptionPolicy>(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); _inputFormatterExceptionPolicy = new CompatibilitySwitch<InputFormatterExceptionPolicy>(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions);
_suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType)); _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType));
_switches = new ICompatibilitySwitch[] _switches = new ICompatibilitySwitch[]
{ {
_allowCombiningAuthorizeFilters,
_inputFormatterExceptionPolicy, _inputFormatterExceptionPolicy,
_suppressBindingUndefinedValueToEnumType, _suppressBindingUndefinedValueToEnumType,
}; };
@ -66,6 +70,44 @@ namespace Microsoft.AspNetCore.Mvc
/// </example> /// </example>
public bool AllowEmptyInputInBodyModelBinding { get; set; } public bool AllowEmptyInputInBodyModelBinding { get; set; }
/// <summary>
/// Gets or sets a value that determines if policies on instances of <see cref="AuthorizeFilter" />
/// will be combined into a single effective policy. The default value of the property is <c>false</c>.
/// </summary>
/// <remarks>
/// <para>
/// Authorization policies are designed such that multiple authorization policies applied to an endpoint
/// should be combined and executed a single policy. The <see cref="AuthorizeFilter"/> (commonly applied
/// by <see cref="AuthorizeAttribute"/>) can be applied globally, to controllers, and to actions - which
/// specifies multiple authorization policies for an action. In all ASP.NET Core releases prior to 2.1
/// these multiple policies would not combine as intended. This compatibility switch configures whether the
/// old (unintended) behavior or the new combining behavior will be used when multiple authorization policies
/// are applied.
/// </para>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired value of the compatibility switch by calling this property's setter will take precedence
/// over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have the value <c>false</c> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have the value <c>true</c> unless explicitly configured.
/// </para>
/// </remarks>
public bool AllowCombiningAuthorizeFilters
{
get => _allowCombiningAuthorizeFilters.Value;
set => _allowCombiningAuthorizeFilters.Value = value;
}
/// <summary> /// <summary>
/// Gets a Dictionary of CacheProfile Names, <see cref="CacheProfile"/> which are pre-defined settings for /// Gets a Dictionary of CacheProfile Names, <see cref="CacheProfile"/> which are pre-defined settings for
/// response caching. /// response caching.
@ -105,13 +147,13 @@ namespace Microsoft.AspNetCore.Mvc
/// </para> /// </para>
/// <para> /// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then /// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have the value <see cref="InputFormatterExceptionPolicy.AllExceptions"/> if /// this setting will have the value <see cref="InputFormatterExceptionPolicy.AllExceptions"/> unless
/// not explicitly configured. /// explicitly configured.
/// </para> /// </para>
/// <para> /// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or /// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have the value /// higher then this setting will have the value
/// <see cref="InputFormatterExceptionPolicy.MalformedInputExceptions"/> if not explicitly configured. /// <see cref="InputFormatterExceptionPolicy.MalformedInputExceptions"/> unless explicitly configured.
/// </para> /// </para>
/// </remarks> /// </remarks>
public InputFormatterExceptionPolicy InputFormatterExceptionPolicy public InputFormatterExceptionPolicy InputFormatterExceptionPolicy
@ -141,11 +183,11 @@ namespace Microsoft.AspNetCore.Mvc
/// </para> /// </para>
/// <para> /// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then /// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have the value <c>false</c> if not explicitly configured. /// this setting will have the value <c>false</c> unless explicitly configured.
/// </para> /// </para>
/// <para> /// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or /// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have the value <c>true</c> if not explicitly configured. /// higher then this setting will have the value <c>true</c> unless explicitly configured.
/// </para> /// </para>
/// </remarks> /// </remarks>
public bool SuppressBindingUndefinedValueToEnumType public bool SuppressBindingUndefinedValueToEnumType
@ -243,13 +285,6 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
public bool RequireHttpsPermanent { get; set; } public bool RequireHttpsPermanent { get; set; }
/// <summary>
/// Gets or sets a value that determines if policies on instances of <see cref="AuthorizeFilter" />
/// will be combined into a single effective policy. This was always to be the intended behavior,
/// but was not the case.
/// </summary>
public bool CombineAuthorizeFilters { get; set;}
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator() IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{ {
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator(); return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();

View File

@ -53,11 +53,11 @@ namespace Microsoft.AspNetCore.Mvc
/// </para> /// </para>
/// <para> /// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then /// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have value <c>false</c> if not explicitly configured. /// this setting will have value <c>false</c> unless explicitly configured.
/// </para> /// </para>
/// <para> /// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or /// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have value <c>true</c> if not explicitly configured. /// higher then this setting will have value <c>true</c> unless explicitly configured.
/// </para> /// </para>
/// </remarks> /// </remarks>
public bool AllowInputFormatterExceptionMessages public bool AllowInputFormatterExceptionMessages

View File

@ -81,11 +81,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </para> /// </para>
/// <para> /// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then /// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have value <c>false</c> if not explicitly configured. /// this setting will have value <c>false</c> unless explicitly configured.
/// </para> /// </para>
/// <para> /// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or /// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have value <c>true</c> if not explicitly configured. /// higher then this setting will have value <c>true</c> unless explicitly configured.
/// </para> /// </para>
/// </remarks> /// </remarks>
public bool AllowAreas public bool AllowAreas

View File

@ -226,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{ {
// Arrange // Arrange
var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.CombineAuthorizeFilters = true)); var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
// Effective policy should fail, if both are combined // Effective policy should fail, if both are combined
authorizationContext.Filters.Add(authorizeFilter); authorizationContext.Filters.Add(authorizeFilter);
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build()); var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build());
@ -244,7 +244,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{ {
// Arrange // Arrange
var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build()); var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build());
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.CombineAuthorizeFilters = true)); var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
// Effective policy should fail, if both are combined // Effective policy should fail, if both are combined
authorizationContext.Filters.Add(authorizeFilter); authorizationContext.Filters.Add(authorizeFilter);
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build()); var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build());
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{ {
// Arrange // Arrange
var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.CombineAuthorizeFilters = true)); var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
// Effective policy should fail, if both are combined // Effective policy should fail, if both are combined
authorizationContext.Filters.Add(authorizeFilter); authorizationContext.Filters.Add(authorizeFilter);
authorizationContext.Filters.Add(new DerivedAuthorizeFilter()); authorizationContext.Filters.Add(new DerivedAuthorizeFilter());

View File

@ -11,9 +11,9 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{ {
public class CombineAuthorizeTests : IClassFixture<MvcTestFixture<StartupWithGlobalAuthorizeAndCombineAuthorizeFilters>> public class CombineAuthorizeTests : IClassFixture<MvcTestFixture<StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters>>
{ {
public CombineAuthorizeTests(MvcTestFixture<StartupWithGlobalAuthorizeAndCombineAuthorizeFilters> fixture) public CombineAuthorizeTests(MvcTestFixture<StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters> fixture)
{ {
Client = fixture.Client; Client = fixture.Client;
} }

View File

@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value; var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
// Assert // Assert
Assert.False(mvcOptions.AllowCombiningAuthorizeFilters);
Assert.False(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.False(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.AllExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.Equal(InputFormatterExceptionPolicy.AllExceptions, mvcOptions.InputFormatterExceptionPolicy);
Assert.False(jsonOptions.AllowInputFormatterExceptionMessages); Assert.False(jsonOptions.AllowInputFormatterExceptionMessages);
@ -55,6 +56,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value; var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
// Assert // Assert
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy);
Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);
@ -77,6 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value; var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
// Assert // Assert
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy);
Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);

View File

@ -20,8 +20,8 @@ namespace SecurityWebSite.Controllers
return Content("Administration.Index"); return Content("Administration.Index");
} }
// Either cookie should allow access to this action (if CombineAuthorizeFilters is true) // Either cookie should allow access to this action (if AllowCombiningAuthorizeFilters is true)
// If CombineAuthorizeFilters is false, the main cookie is required. // If AllowCombiningAuthorizeFilters is false, the main cookie is required.
[Authorize(AuthenticationSchemes = "Cookie2")] [Authorize(AuthenticationSchemes = "Cookie2")]
public IActionResult EitherCookie() public IActionResult EitherCookie()
{ {

View File

@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace SecurityWebSite namespace SecurityWebSite
{ {
public class StartupWithGlobalAuthorizeAndCombineAuthorizeFilters public class StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters
{ {
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
@ -17,7 +17,7 @@ namespace SecurityWebSite
// Add framework services. // Add framework services.
services.AddMvc(o => services.AddMvc(o =>
{ {
o.CombineAuthorizeFilters = true; o.AllowCombiningAuthorizeFilters = true;
o.Filters.Add(new AuthorizeFilter()); o.Filters.Add(new AuthorizeFilter());
}); });