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;
}
if (_mvcOptions.CombineAuthorizeFilters)
if (_mvcOptions.AllowCombiningAuthorizeFilters)
{
if (!context.IsEffectivePolicy<AuthorizeFilter>(this))
{

View File

@ -25,8 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
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.SuppressBindingUndefinedValueToEnumType)] = true;
}
return values;

View File

@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
@ -23,6 +24,7 @@ namespace Microsoft.AspNetCore.Mvc
private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors;
// See CompatibilitySwitch.cs for guide on how to implement these.
private readonly CompatibilitySwitch<bool> _allowCombiningAuthorizeFilters;
private readonly CompatibilitySwitch<InputFormatterExceptionPolicy> _inputFormatterExceptionPolicy;
private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType;
private readonly ICompatibilitySwitch[] _switches;
@ -44,11 +46,13 @@ namespace Microsoft.AspNetCore.Mvc
ModelValidatorProviders = new List<IModelValidatorProvider>();
ValueProviderFactories = new List<IValueProviderFactory>();
_allowCombiningAuthorizeFilters = new CompatibilitySwitch<bool>(nameof(AllowCombiningAuthorizeFilters));
_inputFormatterExceptionPolicy = new CompatibilitySwitch<InputFormatterExceptionPolicy>(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions);
_suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType));
_switches = new ICompatibilitySwitch[]
{
_allowCombiningAuthorizeFilters,
_inputFormatterExceptionPolicy,
_suppressBindingUndefinedValueToEnumType,
};
@ -66,6 +70,44 @@ namespace Microsoft.AspNetCore.Mvc
/// </example>
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>
/// Gets a Dictionary of CacheProfile Names, <see cref="CacheProfile"/> which are pre-defined settings for
/// response caching.
@ -105,13 +147,13 @@ namespace Microsoft.AspNetCore.Mvc
/// </para>
/// <para>
/// 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
/// not explicitly configured.
/// this setting will have the value <see cref="InputFormatterExceptionPolicy.AllExceptions"/> 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
/// <see cref="InputFormatterExceptionPolicy.MalformedInputExceptions"/> if not explicitly configured.
/// <see cref="InputFormatterExceptionPolicy.MalformedInputExceptions"/> unless explicitly configured.
/// </para>
/// </remarks>
public InputFormatterExceptionPolicy InputFormatterExceptionPolicy
@ -141,11 +183,11 @@ namespace Microsoft.AspNetCore.Mvc
/// </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> if not explicitly configured.
/// 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> if not explicitly configured.
/// higher then this setting will have the value <c>true</c> unless explicitly configured.
/// </para>
/// </remarks>
public bool SuppressBindingUndefinedValueToEnumType
@ -243,13 +285,6 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
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()
{
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();

View File

@ -53,11 +53,11 @@ namespace Microsoft.AspNetCore.Mvc
/// </para>
/// <para>
/// 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>
/// 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>
/// </remarks>
public bool AllowInputFormatterExceptionMessages

View File

@ -81,11 +81,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </para>
/// <para>
/// 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>
/// 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>
/// </remarks>
public bool AllowAreas

View File

@ -226,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{
// Arrange
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
authorizationContext.Filters.Add(authorizeFilter);
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build());
@ -244,7 +244,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{
// Arrange
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
authorizationContext.Filters.Add(authorizeFilter);
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build());
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{
// Arrange
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
authorizationContext.Filters.Add(authorizeFilter);
authorizationContext.Filters.Add(new DerivedAuthorizeFilter());

View File

@ -11,9 +11,9 @@ using Xunit;
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;
}

View File

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

View File

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

View File

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