Skip to content

Commit de43b6a

Browse files
Responding to customer and partner feedback re: the Anti-XSRF helpers.
What's new: - Programmatic configuration over various Anti-XSRF behaviors: -> The name of the cookie to use. -> Whether SSL is required. -> Ability to provide a nonce or other "custom data". - The exception message is now a little less cryptic. It tells you exactly what check failed (e.g. the cookie 'foo' was missing, the token was meant for a different user, etc.). - The system tries to detect if the current identity is degenerate (e.g. authenticated but without a name) and fails safe. The exception message specifies how to resolve the problem. (This check can be suppressed via config if necessary.) - Ability to get the cookie and form token strings directly if you want more manual control. - Built-in support for OpenID and Azure ACS (WIF). - For most consumers, the token size is smaller. Breaks: - The salt / domain / path parameters are all obsolete as error. The customer can achieve the same effect by using the <httpCookies> configuration element or calling the AntiForgery.* APIs that are string-based. - Not compatible with MVC 1 / 2 / 3. However, this system makes it easier to recover gracefully when an old token is submitted. CR: marcind; bradwils SR: naziml
1 parent a257938 commit de43b6a

File tree

60 files changed

+4282
-904
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+4282
-904
lines changed

src/System.Web.Mvc/HtmlHelper.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,44 @@ public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAt
106106
return result;
107107
}
108108

109+
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
109110
public MvcHtmlString AntiForgeryToken()
110111
{
111-
return AntiForgeryToken(salt: null);
112+
return new MvcHtmlString(AntiForgery.GetHtml().ToString());
112113
}
113114

115+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
116+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
117+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryToken", Justification = "API name.")]
118+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "httpCookies", Justification = "API name.")]
119+
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Method is obsolete.")]
120+
[Obsolete("This method is deprecated. Use the AntiForgeryToken() method instead. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.", error: true)]
121+
[EditorBrowsable(EditorBrowsableState.Never)]
114122
public MvcHtmlString AntiForgeryToken(string salt)
115123
{
116-
return AntiForgeryToken(salt, domain: null, path: null);
124+
if (!String.IsNullOrEmpty(salt))
125+
{
126+
throw new NotSupportedException("This method is deprecated. Use the AntiForgeryToken() method instead. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.");
127+
}
128+
129+
return AntiForgeryToken();
117130
}
118131

132+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
133+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
134+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryToken", Justification = "API name.")]
135+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "httpCookies", Justification = "API name.")]
136+
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Method is obsolete.")]
137+
[Obsolete("This method is deprecated. Use the AntiForgeryToken() method instead. To specify a custom domain for the generated cookie, use the <httpCookies> configuration element. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.", error: true)]
138+
[EditorBrowsable(EditorBrowsableState.Never)]
119139
public MvcHtmlString AntiForgeryToken(string salt, string domain, string path)
120140
{
121-
return new MvcHtmlString(AntiForgery.GetHtml(ViewContext.HttpContext, salt, domain, path).ToString());
141+
if (!String.IsNullOrEmpty(salt) || !String.IsNullOrEmpty(domain) || !String.IsNullOrEmpty(path))
142+
{
143+
throw new NotSupportedException("This method is deprecated. Use the AntiForgeryToken() method instead. To specify a custom domain for the generated cookie, use the <httpCookies> configuration element. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.");
144+
}
145+
146+
return AntiForgeryToken();
122147
}
123148

124149
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]

src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Diagnostics;
1+
using System.ComponentModel;
2+
using System.Diagnostics;
3+
using System.Diagnostics.CodeAnalysis;
24
using System.Web.Helpers;
35

46
namespace System.Web.Mvc
@@ -13,19 +15,30 @@ public ValidateAntiForgeryTokenAttribute()
1315
{
1416
}
1517

16-
internal ValidateAntiForgeryTokenAttribute(Action<HttpContextBase, string> validateAction)
18+
internal ValidateAntiForgeryTokenAttribute(Action validateAction)
1719
{
1820
Debug.Assert(validateAction != null);
1921
ValidateAction = validateAction;
2022
}
2123

24+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
25+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
26+
[Obsolete("The 'Salt' property is deprecated. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.", error: true)]
27+
[EditorBrowsable(EditorBrowsableState.Never)]
2228
public string Salt
2329
{
24-
get { return _salt ?? String.Empty; }
25-
set { _salt = value; }
30+
get { return _salt; }
31+
set
32+
{
33+
if (!String.IsNullOrEmpty(value))
34+
{
35+
throw new NotSupportedException("The 'Salt' property is deprecated. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.");
36+
}
37+
_salt = value;
38+
}
2639
}
2740

28-
internal Action<HttpContextBase, string> ValidateAction { get; private set; }
41+
internal Action ValidateAction { get; private set; }
2942

3043
public void OnAuthorization(AuthorizationContext filterContext)
3144
{
@@ -34,7 +47,7 @@ public void OnAuthorization(AuthorizationContext filterContext)
3447
throw new ArgumentNullException("filterContext");
3548
}
3649

37-
ValidateAction(filterContext.HttpContext, Salt);
50+
ValidateAction();
3851
}
3952
}
4053
}

src/System.Web.WebPages/GlobalSuppressions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.WebPages.Html", Justification = "The namespace contains types specific to Razor. It allows a way for MVC Razor host to identify and remove the namespace")]
1414
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Mvc", Justification = "This namespace contains TagBuilder and other types forwarded from System.Web.Mvc. The namespace must stay the way it is for type forwarding to work")]
1515
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.WebPages.Instrumentation", Justification = "This namespace contains Instrumentation types and represents an isolated set of functionality.")]
16+
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "accesscontrolservice", Scope = "resource", Target = "System.Web.WebPages.Resources.WebPageResources.resources", Justification = "This is part of a URL.")]
17+
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "identityprovider", Scope = "resource", Target = "System.Web.WebPages.Resources.WebPageResources.resources", Justification = "This is part of a URL.")]
18+
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "nameidentifier", Scope = "resource", Target = "System.Web.WebPages.Resources.WebPageResources.resources", Justification = "This is part of a URL.")]
19+
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "xmlsoap", Scope = "resource", Target = "System.Web.WebPages.Resources.WebPageResources.resources", Justification = "This is part of a URL.")]
Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,146 @@
1-
using System.Web.WebPages.Resources;
1+
using System.ComponentModel;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Web.Helpers.AntiXsrf;
4+
using System.Web.Mvc;
5+
using System.Web.WebPages.Resources;
26

37
namespace System.Web.Helpers
48
{
9+
/// <summary>
10+
/// Provides access to the anti-forgery system, which provides protection against
11+
/// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
12+
/// </summary>
513
public static class AntiForgery
614
{
715
private static readonly AntiForgeryWorker _worker = new AntiForgeryWorker();
816

17+
/// <summary>
18+
/// Generates an anti-forgery token for this request. This token can
19+
/// be validated by calling the Validate() method.
20+
/// </summary>
21+
/// <returns>An HTML string corresponding to an &lt;input type="hidden"&gt;
22+
/// element. This element should be put inside a &lt;form&gt;.</returns>
23+
/// <remarks>
24+
/// This method has a side effect: it may set a response cookie.
25+
/// </remarks>
926
public static HtmlString GetHtml()
1027
{
1128
if (HttpContext.Current == null)
1229
{
1330
throw new ArgumentException(WebPageResources.HttpContextUnavailable);
1431
}
1532

16-
return GetHtml(new HttpContextWrapper(HttpContext.Current), salt: null, domain: null, path: null);
33+
TagBuilder retVal = _worker.GetFormInputElement(new HttpContextWrapper(HttpContext.Current));
34+
return retVal.ToHtmlString(TagRenderMode.SelfClosing);
1735
}
1836

37+
/// <summary>
38+
/// Generates an anti-forgery token pair (cookie and form token) for this request.
39+
/// This method is similar to GetHtml(), but this method gives the caller control
40+
/// over how to persist the returned values. To validate these tokens, call the
41+
/// appropriate overload of Validate.
42+
/// </summary>
43+
/// <param name="oldCookieToken">The anti-forgery token - if any - that already existed
44+
/// for this request. May be null. The anti-forgery system will try to reuse this cookie
45+
/// value when generating a matching form token.</param>
46+
/// <param name="newCookieToken">Will contain a new cookie value if the old cookie token
47+
/// was null or invalid. If this value is non-null when the method completes, the caller
48+
/// must persist this value in the form of a response cookie, and the existing cookie value
49+
/// should be discarded. If this value is null when the method completes, the existing
50+
/// cookie value was valid and needn't be modified.</param>
51+
/// <param name="formToken">The value that should be stored in the &lt;form&gt;. The caller
52+
/// should take care not to accidentally swap the cookie and form tokens.</param>
53+
/// <remarks>
54+
/// Unlike the GetHtml() method, this method has no side effect. The caller
55+
/// is responsible for setting the response cookie and injecting the returned
56+
/// form token as appropriate.
57+
/// </remarks>
58+
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Method is intended for advanced audiences.")]
59+
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Method is intended for advanced audiences.")]
60+
[EditorBrowsable(EditorBrowsableState.Advanced)]
61+
public static void GetTokens(string oldCookieToken, out string newCookieToken, out string formToken)
62+
{
63+
if (HttpContext.Current == null)
64+
{
65+
throw new ArgumentException(WebPageResources.HttpContextUnavailable);
66+
}
67+
68+
_worker.GetTokens(new HttpContextWrapper(HttpContext.Current), oldCookieToken, out newCookieToken, out formToken);
69+
}
70+
71+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
72+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
73+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "GetHtml", Justification = "API name.")]
74+
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "httpCookies", Justification = "API name.")]
75+
[Obsolete("This method is deprecated. Use the GetHtml() method instead. To specify a custom domain for the generated cookie, use the <httpCookies> configuration element. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.", error: true)]
76+
[EditorBrowsable(EditorBrowsableState.Never)]
1977
public static HtmlString GetHtml(HttpContextBase httpContext, string salt, string domain, string path)
2078
{
2179
if (httpContext == null)
2280
{
2381
throw new ArgumentNullException("httpContext");
2482
}
2583

26-
return _worker.GetHtml(httpContext, salt, domain, path);
84+
if (!String.IsNullOrEmpty(salt) || !String.IsNullOrEmpty(domain) || !String.IsNullOrEmpty(path))
85+
{
86+
throw new NotSupportedException("This method is deprecated. Use the GetHtml() method instead. To specify a custom domain for the generated cookie, use the <httpCookies> configuration element. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.");
87+
}
88+
89+
TagBuilder retVal = _worker.GetFormInputElement(httpContext);
90+
return retVal.ToHtmlString(TagRenderMode.SelfClosing);
2791
}
2892

93+
/// <summary>
94+
/// Validates an anti-forgery token that was supplied for this request.
95+
/// The anti-forgery token may be generated by calling GetHtml().
96+
/// </summary>
97+
/// <remarks>
98+
/// Throws an HttpAntiForgeryException if validation fails.
99+
/// </remarks>
29100
public static void Validate()
30101
{
31102
if (HttpContext.Current == null)
32103
{
33104
throw new ArgumentException(WebPageResources.HttpContextUnavailable);
34105
}
35-
Validate(new HttpContextWrapper(HttpContext.Current), salt: null);
106+
107+
_worker.Validate(new HttpContextWrapper(HttpContext.Current));
36108
}
37109

110+
/// <summary>
111+
/// Validates an anti-forgery token pair that was generated by the GetTokens method.
112+
/// </summary>
113+
/// <param name="cookieToken">The token that was supplied in the request cookie.</param>
114+
/// <param name="formToken">The token that was supplied in the request form body.</param>
115+
/// <remarks>
116+
/// Throws an HttpAntiForgeryException if validation fails.
117+
/// </remarks>
118+
[EditorBrowsable(EditorBrowsableState.Advanced)]
119+
public static void Validate(string cookieToken, string formToken)
120+
{
121+
if (HttpContext.Current == null)
122+
{
123+
throw new ArgumentException(WebPageResources.HttpContextUnavailable);
124+
}
125+
126+
_worker.Validate(new HttpContextWrapper(HttpContext.Current), cookieToken, formToken);
127+
}
128+
129+
[Obsolete("This method is deprecated. Use the Validate() method instead.", error: true)]
130+
[EditorBrowsable(EditorBrowsableState.Never)]
38131
public static void Validate(HttpContextBase httpContext, string salt)
39132
{
40133
if (httpContext == null)
41134
{
42135
throw new ArgumentNullException("httpContext");
43136
}
44137

45-
_worker.Validate(httpContext, salt);
138+
if (!String.IsNullOrEmpty(salt))
139+
{
140+
throw new NotSupportedException("This method is deprecated. Use the Validate() method instead.");
141+
}
142+
143+
_worker.Validate(httpContext);
46144
}
47145
}
48146
}

0 commit comments

Comments
 (0)