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

Commit

Permalink
Always render antiforgery tokens if !CanRenderAtEndOfForm
Browse files Browse the repository at this point in the history
- #5005
- also add `FormContext` doc comments
  • Loading branch information
dougbu committed Jul 18, 2016
1 parent e5cb6f9 commit 1184d73
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,19 @@ public virtual IHtmlContent GenerateAntiforgery(ViewContext viewContext)
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@

namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
/// <summary>
/// Collection of 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;
private Dictionary<string, object> _formData;
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
Expand All @@ -30,12 +37,36 @@ public IDictionary<string, object> FormData
}
}

/// <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
Expand All @@ -49,8 +80,23 @@ public IList<IHtmlContent> EndOfFormContent
}
}

/// <summary>
/// Gets or sets an indication the current &lt;form&gt; is associated with a tag helper or will be generated by
/// an HTML helper. In particular, <see cref="EndOfFormContent"/> will be rendered just prior to the next
/// &lt;/form&gt; end tag.
/// </summary>
/// <value>
/// <c>true</c> if the framework will render <see cref="EndOfFormContent"/>; <c>false</c> otherwise.
/// </value>
/// <remarks>
/// <c>true</c> for all framework-created <see cref="FormContext"/> instances except the default.
/// </remarks>
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
Expand All @@ -64,6 +110,14 @@ private Dictionary<string, bool> RenderedFields
}
}

/// <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)
Expand All @@ -77,6 +131,12 @@ public bool RenderedField(string fieldName)
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ public void GenerateAntiforgery_GeneratesAntiforgeryFieldsOnlyIfRequired(
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
Expand All @@ -651,6 +652,26 @@ public void GenerateAntiforgery_GeneratesAntiforgeryFieldsOnlyIfRequired(
Assert.Equal(expectedAntiforgeryHtmlField, antiforgeryField);
}

[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)
{
Expand Down

0 comments on commit 1184d73

Please sign in to comment.