diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs index a355477396..abf76ea191 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs @@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization _mvcOptions = context.HttpContext.RequestServices.GetRequiredService>().Value; } - if (_mvcOptions.CombineAuthorizeFilters) + if (_mvcOptions.AllowCombiningAuthorizeFilters) { if (!context.IsEffectivePolicy(this)) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs index 2f26e761ab..c99042f0d3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs @@ -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; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index 4e6e0c19df..b5db7caa1f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -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 _allowCombiningAuthorizeFilters; private readonly CompatibilitySwitch _inputFormatterExceptionPolicy; private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType; private readonly ICompatibilitySwitch[] _switches; @@ -44,11 +46,13 @@ namespace Microsoft.AspNetCore.Mvc ModelValidatorProviders = new List(); ValueProviderFactories = new List(); + _allowCombiningAuthorizeFilters = new CompatibilitySwitch(nameof(AllowCombiningAuthorizeFilters)); _inputFormatterExceptionPolicy = new CompatibilitySwitch(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType)); _switches = new ICompatibilitySwitch[] { + _allowCombiningAuthorizeFilters, _inputFormatterExceptionPolicy, _suppressBindingUndefinedValueToEnumType, }; @@ -66,6 +70,44 @@ namespace Microsoft.AspNetCore.Mvc /// public bool AllowEmptyInputInBodyModelBinding { get; set; } + /// + /// Gets or sets a value that determines if policies on instances of + /// will be combined into a single effective policy. The default value of the property is false. + /// + /// + /// + /// Authorization policies are designed such that multiple authorization policies applied to an endpoint + /// should be combined and executed a single policy. The (commonly applied + /// by ) 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. + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// 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 . + /// + /// + /// If the application's compatibility version is set to then + /// this setting will have the value false unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have the value true unless explicitly configured. + /// + /// + public bool AllowCombiningAuthorizeFilters + { + get => _allowCombiningAuthorizeFilters.Value; + set => _allowCombiningAuthorizeFilters.Value = value; + } + /// /// Gets a Dictionary of CacheProfile Names, which are pre-defined settings for /// response caching. @@ -105,13 +147,13 @@ namespace Microsoft.AspNetCore.Mvc /// /// /// If the application's compatibility version is set to then - /// this setting will have the value if - /// not explicitly configured. + /// this setting will have the value unless + /// explicitly configured. /// /// /// If the application's compatibility version is set to or /// higher then this setting will have the value - /// if not explicitly configured. + /// unless explicitly configured. /// /// public InputFormatterExceptionPolicy InputFormatterExceptionPolicy @@ -141,11 +183,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// /// If the application's compatibility version is set to then - /// this setting will have the value false if not explicitly configured. + /// this setting will have the value false unless explicitly configured. /// /// /// If the application's compatibility version is set to or - /// higher then this setting will have the value true if not explicitly configured. + /// higher then this setting will have the value true unless explicitly configured. /// /// public bool SuppressBindingUndefinedValueToEnumType @@ -243,13 +285,6 @@ namespace Microsoft.AspNetCore.Mvc /// public bool RequireHttpsPermanent { get; set; } - /// - /// Gets or sets a value that determines if policies on instances of - /// will be combined into a single effective policy. This was always to be the intended behavior, - /// but was not the case. - /// - public bool CombineAuthorizeFilters { get; set;} - IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_switches).GetEnumerator(); diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs index 9c94d7b55c..7174d273ea 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs @@ -53,11 +53,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// /// If the application's compatibility version is set to then - /// this setting will have value false if not explicitly configured. + /// this setting will have value false unless explicitly configured. /// /// /// If the application's compatibility version is set to or - /// higher then this setting will have value true if not explicitly configured. + /// higher then this setting will have value true unless explicitly configured. /// /// public bool AllowInputFormatterExceptionMessages diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs index ac19b3c208..8a5fdf440e 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs @@ -81,11 +81,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// /// If the application's compatibility version is set to then - /// this setting will have value false if not explicitly configured. + /// this setting will have value false unless explicitly configured. /// /// /// If the application's compatibility version is set to or - /// higher then this setting will have value true if not explicitly configured. + /// higher then this setting will have value true unless explicitly configured. /// /// public bool AllowAreas diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs index f989ec77a6..765fccd0e3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs @@ -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(o => o.CombineAuthorizeFilters = true)); + var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure(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(o => o.CombineAuthorizeFilters = true)); + var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure(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(o => o.CombineAuthorizeFilters = true)); + var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure(o => o.AllowCombiningAuthorizeFilters = true)); // Effective policy should fail, if both are combined authorizationContext.Filters.Add(authorizeFilter); authorizationContext.Filters.Add(new DerivedAuthorizeFilter()); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CombineAuthorizeTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CombineAuthorizeTests.cs index 854306e4f9..7f9b520466 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CombineAuthorizeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CombineAuthorizeTests.cs @@ -11,9 +11,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class CombineAuthorizeTests : IClassFixture> + public class CombineAuthorizeTests : IClassFixture> { - public CombineAuthorizeTests(MvcTestFixture fixture) + public CombineAuthorizeTests(MvcTestFixture fixture) { Client = fixture.Client; } diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 22c8a34fbe..f57e48c487 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var razorPagesOptions = services.GetRequiredService>().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>().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>().Value; // Assert + Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); diff --git a/test/WebSites/SecurityWebSite/Controllers/AdministrationController.cs b/test/WebSites/SecurityWebSite/Controllers/AdministrationController.cs index 89ea310500..fdd125284a 100644 --- a/test/WebSites/SecurityWebSite/Controllers/AdministrationController.cs +++ b/test/WebSites/SecurityWebSite/Controllers/AdministrationController.cs @@ -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() { diff --git a/test/WebSites/SecurityWebSite/StartupWithGlobalAuthorizeAndCombineAuthorizeFilters.cs b/test/WebSites/SecurityWebSite/StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters.cs similarity index 92% rename from test/WebSites/SecurityWebSite/StartupWithGlobalAuthorizeAndCombineAuthorizeFilters.cs rename to test/WebSites/SecurityWebSite/StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters.cs index 434260777a..135c153367 100644 --- a/test/WebSites/SecurityWebSite/StartupWithGlobalAuthorizeAndCombineAuthorizeFilters.cs +++ b/test/WebSites/SecurityWebSite/StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters.cs @@ -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()); });