Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Add RequestSizeLimitAttribute #6453

Merged
merged 13 commits into from
Jun 29, 2017
Merged

Add RequestSizeLimitAttribute #6453

merged 13 commits into from
Jun 29, 2017

Conversation

jbagga
Copy link
Contributor

@jbagga jbagga commented Jun 27, 2017

Addresses #6352

cc @Tratcher @rynowak

/// </summary>
/// <param name="bytes">The maximum request body size accepted by the application.</param>
/// <param name="disabled">Indicates whether this attribute is disabled.</param>
public RequestSizeLimitAttribute(long bytes, bool disabled)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This always requires both parameters, correct? I don't think you ever want to set both parameters for this attribute. Get rid of the constructor and only use Property setters?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. [RequestSizeLimit(Bytes=10)], [RequestSizeLimit(Disabled=true)]. Not sure what happens for [RequestSizeLimit] though, I assume it would default to 0, false, which would be weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the limit is not specified, [RequestSizeLimit], is it reasonable to set Disabled to true?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what does Disabled do?

This sounds like a totally different attribute

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rynowak We discussed it here but now we just need one parameter so I think ctor can be used. @Tratcher?


if (Disabled == false)
{
var requestLength = context.HttpContext.Request.Body.Length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// the request body size is greater than the specified limit.
/// </summary>
/// <param name="context">The <see cref="ResourceExecutingContext"/>.</param>
public void OnResourceExecuting(ResourceExecutingContext context)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should support scoping, meaning that the filter closest to the action is the one to act.

Look at:
https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IAntiforgeryPolicy.cs
https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs

The general pattern here is:

  1. Define a policy interface (IRequestSizePolicy)
  2. Define filter attributes that implement that policy interface (RequestSizeLimitAttribute, DisableRequestSizeLimitAttribute).
  3. Each of these filter attributes checks if it's the last IRequestSizePolicy in context.Filters and skip itself if it isn't.

Example: https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs#L61

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be two different classes, one for the filter and one for the attribute like your example? https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs.

I was following the pattern of ConsumesAttribute which implements Attribute and IResourceFilter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be 3.

The two attributes, and then the policy interface.

When we have a separation between filter and attribute - it's usually because DI is involved

@@ -554,5 +554,53 @@ public FiltersTest(MvcTestFixture<FiltersWebSite.Startup> fixture)
$"CurrentCulture:{expected},CurrentUICulture:{expected}",
await response.Content.ReadAsStringAsync());
}

[Fact]
public async Task RequestSizeLimit_ThrowsForInvalidSize()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make a new class for these tests and don't put them in FiltersWebSite. That thing is a mess and we plan to just nuke it from orbit some day.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all we do is set a feature, do we need functional tests for this? Wouldn't unit tests suffice?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually might be the case that we have to do unit tests. The test server may not implement this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I tried and was unable to figure out how to initialize the feature for the functional tests. Updating the PR with just unit tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have tests that use kestrel/httpsys? or just TestServer?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only use testserver

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to find a way to test this with a real server.

@jbagga
Copy link
Contributor Author

jbagga commented Jun 28, 2017

@Tratcher @rynowak @pranavkm Updated

public bool IsReusable => true;

/// <inheritdoc />
public int Order { get; set; } = 1000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems high. This must apply before model binding.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resource filters are always before model binding.

@jbagga - leave this at 0 (unset) unless you have a good reason to do otherwise.

if (IsClosestRequestSizePolicy(context.Filters))
{
var maxRequestBodySizeFeature = context.HttpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
maxRequestBodySizeFeature.MaxRequestBodySize = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check for null feature

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to check IsReadOnly and not try to change the value. Possibly log that the setting could not be applied. This would happen if the request body had already started reading, such as being buffered.
It would also make sense to log if the feature was missing and the attribute couldn't be applied.


private bool IsClosestRequestSizePolicy(IList<IFilterMetadata> filters)
{
// Determine if this instance is the 'effective' antiforgery policy.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

antiforgery?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah update this 👍

using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Microsoft.AspNetCore.Mvc.Core
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Microsoft.AspNetCore.Mvc

The general rule of thumb is that anything I user will have to type in a controller belongs in Microsoft.AspNetCore.Mvc

public class DisableRequestSizeLimitAttribute : Attribute, IRequestSizePolicy, IOrderedFilter, IResourceFilter
{
/// <inheritdoc />
public bool IsReusable => true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this, it's not needed


namespace Microsoft.AspNetCore.Mvc.Core
{
public class DisableRequestSizeLimitAttribute : Attribute, IRequestSizePolicy, IOrderedFilter, IResourceFilter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs

/// <summary>
/// A marker interface for filters which define a policy for maximum size for the request body.
/// </summary>
public interface IRequestSizePolicy : IFilterMetadata
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


using Microsoft.AspNetCore.Mvc.Filters;

namespace Microsoft.AspNetCore.Mvc.Core
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be .Mvc as well

using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Microsoft.AspNetCore.Mvc.Core
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.Mvc

{
/// <summary>
/// A filter that specifies the request body size limit.
/// </summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might help here to put some links to in the docs to the http feature

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature interfaces aren't really meant for direct user consumption. putting it in the docs here would be more confusing than the current description.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequestSizeLimitAttribute : Attribute, IRequestSizePolicy, IOrderedFilter, IResourceFilter
{
private long _bytes;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readonly

}

/// <inheritdoc />
public bool IsReusable => true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

public bool IsReusable => true;

/// <inheritdoc />
public int Order { get; set; } = 1000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set to 0

if (IsClosestRequestSizePolicy(context.Filters))
{
var maxRequestBodySizeFeature = context.HttpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
maxRequestBodySizeFeature.MaxRequestBodySize = _bytes;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments apply here, we need to check for null.

@Tratcher - any ideas on what a reasonable behavior is when a server doesn't support this feature?

I could see throwing, but the problem will be that we're going to encourage users to use things like TestServer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to document that this will no op when the server doesn't support it

@@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.Linq;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😻

using Microsoft.AspNetCore.Routing;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.Core
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the namespace of these tests as well


namespace Microsoft.AspNetCore.Mvc.Core
{
public class RequestSizeLimitAttributeTests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test not Tests ( yeah, I went there. 😲

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Core;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LEL?

Copy link
Member

@rynowak rynowak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design of this is looking overall right. 👍

throw new ArgumentNullException(nameof(context));
}

if (IsClosestRequestSizePolicy(context.Filters))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pretty inexpensive to set IHttpMaxRequestBodySizeFeature.MaxRequestBodySize on both of our servers. Wouldn't it be simpler to just greedily set the property instead of checking IsClosestRequestSizePolicy each time?

This is assuming that the "closest" filter has its OnResourceExecuting method called last which I'm unsure about.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They use this pattern elsewhere. it's better not to run the same functionality twice, especially if it starts logging.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to just run a single policy. These things tend to sprout more features over time so the clean semantics are important


/// <summary>
/// As an <see cref="IResourceFilter"/>, this filter looks at the request and rejects it before going ahead if
/// the request body size is greater than the specified limit.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: there is no specified limit in this case.

@halter73 halter73 dismissed their stale review June 28, 2017 18:03

Don't worry about my IsClosestRequestSizePolicy comment

}

[Fact]
public void LogsFeatureNotFound()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't normally written unit tests for the logging when it's simple like this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I remove all logging unit tests?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've already written them, what's the harm in keeping them? Unless they need to churn do to other API changes.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rynowak is being stingy with 😺 and this is the money stop‼️

@jbagga jbagga force-pushed the jbagga/RequestSizeLimit6352 branch from 0340a6c to e56b4e0 Compare June 29, 2017 15:45
@jbagga
Copy link
Contributor Author

jbagga commented Jun 29, 2017

@rynowak @dougbu Updated

/// <summary>
/// The request body size limit.
/// </summary>
public long Bytes { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did y'all discuss making this a constructor parameter vs property earlier? This seems odd to me as a property because [RequestSizeLimit] will set it to 0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, now that there's a single parameter then making it part of the constructor would be fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK please make that change. Other than that this looks good to me.

Have you tested this out manually (given that we can't functional test it)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I did some sandbox testing. Updated the PR with the constructor

@jbagga
Copy link
Contributor Author

jbagga commented Jun 29, 2017

@rynowak @dougbu Updated

Copy link
Member

@dougbu dougbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll just look at added documentation next time through

}

[Fact]
public void DisableRequestSizeLimitAttribute_LogsMaxRequestBodySizeSetToNull()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has nothing to do w/ DisableRequestSizeLimitAttribute; just call this LogsMaxRequestBodySizeSetToNull

}

[Fact]
public void RequestSizeLimitAttribute_LogsMaxRequestBodySizeSet()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has nothing to do w/ RequestSizeLimitAttribute; just call this LogsMaxRequestBodySizeSet

{
private readonly long _bytes;

public RequestSizeLimitAttribute(long bytes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ doc comments (e.g. parameter description similar to what you had for the Bytes property)

namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Sets the <see cref="IHttpMaxRequestBodySizeFeature.MaxRequestBodySize"/> to <c>null</c>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't seen @Tratcher's comment on the feature when I made my earlier common on the inconsistent <see/> versus descriptive text. Best to consistently describe what's going on here in the public classes. But, leave what you've got in the .Internal classes alone.

The feature interfaces aren't really meant for direct user consumption. putting it in the docs here would be more confusing than the current description.

@jbagga
Copy link
Contributor Author

jbagga commented Jun 29, 2017

@dougbu @rynowak Updated

@rynowak
Copy link
Member

rynowak commented Jun 29, 2017

Looks great!

namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Sets the request body size limit to <c>null</c>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weren't you going to change this to say it disabled the size limit?

@jbagga jbagga force-pushed the jbagga/RequestSizeLimit6352 branch from 2225bcd to d977ad4 Compare June 29, 2017 19:38
@jbagga jbagga merged commit 17f6b17 into dev Jun 29, 2017
@jbagga jbagga deleted the jbagga/RequestSizeLimit6352 branch June 29, 2017 20:04
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants