Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to specify antiforgery request token source #51962

Merged
merged 1 commit into from
Jan 9, 2024
Merged

Allow user to specify antiforgery request token source #51962

merged 1 commit into from
Jan 9, 2024

Conversation

MaceWindu
Copy link
Contributor

Allow user to specify antiforgery request token source

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Description

Currently default antiforgery implementation looks for antiforgery request token in header and when it is not found looks for token in request body (only for form requests).

This PR adds global and endpoint-level option to specify where to look for token. This is especialy useful when you want to prevent default antiforgery from reading request body as it could lead to:

  • performance issues on large requests (forms with file uploads)
  • request body stream cannot be read twice and will result in failure from custom request parser

Fixes #51912

Note that I've added both global and endpoint-level options. If we decide to keep only one, I will remove second.

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Nov 9, 2023
@ghost
Copy link

ghost commented Nov 9, 2023

Thanks for your PR, @MaceWindu. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Nov 9, 2023
@@ -237,6 +329,7 @@ public async Task GetRequestTokens_ReadFormAsyncThrowsIOException_ThrowsAntiforg
var ioException = new IOException();
var httpContext = new Mock<HttpContext>();

httpContext.Setup(r => r.Features).Returns(new FeatureCollection());
Copy link
Contributor Author

@MaceWindu MaceWindu Nov 9, 2023

Choose a reason for hiding this comment

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

Mock context doesn't have Features initialized. It is now needed to endpoint-level source configuration. Some mocked tests in other projects could probably fail for same reason as I run tests only for Antiforgery project

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class RequireAntiforgeryTokenAttribute(bool required = true) : Attribute, IAntiforgeryMetadata
public class RequireAntiforgeryTokenAttribute(bool required = true, AntiforgeryRequestTokenSource? requestTokenSource = null) : Attribute, IAntiforgeryMetadata
{
/// <summary>
/// Gets or sets a value indicating whether the antiforgery token should be validated.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW, it looks like documentation bug to me as there is no property setter to make it "Gets or sets"

@martincostello martincostello added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-mvc-antiforgery and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Nov 9, 2023
@mkArtakMSFT mkArtakMSFT added area-ui-rendering Includes: MVC Views/Pages, Razor Views/Pages and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels Nov 13, 2023
@mkArtakMSFT mkArtakMSFT assigned halter73 and unassigned javiercn Nov 13, 2023
@mkArtakMSFT
Copy link
Member

Thanks for your PR, @MaceWindu.
@halter73 can you please review this? Thanks!

Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

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

Overall the change looks good, but needs more integration tests. It is hard to asses the correctness of the change based on the tests in DefaultAntiforgeryTokenStore and this kind of change warrants some tests here

We need to make sure we cover the following combos:

  • HeaderOrBody (Token present / Token not present) -> (should already be covered by existing tests, point them on the PR if possible).
  • HeaderOnly (Token present / Token not present) -> This is the new behavior, so we need to get a clear understanding that the implementation matches our expectations.
    • We should only check the header during validation, but nothing should change for generation.
  • Body only (Token present / Token not present) -> This should match the current semantics and skip the header altogether.

This also needs to go through API review @halter73.

@MaceWindu
Copy link
Contributor Author

It is hard to asses the correctness of the change based on the tests in DefaultAntiforgeryTokenStore and this kind of change warrants some tests here

Could you provide more details on expected tests? I don't see how AntiforgeryMiddlewareTest related to this change as it tests how AntiforgeryMiddleware interacts with IAntiforgery interface using mocks and don't use any code from this PR. All tests for affected functionality as I can see located in DefaultAntiforgeryTokenStoreTest

HeaderOrBody (Token present / Token not present) -> (should already be covered by existing tests, point them on the PR if possible).

  • with token: GetRequestTokens_HeaderTokenTakensPriority_OverFormToken
  • without: GetRequestTokens_BothHeaderValueAndFormFieldsEmpty_ReturnsNullTokens

HeaderOnly (Token present / Token not present) -> This is the new behavior, so we need to get a clear understanding that the implementation matches our expectations.

  • with token: added GetRequestTokens_FormTokenDisabled_ReturnsHeaderToken
  • without: GetRequestTokens_FormTokenDisabled_ReturnsNullToken

We should only check the header during validation, but nothing should change for generation.

what kind of test you expect here?

Body only (Token present / Token not present) -> This should match the current semantics and skip the header altogether.
added GetRequestTokens_FormTokenDisabled_ReturnsHeaderToken test

  • with token: GetRequestTokens_HeaderTokenDisabled_FallsBackToFormToken, added GetRequestTokens_HeaderTokenDisabled_ReturnsFormToken
  • without: added GetRequestTokens_HeaderTokenDisabled_ReturnsNull

@ghost
Copy link

ghost commented Nov 21, 2023

Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime.
To make sure no conflicting changes have occurred, please rerun validation before merging. You can do this by leaving an /azp run comment here (requires commit rights), or by simply closing and reopening.

@ghost ghost added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Nov 21, 2023
@halter73
Copy link
Member

This also needs to go through API review @halter73.

@MaceWindu do you mind filling out and submitting the API proposal issue template for the new public AntiforgeryRequestTokenSource flags enum and the corresponding RequestTokenSource properties on AntiforgeryOptions and RequireAntiforgeryTokenAttribute? You can look at api-approved labeled issues for more examples.

/// Gets or sets a value indicating the locations to look for the antiforgery token in the request.
/// If not set, <see cref="AntiforgeryOptions.RequestTokenSource"/> value used.
/// </summary>
public AntiforgeryRequestTokenSource RequestTokenSource
Copy link
Member

Choose a reason for hiding this comment

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

Should we just make this nullable so anyone can determine the difference between unconfigured and explicitly HeaderOrFormBody? Then we can go back to just using auto properties.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was initial design, but such types are not supported by metadata

@MaceWindu
Copy link
Contributor Author

@halter73 done #52281

@@ -39,16 +39,19 @@ public async Task<AntiforgeryTokenSet> GetRequestTokensAsync(HttpContext httpCon

var cookieToken = httpContext.Request.Cookies[_options.Cookie.Name!];

// endpoint could override global request token source option
var tokenSource = httpContext.GetEndpoint()?.Metadata.GetMetadata<RequireAntiforgeryTokenAttribute>()?.RequestTokenSourceValue ?? _options.RequestTokenSource;
Copy link
Member

Choose a reason for hiding this comment

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

What if you have something like this?

[RequireAntiforgeryToken(RequestTokenSource = AntiforgeryRequestTokenSource.Header)]
public class MyController
{
    [HttpPost]
    [RequireAntiforgeryToken]
    public IActionResult Post() { ... }
}

Based on this line, it looks like it will resolve the most specific RequireAntiforgeryTokenAttribute which would be the one on the Post() method and see that the RequestTokenSourceValue == null and fall back to _options.RequestTokenSource which is HeaderOrFormBody when it ought to be using Header as specified by the attribute on MyController.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will look into this scenario

@MaceWindu
Copy link
Contributor Author

@halter73, code updated to latest API design

Copy link
Member

@halter73 halter73 left a comment

Choose a reason for hiding this comment

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

Thank you @MaceWindu!

@halter73 halter73 enabled auto-merge (squash) January 8, 2024 23:30
@halter73 halter73 merged commit dca5b04 into dotnet:main Jan 9, 2024
26 checks passed
@ghost ghost added this to the 9.0-preview1 milestone Jan 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-ui-rendering Includes: MVC Views/Pages, Razor Views/Pages community-contribution Indicates that the PR has been added by a community member feature-mvc-antiforgery pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add option to disable form field as CSRF token source from AntiforgeryOptions
5 participants