From 8a6e99c7c0efc3f88ae8fd8a2ce6255fb57c72f1 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Mon, 18 Jul 2016 16:25:13 -0700 Subject: [PATCH] Always render antiforgery tokens if `!CanRenderAtEndOfForm` - #5005 - also add `FormContext` doc comments --- .../ViewFeatures/DefaultHtmlGenerator.cs | 18 +++--- .../ViewFeatures/FormContext.cs | 61 ++++++++++++++++++- .../ViewFeatures/DefaultHtmlGeneratorTest.cs | 25 ++++++++ 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs index f603b2a61f..b63e755699 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs @@ -156,15 +156,19 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures throw new ArgumentNullException(nameof(viewContext)); } - // If we're inside a BeginForm/BeginRouteForm, the antiforgery token might have already been - // created and appended to the 'end form' content OR the form tag helper might have already generated - // an antiforgery token. - if (viewContext.FormContext.HasAntiforgeryToken) + var formContext = viewContext.FormContext; + if (formContext.CanRenderAtEndOfForm) { - return HtmlString.Empty; - } + // Inside a BeginForm/BeginRouteForm or a
tag helper. So, the antiforgery token might have + // already been created and appended to the 'end form' content (the AntiForgeryToken HTML helper does + // this) OR the tag helper might have already generated an antiforgery token. + if (formContext.HasAntiforgeryToken) + { + return HtmlString.Empty; + } - viewContext.FormContext.HasAntiforgeryToken = true; + formContext.HasAntiforgeryToken = true; + } return _antiforgery.GetHtml(viewContext.HttpContext); } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/FormContext.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/FormContext.cs index 2a7460f6fb..4245044e29 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/FormContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/FormContext.cs @@ -7,6 +7,13 @@ using Microsoft.AspNetCore.Html; namespace Microsoft.AspNetCore.Mvc.ViewFeatures { + /// + /// Information about the current <form>. + /// + /// + /// Literal <form> elements in a view will share the default instance unless tag + /// helpers are enabled. + /// public class FormContext { private Dictionary _renderedFields; @@ -14,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures private IList _endOfFormContent; /// - /// Property bag for any information you wish to associate with a <form/> in an + /// Gets a property bag for any information you wish to associate with a <form/> in an /// implementation or extension method. /// public IDictionary FormData @@ -30,12 +37,36 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } } + /// + /// Gets or sets an indication the current <form> element contains an antiforgery token. Do not use + /// unless is true. + /// + /// + /// true if the current <form> element contains an antiforgery token; false otherwise. + /// public bool HasAntiforgeryToken { get; set; } + /// + /// Gets an indication the property bag has been used and likely contains entries. + /// + /// + /// true if the backing field for is non-null; false otherwise. + /// public bool HasFormData => _formData != null; + /// + /// Gets an indication the collection has been used and likely contains entries. + /// + /// + /// true if the backing field for is non-null; false + /// otherwise. + /// public bool HasEndOfFormContent => _endOfFormContent != null; + /// + /// Gets an collection that should be rendered just prior to the next </form> + /// end tag. Do not use unless is true. + /// public IList EndOfFormContent { get @@ -49,8 +80,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } } + /// + /// Gets or sets an indication whether extra content can be rendered at the end of the content of this + /// <form> element. That is, will be rendered just prior to the + /// </form> end tag. + /// + /// + /// true if the framework will render ; false otherwise. In + /// particular, true if the current <form> is associated with a tag helper or will be generated by + /// an HTML helper; false when using the default instance. + /// public bool CanRenderAtEndOfForm { get; set; } + /// + /// Gets a dictionary mapping full HTML field names to indications that the named field has been rendered in + /// this <form>. + /// private Dictionary RenderedFields { get @@ -64,6 +109,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } } + /// + /// Returns an indication based on that the given has + /// been rendered in this <form>. + /// + /// The full HTML name of a field that may have been rendered. + /// + /// true if the given has been rendered; false otherwise. + /// public bool RenderedField(string fieldName) { if (fieldName == null) @@ -77,6 +130,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures return result; } + /// + /// Updates to indicate has been rendered in this + /// <form>. + /// + /// The full HTML name of a field that may have been rendered. + /// If true, the given has been rendered. public void RenderedField(string fieldName, bool value) { if (fieldName == null) diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs index 933623f246..9469f15d6a 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs @@ -641,6 +641,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var htmlGenerator = GetGenerator(metadataProvider); var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + viewContext.FormContext.CanRenderAtEndOfForm = true; viewContext.FormContext.HasAntiforgeryToken = hasAntiforgeryToken; // Act @@ -651,6 +652,30 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures Assert.Equal(expectedAntiforgeryHtmlField, antiforgeryField); } + // This test covers use of the helper within literal tags when tag helpers are not enabled e.g. + // + // @Html.AntiForgeryToken() + // + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GenerateAntiforgery_AlwaysGeneratesAntiforgeryToken_IfCannotRenderAtEnd(bool hasAntiforgeryToken) + { + // Arrange + var expected = ""; + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var htmlGenerator = GetGenerator(metadataProvider); + var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + viewContext.FormContext.HasAntiforgeryToken = hasAntiforgeryToken; + + // Act + var result = htmlGenerator.GenerateAntiforgery(viewContext); + + // Assert + var antiforgeryField = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default); + Assert.Equal(expected, antiforgeryField); + } + // GetCurrentValues uses only the IModelMetadataProvider passed to the DefaultHtmlGenerator constructor. private static IHtmlGenerator GetGenerator(IModelMetadataProvider metadataProvider) {