From 06f6de6c119b2f5ffd79f1d23f18dc81f648bd2e Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Sat, 9 Sep 2017 12:27:40 -0700 Subject: [PATCH] Changed RequestSizeLimitAttribute to create an authorization filter rather than a resource filter. [Fixes #6777] RequestSizeLimit is ignored --- .../MvcCoreServiceCollectionExtensions.cs | 6 +- .../DisableRequestSizeLimitAttribute.cs | 21 +++- ...er.cs => DisableRequestSizeLimitFilter.cs} | 20 +--- ...rceFilter.cs => RequestSizeLimitFilter.cs} | 17 +-- .../RequestSizeLimitAttribute.cs | 21 +++- ...s => DisableRequestSizeLimitFilterTest.cs} | 54 +++++---- ...rTest.cs => RequestSizeLimitFilterTest.cs} | 54 +++++---- .../RequestSizeLimitTest.cs | 90 +++++++++++++++ .../Controllers/RequestSizeLimitController.cs | 37 +++++++ .../BasicWebSite/StartupRequestLimitSize.cs | 104 ++++++++++++++++++ 10 files changed, 332 insertions(+), 92 deletions(-) rename src/Microsoft.AspNetCore.Mvc.Core/Internal/{DisableRequestSizeLimitResourceFilter.cs => DisableRequestSizeLimitFilter.cs} (81%) rename src/Microsoft.AspNetCore.Mvc.Core/Internal/{RequestSizeLimitResourceFilter.cs => RequestSizeLimitFilter.cs} (84%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/{DisableRequestSizeLimitResourceFilterTest.cs => DisableRequestSizeLimitFilterTest.cs} (60%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/{RequestSizeLimitResourceFilterTest.cs => RequestSizeLimitFilterTest.cs} (64%) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestSizeLimitTest.cs create mode 100644 test/WebSites/BasicWebSite/Controllers/RequestSizeLimitController.cs create mode 100644 test/WebSites/BasicWebSite/StartupRequestLimitSize.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index c0ab1b47b2..1e38d3c00f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -199,10 +199,10 @@ namespace Microsoft.Extensions.DependencyInjection ServiceDescriptor.Singleton()); // - // Resource Filters + // RequestSizeLimit filters // - services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); // // ModelBinding, Validation diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs index 3b6cbc1318..b92092667e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs @@ -14,8 +14,23 @@ namespace Microsoft.AspNetCore.Mvc [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class DisableRequestSizeLimitAttribute : Attribute, IFilterFactory, IOrderedFilter { - /// - public int Order { get; set; } + /// + /// Gets the order value for determining the order of execution of filters. Filters execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Filters are executed in an ordering determined by an ascending sort of the property. + /// + /// + /// The default Order for this attribute is 900 because it must run before ValidateAntiForgeryTokenAttribute and + /// after any filter which does authentication or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400). + /// + /// + /// Look at for more detailed info. + /// + /// + public int Order { get; set; } = 900; /// public bool IsReusable => true; @@ -23,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc /// public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { - var filter = serviceProvider.GetRequiredService(); + var filter = serviceProvider.GetRequiredService(); return filter; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitResourceFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs similarity index 81% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitResourceFilter.cs rename to src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs index 9781977032..e34679a335 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitResourceFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs @@ -14,34 +14,26 @@ namespace Microsoft.AspNetCore.Mvc.Internal /// A filter that sets /// to null. /// - public class DisableRequestSizeLimitResourceFilter : IResourceFilter, IRequestSizePolicy + public class DisableRequestSizeLimitFilter : IAuthorizationFilter, IRequestSizePolicy { private readonly ILogger _logger; /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public DisableRequestSizeLimitResourceFilter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - /// - public int Order { get; set; } - - /// - public void OnResourceExecuted(ResourceExecutedContext context) + public DisableRequestSizeLimitFilter(ILoggerFactory loggerFactory) { + _logger = loggerFactory.CreateLogger(); } /// /// Sets the /// to null. /// - /// The . + /// The . /// If is not enabled or is read-only, /// the is not applied. - public void OnResourceExecuting(ResourceExecutingContext context) + public void OnAuthorization(AuthorizationFilterContext context) { if (context == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitResourceFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs similarity index 84% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitResourceFilter.cs rename to src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs index 24a0d67dcf..92d8cc40e6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitResourceFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs @@ -14,32 +14,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal /// A filter that sets the /// to the specified . /// - public class RequestSizeLimitResourceFilter : IResourceFilter, IRequestSizePolicy + public class RequestSizeLimitFilter : IAuthorizationFilter, IRequestSizePolicy { private readonly ILogger _logger; /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public RequestSizeLimitResourceFilter(ILoggerFactory loggerFactory) + public RequestSizeLimitFilter(ILoggerFactory loggerFactory) { - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); } public long Bytes { get; set; } - /// - public void OnResourceExecuted(ResourceExecutedContext context) - { - } - /// /// Sets the to . /// - /// The . + /// The . /// If is not enabled or is read-only, /// the is not applied. - public void OnResourceExecuting(ResourceExecutingContext context) + public void OnAuthorization(AuthorizationFilterContext context) { if (context == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs index 2448872606..95063ff857 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs @@ -25,8 +25,23 @@ namespace Microsoft.AspNetCore.Mvc _bytes = bytes; } - /// - public int Order { get; set; } + /// + /// Gets the order value for determining the order of execution of filters. Filters execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Filters are executed in an ordering determined by an ascending sort of the property. + /// + /// + /// The default Order for this attribute is 900 because it must run before ValidateAntiForgeryTokenAttribute and + /// after any filter which does authentication or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400). + /// + /// + /// Look at for more detailed info. + /// + /// + public int Order { get; set; } = 900; /// public bool IsReusable => true; @@ -34,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc /// public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { - var filter = serviceProvider.GetRequiredService(); + var filter = serviceProvider.GetRequiredService(); filter.Bytes = _bytes; return filter; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitResourceFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs similarity index 60% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitResourceFilterTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs index 26bb03eee1..4340460963 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitResourceFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs @@ -1,12 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; @@ -14,20 +12,20 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Internal { - public class DisableRequestSizeLimitResourceFilterTest + public class DisableRequestSizeLimitFilterTest { [Fact] public void SetsMaxRequestBodySizeToNull() { // Arrange - var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitResourceFilter(NullLoggerFactory.Instance); - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); + var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); + var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); - resourceExecutingContext.HttpContext.Features.Set(httpMaxRequestBodySize); + authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); // Act - disableRequestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); + disableRequestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); // Assert Assert.Null(httpMaxRequestBodySize.MaxRequestBodySize); @@ -37,16 +35,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal public void SkipsWhenOverridden() { // Arrange - var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitResourceFilter(NullLoggerFactory.Instance); - var disableRequestSizeLimitResourceFilterFinal = new DisableRequestSizeLimitResourceFilter(NullLoggerFactory.Instance); - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter, disableRequestSizeLimitResourceFilterFinal }); + var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); + var disableRequestSizeLimitResourceFilterFinal = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); + var authorizationFilterContext = CreateauthorizationFilterContext( + new IFilterMetadata[] { disableRequestSizeLimitResourceFilter, disableRequestSizeLimitResourceFilterFinal }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); - resourceExecutingContext.HttpContext.Features.Set(httpMaxRequestBodySize); + authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); // Act - disableRequestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); - disableRequestSizeLimitResourceFilterFinal.OnResourceExecuting(resourceExecutingContext); + disableRequestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); + disableRequestSizeLimitResourceFilterFinal.OnAuthorization(authorizationFilterContext); // Assert Assert.Null(httpMaxRequestBodySize.MaxRequestBodySize); @@ -60,11 +59,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal var sink = new TestSink(); var loggerFactory = new TestLoggerFactory(sink, enabled: true); - var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitResourceFilter(loggerFactory); - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); + var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); + var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); // Act - disableRequestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); + disableRequestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); // Assert var write = Assert.Single(sink.Writes); @@ -79,15 +78,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal var sink = new TestSink(); var loggerFactory = new TestLoggerFactory(sink, enabled: true); - var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitResourceFilter(loggerFactory); - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); + var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); + var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); httpMaxRequestBodySize.IsReadOnly = true; - resourceExecutingContext.HttpContext.Features.Set(httpMaxRequestBodySize); + authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); // Act - disableRequestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); + disableRequestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); // Assert var write = Assert.Single(sink.Writes); @@ -101,26 +100,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal var sink = new TestSink(); var loggerFactory = new TestLoggerFactory(sink, enabled: true); - var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitResourceFilter(loggerFactory); - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); + var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); + var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); - resourceExecutingContext.HttpContext.Features.Set(httpMaxRequestBodySize); + authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); // Act - disableRequestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); + disableRequestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); // Assert var write = Assert.Single(sink.Writes); Assert.Equal($"The request body size limit has been disabled.", write.State.ToString()); } - private static ResourceExecutingContext CreateResourceExecutingContext(IFilterMetadata[] filters) + private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters) { - return new ResourceExecutingContext( - CreateActionContext(), - filters, - new List()); + return new AuthorizationFilterContext(CreateActionContext(), filters); } private static ActionContext CreateActionContext() diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitResourceFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs similarity index 64% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitResourceFilterTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs index 9b05c2780f..82c040508d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitResourceFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs @@ -1,12 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; @@ -14,21 +12,21 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Internal { - public class RequestSizeLimitResourceFilterTest + public class RequestSizeLimitFilterTest { [Fact] public void SetsMaxRequestBodySize() { // Arrange - var requestSizeLimitResourceFilter = new RequestSizeLimitResourceFilter(NullLoggerFactory.Instance); + var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(NullLoggerFactory.Instance); requestSizeLimitResourceFilter.Bytes = 12345; - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); - resourceExecutingContext.HttpContext.Features.Set(httpMaxRequestBodySize); + authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); // Act - requestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); + requestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); // Assert Assert.Equal(12345, httpMaxRequestBodySize.MaxRequestBodySize); @@ -38,18 +36,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal public void SkipsWhenOverridden() { // Arrange - var requestSizeLimitResourceFilter = new RequestSizeLimitResourceFilter(NullLoggerFactory.Instance); + var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(NullLoggerFactory.Instance); requestSizeLimitResourceFilter.Bytes = 12345; - var requestSizeLimitResourceFilterFinal = new RequestSizeLimitResourceFilter(NullLoggerFactory.Instance); + var requestSizeLimitResourceFilterFinal = new RequestSizeLimitFilter(NullLoggerFactory.Instance); requestSizeLimitResourceFilterFinal.Bytes = 0; - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { requestSizeLimitResourceFilter, requestSizeLimitResourceFilterFinal }); + var authorizationFilterContext = CreateauthorizationFilterContext( + new IFilterMetadata[] { requestSizeLimitResourceFilter, requestSizeLimitResourceFilterFinal }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); - resourceExecutingContext.HttpContext.Features.Set(httpMaxRequestBodySize); + authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); // Act - requestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); - requestSizeLimitResourceFilterFinal.OnResourceExecuting(resourceExecutingContext); + requestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); + requestSizeLimitResourceFilterFinal.OnAuthorization(authorizationFilterContext); // Assert Assert.Equal(0, httpMaxRequestBodySize.MaxRequestBodySize); @@ -63,12 +62,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal var sink = new TestSink(); var loggerFactory = new TestLoggerFactory(sink, enabled: true); - var requestSizeLimitResourceFilter = new RequestSizeLimitResourceFilter(loggerFactory); + var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); requestSizeLimitResourceFilter.Bytes = 12345; - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); // Act - requestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); + requestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); // Assert var write = Assert.Single(sink.Writes); @@ -83,16 +82,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal var sink = new TestSink(); var loggerFactory = new TestLoggerFactory(sink, enabled: true); - var requestSizeLimitResourceFilter = new RequestSizeLimitResourceFilter(loggerFactory); + var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); requestSizeLimitResourceFilter.Bytes = 12345; - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); httpMaxRequestBodySize.IsReadOnly = true; - resourceExecutingContext.HttpContext.Features.Set(httpMaxRequestBodySize); + authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); // Act - requestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); + requestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); // Assert var write = Assert.Single(sink.Writes); @@ -106,27 +105,24 @@ namespace Microsoft.AspNetCore.Mvc.Internal var sink = new TestSink(); var loggerFactory = new TestLoggerFactory(sink, enabled: true); - var requestSizeLimitResourceFilter = new RequestSizeLimitResourceFilter(loggerFactory); + var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); requestSizeLimitResourceFilter.Bytes = 12345; - var resourceExecutingContext = CreateResourceExecutingContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); - resourceExecutingContext.HttpContext.Features.Set(httpMaxRequestBodySize); + authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); // Act - requestSizeLimitResourceFilter.OnResourceExecuting(resourceExecutingContext); + requestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); // Assert var write = Assert.Single(sink.Writes); Assert.Equal($"The maximum request body size has been set to 12345.", write.State.ToString()); } - private static ResourceExecutingContext CreateResourceExecutingContext(IFilterMetadata[] filters) + private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters) { - return new ResourceExecutingContext( - CreateActionContext(), - filters, - new List()); + return new AuthorizationFilterContext(CreateActionContext(), filters); } private static ActionContext CreateActionContext() diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestSizeLimitTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestSizeLimitTest.cs new file mode 100644 index 0000000000..7376e77fc4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestSizeLimitTest.cs @@ -0,0 +1,90 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class RequestSizeLimitTest : IClassFixture> + { + // Some tests require comparing the actual response body against an expected response baseline + // so they require a reference to the assembly on which the resources are located, in order to + // make the tests less verbose, we get a reference to the assembly with the resources and we + // use it on all the rest of the tests. + private static readonly Assembly _resourcesAssembly = typeof(BasicTests).GetTypeInfo().Assembly; + + public RequestSizeLimitTest(MvcTestFixture fixture) + { + Client = fixture.Client; + } + + public HttpClient Client { get; } + + [Fact] + public async Task RequestSizeLimitCheckHappens_BeforeAntiforgeryTokenValidation() + { + // Arrange + var request = new HttpRequestMessage(); + var kvps = new List>(); + kvps.Add(new KeyValuePair("SampleString", new string('p', 1024))); + kvps.Add(new KeyValuePair("RequestVerificationToken", "invalid-data")); + + // Act + var response = await Client.PostAsync( + "RequestSizeLimit/RequestSizeLimitCheckBeforeAntiforgeryValidation", + new FormUrlEncodedContent(kvps)); + + // Assert + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Contains( + "InvalidOperationException: Request content size is greater than the limit size", + result); + } + + [Fact] + public async Task AntiforgeryTokenValidationHappens_AfterRequestSizeLimitCheck() + { + // Arrange + var request = new HttpRequestMessage(); + var kvps = new List>(); + kvps.Add(new KeyValuePair("SampleString", "string")); + kvps.Add(new KeyValuePair("RequestVerificationToken", "invalid-data")); + + // Act + var response = await Client.PostAsync( + "RequestSizeLimit/RequestSizeLimitCheckBeforeAntiforgeryValidation", + new FormUrlEncodedContent(kvps)); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task DisableRequestSizeLimitOnAction_OverridesControllerLevelSettings() + { + // Arrange + var expected = $"{{\"sampleInt\":10,\"sampleString\":\"{new string('p', 1024)}\"}}"; + var request = new HttpRequestMessage(); + request.Method = HttpMethod.Post; + request.Content = new StringContent(expected, Encoding.UTF8, "text/json"); + request.RequestUri = new Uri("http://localhost/RequestSizeLimit/DisableRequestSizeLimit"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var actual = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, actual); + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Controllers/RequestSizeLimitController.cs b/test/WebSites/BasicWebSite/Controllers/RequestSizeLimitController.cs new file mode 100644 index 0000000000..f4e8f3c3b7 --- /dev/null +++ b/test/WebSites/BasicWebSite/Controllers/RequestSizeLimitController.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using BasicWebSite.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace BasicWebSite.Controllers +{ + [RequestSizeLimit(500)] + public class RequestSizeLimitController : Controller + { + [HttpPost] + [ValidateAntiForgeryToken] + public IActionResult RequestSizeLimitCheckBeforeAntiforgeryValidation(Product product) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return Json(product); + } + + [HttpPost] + [DisableRequestSizeLimit] + public IActionResult DisableRequestSizeLimit([FromBody] Product product) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + return Json(product); + } + } +} diff --git a/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs b/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs new file mode 100644 index 0000000000..192dc63d80 --- /dev/null +++ b/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; + +namespace BasicWebSite +{ + public class StartupRequestLimitSize + { + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + + app.Use((httpContext, next) => + { + var testHttpMaxRequestBodySizeFeature = new TestHttpMaxRequestBodySizeFeature(); + httpContext.Features.Set( + testHttpMaxRequestBodySizeFeature); + + httpContext.Request.Body = new RequestBodySizeCheckingStream( + httpContext.Request.Body, + testHttpMaxRequestBodySizeFeature); + + return next(); + }); + + app.UseMvcWithDefaultRoute(); + } + + private class RequestBodySizeCheckingStream : Stream + { + private readonly Stream _innerStream; + private readonly IHttpMaxRequestBodySizeFeature _maxRequestBodySizeFeature; + + public RequestBodySizeCheckingStream( + Stream innerStream, + IHttpMaxRequestBodySizeFeature maxRequestBodySizeFeature) + { + _innerStream = innerStream; + _maxRequestBodySizeFeature = maxRequestBodySizeFeature; + } + public override bool CanRead => _innerStream.CanRead; + + public override bool CanSeek => _innerStream.CanSeek; + + public override bool CanWrite => _innerStream.CanWrite; + + public override long Length => _innerStream.Length; + + public override long Position + { + get { return _innerStream.Position; } + set { _innerStream.Position = value; } + } + + public override void Flush() + { + _innerStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (_maxRequestBodySizeFeature.MaxRequestBodySize != null + && _innerStream.Length > _maxRequestBodySizeFeature.MaxRequestBodySize) + { + throw new InvalidOperationException("Request content size is greater than the limit size"); + } + + return _innerStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _innerStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _innerStream.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _innerStream.Write(buffer, offset, count); + } + } + + private class TestHttpMaxRequestBodySizeFeature : IHttpMaxRequestBodySizeFeature + { + public bool IsReadOnly => false; + public long? MaxRequestBodySize { get; set; } + } + } +} +