Always render antiforgery tokens if `!CanRenderAtEndOfForm`

- #5005
- also add `FormContext` doc comments
This commit is contained in:
Doug Bunting 2016-07-18 16:25:13 -07:00
parent 581a5ea573
commit 8a6e99c7c0
3 changed files with 96 additions and 8 deletions

View File

@ -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 <form> 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 <form> 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);
}

View File

@ -7,6 +7,13 @@ using Microsoft.AspNetCore.Html;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
/// <summary>
/// Information about the current &lt;form&gt;.
/// </summary>
/// <remarks>
/// Literal &lt;form&gt; elements in a view will share the default <see cref="FormContext"/> instance unless tag
/// helpers are enabled.
/// </remarks>
public class FormContext
{
private Dictionary<string, bool> _renderedFields;
@ -14,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
private IList<IHtmlContent> _endOfFormContent;
/// <summary>
/// Property bag for any information you wish to associate with a &lt;form/&gt; in an
/// Gets a property bag for any information you wish to associate with a &lt;form/&gt; in an
/// <see cref="Rendering.IHtmlHelper"/> implementation or extension method.
/// </summary>
public IDictionary<string, object> FormData
@ -30,12 +37,36 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
}
}
/// <summary>
/// Gets or sets an indication the current &lt;form&gt; element contains an antiforgery token. Do not use
/// unless <see cref="CanRenderAtEndOfForm"/> is <c>true</c>.
/// </summary>
/// <value>
/// <c>true</c> if the current &lt;form&gt; element contains an antiforgery token; <c>false</c> otherwise.
/// </value>
public bool HasAntiforgeryToken { get; set; }
/// <summary>
/// Gets an indication the <see cref="FormData"/> property bag has been used and likely contains entries.
/// </summary>
/// <value>
/// <c>true</c> if the backing field for <see cref="FormData"/> is non-<c>null</c>; <c>false</c> otherwise.
/// </value>
public bool HasFormData => _formData != null;
/// <summary>
/// Gets an indication the <see cref="EndOfFormContent"/> collection has been used and likely contains entries.
/// </summary>
/// <value>
/// <c>true</c> if the backing field for <see cref="EndOfFormContent"/> is non-<c>null</c>; <c>false</c>
/// otherwise.
/// </value>
public bool HasEndOfFormContent => _endOfFormContent != null;
/// <summary>
/// Gets an <see cref="IHtmlContent"/> collection that should be rendered just prior to the next &lt;/form&gt;
/// end tag. Do not use unless <see cref="CanRenderAtEndOfForm"/> is <c>true</c>.
/// </summary>
public IList<IHtmlContent> EndOfFormContent
{
get
@ -49,8 +80,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
}
}
/// <summary>
/// Gets or sets an indication whether extra content can be rendered at the end of the content of this
/// &lt;form&gt; element. That is, <see cref="EndOfFormContent"/> will be rendered just prior to the
/// &lt;/form&gt; end tag.
/// </summary>
/// <value>
/// <c>true</c> if the framework will render <see cref="EndOfFormContent"/>; <c>false</c> otherwise. In
/// particular, <c>true</c> if the current &lt;form&gt; is associated with a tag helper or will be generated by
/// an HTML helper; <c>false</c> when using the default <see cref="FormContext"/> instance.
/// </value>
public bool CanRenderAtEndOfForm { get; set; }
/// <summary>
/// Gets a dictionary mapping full HTML field names to indications that the named field has been rendered in
/// this &lt;form&gt;.
/// </summary>
private Dictionary<string, bool> RenderedFields
{
get
@ -64,6 +109,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
}
}
/// <summary>
/// Returns an indication based on <see cref="RenderedFields"/> that the given <paramref name="fieldName"/> has
/// been rendered in this &lt;form&gt;.
/// </summary>
/// <param name="fieldName">The full HTML name of a field that may have been rendered.</param>
/// <returns>
/// <c>true</c> if the given <paramref name="fieldName"/> has been rendered; <c>false</c> otherwise.
/// </returns>
public bool RenderedField(string fieldName)
{
if (fieldName == null)
@ -77,6 +130,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return result;
}
/// <summary>
/// Updates <see cref="RenderedFields"/> to indicate <paramref name="fieldName"/> has been rendered in this
/// &lt;form&gt;.
/// </summary>
/// <param name="fieldName">The full HTML name of a field that may have been rendered.</param>
/// <param name="value">If <c>true</c>, the given <paramref name="fieldName"/> has been rendered.</param>
public void RenderedField(string fieldName, bool value)
{
if (fieldName == null)

View File

@ -641,6 +641,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var htmlGenerator = GetGenerator(metadataProvider);
var viewContext = GetViewContext<Model>(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 <form> tags when tag helpers are not enabled e.g.
// <form action="/Home/Create">
// @Html.AntiForgeryToken()
// </form>
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GenerateAntiforgery_AlwaysGeneratesAntiforgeryToken_IfCannotRenderAtEnd(bool hasAntiforgeryToken)
{
// Arrange
var expected = "<input name=\"formFieldName\" type=\"hidden\" value=\"requestToken\" />";
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var htmlGenerator = GetGenerator(metadataProvider);
var viewContext = GetViewContext<Model>(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)
{