Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/aspnetwebstack.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlevib <levib@microsoft.com>2012-04-07 03:40:59 +0400
committerlevib <levib@microsoft.com>2012-04-07 03:42:35 +0400
commitde43b6ad756800fd3b6e984ec65f9f37dbad723f (patch)
treedcf9b9af761a2ac8924b37084938a1bf79807883
parenta257938cd04948862e4af29f44aa45ffaea86592 (diff)
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
-rw-r--r--src/System.Web.Mvc/HtmlHelper.cs31
-rw-r--r--src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs25
-rw-r--r--src/System.Web.WebPages/GlobalSuppressions.cs4
-rw-r--r--src/System.Web.WebPages/Helpers/AntiForgery.cs108
-rw-r--r--src/System.Web.WebPages/Helpers/AntiForgeryConfig.cs124
-rw-r--r--src/System.Web.WebPages/Helpers/AntiForgeryData.cs117
-rw-r--r--src/System.Web.WebPages/Helpers/AntiForgeryDataSerializer.cs109
-rw-r--r--src/System.Web.WebPages/Helpers/AntiForgeryWorker.cs117
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryConfigWrapper.cs38
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryToken.cs58
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenSerializer.cs138
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenStore.cs68
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryWorker.cs179
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/BinaryBlob.cs98
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/ClaimUidExtractor.cs97
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/IAntiForgeryConfig.cs24
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/IAntiForgeryTokenSerializer.cs9
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/IClaimUidExtractor.cs10
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/ICryptoSystem.cs9
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/ITokenStore.cs10
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/ITokenValidator.cs22
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/MachineKeyCryptoSystem.cs97
-rw-r--r--src/System.Web.WebPages/Helpers/AntiXsrf/TokenValidator.cs145
-rw-r--r--src/System.Web.WebPages/Helpers/Claims/Claim.cs69
-rw-r--r--src/System.Web.WebPages/Helpers/Claims/ClaimsIdentity.cs61
-rw-r--r--src/System.Web.WebPages/Helpers/Claims/ClaimsIdentityConverter.cs97
-rw-r--r--src/System.Web.WebPages/Helpers/CryptoUtil.cs83
-rw-r--r--src/System.Web.WebPages/Helpers/IAntiForgeryAdditionalDataProvider.cs34
-rw-r--r--src/System.Web.WebPages/Mvc/HttpAntiForgeryException.cs49
-rw-r--r--src/System.Web.WebPages/Mvc/TagBuilder.cs2
-rw-r--r--src/System.Web.WebPages/Resources/WebPageResources.Designer.cs107
-rw-r--r--src/System.Web.WebPages/Resources/WebPageResources.resx37
-rw-r--r--src/System.Web.WebPages/System.Web.WebPages.csproj24
-rw-r--r--test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs19
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryConfigTest.cs22
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryDataSerializerTest.cs102
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryDataTest.cs168
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs14
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiForgeryWorkerTest.cs237
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenSerializerTest.cs176
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenStoreTest.cs234
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenTest.cs106
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryWorkerTest.cs401
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/BinaryBlobTest.cs127
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/ClaimUidExtractorTest.cs237
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/HexUtil.cs29
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/MachineKeyCryptoSystemTest.cs120
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockAntiForgeryConfig.cs41
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableAntiForgeryTokenSerializer.cs19
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableClaimUidExtractor.cs15
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableCryptoSystem.cs9
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableTokenStore.cs27
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableTokenValidator.cs33
-rw-r--r--test/System.Web.WebPages.Test/Helpers/AntiXsrf/TokenValidatorTest.cs515
-rw-r--r--test/System.Web.WebPages.Test/Helpers/Claims/ClaimTest.cs62
-rw-r--r--test/System.Web.WebPages.Test/Helpers/Claims/ClaimsIdentityConverterTest.cs98
-rw-r--r--test/System.Web.WebPages.Test/Helpers/Claims/ClaimsIdentityTest.cs73
-rw-r--r--test/System.Web.WebPages.Test/Helpers/Claims/MockClaimsIdentity.cs21
-rw-r--r--test/System.Web.WebPages.Test/Helpers/CryptoUtilTest.cs57
-rw-r--r--test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj24
60 files changed, 4282 insertions, 904 deletions
diff --git a/src/System.Web.Mvc/HtmlHelper.cs b/src/System.Web.Mvc/HtmlHelper.cs
index d9a5d2d0..43c483bf 100644
--- a/src/System.Web.Mvc/HtmlHelper.cs
+++ b/src/System.Web.Mvc/HtmlHelper.cs
@@ -106,19 +106,44 @@ namespace System.Web.Mvc
return result;
}
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
public MvcHtmlString AntiForgeryToken()
{
- return AntiForgeryToken(salt: null);
+ return new MvcHtmlString(AntiForgery.GetHtml().ToString());
}
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryToken", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "httpCookies", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Method is obsolete.")]
+ [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)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public MvcHtmlString AntiForgeryToken(string salt)
{
- return AntiForgeryToken(salt, domain: null, path: null);
+ if (!String.IsNullOrEmpty(salt))
+ {
+ 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.");
+ }
+
+ return AntiForgeryToken();
}
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryToken", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "httpCookies", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Method is obsolete.")]
+ [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)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public MvcHtmlString AntiForgeryToken(string salt, string domain, string path)
{
- return new MvcHtmlString(AntiForgery.GetHtml(ViewContext.HttpContext, salt, domain, path).ToString());
+ if (!String.IsNullOrEmpty(salt) || !String.IsNullOrEmpty(domain) || !String.IsNullOrEmpty(path))
+ {
+ 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.");
+ }
+
+ return AntiForgeryToken();
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
diff --git a/src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs b/src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs
index b948a0b0..810344c0 100644
--- a/src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs
+++ b/src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs
@@ -1,4 +1,6 @@
-using System.Diagnostics;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Web.Helpers;
namespace System.Web.Mvc
@@ -13,19 +15,30 @@ namespace System.Web.Mvc
{
}
- internal ValidateAntiForgeryTokenAttribute(Action<HttpContextBase, string> validateAction)
+ internal ValidateAntiForgeryTokenAttribute(Action validateAction)
{
Debug.Assert(validateAction != null);
ValidateAction = validateAction;
}
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
+ [Obsolete("The 'Salt' property is deprecated. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.", error: true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public string Salt
{
- get { return _salt ?? String.Empty; }
- set { _salt = value; }
+ get { return _salt; }
+ set
+ {
+ if (!String.IsNullOrEmpty(value))
+ {
+ throw new NotSupportedException("The 'Salt' property is deprecated. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.");
+ }
+ _salt = value;
+ }
}
- internal Action<HttpContextBase, string> ValidateAction { get; private set; }
+ internal Action ValidateAction { get; private set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
@@ -34,7 +47,7 @@ namespace System.Web.Mvc
throw new ArgumentNullException("filterContext");
}
- ValidateAction(filterContext.HttpContext, Salt);
+ ValidateAction();
}
}
}
diff --git a/src/System.Web.WebPages/GlobalSuppressions.cs b/src/System.Web.WebPages/GlobalSuppressions.cs
index 07747b70..6f1e5832 100644
--- a/src/System.Web.WebPages/GlobalSuppressions.cs
+++ b/src/System.Web.WebPages/GlobalSuppressions.cs
@@ -13,3 +13,7 @@ using System.Diagnostics.CodeAnalysis;
[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")]
[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")]
[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.")]
+[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "accesscontrolservice", Scope = "resource", Target = "System.Web.WebPages.Resources.WebPageResources.resources", Justification = "This is part of a URL.")]
+[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "identityprovider", Scope = "resource", Target = "System.Web.WebPages.Resources.WebPageResources.resources", Justification = "This is part of a URL.")]
+[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "nameidentifier", Scope = "resource", Target = "System.Web.WebPages.Resources.WebPageResources.resources", Justification = "This is part of a URL.")]
+[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "xmlsoap", Scope = "resource", Target = "System.Web.WebPages.Resources.WebPageResources.resources", Justification = "This is part of a URL.")]
diff --git a/src/System.Web.WebPages/Helpers/AntiForgery.cs b/src/System.Web.WebPages/Helpers/AntiForgery.cs
index afd827f9..4bfac015 100644
--- a/src/System.Web.WebPages/Helpers/AntiForgery.cs
+++ b/src/System.Web.WebPages/Helpers/AntiForgery.cs
@@ -1,11 +1,28 @@
-using System.Web.WebPages.Resources;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Helpers.AntiXsrf;
+using System.Web.Mvc;
+using System.Web.WebPages.Resources;
namespace System.Web.Helpers
{
+ /// <summary>
+ /// Provides access to the anti-forgery system, which provides protection against
+ /// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
+ /// </summary>
public static class AntiForgery
{
private static readonly AntiForgeryWorker _worker = new AntiForgeryWorker();
+ /// <summary>
+ /// Generates an anti-forgery token for this request. This token can
+ /// be validated by calling the Validate() method.
+ /// </summary>
+ /// <returns>An HTML string corresponding to an &lt;input type="hidden"&gt;
+ /// element. This element should be put inside a &lt;form&gt;.</returns>
+ /// <remarks>
+ /// This method has a side effect: it may set a response cookie.
+ /// </remarks>
public static HtmlString GetHtml()
{
if (HttpContext.Current == null)
@@ -13,9 +30,50 @@ namespace System.Web.Helpers
throw new ArgumentException(WebPageResources.HttpContextUnavailable);
}
- return GetHtml(new HttpContextWrapper(HttpContext.Current), salt: null, domain: null, path: null);
+ TagBuilder retVal = _worker.GetFormInputElement(new HttpContextWrapper(HttpContext.Current));
+ return retVal.ToHtmlString(TagRenderMode.SelfClosing);
}
+ /// <summary>
+ /// Generates an anti-forgery token pair (cookie and form token) for this request.
+ /// This method is similar to GetHtml(), but this method gives the caller control
+ /// over how to persist the returned values. To validate these tokens, call the
+ /// appropriate overload of Validate.
+ /// </summary>
+ /// <param name="oldCookieToken">The anti-forgery token - if any - that already existed
+ /// for this request. May be null. The anti-forgery system will try to reuse this cookie
+ /// value when generating a matching form token.</param>
+ /// <param name="newCookieToken">Will contain a new cookie value if the old cookie token
+ /// was null or invalid. If this value is non-null when the method completes, the caller
+ /// must persist this value in the form of a response cookie, and the existing cookie value
+ /// should be discarded. If this value is null when the method completes, the existing
+ /// cookie value was valid and needn't be modified.</param>
+ /// <param name="formToken">The value that should be stored in the &lt;form&gt;. The caller
+ /// should take care not to accidentally swap the cookie and form tokens.</param>
+ /// <remarks>
+ /// Unlike the GetHtml() method, this method has no side effect. The caller
+ /// is responsible for setting the response cookie and injecting the returned
+ /// form token as appropriate.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Method is intended for advanced audiences.")]
+ [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Method is intended for advanced audiences.")]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public static void GetTokens(string oldCookieToken, out string newCookieToken, out string formToken)
+ {
+ if (HttpContext.Current == null)
+ {
+ throw new ArgumentException(WebPageResources.HttpContextUnavailable);
+ }
+
+ _worker.GetTokens(new HttpContextWrapper(HttpContext.Current), oldCookieToken, out newCookieToken, out formToken);
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "GetHtml", Justification = "API name.")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "httpCookies", Justification = "API name.")]
+ [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)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static HtmlString GetHtml(HttpContextBase httpContext, string salt, string domain, string path)
{
if (httpContext == null)
@@ -23,18 +81,53 @@ namespace System.Web.Helpers
throw new ArgumentNullException("httpContext");
}
- return _worker.GetHtml(httpContext, salt, domain, path);
+ if (!String.IsNullOrEmpty(salt) || !String.IsNullOrEmpty(domain) || !String.IsNullOrEmpty(path))
+ {
+ 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.");
+ }
+
+ TagBuilder retVal = _worker.GetFormInputElement(httpContext);
+ return retVal.ToHtmlString(TagRenderMode.SelfClosing);
}
+ /// <summary>
+ /// Validates an anti-forgery token that was supplied for this request.
+ /// The anti-forgery token may be generated by calling GetHtml().
+ /// </summary>
+ /// <remarks>
+ /// Throws an HttpAntiForgeryException if validation fails.
+ /// </remarks>
public static void Validate()
{
if (HttpContext.Current == null)
{
throw new ArgumentException(WebPageResources.HttpContextUnavailable);
}
- Validate(new HttpContextWrapper(HttpContext.Current), salt: null);
+
+ _worker.Validate(new HttpContextWrapper(HttpContext.Current));
}
+ /// <summary>
+ /// Validates an anti-forgery token pair that was generated by the GetTokens method.
+ /// </summary>
+ /// <param name="cookieToken">The token that was supplied in the request cookie.</param>
+ /// <param name="formToken">The token that was supplied in the request form body.</param>
+ /// <remarks>
+ /// Throws an HttpAntiForgeryException if validation fails.
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public static void Validate(string cookieToken, string formToken)
+ {
+ if (HttpContext.Current == null)
+ {
+ throw new ArgumentException(WebPageResources.HttpContextUnavailable);
+ }
+
+ _worker.Validate(new HttpContextWrapper(HttpContext.Current), cookieToken, formToken);
+ }
+
+ [Obsolete("This method is deprecated. Use the Validate() method instead.", error: true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void Validate(HttpContextBase httpContext, string salt)
{
if (httpContext == null)
@@ -42,7 +135,12 @@ namespace System.Web.Helpers
throw new ArgumentNullException("httpContext");
}
- _worker.Validate(httpContext, salt);
+ if (!String.IsNullOrEmpty(salt))
+ {
+ throw new NotSupportedException("This method is deprecated. Use the Validate() method instead.");
+ }
+
+ _worker.Validate(httpContext);
}
}
}
diff --git a/src/System.Web.WebPages/Helpers/AntiForgeryConfig.cs b/src/System.Web.WebPages/Helpers/AntiForgeryConfig.cs
new file mode 100644
index 00000000..fcae9bb4
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiForgeryConfig.cs
@@ -0,0 +1,124 @@
+using System.ComponentModel;
+using System.Text;
+
+namespace System.Web.Helpers
+{
+ /// <summary>
+ /// Provides programmatic configuration for the anti-forgery token system.
+ /// </summary>
+ public static class AntiForgeryConfig
+ {
+ internal const string AntiForgeryTokenFieldName = "__RequestVerificationToken";
+
+ private static string _cookieName;
+ private static string _uniqueClaimTypeIdentifier;
+
+ /// <summary>
+ /// Specifies an object that can provide additional data to put into all
+ /// generated tokens and that can validate additional data in incoming
+ /// tokens.
+ /// </summary>
+ public static IAntiForgeryAdditionalDataProvider AdditionalDataProvider
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Specifies the name of the cookie that is used by the anti-forgery
+ /// system.
+ /// </summary>
+ /// <remarks>
+ /// If an explicit name is not provided, the system will automatically
+ /// generate a name.
+ /// </remarks>
+ public static string CookieName
+ {
+ get
+ {
+ if (_cookieName == null)
+ {
+ _cookieName = GetAntiForgeryCookieName();
+ }
+ return _cookieName;
+ }
+ set
+ {
+ _cookieName = value;
+ }
+ }
+
+ /// <summary>
+ /// Specifies whether SSL is required for the anti-forgery system
+ /// to operate. If this setting is 'true' and a non-SSL request
+ /// comes into the system, all anti-forgery APIs will fail.
+ /// </summary>
+ public static bool RequireSsl
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Specifies whether the anti-forgery system should skip checking
+ /// for conditions that might indicate misuse of the system. Please
+ /// use caution when setting this switch, as improper use could open
+ /// security holes in the application.
+ /// </summary>
+ /// <remarks>
+ /// Setting this switch will disable several checks, including:
+ /// - Identity.IsAuthenticated = true without Identity.Name being set
+ /// - special-casing claims-based identities
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static bool SuppressIdentityHeuristicChecks
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// If claims-based authorization is in use, specifies the claim
+ /// type from the identity that is used to uniquely identify the
+ /// user. If this property is set, all claims-based identities
+ /// <em>must</em> return unique values for this claim type.
+ /// </summary>
+ /// <remarks>
+ /// If claims-based authorization is in use and this property has
+ /// not been set, the anti-forgery system will automatically look
+ /// for claim types "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
+ /// and "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider".
+ /// </remarks>
+ public static string UniqueClaimTypeIdentifier
+ {
+ get
+ {
+ return _uniqueClaimTypeIdentifier ?? String.Empty;
+ }
+ set
+ {
+ _uniqueClaimTypeIdentifier = value;
+ }
+ }
+
+ private static string GetAntiForgeryCookieName()
+ {
+ return GetAntiForgeryCookieName(HttpRuntime.AppDomainAppVirtualPath);
+ }
+
+ // If the app path is provided, we're generating a cookie name rather than a field name, and the cookie names should
+ // be unique so that a development server cookie and an IIS cookie - both running on localhost - don't stomp on
+ // each other.
+ internal static string GetAntiForgeryCookieName(string appPath)
+ {
+ if (String.IsNullOrEmpty(appPath) || appPath == "/")
+ {
+ return AntiForgeryTokenFieldName;
+ }
+ else
+ {
+ return AntiForgeryTokenFieldName + "_" + HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(appPath));
+ }
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiForgeryData.cs b/src/System.Web.WebPages/Helpers/AntiForgeryData.cs
deleted file mode 100644
index 5af09b02..00000000
--- a/src/System.Web.WebPages/Helpers/AntiForgeryData.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using System.Security.Cryptography;
-using System.Security.Principal;
-using System.Text;
-
-namespace System.Web.Helpers
-{
- internal sealed class AntiForgeryData
- {
- private const string AntiForgeryTokenFieldName = "__RequestVerificationToken";
-
- private const int TokenLength = 128 / 8;
- private static readonly RNGCryptoServiceProvider _prng = new RNGCryptoServiceProvider();
-
- private DateTime _creationDate = DateTime.UtcNow;
- private string _salt;
- private string _username;
- private string _value;
-
- public AntiForgeryData()
- {
- }
-
- // copy constructor
- public AntiForgeryData(AntiForgeryData token)
- {
- if (token == null)
- {
- throw new ArgumentNullException("token");
- }
-
- CreationDate = token.CreationDate;
- Salt = token.Salt;
- Username = token.Username;
- Value = token.Value;
- }
-
- public DateTime CreationDate
- {
- get { return _creationDate; }
- set { _creationDate = value; }
- }
-
- public string Salt
- {
- get { return _salt ?? String.Empty; }
- set { _salt = value; }
- }
-
- public string Username
- {
- get { return _username ?? String.Empty; }
- set { _username = value; }
- }
-
- public string Value
- {
- get { return _value ?? String.Empty; }
- set { _value = value; }
- }
-
- private static string Base64EncodeForCookieName(string s)
- {
- byte[] rawBytes = Encoding.UTF8.GetBytes(s);
- string base64String = Convert.ToBase64String(rawBytes);
-
- // replace base64-specific characters with characters that are safe for a cookie name
- return base64String.Replace('+', '.').Replace('/', '-').Replace('=', '_');
- }
-
- private static string GenerateRandomTokenString()
- {
- byte[] tokenBytes = new byte[TokenLength];
- _prng.GetBytes(tokenBytes);
-
- string token = Convert.ToBase64String(tokenBytes);
- return token;
- }
-
- // If the app path is provided, we're generating a cookie name rather than a field name, and the cookie names should
- // be unique so that a development server cookie and an IIS cookie - both running on localhost - don't stomp on
- // each other.
- internal static string GetAntiForgeryTokenName(string appPath)
- {
- if (String.IsNullOrEmpty(appPath))
- {
- return AntiForgeryTokenFieldName;
- }
- else
- {
- return AntiForgeryTokenFieldName + "_" + Base64EncodeForCookieName(appPath);
- }
- }
-
- internal static string GetUsername(IPrincipal user)
- {
- if (user != null)
- {
- IIdentity identity = user.Identity;
- if (identity != null && identity.IsAuthenticated)
- {
- return identity.Name;
- }
- }
-
- return String.Empty;
- }
-
- public static AntiForgeryData NewToken()
- {
- string tokenString = GenerateRandomTokenString();
- return new AntiForgeryData()
- {
- Value = tokenString
- };
- }
- }
-}
diff --git a/src/System.Web.WebPages/Helpers/AntiForgeryDataSerializer.cs b/src/System.Web.WebPages/Helpers/AntiForgeryDataSerializer.cs
deleted file mode 100644
index f8df2564..00000000
--- a/src/System.Web.WebPages/Helpers/AntiForgeryDataSerializer.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Text;
-using System.Web.Mvc;
-using System.Web.Security;
-using System.Web.WebPages.Resources;
-using Microsoft.Internal.Web.Utils;
-
-namespace System.Web.Helpers
-{
- internal class AntiForgeryDataSerializer
- {
- // Testing hooks
-
- internal Func<string, byte[]> Decoder =
- (value) => MachineKey.Decode(Base64ToHex(value), MachineKeyProtection.All);
-
- internal Func<byte[], string> Encoder =
- (bytes) => HexToBase64(MachineKey.Encode(bytes, MachineKeyProtection.All).ToUpperInvariant());
-
- [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")]
- public virtual AntiForgeryData Deserialize(string serializedToken)
- {
- if (String.IsNullOrEmpty(serializedToken))
- {
- throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "serializedToken");
- }
-
- try
- {
- using (MemoryStream stream = new MemoryStream(Decoder(serializedToken)))
- {
- using (BinaryReader reader = new BinaryReader(stream))
- {
- return new AntiForgeryData
- {
- Salt = reader.ReadString(),
- Value = reader.ReadString(),
- CreationDate = new DateTime(reader.ReadInt64()),
- Username = reader.ReadString()
- };
- }
- }
- }
- catch
- {
- throw new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_ValidationFailed);
- }
- }
-
- [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")]
- public virtual string Serialize(AntiForgeryData token)
- {
- if (token == null)
- {
- throw new ArgumentNullException("token");
- }
-
- using (MemoryStream stream = new MemoryStream())
- {
- using (BinaryWriter writer = new BinaryWriter(stream))
- {
- writer.Write(token.Salt);
- writer.Write(token.Value);
- writer.Write(token.CreationDate.Ticks);
- writer.Write(token.Username);
-
- return Encoder(stream.ToArray());
- }
- }
- }
-
- // String transformation helpers
-
- private static string Base64ToHex(string base64)
- {
- StringBuilder builder = new StringBuilder(base64.Length * 4);
- foreach (byte b in Convert.FromBase64String(base64))
- {
- builder.Append(HexDigit(b >> 4));
- builder.Append(HexDigit(b & 0x0F));
- }
- string result = builder.ToString();
- return result;
- }
-
- internal static char HexDigit(int value)
- {
- return (char)(value > 9 ? value + '7' : value + '0');
- }
-
- internal static int HexValue(char digit)
- {
- return digit > '9' ? digit - '7' : digit - '0';
- }
-
- private static string HexToBase64(string hex)
- {
- int size = hex.Length / 2;
- byte[] bytes = new byte[size];
- for (int idx = 0; idx < size; idx++)
- {
- bytes[idx] = (byte)((HexValue(hex[idx * 2]) << 4) + HexValue(hex[(idx * 2) + 1]));
- }
- string result = Convert.ToBase64String(bytes);
- return result;
- }
- }
-}
diff --git a/src/System.Web.WebPages/Helpers/AntiForgeryWorker.cs b/src/System.Web.WebPages/Helpers/AntiForgeryWorker.cs
deleted file mode 100644
index 314921b8..00000000
--- a/src/System.Web.WebPages/Helpers/AntiForgeryWorker.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using System.Diagnostics;
-using System.Web.Mvc;
-using System.Web.WebPages.Resources;
-
-namespace System.Web.Helpers
-{
- internal class AntiForgeryWorker
- {
- public AntiForgeryWorker()
- {
- Serializer = new AntiForgeryDataSerializer();
- }
-
- internal AntiForgeryDataSerializer Serializer { get; set; }
-
- private static HttpAntiForgeryException CreateValidationException()
- {
- return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_ValidationFailed);
- }
-
- public HtmlString GetHtml(HttpContextBase httpContext, string salt, string domain, string path)
- {
- Debug.Assert(httpContext != null);
-
- string formValue = GetAntiForgeryTokenAndSetCookie(httpContext, salt, domain, path);
- string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
-
- TagBuilder builder = new TagBuilder("input");
- builder.Attributes["type"] = "hidden";
- builder.Attributes["name"] = fieldName;
- builder.Attributes["value"] = formValue;
- return new HtmlString(builder.ToString(TagRenderMode.SelfClosing));
- }
-
- private string GetAntiForgeryTokenAndSetCookie(HttpContextBase httpContext, string salt, string domain, string path)
- {
- string cookieName = AntiForgeryData.GetAntiForgeryTokenName(httpContext.Request.ApplicationPath);
-
- AntiForgeryData cookieToken = null;
- HttpCookie cookie = httpContext.Request.Cookies[cookieName];
- if (cookie != null)
- {
- try
- {
- cookieToken = Serializer.Deserialize(cookie.Value);
- }
- catch (HttpAntiForgeryException)
- {
- }
- }
-
- if (cookieToken == null)
- {
- cookieToken = AntiForgeryData.NewToken();
- string cookieValue = Serializer.Serialize(cookieToken);
-
- HttpCookie newCookie = new HttpCookie(cookieName, cookieValue) { HttpOnly = true, Domain = domain };
- if (!String.IsNullOrEmpty(path))
- {
- newCookie.Path = path;
- }
- httpContext.Response.Cookies.Set(newCookie);
- }
-
- AntiForgeryData formToken = new AntiForgeryData(cookieToken)
- {
- Salt = salt,
- Username = AntiForgeryData.GetUsername(httpContext.User)
- };
- return Serializer.Serialize(formToken);
- }
-
- public void Validate(HttpContextBase context, string salt)
- {
- Debug.Assert(context != null);
-
- string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
- string cookieName = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);
-
- HttpCookie cookie = context.Request.Cookies[cookieName];
- if (cookie == null || String.IsNullOrEmpty(cookie.Value))
- {
- // error: cookie token is missing
- throw CreateValidationException();
- }
- AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);
-
- string formValue = context.Request.Form[fieldName];
- if (String.IsNullOrEmpty(formValue))
- {
- // error: form token is missing
- throw CreateValidationException();
- }
- AntiForgeryData formToken = Serializer.Deserialize(formValue);
-
- if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal))
- {
- // error: form token does not match cookie token
- throw CreateValidationException();
- }
-
- string currentUsername = AntiForgeryData.GetUsername(context.User);
- if (!String.Equals(formToken.Username, currentUsername, StringComparison.OrdinalIgnoreCase))
- {
- // error: form token is not valid for this user
- // (don't care about cookie token)
- throw CreateValidationException();
- }
-
- if (!String.Equals(salt ?? String.Empty, formToken.Salt, StringComparison.Ordinal))
- {
- // error: custom validation failed
- throw CreateValidationException();
- }
- }
- }
-}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryConfigWrapper.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryConfigWrapper.cs
new file mode 100644
index 00000000..dd7cfedd
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryConfigWrapper.cs
@@ -0,0 +1,38 @@
+namespace System.Web.Helpers.AntiXsrf
+{
+ internal sealed class AntiForgeryConfigWrapper : IAntiForgeryConfig
+ {
+ public IAntiForgeryAdditionalDataProvider AdditionalDataProvider
+ {
+ get
+ {
+ return AntiForgeryConfig.AdditionalDataProvider;
+ }
+ }
+
+ public string CookieName
+ {
+ get { return AntiForgeryConfig.CookieName; }
+ }
+
+ public string FormFieldName
+ {
+ get { return AntiForgeryConfig.AntiForgeryTokenFieldName; }
+ }
+
+ public bool RequireSSL
+ {
+ get { return AntiForgeryConfig.RequireSsl; }
+ }
+
+ public bool SuppressIdentityHeuristicChecks
+ {
+ get { return AntiForgeryConfig.SuppressIdentityHeuristicChecks; }
+ }
+
+ public string UniqueClaimTypeIdentifier
+ {
+ get { return AntiForgeryConfig.UniqueClaimTypeIdentifier; }
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryToken.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryToken.cs
new file mode 100644
index 00000000..dd4b405d
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryToken.cs
@@ -0,0 +1,58 @@
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Represents the security token for the Anti-XSRF system.
+ // The token is a random 128-bit value that correlates the session with the request body.
+ internal sealed class AntiForgeryToken
+ {
+ internal const int SecurityTokenBitLength = 128;
+ internal const int ClaimUidBitLength = 256;
+
+ private string _additionalData;
+ private BinaryBlob _securityToken;
+ private string _username;
+
+ public string AdditionalData
+ {
+ get
+ {
+ return _additionalData ?? String.Empty;
+ }
+ set
+ {
+ _additionalData = value;
+ }
+ }
+
+ public BinaryBlob ClaimUid { get; set; }
+
+ public bool IsSessionToken { get; set; }
+
+ public BinaryBlob SecurityToken
+ {
+ get
+ {
+ if (_securityToken == null)
+ {
+ _securityToken = new BinaryBlob(SecurityTokenBitLength);
+ }
+ return _securityToken;
+ }
+ set
+ {
+ _securityToken = value;
+ }
+ }
+
+ public string Username
+ {
+ get
+ {
+ return _username ?? String.Empty;
+ }
+ set
+ {
+ _username = value;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenSerializer.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenSerializer.cs
new file mode 100644
index 00000000..ba5443a2
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenSerializer.cs
@@ -0,0 +1,138 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.IO;
+using System.Web.Mvc;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ internal sealed class AntiForgeryTokenSerializer : IAntiForgeryTokenSerializer
+ {
+ private const byte TokenVersion = 0x01;
+ private readonly ICryptoSystem _cryptoSystem;
+
+ public AntiForgeryTokenSerializer()
+ : this(new MachineKeyCryptoSystem())
+ {
+ }
+
+ // for unit testing
+ internal AntiForgeryTokenSerializer(ICryptoSystem cryptoSystem)
+ {
+ _cryptoSystem = cryptoSystem;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Failures are homogenized; caller handles appropriately.")]
+ [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is safe for multi-dispose.")]
+ public AntiForgeryToken Deserialize(string serializedToken)
+ {
+ try
+ {
+ using (MemoryStream stream = new MemoryStream(_cryptoSystem.Unprotect(serializedToken)))
+ {
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ AntiForgeryToken token = DeserializeImpl(reader);
+ if (token != null)
+ {
+ return token;
+ }
+ }
+ }
+ }
+ catch
+ {
+ // swallow all exceptions - homogenize error if something went wrong
+ }
+
+ // if we reached this point, something went wrong deserializing
+ throw HttpAntiForgeryException.CreateDeserializationFailedException();
+ }
+
+ /* The serialized format of the anti-XSRF token is as follows:
+ * Version: 1 byte integer
+ * SecurityToken: 16 byte binary blob
+ * IsSessionToken: 1 byte Boolean
+ * [if IsSessionToken = true]
+ * +- IsClaimsBased: 1 byte Boolean
+ * | [if IsClaimsBased = true]
+ * | `- ClaimUid: 32 byte binary blob
+ * | [if IsClaimsBased = false]
+ * | `- Username: UTF-8 string with 7-bit integer length prefix
+ * `- AdditionalData: UTF-8 string with 7-bit integer length prefix
+ */
+ private static AntiForgeryToken DeserializeImpl(BinaryReader reader)
+ {
+ // we can only consume tokens of the same serialized version that we generate
+ byte embeddedVersion = reader.ReadByte();
+ if (embeddedVersion != TokenVersion)
+ {
+ return null;
+ }
+
+ AntiForgeryToken deserializedToken = new AntiForgeryToken();
+ byte[] securityTokenBytes = reader.ReadBytes(AntiForgeryToken.SecurityTokenBitLength / 8);
+ deserializedToken.SecurityToken = new BinaryBlob(AntiForgeryToken.SecurityTokenBitLength, securityTokenBytes);
+ deserializedToken.IsSessionToken = reader.ReadBoolean();
+
+ if (!deserializedToken.IsSessionToken)
+ {
+ bool isClaimsBased = reader.ReadBoolean();
+ if (isClaimsBased)
+ {
+ byte[] claimUidBytes = reader.ReadBytes(AntiForgeryToken.ClaimUidBitLength / 8);
+ deserializedToken.ClaimUid = new BinaryBlob(AntiForgeryToken.ClaimUidBitLength, claimUidBytes);
+ }
+ else
+ {
+ deserializedToken.Username = reader.ReadString();
+ }
+
+ deserializedToken.AdditionalData = reader.ReadString();
+ }
+
+ // if there's still unconsumed data in the stream, fail
+ if (reader.BaseStream.ReadByte() != -1)
+ {
+ return null;
+ }
+
+ // success
+ return deserializedToken;
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is safe for multi-dispose.")]
+ public string Serialize(AntiForgeryToken token)
+ {
+ Contract.Assert(token != null);
+
+ using (MemoryStream stream = new MemoryStream())
+ {
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.Write(TokenVersion);
+ writer.Write(token.SecurityToken.GetData());
+ writer.Write(token.IsSessionToken);
+
+ if (!token.IsSessionToken)
+ {
+ if (token.ClaimUid != null)
+ {
+ writer.Write(true /* isClaimsBased */);
+ writer.Write(token.ClaimUid.GetData());
+ }
+ else
+ {
+ writer.Write(false /* isClaimsBased */);
+ writer.Write(token.Username);
+ }
+
+ writer.Write(token.AdditionalData);
+ }
+
+ writer.Flush();
+ return _cryptoSystem.Protect(stream.ToArray());
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenStore.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenStore.cs
new file mode 100644
index 00000000..6d7f290d
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenStore.cs
@@ -0,0 +1,68 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Web.Mvc;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Saves anti-XSRF tokens split between HttpRequest.Cookies and HttpRequest.Form
+ internal sealed class AntiForgeryTokenStore : ITokenStore
+ {
+ private readonly IAntiForgeryConfig _config;
+ private readonly IAntiForgeryTokenSerializer _serializer;
+
+ public AntiForgeryTokenStore()
+ : this(new AntiForgeryConfigWrapper(), new AntiForgeryTokenSerializer())
+ {
+ }
+
+ // for unit testing
+ internal AntiForgeryTokenStore(IAntiForgeryConfig config, IAntiForgeryTokenSerializer serializer)
+ {
+ _config = config;
+ _serializer = serializer;
+ }
+
+ public AntiForgeryToken GetCookieToken(HttpContextBase httpContext)
+ {
+ HttpCookie cookie = httpContext.Request.Cookies[_config.CookieName];
+ if (cookie == null || String.IsNullOrEmpty(cookie.Value))
+ {
+ // did not exist
+ return null;
+ }
+
+ return _serializer.Deserialize(cookie.Value);
+ }
+
+ public AntiForgeryToken GetFormToken(HttpContextBase httpContext)
+ {
+ string value = httpContext.Request.Form[_config.FormFieldName];
+ if (String.IsNullOrEmpty(value))
+ {
+ // did not exist
+ return null;
+ }
+
+ return _serializer.Deserialize(value);
+ }
+
+ public void SaveCookieToken(HttpContextBase httpContext, AntiForgeryToken token)
+ {
+ string serializedToken = _serializer.Serialize(token);
+ HttpCookie newCookie = new HttpCookie(_config.CookieName, serializedToken)
+ {
+ HttpOnly = true
+ };
+
+ // Note: don't use "newCookie.Secure = _config.RequireSSL;" since the default
+ // value of newCookie.Secure is automatically populated from the <httpCookies>
+ // config element.
+ if (_config.RequireSSL)
+ {
+ newCookie.Secure = true;
+ }
+
+ httpContext.Response.Cookies.Set(newCookie);
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryWorker.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryWorker.cs
new file mode 100644
index 00000000..f5a06c3c
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryWorker.cs
@@ -0,0 +1,179 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Security.Principal;
+using System.Web.Mvc;
+using System.Web.WebPages.Resources;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ internal sealed class AntiForgeryWorker
+ {
+ private readonly IAntiForgeryConfig _config;
+ private readonly IAntiForgeryTokenSerializer _serializer;
+ private readonly ITokenStore _tokenStore;
+ private readonly ITokenValidator _validator;
+
+ public AntiForgeryWorker()
+ : this(new AntiForgeryTokenSerializer(), new AntiForgeryConfigWrapper(), new AntiForgeryTokenStore(), new TokenValidator())
+ {
+ }
+
+ // for unit testing
+ internal AntiForgeryWorker(IAntiForgeryTokenSerializer serializer, IAntiForgeryConfig config, ITokenStore tokenStore, ITokenValidator validator)
+ {
+ _serializer = serializer;
+ _config = config;
+ _tokenStore = tokenStore;
+ _validator = validator;
+ }
+
+ private void CheckSSLConfig(HttpContextBase httpContext)
+ {
+ if (_config.RequireSSL && !httpContext.Request.IsSecureConnection)
+ {
+ throw new InvalidOperationException(WebPageResources.AntiForgeryWorker_RequireSSL);
+ }
+ }
+
+ private AntiForgeryToken DeserializeToken(string serializedToken)
+ {
+ return (!String.IsNullOrEmpty(serializedToken))
+ ? _serializer.Deserialize(serializedToken)
+ : null;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Caller will just regenerate token in case of failure.")]
+ private AntiForgeryToken DeserializeTokenNoThrow(string serializedToken)
+ {
+ try
+ {
+ return DeserializeToken(serializedToken);
+ }
+ catch
+ {
+ // ignore failures since we'll just generate a new token
+ return null;
+ }
+ }
+
+ private static IIdentity ExtractIdentity(HttpContextBase httpContext)
+ {
+ if (httpContext != null)
+ {
+ IPrincipal user = httpContext.User;
+ if (user != null)
+ {
+ return user.Identity;
+ }
+ }
+ return null;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Caller will just regenerate token in case of failure.")]
+ private AntiForgeryToken GetCookieTokenNoThrow(HttpContextBase httpContext)
+ {
+ try
+ {
+ return _tokenStore.GetCookieToken(httpContext);
+ }
+ catch
+ {
+ // ignore failures since we'll just generate a new token
+ return null;
+ }
+ }
+
+ // [ ENTRY POINT ]
+ // Generates an anti-XSRF token pair for the current user. The return
+ // value is the hidden input form element that should be rendered in
+ // the <form>. This method has a side effect: it may set a response
+ // cookie.
+ public TagBuilder GetFormInputElement(HttpContextBase httpContext)
+ {
+ CheckSSLConfig(httpContext);
+
+ AntiForgeryToken oldCookieToken = GetCookieTokenNoThrow(httpContext);
+ AntiForgeryToken newCookieToken, formToken;
+ GetTokens(httpContext, oldCookieToken, out newCookieToken, out formToken);
+
+ if (newCookieToken != null)
+ {
+ // If a new cookie was generated, persist it.
+ _tokenStore.SaveCookieToken(httpContext, newCookieToken);
+ }
+
+ // <input type="hidden" name="__AntiForgeryToken" value="..." />
+ TagBuilder retVal = new TagBuilder("input");
+ retVal.Attributes["type"] = "hidden";
+ retVal.Attributes["name"] = _config.FormFieldName;
+ retVal.Attributes["value"] = _serializer.Serialize(formToken);
+ return retVal;
+ }
+
+ // [ ENTRY POINT ]
+ // Generates a (cookie, form) serialized token pair for the current user.
+ // The caller may specify an existing cookie value if one exists. If the
+ // 'new cookie value' out param is non-null, the caller *must* persist
+ // the new value to cookie storage since the original value was null or
+ // invalid. This method is side-effect free.
+ public void GetTokens(HttpContextBase httpContext, string serializedOldCookieToken, out string serializedNewCookieToken, out string serializedFormToken)
+ {
+ CheckSSLConfig(httpContext);
+
+ AntiForgeryToken oldCookieToken = DeserializeTokenNoThrow(serializedOldCookieToken);
+ AntiForgeryToken newCookieToken, formToken;
+ GetTokens(httpContext, oldCookieToken, out newCookieToken, out formToken);
+
+ serializedNewCookieToken = Serialize(newCookieToken);
+ serializedFormToken = Serialize(formToken);
+ }
+
+ private void GetTokens(HttpContextBase httpContext, AntiForgeryToken oldCookieToken, out AntiForgeryToken newCookieToken, out AntiForgeryToken formToken)
+ {
+ newCookieToken = null;
+ if (!_validator.IsCookieTokenValid(oldCookieToken))
+ {
+ // Need to make sure we're always operating with a good cookie token.
+ oldCookieToken = newCookieToken = _validator.GenerateCookieToken();
+ }
+
+ Contract.Assert(_validator.IsCookieTokenValid(oldCookieToken));
+ formToken = _validator.GenerateFormToken(httpContext, ExtractIdentity(httpContext), oldCookieToken);
+ }
+
+ private string Serialize(AntiForgeryToken token)
+ {
+ return (token != null) ? _serializer.Serialize(token) : null;
+ }
+
+ // [ ENTRY POINT ]
+ // Given an HttpContext, validates that the anti-XSRF tokens contained
+ // in the cookies & form are OK for this request.
+ public void Validate(HttpContextBase httpContext)
+ {
+ CheckSSLConfig(httpContext);
+
+ // Extract cookie & form tokens
+ AntiForgeryToken cookieToken = _tokenStore.GetCookieToken(httpContext);
+ AntiForgeryToken formToken = _tokenStore.GetFormToken(httpContext);
+
+ // Validate
+ _validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), cookieToken, formToken);
+ }
+
+ // [ ENTRY POINT ]
+ // Given the serialized string representations of a cookie & form token,
+ // validates that the pair is OK for this request.
+ public void Validate(HttpContextBase httpContext, string cookieToken, string formToken)
+ {
+ CheckSSLConfig(httpContext);
+
+ // Extract cookie & form tokens
+ AntiForgeryToken deserializedCookieToken = DeserializeToken(cookieToken);
+ AntiForgeryToken deserializedFormToken = DeserializeToken(formToken);
+
+ // Validate
+ _validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), deserializedCookieToken, deserializedFormToken);
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/BinaryBlob.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/BinaryBlob.cs
new file mode 100644
index 00000000..69c08cf9
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/BinaryBlob.cs
@@ -0,0 +1,98 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Represents a binary blob (token) that contains random data.
+ // Useful for binary data inside a serialized stream.
+ [DebuggerDisplay("{DebuggerString}")]
+ internal sealed class BinaryBlob : IEquatable<BinaryBlob>
+ {
+ private static readonly RNGCryptoServiceProvider _prng = new RNGCryptoServiceProvider();
+
+ private readonly byte[] _data;
+
+ // Generates a new token using a specified bit length.
+ public BinaryBlob(int bitLength)
+ : this(bitLength, GenerateNewToken(bitLength))
+ {
+ }
+
+ // Generates a token using an existing binary value.
+ public BinaryBlob(int bitLength, byte[] data)
+ {
+ if (bitLength < 32 || bitLength % 8 != 0)
+ {
+ throw new ArgumentOutOfRangeException("bitLength");
+ }
+ if (data == null || data.Length != bitLength / 8)
+ {
+ throw new ArgumentOutOfRangeException("data");
+ }
+
+ _data = data;
+ }
+
+ public int BitLength
+ {
+ get
+ {
+ return checked(_data.Length * 8);
+ }
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by debugger.")]
+ private string DebuggerString
+ {
+ get
+ {
+ StringBuilder sb = new StringBuilder("0x", 2 + (_data.Length * 2));
+ for (int i = 0; i < _data.Length; i++)
+ {
+ sb.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", _data[i]);
+ }
+ return sb.ToString();
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as BinaryBlob);
+ }
+
+ public bool Equals(BinaryBlob other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ Contract.Assert(this._data.Length == other._data.Length);
+ return CryptoUtil.AreByteArraysEqual(this._data, other._data);
+ }
+
+ public byte[] GetData()
+ {
+ return _data;
+ }
+
+ public override int GetHashCode()
+ {
+ // Since data should contain uniformly-distributed entropy, the
+ // first 32 bits can serve as the hash code.
+ Contract.Assert(_data != null && _data.Length >= (32 / 8));
+ return BitConverter.ToInt32(_data, 0);
+ }
+
+ private static byte[] GenerateNewToken(int bitLength)
+ {
+ byte[] data = new byte[bitLength / 8];
+ _prng.GetBytes(data);
+ return data;
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/ClaimUidExtractor.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/ClaimUidExtractor.cs
new file mode 100644
index 00000000..3c4b207f
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/ClaimUidExtractor.cs
@@ -0,0 +1,97 @@
+using System.Globalization;
+using System.Linq;
+using System.Security.Principal;
+using System.Web.Helpers.Claims;
+using System.Web.WebPages.Resources;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ internal sealed class ClaimUidExtractor : IClaimUidExtractor
+ {
+ internal const string NameIdentifierClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
+ internal const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
+
+ private readonly ClaimsIdentityConverter _claimsIdentityConverter;
+ private readonly IAntiForgeryConfig _config;
+
+ public ClaimUidExtractor()
+ : this(new AntiForgeryConfigWrapper(), ClaimsIdentityConverter.Default)
+ {
+ }
+
+ // for unit testing
+ internal ClaimUidExtractor(IAntiForgeryConfig config, ClaimsIdentityConverter claimsIdentityConverter)
+ {
+ _config = config;
+ _claimsIdentityConverter = claimsIdentityConverter;
+ }
+
+ public BinaryBlob ExtractClaimUid(IIdentity identity)
+ {
+ if (identity == null || !identity.IsAuthenticated || _config.SuppressIdentityHeuristicChecks)
+ {
+ // Skip anonymous users
+ // Skip when claims-based checks are disabled
+ return null;
+ }
+
+ ClaimsIdentity claimsIdentity = _claimsIdentityConverter.TryConvert(identity);
+ if (claimsIdentity == null)
+ {
+ // not a claims-based identity
+ return null;
+ }
+
+ string[] uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsIdentity, _config.UniqueClaimTypeIdentifier);
+ byte[] claimUidBytes = CryptoUtil.ComputeSHA256(uniqueIdentifierParameters);
+ return new BinaryBlob(256, claimUidBytes);
+ }
+
+ internal static string[] GetUniqueIdentifierParameters(ClaimsIdentity claimsIdentity, string uniqueClaimTypeIdentifier)
+ {
+ var claims = claimsIdentity.GetClaims();
+
+ // The application developer might not want to use our default behavior
+ // and instead might want us to use a claim he knows is unique within
+ // the security realm of his application. (Perhaps he has crafted this
+ // claim himself.)
+ if (!String.IsNullOrEmpty(uniqueClaimTypeIdentifier))
+ {
+ Claim matchingClaim = claims.SingleOrDefault(claim => String.Equals(uniqueClaimTypeIdentifier, claim.ClaimType, StringComparison.Ordinal));
+ if (matchingClaim == null || String.IsNullOrEmpty(matchingClaim.Value))
+ {
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, WebPageResources.ClaimUidExtractor_ClaimNotPresent, uniqueClaimTypeIdentifier));
+ }
+
+ return new string[]
+ {
+ uniqueClaimTypeIdentifier,
+ matchingClaim.Value
+ };
+ }
+
+ // By default, we look for 'nameIdentifier' and 'identityProvider' claims.
+ // For a correctly configured ACS consumer, this tuple will uniquely
+ // identify a user of the application. We assume that a well-behaved
+ // identity provider will never assign the same name identifier to multiple
+ // users within its security realm, and we assume that ACS has been
+ // configured so that each identity provider has a unique 'identityProvider'
+ // claim.
+ Claim nameIdentifierClaim = claims.SingleOrDefault(claim => String.Equals(NameIdentifierClaimType, claim.ClaimType, StringComparison.Ordinal));
+ Claim identityProviderClaim = claims.SingleOrDefault(claim => String.Equals(IdentityProviderClaimType, claim.ClaimType, StringComparison.Ordinal));
+ if (nameIdentifierClaim == null || String.IsNullOrEmpty(nameIdentifierClaim.Value)
+ || identityProviderClaim == null || String.IsNullOrEmpty(identityProviderClaim.Value))
+ {
+ throw new InvalidOperationException(WebPageResources.ClaimUidExtractor_DefaultClaimsNotPresent);
+ }
+
+ return new string[]
+ {
+ NameIdentifierClaimType,
+ nameIdentifierClaim.Value,
+ IdentityProviderClaimType,
+ identityProviderClaim.Value
+ };
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/IAntiForgeryConfig.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/IAntiForgeryConfig.cs
new file mode 100644
index 00000000..76a31b0f
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/IAntiForgeryConfig.cs
@@ -0,0 +1,24 @@
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Provides configuration information about the anti-forgery system.
+ internal interface IAntiForgeryConfig
+ {
+ // Provides additional data to go into the tokens.
+ IAntiForgeryAdditionalDataProvider AdditionalDataProvider { get; }
+
+ // Name of the cookie to use.
+ string CookieName { get; }
+
+ // Name of the form field to use.
+ string FormFieldName { get; }
+
+ // Whether SSL is mandatory for this request.
+ bool RequireSSL { get; }
+
+ // Skip ClaimsIdentity & related logic.
+ bool SuppressIdentityHeuristicChecks { get; }
+
+ // ClaimType to use for ClaimsIdentity.
+ string UniqueClaimTypeIdentifier { get; }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/IAntiForgeryTokenSerializer.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/IAntiForgeryTokenSerializer.cs
new file mode 100644
index 00000000..a80c52e3
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/IAntiForgeryTokenSerializer.cs
@@ -0,0 +1,9 @@
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Abstracts out the serialization process for an anti-forgery token
+ internal interface IAntiForgeryTokenSerializer
+ {
+ AntiForgeryToken Deserialize(string serializedToken);
+ string Serialize(AntiForgeryToken token);
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/IClaimUidExtractor.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/IClaimUidExtractor.cs
new file mode 100644
index 00000000..bf54ca13
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/IClaimUidExtractor.cs
@@ -0,0 +1,10 @@
+using System.Security.Principal;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Can extract unique identifers for a claims-based identity
+ internal interface IClaimUidExtractor
+ {
+ BinaryBlob ExtractClaimUid(IIdentity identity);
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/ICryptoSystem.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/ICryptoSystem.cs
new file mode 100644
index 00000000..48ee03f2
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/ICryptoSystem.cs
@@ -0,0 +1,9 @@
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Provides an abstraction around the cryptographic subsystem for the anti-XSRF helpers.
+ internal interface ICryptoSystem
+ {
+ string Protect(byte[] data);
+ byte[] Unprotect(string protectedData);
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/ITokenStore.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/ITokenStore.cs
new file mode 100644
index 00000000..19ac008f
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/ITokenStore.cs
@@ -0,0 +1,10 @@
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Provides an abstraction around how tokens are persisted and retrieved for a request
+ internal interface ITokenStore
+ {
+ AntiForgeryToken GetCookieToken(HttpContextBase httpContext);
+ AntiForgeryToken GetFormToken(HttpContextBase httpContext);
+ void SaveCookieToken(HttpContextBase httpContext, AntiForgeryToken token);
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/ITokenValidator.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/ITokenValidator.cs
new file mode 100644
index 00000000..42110155
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/ITokenValidator.cs
@@ -0,0 +1,22 @@
+using System.Security.Principal;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Provides an abstraction around something that can validate anti-XSRF tokens
+ internal interface ITokenValidator
+ {
+ // Generates a new random cookie token.
+ AntiForgeryToken GenerateCookieToken();
+
+ // Given a cookie token, generates a corresponding form token.
+ // The incoming cookie token must be valid.
+ AntiForgeryToken GenerateFormToken(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken);
+
+ // Determines whether an existing cookie token is valid (well-formed).
+ // If it is not, the caller must call GenerateCookieToken() before calling GenerateFormToken().
+ bool IsCookieTokenValid(AntiForgeryToken cookieToken);
+
+ // Validates a (cookie, form) token pair.
+ void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken, AntiForgeryToken formToken);
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/MachineKeyCryptoSystem.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/MachineKeyCryptoSystem.cs
new file mode 100644
index 00000000..ad1e4bc5
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/MachineKeyCryptoSystem.cs
@@ -0,0 +1,97 @@
+using System.Net;
+using System.Text;
+using System.Web.Security;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ // Interfaces with the System.Web.MachineKey static class
+ internal sealed class MachineKeyCryptoSystem : ICryptoSystem
+ {
+ // This is the magic header that identifies an AntiForgeryToken payload.
+ // It helps differentiate this from other encrypted payloads.
+ private const uint MagicHeader = 0x8587f266;
+
+ private readonly Func<string, MachineKeyProtection, byte[]> _decoder;
+ private readonly Func<byte[], MachineKeyProtection, string> _encoder;
+
+ public MachineKeyCryptoSystem()
+ : this(MachineKey.Encode, MachineKey.Decode)
+ {
+ }
+
+ // for unit testing
+ internal MachineKeyCryptoSystem(Func<byte[], MachineKeyProtection, string> encoder, Func<string, MachineKeyProtection, byte[]> decoder)
+ {
+ _encoder = encoder;
+ _decoder = decoder;
+ }
+
+ public string Protect(byte[] data)
+ {
+ byte[] dataWithHeader = new byte[data.Length + 4];
+ Buffer.BlockCopy(data, 0, dataWithHeader, 4, data.Length);
+ unchecked
+ {
+ dataWithHeader[0] = (byte)(MagicHeader >> 24);
+ dataWithHeader[1] = (byte)(MagicHeader >> 16);
+ dataWithHeader[2] = (byte)(MagicHeader >> 8);
+ dataWithHeader[3] = (byte)(MagicHeader);
+ }
+
+ string hex = _encoder(dataWithHeader, MachineKeyProtection.All);
+ return HexToBase64(hex);
+ }
+
+ public byte[] Unprotect(string protectedData)
+ {
+ string hex = Base64ToHex(protectedData);
+ byte[] dataWithHeader = _decoder(hex, MachineKeyProtection.All);
+
+ if (dataWithHeader == null || dataWithHeader.Length < 4 || (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(dataWithHeader, 0)) != MagicHeader)
+ {
+ // the decoded data is blank or doesn't begin with the magic header
+ return null;
+ }
+
+ byte[] retVal = new byte[dataWithHeader.Length - 4];
+ Buffer.BlockCopy(dataWithHeader, 4, retVal, 0, retVal.Length);
+ return retVal;
+ }
+
+ // String transformation helpers
+
+ internal static string Base64ToHex(string base64)
+ {
+ StringBuilder builder = new StringBuilder((int)(base64.Length * 1.5));
+ foreach (byte b in HttpServerUtility.UrlTokenDecode(base64))
+ {
+ builder.Append(HexDigit(b >> 4));
+ builder.Append(HexDigit(b & 0x0F));
+ }
+ string result = builder.ToString();
+ return result;
+ }
+
+ private static char HexDigit(int value)
+ {
+ return (char)(value > 9 ? value + '7' : value + '0');
+ }
+
+ private static int HexValue(char digit)
+ {
+ return digit > '9' ? digit - '7' : digit - '0';
+ }
+
+ internal static string HexToBase64(string hex)
+ {
+ int size = hex.Length / 2;
+ byte[] bytes = new byte[size];
+ for (int idx = 0; idx < size; idx++)
+ {
+ bytes[idx] = (byte)((HexValue(hex[idx * 2]) << 4) + HexValue(hex[(idx * 2) + 1]));
+ }
+ string result = HttpServerUtility.UrlTokenEncode(bytes);
+ return result;
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/AntiXsrf/TokenValidator.cs b/src/System.Web.WebPages/Helpers/AntiXsrf/TokenValidator.cs
new file mode 100644
index 00000000..67625598
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/AntiXsrf/TokenValidator.cs
@@ -0,0 +1,145 @@
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Security.Principal;
+using System.Web.Mvc;
+using System.Web.WebPages.Resources;
+
+namespace System.Web.Helpers.AntiXsrf
+{
+ internal sealed class TokenValidator : ITokenValidator
+ {
+ private readonly IClaimUidExtractor _claimUidExtractor;
+ private readonly IAntiForgeryConfig _config;
+
+ public TokenValidator()
+ : this(new AntiForgeryConfigWrapper(), new ClaimUidExtractor())
+ {
+ }
+
+ // for unit testing
+ internal TokenValidator(IAntiForgeryConfig config, IClaimUidExtractor claimUidExtractor)
+ {
+ _config = config;
+ _claimUidExtractor = claimUidExtractor;
+ }
+
+ public AntiForgeryToken GenerateCookieToken()
+ {
+ return new AntiForgeryToken()
+ {
+ // SecurityToken will be populated automatically.
+ IsSessionToken = true
+ };
+ }
+
+ public AntiForgeryToken GenerateFormToken(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken)
+ {
+ Contract.Assert(IsCookieTokenValid(cookieToken));
+
+ AntiForgeryToken formToken = new AntiForgeryToken()
+ {
+ SecurityToken = cookieToken.SecurityToken,
+ IsSessionToken = false
+ };
+
+ bool requireAuthenticatedUserHeuristicChecks = false;
+ // populate Username and ClaimUid
+ if (identity != null && identity.IsAuthenticated)
+ {
+ if (!_config.SuppressIdentityHeuristicChecks)
+ {
+ // If the user is authenticated and heuristic checks are not suppressed,
+ // then Username, ClaimUid, or AdditionalData must be set.
+ requireAuthenticatedUserHeuristicChecks = true;
+ }
+
+ formToken.ClaimUid = _claimUidExtractor.ExtractClaimUid(identity);
+ if (formToken.ClaimUid == null)
+ {
+ formToken.Username = identity.Name;
+ }
+ }
+
+ // populate AdditionalData
+ if (_config.AdditionalDataProvider != null)
+ {
+ formToken.AdditionalData = _config.AdditionalDataProvider.GetAdditionalData(httpContext);
+ }
+
+ if (requireAuthenticatedUserHeuristicChecks
+ && String.IsNullOrEmpty(formToken.Username)
+ && formToken.ClaimUid == null
+ && String.IsNullOrEmpty(formToken.AdditionalData))
+ {
+ // Application says user is authenticated, but we have no identifier for the user.
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
+ WebPageResources.TokenValidator_AuthenticatedUserWithoutUsername, identity.GetType()));
+ }
+
+ return formToken;
+ }
+
+ public bool IsCookieTokenValid(AntiForgeryToken cookieToken)
+ {
+ return (cookieToken != null && cookieToken.IsSessionToken);
+ }
+
+ public void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
+ {
+ // Were the tokens even present at all?
+ if (sessionToken == null)
+ {
+ throw HttpAntiForgeryException.CreateCookieMissingException(_config.CookieName);
+ }
+ if (fieldToken == null)
+ {
+ throw HttpAntiForgeryException.CreateFormFieldMissingException(_config.FormFieldName);
+ }
+
+ // Do the tokens have the correct format?
+ if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
+ {
+ throw HttpAntiForgeryException.CreateTokensSwappedException(_config.CookieName, _config.FormFieldName);
+ }
+
+ // Are the security tokens embedded in each incoming token identical?
+ if (!Equals(sessionToken.SecurityToken, fieldToken.SecurityToken))
+ {
+ throw HttpAntiForgeryException.CreateSecurityTokenMismatchException();
+ }
+
+ // Is the incoming token meant for the current user?
+ string currentUsername = String.Empty;
+ BinaryBlob currentClaimUid = null;
+
+ if (identity != null && identity.IsAuthenticated)
+ {
+ currentClaimUid = _claimUidExtractor.ExtractClaimUid(identity);
+ if (currentClaimUid == null)
+ {
+ currentUsername = identity.Name ?? String.Empty;
+ }
+ }
+
+ // OpenID and other similar authentication schemes use URIs for the username.
+ // These should be treated as case-sensitive.
+ bool useCaseSensitiveUsernameComparison = currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
+ || currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
+
+ if (!String.Equals(fieldToken.Username, currentUsername, (useCaseSensitiveUsernameComparison) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
+ {
+ throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, currentUsername);
+ }
+ if (!Equals(fieldToken.ClaimUid, currentClaimUid))
+ {
+ throw HttpAntiForgeryException.CreateClaimUidMismatchException();
+ }
+
+ // Is the AdditionalData valid?
+ if (_config.AdditionalDataProvider != null && !_config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
+ {
+ throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException();
+ }
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/Claims/Claim.cs b/src/System.Web.WebPages/Helpers/Claims/Claim.cs
new file mode 100644
index 00000000..f68b345e
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/Claims/Claim.cs
@@ -0,0 +1,69 @@
+using System.Reflection;
+
+namespace System.Web.Helpers.Claims
+{
+ // Represents a Claim; serves as an abstraction around the WIF SDK and 4.5 Claim types since
+ // we can't compile directly against them.
+ internal sealed class Claim
+ {
+ public Claim(string claimType, string value)
+ {
+ ClaimType = claimType;
+ Value = value;
+ }
+
+ public string ClaimType { get; private set; }
+
+ public string Value { get; private set; }
+
+ // Creates a Claim from a TClaim object (duck typing).
+ //
+ // The TClaim must have the following shape:
+ // class TClaim {
+ // string ClaimType { get; } // or just 'Type'
+ // string Value { get; }
+ // }
+ internal static Claim Create<TClaim>(TClaim claim)
+ {
+ return ClaimFactory<TClaim>.Create(claim);
+ }
+
+ private static class ClaimFactory<TClaim>
+ {
+ private static readonly Func<TClaim, string> _claimTypeGetter = CreateClaimTypeGetter();
+ private static readonly Func<TClaim, string> _valueGetter = CreateValueGetter();
+
+ public static Claim Create(TClaim claim)
+ {
+ return new Claim(_claimTypeGetter(claim), _valueGetter(claim));
+ }
+
+ private static Func<TClaim, string> CreateClaimTypeGetter()
+ {
+ // the claim type might go by one of two different property names
+ return CreateGeneralPropertyGetter("ClaimType") ?? CreateGeneralPropertyGetter("Type");
+ }
+
+ private static Func<TClaim, string> CreateGeneralPropertyGetter(string propertyName)
+ {
+ PropertyInfo propInfo = typeof(TClaim).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance, null, typeof(string), Type.EmptyTypes, null);
+ if (propInfo == null)
+ {
+ return null;
+ }
+
+ MethodInfo propGetter = propInfo.GetGetMethod();
+
+ // For improved perf, instance methods can be treated as static methods by leaving
+ // the 'this' parameter unbound. Virtual dispatch for the property getter will
+ // still take place as expected.
+ return (Func<TClaim, string>)Delegate.CreateDelegate(typeof(Func<TClaim, string>), propGetter);
+ }
+
+ private static Func<TClaim, string> CreateValueGetter()
+ {
+ return CreateGeneralPropertyGetter("Value");
+ }
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/Claims/ClaimsIdentity.cs b/src/System.Web.WebPages/Helpers/Claims/ClaimsIdentity.cs
new file mode 100644
index 00000000..f2171b91
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/Claims/ClaimsIdentity.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Security.Principal;
+
+namespace System.Web.Helpers.Claims
+{
+ // Represents a ClaimsIdentity; serves as an abstraction around the WIF SDK and 4.5
+ // ClaimIdentity types since we can't compile directly against them.
+ internal abstract class ClaimsIdentity
+ {
+ public abstract IEnumerable<Claim> GetClaims();
+
+ // Attempts to convert an IIdentity into a ClaimsIdentity;
+ // returns null if the conversion fails (duck typing).
+ //
+ // The TClaimsIdentity must have the following shape:
+ // class TClaimsIdentity : IIdentity {
+ // TClaimsCollection Claims { get; }
+ // }
+ // where TClaimsCollection is assignable to IEnumerable<TClaim>,
+ // and where TClaim is valid for Claim.Create<TClaim>.
+ internal static ClaimsIdentity TryConvert<TClaimsIdentity, TClaim>(IIdentity identity)
+ where TClaimsIdentity : class, IIdentity
+ {
+ TClaimsIdentity castClaimsIdentity = identity as TClaimsIdentity;
+ return (castClaimsIdentity != null)
+ ? new ClaimsIdentityImpl<TClaimsIdentity, TClaim>(castClaimsIdentity)
+ : null;
+ }
+
+ private sealed class ClaimsIdentityImpl<TClaimsIdentity, TClaim> : ClaimsIdentity
+ where TClaimsIdentity : class, IIdentity
+ {
+ private static readonly Func<TClaimsIdentity, IEnumerable<TClaim>> _claimsGetter = CreateClaimsGetter();
+
+ private readonly TClaimsIdentity _claimsIdentity;
+
+ public ClaimsIdentityImpl(TClaimsIdentity claimsIdentity)
+ {
+ _claimsIdentity = claimsIdentity;
+ }
+
+ private static Func<TClaimsIdentity, IEnumerable<TClaim>> CreateClaimsGetter()
+ {
+ PropertyInfo propInfo = typeof(TClaimsIdentity).GetProperty("Claims", BindingFlags.Public | BindingFlags.Instance);
+ MethodInfo propGetter = propInfo.GetGetMethod();
+
+ // For improved perf, instance methods can be treated as static methods by leaving
+ // the 'this' parameter unbound. Virtual dispatch for the property getter will
+ // still take place as expected.
+ return (Func<TClaimsIdentity, IEnumerable<TClaim>>)Delegate.CreateDelegate(typeof(Func<TClaimsIdentity, IEnumerable<TClaim>>), propGetter);
+ }
+
+ public override IEnumerable<Claim> GetClaims()
+ {
+ return _claimsGetter(_claimsIdentity).Select(Claim.Create);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/Claims/ClaimsIdentityConverter.cs b/src/System.Web.WebPages/Helpers/Claims/ClaimsIdentityConverter.cs
new file mode 100644
index 00000000..b35c4a14
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/Claims/ClaimsIdentityConverter.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using System.Reflection;
+using System.Security.Principal;
+using System.Web.Security;
+
+namespace System.Web.Helpers.Claims
+{
+ // Can convert IIdentity instances into our ClaimsIdentity wrapper.
+ internal sealed class ClaimsIdentityConverter
+ {
+ private static readonly MethodInfo _claimsIdentityTryConvertOpenMethod = typeof(ClaimsIdentity).GetMethod("TryConvert", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
+ private static readonly ClaimsIdentityConverter _default = new ClaimsIdentityConverter(GetDefaultConverters());
+
+ private readonly Func<IIdentity, ClaimsIdentity>[] _converters;
+
+ // Internal for unit testing; nobody should ever be calling this in production.
+ internal ClaimsIdentityConverter(Func<IIdentity, ClaimsIdentity>[] converters)
+ {
+ _converters = converters;
+ }
+
+ // By default, we understand the ClaimsIdentity / Claim types included
+ // with the WIF SDK and FX 4.5.
+ public static ClaimsIdentityConverter Default
+ {
+ get
+ {
+ return _default;
+ }
+ }
+
+ private static bool IsGrandfatheredIdentityType(IIdentity claimsIdentity)
+ {
+ // These specific types might also be claims-based types depending on
+ // the version of the framework we're running, but we don't want to
+ // treat them as claims-based types since we know their Name property
+ // will suffice as a unique identifier within the security realm of the
+ // current application.
+ return claimsIdentity is FormsIdentity
+ || claimsIdentity is WindowsIdentity
+ || claimsIdentity is GenericIdentity;
+ }
+
+ public ClaimsIdentity TryConvert(IIdentity identity)
+ {
+ if (IsGrandfatheredIdentityType(identity))
+ {
+ return null;
+ }
+
+ // loop through all registered converters until one matches
+ for (int i = 0; i < _converters.Length; i++)
+ {
+ ClaimsIdentity retVal = _converters[i](identity);
+ if (retVal != null)
+ {
+ return retVal;
+ }
+ }
+
+ return null;
+ }
+
+ private static void AddToList(IList<Func<IIdentity, ClaimsIdentity>> converters, Type claimsIdentityType, Type claimType)
+ {
+ if (claimsIdentityType != null && claimType != null)
+ {
+ MethodInfo tryConvertClosedMethod = _claimsIdentityTryConvertOpenMethod.MakeGenericMethod(claimsIdentityType, claimType);
+ Func<IIdentity, ClaimsIdentity> converter = (Func<IIdentity, ClaimsIdentity>)Delegate.CreateDelegate(typeof(Func<IIdentity, ClaimsIdentity>), tryConvertClosedMethod);
+ converters.Add(converter);
+ }
+ }
+
+ private static Func<IIdentity, ClaimsIdentity>[] GetDefaultConverters()
+ {
+ List<Func<IIdentity, ClaimsIdentity>> converters = new List<Func<IIdentity, ClaimsIdentity>>();
+
+ // WIF SDK is only available in full trust scenarios
+ if (AppDomain.CurrentDomain.IsHomogenous && AppDomain.CurrentDomain.IsFullyTrusted)
+ {
+ Type claimsIdentityType = Type.GetType("Microsoft.IdentityModel.Claims.IClaimsIdentity, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+ Type claimType = Type.GetType("Microsoft.IdentityModel.Claims.Claim, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+ AddToList(converters, claimsIdentityType, claimType);
+ }
+
+ // 4.5 ClaimsIdentity type
+ {
+ Module mscorlibModule = typeof(object).Module;
+ Type claimsIdentityType = mscorlibModule.GetType("System.Security.Claims.ClaimsIdentity");
+ Type claimType = mscorlibModule.GetType("System.Security.Claims.Claim");
+ AddToList(converters, claimsIdentityType, claimType);
+ }
+
+ return converters.ToArray();
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/CryptoUtil.cs b/src/System.Web.WebPages/Helpers/CryptoUtil.cs
new file mode 100644
index 00000000..9555cced
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/CryptoUtil.cs
@@ -0,0 +1,83 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace System.Web.Helpers
+{
+ internal static class CryptoUtil
+ {
+ private static readonly Func<SHA256> _sha256Factory = GetSHA256Factory();
+
+ // This method is specially written to take the same amount of time
+ // regardless of where 'a' and 'b' differ. Please do not optimize it.
+ public static bool AreByteArraysEqual(byte[] a, byte[] b)
+ {
+ if (a == null || b == null || a.Length != b.Length)
+ {
+ return false;
+ }
+
+ bool areEqual = true;
+ for (int i = 0; i < a.Length; i++)
+ {
+ areEqual &= (a[i] == b[i]);
+ }
+ return areEqual;
+ }
+
+ // Computes a SHA256 hash over all of the input parameters.
+ // Each parameter is UTF8 encoded and preceded by a 7-bit encoded
+ // integer describing the encoded byte length of the string.
+ [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")]
+ public static byte[] ComputeSHA256(IList<string> parameters)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (BinaryWriter bw = new BinaryWriter(ms))
+ {
+ foreach (string parameter in parameters)
+ {
+ bw.Write(parameter); // also writes the length as a prefix; unambiguous
+ }
+ bw.Flush();
+
+ using (SHA256 sha256 = _sha256Factory())
+ {
+ byte[] retVal = sha256.ComputeHash(ms.GetBuffer(), 0, checked((int)ms.Length));
+ return retVal;
+ }
+ }
+ }
+ }
+
+ private static Func<SHA256> GetSHA256Factory()
+ {
+ // Note: ASP.NET 4.5 always prefers CNG, but the CNG algorithms are not that
+ // performant on 4.0 and below. The following list is optimized for speed
+ // given our scenarios.
+
+ if (!CryptoConfig.AllowOnlyFipsAlgorithms)
+ {
+ // This provider is not FIPS-compliant, so we can't use it if FIPS compliance
+ // is mandatory.
+ return () => new SHA256Managed();
+ }
+
+ try
+ {
+ using (SHA256Cng sha256 = new SHA256Cng())
+ {
+ return () => new SHA256Cng();
+ }
+ }
+ catch (PlatformNotSupportedException)
+ {
+ // CNG not supported (perhaps because we're not on Windows Vista or above); move on
+ }
+
+ // If all else fails, fall back to CAPI.
+ return () => new SHA256CryptoServiceProvider();
+ }
+ }
+}
diff --git a/src/System.Web.WebPages/Helpers/IAntiForgeryAdditionalDataProvider.cs b/src/System.Web.WebPages/Helpers/IAntiForgeryAdditionalDataProvider.cs
new file mode 100644
index 00000000..0043fbab
--- /dev/null
+++ b/src/System.Web.WebPages/Helpers/IAntiForgeryAdditionalDataProvider.cs
@@ -0,0 +1,34 @@
+namespace System.Web.Helpers
+{
+ /// <summary>
+ /// Allows providing or validating additional custom data for anti-forgery tokens.
+ /// For example, the developer could use this to supply a nonce when the token is
+ /// generated, then he could validate the nonce when the token is validated.
+ /// </summary>
+ /// <remarks>
+ /// The anti-forgery system already embeds the client's username within the
+ /// generated tokens. This interface provides and consumes <em>supplemental</em>
+ /// data. If an incoming anti-forgery token contains supplemental data but no
+ /// additional data provider is configured, the supplemental data will not be
+ /// validated.
+ /// </remarks>
+ public interface IAntiForgeryAdditionalDataProvider
+ {
+ /// <summary>
+ /// Provides additional data to be stored for the anti-forgery tokens generated
+ /// during this request.
+ /// </summary>
+ /// <param name="context">Information about the current request.</param>
+ /// <returns>Supplemental data to embed within the anti-forgery token.</returns>
+ string GetAdditionalData(HttpContextBase context);
+
+ /// <summary>
+ /// Validates additional data that was embedded inside an incoming anti-forgery
+ /// token.
+ /// </summary>
+ /// <param name="context">Information about the current request.</param>
+ /// <param name="additionalData">Supplemental data that was embedded within the token.</param>
+ /// <returns>True if the data is valid; false if the data is invalid.</returns>
+ bool ValidateAdditionalData(HttpContextBase context, string additionalData);
+ }
+}
diff --git a/src/System.Web.WebPages/Mvc/HttpAntiForgeryException.cs b/src/System.Web.WebPages/Mvc/HttpAntiForgeryException.cs
index 4d987bb4..580fc9d0 100644
--- a/src/System.Web.WebPages/Mvc/HttpAntiForgeryException.cs
+++ b/src/System.Web.WebPages/Mvc/HttpAntiForgeryException.cs
@@ -1,5 +1,7 @@
-using System.Runtime.CompilerServices;
+using System.Globalization;
+using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
+using System.Web.WebPages.Resources;
namespace System.Web.Mvc
{
@@ -21,9 +23,54 @@ namespace System.Web.Mvc
{
}
+ private HttpAntiForgeryException(string message, params object[] args)
+ : this(String.Format(CultureInfo.CurrentCulture, message, args))
+ {
+ }
+
public HttpAntiForgeryException(string message, Exception innerException)
: base(message, innerException)
{
}
+
+ internal static HttpAntiForgeryException CreateAdditionalDataCheckFailedException()
+ {
+ return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_AdditionalDataCheckFailed);
+ }
+
+ internal static HttpAntiForgeryException CreateClaimUidMismatchException()
+ {
+ return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_ClaimUidMismatch);
+ }
+
+ internal static HttpAntiForgeryException CreateCookieMissingException(string cookieName)
+ {
+ return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_CookieMissing, cookieName);
+ }
+
+ internal static HttpAntiForgeryException CreateDeserializationFailedException()
+ {
+ return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_DeserializationFailed);
+ }
+
+ internal static HttpAntiForgeryException CreateFormFieldMissingException(string formFieldName)
+ {
+ return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_FormFieldMissing, formFieldName);
+ }
+
+ internal static HttpAntiForgeryException CreateSecurityTokenMismatchException()
+ {
+ return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_SecurityTokenMismatch);
+ }
+
+ internal static HttpAntiForgeryException CreateTokensSwappedException(string cookieName, string formFieldName)
+ {
+ return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_TokensSwapped, cookieName, formFieldName);
+ }
+
+ internal static HttpAntiForgeryException CreateUsernameMismatchException(string usernameInToken, string currentUsername)
+ {
+ return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_UsernameMismatch, usernameInToken, currentUsername);
+ }
}
}
diff --git a/src/System.Web.WebPages/Mvc/TagBuilder.cs b/src/System.Web.WebPages/Mvc/TagBuilder.cs
index 47bd8c29..8ad0e1d5 100644
--- a/src/System.Web.WebPages/Mvc/TagBuilder.cs
+++ b/src/System.Web.WebPages/Mvc/TagBuilder.cs
@@ -176,7 +176,7 @@ namespace System.Web.Mvc
InnerHtml = HttpUtility.HtmlEncode(innerText);
}
- internal IHtmlString ToHtmlString(TagRenderMode renderMode)
+ internal HtmlString ToHtmlString(TagRenderMode renderMode)
{
return new HtmlString(ToString(renderMode));
}
diff --git a/src/System.Web.WebPages/Resources/WebPageResources.Designer.cs b/src/System.Web.WebPages/Resources/WebPageResources.Designer.cs
index f374f093..e5ba177f 100644
--- a/src/System.Web.WebPages/Resources/WebPageResources.Designer.cs
+++ b/src/System.Web.WebPages/Resources/WebPageResources.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30319.239
+// Runtime Version:4.0.30319.261
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -61,11 +61,83 @@ namespace System.Web.WebPages.Resources {
}
/// <summary>
- /// Looks up a localized string similar to A required anti-forgery token was not supplied or was invalid..
+ /// Looks up a localized string similar to The provided anti-forgery token failed a custom data check..
/// </summary>
- internal static string AntiForgeryToken_ValidationFailed {
+ internal static string AntiForgeryToken_AdditionalDataCheckFailed {
get {
- return ResourceManager.GetString("AntiForgeryToken_ValidationFailed", resourceCulture);
+ return ResourceManager.GetString("AntiForgeryToken_AdditionalDataCheckFailed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The provided anti-forgery token was meant for a different claims-based user than the current user..
+ /// </summary>
+ internal static string AntiForgeryToken_ClaimUidMismatch {
+ get {
+ return ResourceManager.GetString("AntiForgeryToken_ClaimUidMismatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The required anti-forgery cookie &quot;{0}&quot; is not present..
+ /// </summary>
+ internal static string AntiForgeryToken_CookieMissing {
+ get {
+ return ResourceManager.GetString("AntiForgeryToken_CookieMissing", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the &lt;machineKey&gt; configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster..
+ /// </summary>
+ internal static string AntiForgeryToken_DeserializationFailed {
+ get {
+ return ResourceManager.GetString("AntiForgeryToken_DeserializationFailed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The required anti-forgery form field &quot;{0}&quot; is not present..
+ /// </summary>
+ internal static string AntiForgeryToken_FormFieldMissing {
+ get {
+ return ResourceManager.GetString("AntiForgeryToken_FormFieldMissing", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The anti-forgery cookie token and form field token do not match..
+ /// </summary>
+ internal static string AntiForgeryToken_SecurityTokenMismatch {
+ get {
+ return ResourceManager.GetString("AntiForgeryToken_SecurityTokenMismatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Validation of the provided anti-forgery token failed. The cookie &quot;{0}&quot; and the form field &quot;{1}&quot; were swapped..
+ /// </summary>
+ internal static string AntiForgeryToken_TokensSwapped {
+ get {
+ return ResourceManager.GetString("AntiForgeryToken_TokensSwapped", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The provided anti-forgery token was meant for user &quot;{0}&quot;, but the current user is &quot;{1}&quot;..
+ /// </summary>
+ internal static string AntiForgeryToken_UsernameMismatch {
+ get {
+ return ResourceManager.GetString("AntiForgeryToken_UsernameMismatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request..
+ /// </summary>
+ internal static string AntiForgeryWorker_RequireSSL {
+ get {
+ return ResourceManager.GetString("AntiForgeryWorker_RequireSSL", resourceCulture);
}
}
@@ -115,6 +187,24 @@ namespace System.Web.WebPages.Resources {
}
/// <summary>
+ /// Looks up a localized string similar to A claim of type &apos;{0}&apos; was not present on the provided ClaimsIdentity..
+ /// </summary>
+ internal static string ClaimUidExtractor_ClaimNotPresent {
+ get {
+ return ResourceManager.GetString("ClaimUidExtractor_ClaimNotPresent", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A claim of type &apos;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&apos; or &apos;http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider&apos; was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identif [rest of string was truncated]&quot;;.
+ /// </summary>
+ internal static string ClaimUidExtractor_DefaultClaimsNotPresent {
+ get {
+ return ResourceManager.GetString("ClaimUidExtractor_DefaultClaimsNotPresent", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Index length must be exactly one..
/// </summary>
internal static string DynamicDictionary_InvalidNumberOfIndexes {
@@ -214,6 +304,15 @@ namespace System.Web.WebPages.Resources {
}
/// <summary>
+ /// Looks up a localized string similar to The provided identity of type &apos;{0}&apos; is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user..
+ /// </summary>
+ internal static string TokenValidator_AuthenticatedUserWithoutUsername {
+ get {
+ return ResourceManager.GetString("TokenValidator_AuthenticatedUserWithoutUsername", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Validation parameter names in unobtrusive client validation rules cannot be empty. Client rule type: {0}.
/// </summary>
internal static string UnobtrusiveJavascript_ValidationParameterCannotBeEmpty {
diff --git a/src/System.Web.WebPages/Resources/WebPageResources.resx b/src/System.Web.WebPages/Resources/WebPageResources.resx
index e88eb921..958d9c4b 100644
--- a/src/System.Web.WebPages/Resources/WebPageResources.resx
+++ b/src/System.Web.WebPages/Resources/WebPageResources.resx
@@ -117,8 +117,32 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="AntiForgeryToken_ValidationFailed" xml:space="preserve">
- <value>A required anti-forgery token was not supplied or was invalid.</value>
+ <data name="AntiForgeryToken_AdditionalDataCheckFailed" xml:space="preserve">
+ <value>The provided anti-forgery token failed a custom data check.</value>
+ </data>
+ <data name="AntiForgeryToken_ClaimUidMismatch" xml:space="preserve">
+ <value>The provided anti-forgery token was meant for a different claims-based user than the current user.</value>
+ </data>
+ <data name="AntiForgeryToken_CookieMissing" xml:space="preserve">
+ <value>The required anti-forgery cookie "{0}" is not present.</value>
+ </data>
+ <data name="AntiForgeryToken_DeserializationFailed" xml:space="preserve">
+ <value>The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the &lt;machineKey&gt; configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.</value>
+ </data>
+ <data name="AntiForgeryToken_FormFieldMissing" xml:space="preserve">
+ <value>The required anti-forgery form field "{0}" is not present.</value>
+ </data>
+ <data name="AntiForgeryToken_SecurityTokenMismatch" xml:space="preserve">
+ <value>The anti-forgery cookie token and form field token do not match.</value>
+ </data>
+ <data name="AntiForgeryToken_TokensSwapped" xml:space="preserve">
+ <value>Validation of the provided anti-forgery token failed. The cookie "{0}" and the form field "{1}" were swapped.</value>
+ </data>
+ <data name="AntiForgeryToken_UsernameMismatch" xml:space="preserve">
+ <value>The provided anti-forgery token was meant for user "{0}", but the current user is "{1}".</value>
+ </data>
+ <data name="AntiForgeryWorker_RequireSSL" xml:space="preserve">
+ <value>The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.</value>
</data>
<data name="ApplicationPart_ModuleAlreadyRegistered" xml:space="preserve">
<value>The assembly "{0}" is already registered.</value>
@@ -135,6 +159,12 @@
<data name="ApplicationPart_ResourceNotFound" xml:space="preserve">
<value>The resource file "{0}" could not be found.</value>
</data>
+ <data name="ClaimUidExtractor_ClaimNotPresent" xml:space="preserve">
+ <value>A claim of type '{0}' was not present on the provided ClaimsIdentity.</value>
+ </data>
+ <data name="ClaimUidExtractor_DefaultClaimsNotPresent" xml:space="preserve">
+ <value>A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier.</value>
+ </data>
<data name="DynamicDictionary_InvalidNumberOfIndexes" xml:space="preserve">
<value>Index length must be exactly one.</value>
</data>
@@ -168,6 +198,9 @@
<data name="StateStorage_StorageScopesCannotBeCreated" xml:space="preserve">
<value>Storage scopes cannot be created when _AppStart is executing.</value>
</data>
+ <data name="TokenValidator_AuthenticatedUserWithoutUsername" xml:space="preserve">
+ <value>The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user.</value>
+ </data>
<data name="UnobtrusiveJavascript_ValidationParameterCannotBeEmpty" xml:space="preserve">
<value>Validation parameter names in unobtrusive client validation rules cannot be empty. Client rule type: {0}</value>
</data>
diff --git a/src/System.Web.WebPages/System.Web.WebPages.csproj b/src/System.Web.WebPages/System.Web.WebPages.csproj
index 571e2c87..2990917f 100644
--- a/src/System.Web.WebPages/System.Web.WebPages.csproj
+++ b/src/System.Web.WebPages/System.Web.WebPages.csproj
@@ -101,13 +101,31 @@
<Compile Include="DisplayInfo.cs" />
<Compile Include="DisplayModeProvider.cs" />
<Compile Include="BrowserHelpers.cs" />
+ <Compile Include="Helpers\AntiXsrf\ClaimUidExtractor.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryTokenStore.cs" />
+ <Compile Include="Helpers\AntiXsrf\ITokenStore.cs" />
+ <Compile Include="Helpers\AntiXsrf\TokenValidator.cs" />
+ <Compile Include="Helpers\AntiXsrf\ITokenValidator.cs" />
+ <Compile Include="Helpers\AntiXsrf\IClaimUidExtractor.cs" />
+ <Compile Include="Helpers\AntiXsrf\MachineKeyCryptoSystem.cs" />
+ <Compile Include="Helpers\AntiXsrf\ICryptoSystem.cs" />
+ <Compile Include="Helpers\Claims\Claim.cs" />
+ <Compile Include="Helpers\Claims\ClaimsIdentity.cs" />
+ <Compile Include="Helpers\Claims\ClaimsIdentityConverter.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryConfigWrapper.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryWorker.cs" />
+ <Compile Include="Helpers\AntiXsrf\IAntiForgeryTokenSerializer.cs" />
+ <Compile Include="Helpers\AntiXsrf\IAntiForgeryConfig.cs" />
+ <Compile Include="Helpers\CryptoUtil.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryToken.cs" />
+ <Compile Include="Helpers\AntiXsrf\BinaryBlob.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryTokenSerializer.cs" />
+ <Compile Include="Helpers\AntiForgeryConfig.cs" />
+ <Compile Include="Helpers\IAntiForgeryAdditionalDataProvider.cs" />
<Compile Include="IDisplayMode.cs" />
<Compile Include="DefaultDisplayMode.cs" />
<Compile Include="FileExistenceCache.cs" />
- <Compile Include="Helpers\AntiForgeryData.cs" />
- <Compile Include="Helpers\AntiForgeryDataSerializer.cs" />
<Compile Include="Helpers\AntiForgery.cs" />
- <Compile Include="Helpers\AntiForgeryWorker.cs" />
<Compile Include="HttpContextExtensions.cs" />
<Compile Include="Instrumentation\HttpContextAdapter.Availability.cs" />
<Compile Include="Instrumentation\HttpContextAdapter.generated.cs">
diff --git a/test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs b/test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs
index 0b9464cf..7dfb5258 100644
--- a/test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs
+++ b/test/System.Web.Mvc.Test/Test/ValidateAntiForgeryTokenAttributeTest.cs
@@ -27,16 +27,11 @@ namespace System.Web.Mvc.Test
Mock<AuthorizationContext> authorizationContextMock = new Mock<AuthorizationContext>();
authorizationContextMock.SetupGet(ac => ac.HttpContext).Returns(context);
bool validateCalled = false;
- Action<HttpContextBase, string> validateMethod = (c, s) =>
+ Action validateMethod = () =>
{
- Assert.Same(context, c);
- Assert.Equal("some salt", s);
validateCalled = true;
};
- ValidateAntiForgeryTokenAttribute attribute = new ValidateAntiForgeryTokenAttribute(validateMethod)
- {
- Salt = "some salt"
- };
+ ValidateAntiForgeryTokenAttribute attribute = new ValidateAntiForgeryTokenAttribute(validateMethod);
// Act
attribute.OnAuthorization(authorizationContextMock.Object);
@@ -46,16 +41,6 @@ namespace System.Web.Mvc.Test
}
[Fact]
- public void SaltProperty()
- {
- // Arrange
- ValidateAntiForgeryTokenAttribute attribute = new ValidateAntiForgeryTokenAttribute();
-
- // Act & Assert
- MemberHelper.TestStringProperty(attribute, "Salt", String.Empty);
- }
-
- [Fact]
public void ValidateThunk_DefaultsToAntiForgeryMethod()
{
// Arrange
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryConfigTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryConfigTest.cs
new file mode 100644
index 00000000..2d05bc13
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiForgeryConfigTest.cs
@@ -0,0 +1,22 @@
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class AntiForgeryConfigTest
+ {
+ [Theory]
+ [InlineData(null, "__RequestVerificationToken")]
+ [InlineData("", "__RequestVerificationToken")]
+ [InlineData("/", "__RequestVerificationToken")]
+ [InlineData("/path", "__RequestVerificationToken_L3BhdGg1")]
+ public void GetAntiForgeryCookieName(string appPath, string expectedCookieName)
+ {
+ // Act
+ string retVal = AntiForgeryConfig.GetAntiForgeryCookieName(appPath);
+
+ // Assert
+ Assert.Equal(expectedCookieName, retVal);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataSerializerTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataSerializerTest.cs
deleted file mode 100644
index 09b9af22..00000000
--- a/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataSerializerTest.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using System.Web.Mvc;
-using Xunit;
-using Assert = Microsoft.TestCommon.AssertEx;
-
-namespace System.Web.Helpers.Test
-{
- public class AntiForgeryDataSerializerTest
- {
- [Fact]
- public void GuardClauses()
- {
- // Arrange
- AntiForgeryDataSerializer serializer = new AntiForgeryDataSerializer();
-
- // Act & assert
- Assert.ThrowsArgumentNull(
- () => serializer.Serialize(null),
- "token"
- );
- Assert.ThrowsArgumentNullOrEmptyString(
- () => serializer.Deserialize(null),
- "serializedToken"
- );
- Assert.ThrowsArgumentNullOrEmptyString(
- () => serializer.Deserialize(String.Empty),
- "serializedToken"
- );
- Assert.Throws<HttpAntiForgeryException>(
- () => serializer.Deserialize("Corrupted Base-64 Value"),
- "A required anti-forgery token was not supplied or was invalid."
- );
- }
-
- [Fact]
- public void DeserializationExceptionDoesNotContainInnerException()
- {
- // Arrange
- AntiForgeryDataSerializer serializer = new AntiForgeryDataSerializer();
-
- // Act & assert
- HttpAntiForgeryException exception = null;
- try
- {
- serializer.Deserialize("Can't deserialize this.");
- }
- catch (HttpAntiForgeryException ex)
- {
- exception = ex;
- }
-
- Assert.NotNull(exception);
- Assert.Null(exception.InnerException);
- }
-
- [Fact]
- public void CanRoundTripData()
- {
- // Arrange
- AntiForgeryDataSerializer serializer = new AntiForgeryDataSerializer
- {
- Decoder = value => Convert.FromBase64String(value),
- Encoder = bytes => Convert.ToBase64String(bytes),
- };
- AntiForgeryData input = new AntiForgeryData
- {
- Salt = "The Salt",
- Username = "The Username",
- Value = "The Value",
- CreationDate = DateTime.Now,
- };
-
- // Act
- AntiForgeryData output = serializer.Deserialize(serializer.Serialize(input));
-
- // Assert
- Assert.NotNull(output);
- Assert.Equal(input.Salt, output.Salt);
- Assert.Equal(input.Username, output.Username);
- Assert.Equal(input.Value, output.Value);
- Assert.Equal(input.CreationDate, output.CreationDate);
- }
-
- [Fact]
- public void HexDigitConvertsIntegersToHexCharsCorrectly()
- {
- for (int i = 0; i < 0x10; i++)
- {
- Assert.Equal(i.ToString("X")[0], AntiForgeryDataSerializer.HexDigit(i));
- }
- }
-
- [Fact]
- public void HexValueConvertsCharValuesToIntegersCorrectly()
- {
- for (int i = 0; i < 0x10; i++)
- {
- var hexChar = i.ToString("X")[0];
- Assert.Equal(i, AntiForgeryDataSerializer.HexValue(hexChar));
- }
- }
- }
-}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataTest.cs
deleted file mode 100644
index 442b0c9f..00000000
--- a/test/System.Web.WebPages.Test/Helpers/AntiForgeryDataTest.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-using System.Security.Principal;
-using Moq;
-using Xunit;
-using Assert = Microsoft.TestCommon.AssertEx;
-
-namespace System.Web.Helpers.Test
-{
- public class AntiForgeryDataTest
- {
- [Fact]
- public void CopyConstructor()
- {
- // Arrange
- AntiForgeryData originalToken = new AntiForgeryData()
- {
- CreationDate = DateTime.Now,
- Salt = "some salt",
- Value = "some value"
- };
-
- // Act
- AntiForgeryData newToken = new AntiForgeryData(originalToken);
-
- // Assert
- Assert.Equal(originalToken.CreationDate, newToken.CreationDate);
- Assert.Equal(originalToken.Salt, newToken.Salt);
- Assert.Equal(originalToken.Value, newToken.Value);
- }
-
- [Fact]
- public void CopyConstructorThrowsIfTokenIsNull()
- {
- // Act & Assert
- Assert.ThrowsArgumentNull(
- delegate { new AntiForgeryData(null); }, "token");
- }
-
- [Fact]
- public void CreationDateProperty()
- {
- // Arrange
- AntiForgeryData token = new AntiForgeryData();
-
- // Act & Assert
- var now = DateTime.UtcNow;
- token.CreationDate = now;
- Assert.Equal(now, token.CreationDate);
- }
-
- [Fact]
- public void GetAntiForgeryTokenNameReturnsEncodedCookieNameIfAppPathIsNotEmpty()
- {
- // Arrange
- // the string below (as UTF-8 bytes) base64-encodes to "Pz4/Pj8+Pz4/Pj8+Pz4/Pg=="
- string original = "?>?>?>?>?>?>?>?>";
-
- // Act
- string tokenName = AntiForgeryData.GetAntiForgeryTokenName(original);
-
- // Assert
- Assert.Equal("__RequestVerificationToken_Pz4-Pj8.Pz4-Pj8.Pz4-Pg__", tokenName);
- }
-
- [Fact]
- public void GetAntiForgeryTokenNameReturnsFieldNameIfAppPathIsNull()
- {
- // Act
- string tokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
-
- // Assert
- Assert.Equal("__RequestVerificationToken", tokenName);
- }
-
- [Fact]
- public void GetUsername_ReturnsEmptyStringIfIdentityIsNull()
- {
- // Arrange
- Mock<IPrincipal> mockPrincipal = new Mock<IPrincipal>();
- mockPrincipal.Setup(o => o.Identity).Returns((IIdentity)null);
-
- // Act
- string username = AntiForgeryData.GetUsername(mockPrincipal.Object);
-
- // Assert
- Assert.Equal("", username);
- }
-
- [Fact]
- public void GetUsername_ReturnsEmptyStringIfPrincipalIsNull()
- {
- // Act
- string username = AntiForgeryData.GetUsername(null);
-
- // Assert
- Assert.Equal("", username);
- }
-
- [Fact]
- public void GetUsername_ReturnsEmptyStringIfUserNotAuthenticated()
- {
- // Arrange
- Mock<IPrincipal> mockPrincipal = new Mock<IPrincipal>();
- mockPrincipal.Setup(o => o.Identity.IsAuthenticated).Returns(false);
- mockPrincipal.Setup(o => o.Identity.Name).Returns("SampleName");
-
- // Act
- string username = AntiForgeryData.GetUsername(mockPrincipal.Object);
-
- // Assert
- Assert.Equal("", username);
- }
-
- [Fact]
- public void GetUsername_ReturnsUsernameIfUserIsAuthenticated()
- {
- // Arrange
- Mock<IPrincipal> mockPrincipal = new Mock<IPrincipal>();
- mockPrincipal.Setup(o => o.Identity.IsAuthenticated).Returns(true);
- mockPrincipal.Setup(o => o.Identity.Name).Returns("SampleName");
-
- // Act
- string username = AntiForgeryData.GetUsername(mockPrincipal.Object);
-
- // Assert
- Assert.Equal("SampleName", username);
- }
-
- [Fact]
- public void NewToken()
- {
- // Act
- AntiForgeryData token = AntiForgeryData.NewToken();
-
- // Assert
- int valueLength = Convert.FromBase64String(token.Value).Length;
- Assert.Equal(16, valueLength);
- Assert.NotEqual(default(DateTime), token.CreationDate);
- }
-
- [Fact]
- public void SaltProperty()
- {
- // Arrange
- AntiForgeryData token = new AntiForgeryData();
-
- // Act & Assert
- Assert.Equal(String.Empty, token.Salt);
- token.Salt = null;
- Assert.Equal(String.Empty, token.Salt);
- token.Salt = String.Empty;
- Assert.Equal(String.Empty, token.Salt);
- }
-
- [Fact]
- public void ValueProperty()
- {
- // Arrange
- AntiForgeryData token = new AntiForgeryData();
-
- // Act & Assert
- Assert.Equal(String.Empty, token.Value);
- token.Value = null;
- Assert.Equal(String.Empty, token.Value);
- token.Value = String.Empty;
- Assert.Equal(String.Empty, token.Value);
- }
- }
-}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs
index db26de50..d1d89147 100644
--- a/test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs
+++ b/test/System.Web.WebPages.Test/Helpers/AntiForgeryTest.cs
@@ -5,8 +5,6 @@ namespace System.Web.Helpers.Test
{
public class AntiForgeryTest
{
- private static string _antiForgeryTokenCookieName = AntiForgeryData.GetAntiForgeryTokenName("/SomeAppPath");
-
[Fact]
public void GetHtml_ThrowsWhenNotCalledInWebContext()
{
@@ -15,9 +13,10 @@ namespace System.Web.Helpers.Test
}
[Fact]
- public void GetHtml_ThrowsOnNullContext()
+ public void GetTokens_ThrowsWhenNotCalledInWebContext()
{
- Assert.ThrowsArgumentNull(() => AntiForgery.GetHtml(null, null, null, null), "httpContext");
+ Assert.Throws<ArgumentException>(() => { string dummy1, dummy2; AntiForgery.GetTokens("dummy", out dummy1, out dummy2); },
+ "An HttpContext is required to perform this operation. Check that this operation is being performed during a web request.");
}
[Fact]
@@ -25,12 +24,9 @@ namespace System.Web.Helpers.Test
{
Assert.Throws<ArgumentException>(() => AntiForgery.Validate(),
"An HttpContext is required to perform this operation. Check that this operation is being performed during a web request.");
- }
- [Fact]
- public void Validate_ThrowsOnNullContext()
- {
- Assert.ThrowsArgumentNull(() => AntiForgery.Validate(httpContext: null, salt: null), "httpContext");
+ Assert.Throws<ArgumentException>(() => AntiForgery.Validate("cookie-token", "form-token"),
+ "An HttpContext is required to perform this operation. Check that this operation is being performed during a web request.");
}
}
}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiForgeryWorkerTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiForgeryWorkerTest.cs
deleted file mode 100644
index 81cd287d..00000000
--- a/test/System.Web.WebPages.Test/Helpers/AntiForgeryWorkerTest.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-using System.Collections.Specialized;
-using System.Globalization;
-using System.Text.RegularExpressions;
-using System.Web.Mvc;
-using Moq;
-using Xunit;
-using Assert = Microsoft.TestCommon.AssertEx;
-using Match = System.Text.RegularExpressions.Match;
-
-namespace System.Web.Helpers.Test
-{
- public class AntiForgeryWorkerTest
- {
- private static string _antiForgeryTokenCookieName = AntiForgeryData.GetAntiForgeryTokenName("/SomeAppPath");
- private const string _serializedValuePrefix = @"<input name=""__RequestVerificationToken"" type=""hidden"" value=""Creation: ";
- private const string _someValueSuffix = @", Value: some value, Salt: some other salt, Username: username"" />";
- private readonly Regex _randomFormValueSuffixRegex = new Regex(@", Value: (?<value>[A-Za-z0-9/\+=]{24}), Salt: some other salt, Username: username"" />$");
- private readonly Regex _randomCookieValueSuffixRegex = new Regex(@", Value: (?<value>[A-Za-z0-9/\+=]{24}), Salt: ");
-
- [Fact]
- public void Serializer_DefaultValueIsAntiForgeryDataSerializer()
- {
- Assert.Same(typeof(AntiForgeryDataSerializer), new AntiForgeryWorker().Serializer.GetType());
- }
-
- [Fact]
- public void GetHtml_ReturnsFormFieldAndSetsCookieValueIfDoesNotExist()
- {
- // Arrange
- AntiForgeryWorker worker = new AntiForgeryWorker()
- {
- Serializer = new DummyAntiForgeryTokenSerializer()
- };
- var context = CreateContext();
-
- // Act
- string formValue = worker.GetHtml(context, "some other salt", null, null).ToHtmlString();
-
- // Assert
- Assert.True(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
-
- Match formMatch = _randomFormValueSuffixRegex.Match(formValue);
- string formTokenValue = formMatch.Groups["value"].Value;
-
- HttpCookie cookie = context.Response.Cookies[_antiForgeryTokenCookieName];
- Assert.NotNull(cookie);
- Assert.True(cookie.HttpOnly, "Cookie should have HTTP-only flag set.");
- Assert.True(String.IsNullOrEmpty(cookie.Domain), "Domain should not have been set.");
- Assert.Equal("/", cookie.Path);
-
- Match cookieMatch = _randomCookieValueSuffixRegex.Match(cookie.Value);
- string cookieTokenValue = cookieMatch.Groups["value"].Value;
-
- Assert.Equal(formTokenValue, cookieTokenValue);
- }
-
- [Fact]
- public void GetHtml_SetsCookieDomainAndPathIfSpecified()
- {
- // Arrange
- AntiForgeryWorker worker = new AntiForgeryWorker()
- {
- Serializer = new DummyAntiForgeryTokenSerializer()
- };
- var context = CreateContext();
-
- // Act
- string formValue = worker.GetHtml(context, "some other salt", "theDomain", "thePath").ToHtmlString();
-
- // Assert
- Assert.True(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
-
- Match formMatch = _randomFormValueSuffixRegex.Match(formValue);
- string formTokenValue = formMatch.Groups["value"].Value;
-
- HttpCookie cookie = context.Response.Cookies[_antiForgeryTokenCookieName];
- Assert.NotNull(cookie);
- Assert.True(cookie.HttpOnly, "Cookie should have HTTP-only flag set.");
- Assert.Equal("theDomain", cookie.Domain);
- Assert.Equal("thePath", cookie.Path);
-
- Match cookieMatch = _randomCookieValueSuffixRegex.Match(cookie.Value);
- string cookieTokenValue = cookieMatch.Groups["value"].Value;
-
- Assert.Equal(formTokenValue, cookieTokenValue);
- }
-
- [Fact]
- public void GetHtml_ReusesCookieValueIfExistsAndIsValid()
- {
- // Arrange
- AntiForgeryWorker worker = new AntiForgeryWorker()
- {
- Serializer = new DummyAntiForgeryTokenSerializer()
- };
- var context = CreateContext("2001-01-01:some value:some salt:username");
-
- // Act
- string formValue = worker.GetHtml(context, "some other salt", null, null).ToHtmlString();
-
- // Assert
- Assert.True(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
- Assert.True(formValue.EndsWith(_someValueSuffix), "Form value suffix did not match.");
- Assert.Equal(0, context.Response.Cookies.Count);
- }
-
- [Fact]
- public void GetHtml_CreatesNewCookieValueIfCookieExistsButIsNotValid()
- {
- // Arrange
- AntiForgeryWorker worker = new AntiForgeryWorker()
- {
- Serializer = new DummyAntiForgeryTokenSerializer()
- };
- var context = CreateContext("invalid");
-
- // Act
- string formValue = worker.GetHtml(context, "some other salt", null, null).ToHtmlString();
-
- // Assert
- Assert.True(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
-
- Match formMatch = _randomFormValueSuffixRegex.Match(formValue);
- string formTokenValue = formMatch.Groups["value"].Value;
-
- HttpCookie cookie = context.Response.Cookies[_antiForgeryTokenCookieName];
- Assert.NotNull(cookie);
- Assert.True(cookie.HttpOnly, "Cookie should have HTTP-only flag set.");
- Assert.True(String.IsNullOrEmpty(cookie.Domain), "Domain should not have been set.");
- Assert.Equal("/", cookie.Path);
-
- Match cookieMatch = _randomCookieValueSuffixRegex.Match(cookie.Value);
- string cookieTokenValue = cookieMatch.Groups["value"].Value;
-
- Assert.Equal(formTokenValue, cookieTokenValue);
- }
-
- [Fact]
- public void Validate_ThrowsIfCookieMissing()
- {
- Validate_Helper(null, "2001-01-01:some other value:the real salt:username");
- }
-
- [Fact]
- public void Validate_ThrowsIfCookieValueDoesNotMatchFormValue()
- {
- Validate_Helper("2001-01-01:some value:the real salt:username", "2001-01-01:some other value:the real salt:username");
- }
-
- [Fact]
- public void Validate_ThrowsIfFormSaltDoesNotMatchAttributeSalt()
- {
- Validate_Helper("2001-01-01:some value:some salt:username", "2001-01-01:some value:some other salt:username");
- }
-
- [Fact]
- public void Validate_ThrowsIfFormValueMissing()
- {
- Validate_Helper("2001-01-01:some value:the real salt:username", null);
- }
-
- [Fact]
- public void Validate_ThrowsIfUsernameInFormIsIncorrect()
- {
- Validate_Helper("2001-01-01:value:salt:username", "2001-01-01:value:salt:different username");
- }
-
- private static void Validate_Helper(string cookieValue, string formValue, string username = "username")
- {
- // Arrange
- //ValidateAntiForgeryTokenAttribute attribute = GetAttribute();
- var context = CreateContext(cookieValue, formValue, username);
-
- AntiForgeryWorker worker = new AntiForgeryWorker()
- {
- Serializer = new DummyAntiForgeryTokenSerializer()
- };
-
- // Act & Assert
- Assert.Throws<HttpAntiForgeryException>(
- delegate
- {
- //attribute.OnAuthorization(authContext);
- worker.Validate(context, "the real salt");
- }, "A required anti-forgery token was not supplied or was invalid.");
- }
-
- private static HttpContextBase CreateContext(string cookieValue = null, string formValue = null, string username = "username")
- {
- HttpCookieCollection requestCookies = new HttpCookieCollection();
- if (!String.IsNullOrEmpty(cookieValue))
- {
- requestCookies.Set(new HttpCookie(_antiForgeryTokenCookieName, cookieValue));
- }
- NameValueCollection formCollection = new NameValueCollection();
- if (!String.IsNullOrEmpty(formValue))
- {
- formCollection.Set(AntiForgeryData.GetAntiForgeryTokenName(null), formValue);
- }
-
- Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
- mockContext.Setup(c => c.Request.ApplicationPath).Returns("/SomeAppPath");
- mockContext.Setup(c => c.Request.Cookies).Returns(requestCookies);
- mockContext.Setup(c => c.Request.Form).Returns(formCollection);
- mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
- mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);
- mockContext.Setup(c => c.User.Identity.Name).Returns(username);
-
- return mockContext.Object;
- }
-
- internal class DummyAntiForgeryTokenSerializer : AntiForgeryDataSerializer
- {
- public override string Serialize(AntiForgeryData token)
- {
- return String.Format(CultureInfo.InvariantCulture, "Creation: {0}, Value: {1}, Salt: {2}, Username: {3}",
- token.CreationDate, token.Value, token.Salt, token.Username);
- }
-
- public override AntiForgeryData Deserialize(string serializedToken)
- {
- if (serializedToken == "invalid")
- {
- throw new HttpAntiForgeryException();
- }
- string[] parts = serializedToken.Split(':');
- return new AntiForgeryData()
- {
- CreationDate = DateTime.Parse(parts[0], CultureInfo.InvariantCulture),
- Value = parts[1],
- Salt = parts[2],
- Username = parts[3]
- };
- }
- }
- }
-}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenSerializerTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenSerializerTest.cs
new file mode 100644
index 00000000..4917a78f
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenSerializerTest.cs
@@ -0,0 +1,176 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public class AntiForgeryTokenSerializerTest
+ {
+ private static readonly AntiForgeryTokenSerializer _testSerializer = new AntiForgeryTokenSerializer(cryptoSystem: CreateIdentityTransformCryptoSystem());
+
+ private static readonly BinaryBlob _claimUid = new BinaryBlob(256, new byte[] { 0x6F, 0x16, 0x48, 0xE9, 0x72, 0x49, 0xAA, 0x58, 0x75, 0x40, 0x36, 0xA6, 0x7E, 0x24, 0x8C, 0xF0, 0x44, 0xF0, 0x7E, 0xCF, 0xB0, 0xED, 0x38, 0x75, 0x56, 0xCE, 0x02, 0x9A, 0x4F, 0x9A, 0x40, 0xE0 });
+ private static readonly BinaryBlob _securityToken = new BinaryBlob(128, new byte[] { 0x70, 0x5E, 0xED, 0xCC, 0x7D, 0x42, 0xF1, 0xD6, 0xB3, 0xB9, 0x8A, 0x59, 0x36, 0x25, 0xBB, 0x4C });
+
+ [Theory]
+ [InlineData(
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B9" // SecurityToken
+ // (WRONG!) Stream ends too early
+ )]
+ [InlineData(
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "01" // IsSessionToken
+ + "00" // (WRONG!) Too much data in stream
+ )]
+ [InlineData(
+ "02" // (WRONG! - must be 0x01) Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "01" // IsSessionToken
+ )]
+ [InlineData(
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "00" // IsSessionToken
+ + "00" // IsClaimsBased
+ + "05" // Username length header
+ + "0000" // (WRONG!) Too little data in stream
+ )]
+ public void Deserialize_BadToken(string serializedToken)
+ {
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => _testSerializer.Deserialize(serializedToken));
+ Assert.Equal(@"The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.", ex.Message);
+ }
+
+ [Fact]
+ public void Serialize_FieldToken_WithClaimUid()
+ {
+ // Arrange
+ const string expectedSerializedData =
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "00" // IsSessionToken
+ + "01" // IsClaimsBased
+ + "6F1648E97249AA58754036A67E248CF044F07ECFB0ED387556CE029A4F9A40E0" // ClaimUid
+ + "05" // AdditionalData length header
+ + "E282AC3437"; // AdditionalData ("€47") as UTF8
+
+ AntiForgeryToken token = new AntiForgeryToken()
+ {
+ SecurityToken = _securityToken,
+ IsSessionToken = false,
+ ClaimUid = _claimUid,
+ AdditionalData = "€47"
+ };
+
+ // Act & assert - serialization
+ string actualSerializedData = _testSerializer.Serialize(token);
+ Assert.Equal(expectedSerializedData, actualSerializedData);
+
+ // Act & assert - deserialization
+ AntiForgeryToken deserializedToken = _testSerializer.Deserialize(actualSerializedData);
+ AssertTokensEqual(token, deserializedToken);
+ }
+
+ [Fact]
+ public void Serialize_FieldToken_WithUsername()
+ {
+ // Arrange
+ const string expectedSerializedData =
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "00" // IsSessionToken
+ + "00" // IsClaimsBased
+ + "08" // Username length header
+ + "4AC3A972C3B46D65" // Username ("Jérôme") as UTF8
+ + "05" // AdditionalData length header
+ + "E282AC3437"; // AdditionalData ("€47") as UTF8
+
+ AntiForgeryToken token = new AntiForgeryToken()
+ {
+ SecurityToken = _securityToken,
+ IsSessionToken = false,
+ Username = "Jérôme",
+ AdditionalData = "€47"
+ };
+
+ // Act & assert - serialization
+ string actualSerializedData = _testSerializer.Serialize(token);
+ Assert.Equal(expectedSerializedData, actualSerializedData);
+
+ // Act & assert - deserialization
+ AntiForgeryToken deserializedToken = _testSerializer.Deserialize(actualSerializedData);
+ AssertTokensEqual(token, deserializedToken);
+ }
+
+ [Fact]
+ public void Serialize_SessionToken()
+ {
+ // Arrange
+ const string expectedSerializedData =
+ "01" // Version
+ + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
+ + "01"; // IsSessionToken
+
+ AntiForgeryToken token = new AntiForgeryToken()
+ {
+ SecurityToken = _securityToken,
+ IsSessionToken = true
+ };
+
+ // Act & assert - serialization
+ string actualSerializedData = _testSerializer.Serialize(token);
+ Assert.Equal(expectedSerializedData, actualSerializedData);
+
+ // Act & assert - deserialization
+ AntiForgeryToken deserializedToken = _testSerializer.Deserialize(actualSerializedData);
+ AssertTokensEqual(token, deserializedToken);
+ }
+
+ private static string BytesToHex(byte[] bytes)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (byte b in bytes)
+ {
+ sb.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", b);
+ }
+ return sb.ToString();
+ }
+
+ private static byte[] HexToBytes(string hex)
+ {
+ List<byte> bytes = new List<byte>();
+ for (int i = 0; i < hex.Length; i += 2)
+ {
+ byte b = Byte.Parse(hex.Substring(i, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
+ bytes.Add(b);
+ }
+ return bytes.ToArray();
+ }
+
+ private static ICryptoSystem CreateIdentityTransformCryptoSystem()
+ {
+ Mock<MockableCryptoSystem> mockCryptoSystem = new Mock<MockableCryptoSystem>();
+ mockCryptoSystem.Setup(o => o.Protect(It.IsAny<byte[]>())).Returns<byte[]>(HexUtil.HexEncode);
+ mockCryptoSystem.Setup(o => o.Unprotect(It.IsAny<string>())).Returns<string>(HexUtil.HexDecode);
+ return mockCryptoSystem.Object;
+ }
+
+ private static void AssertTokensEqual(AntiForgeryToken expected, AntiForgeryToken actual)
+ {
+ Assert.NotNull(expected);
+ Assert.NotNull(actual);
+ Assert.Equal(expected.AdditionalData, actual.AdditionalData);
+ Assert.Equal(expected.ClaimUid, actual.ClaimUid);
+ Assert.Equal(expected.IsSessionToken, actual.IsSessionToken);
+ Assert.Equal(expected.SecurityToken, actual.SecurityToken);
+ Assert.Equal(expected.Username, actual.Username);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenStoreTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenStoreTest.cs
new file mode 100644
index 00000000..98a00ccc
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenStoreTest.cs
@@ -0,0 +1,234 @@
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public class AntiForgeryTokenStoreTest
+ {
+ [Fact]
+ public void GetCookieToken_CookieDoesNotExist_ReturnsNull()
+ {
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request.Cookies).Returns(new HttpCookieCollection());
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ CookieName = "cookie-name"
+ };
+
+ AntiForgeryTokenStore tokenStore = new AntiForgeryTokenStore(
+ config: config,
+ serializer: null);
+
+ // Act
+ AntiForgeryToken token = tokenStore.GetCookieToken(mockHttpContext.Object);
+
+ // Assert
+ Assert.Null(token);
+ }
+
+ [Fact]
+ public void GetCookieToken_CookieIsEmpty_ReturnsNull()
+ {
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request.Cookies).Returns(new HttpCookieCollection()
+ {
+ new HttpCookie("cookie-name", "")
+ });
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ CookieName = "cookie-name"
+ };
+
+ AntiForgeryTokenStore tokenStore = new AntiForgeryTokenStore(
+ config: config,
+ serializer: null);
+
+ // Act
+ AntiForgeryToken token = tokenStore.GetCookieToken(mockHttpContext.Object);
+
+ // Assert
+ Assert.Null(token);
+ }
+
+ [Fact]
+ public void GetCookieToken_CookieIsInvalid_PropagatesException()
+ {
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request.Cookies).Returns(new HttpCookieCollection()
+ {
+ new HttpCookie("cookie-name", "invalid-value")
+ });
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ CookieName = "cookie-name"
+ };
+
+ HttpAntiForgeryException expectedException = new HttpAntiForgeryException("some exception");
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>();
+ mockSerializer.Setup(o => o.Deserialize("invalid-value")).Throws(expectedException);
+
+ AntiForgeryTokenStore tokenStore = new AntiForgeryTokenStore(
+ config: config,
+ serializer: mockSerializer.Object);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => tokenStore.GetCookieToken(mockHttpContext.Object));
+ Assert.Equal(expectedException, ex);
+ }
+
+ [Fact]
+ public void GetCookieToken_CookieIsValid_ReturnsToken()
+ {
+ // Arrange
+ AntiForgeryToken expectedToken = new AntiForgeryToken();
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request.Cookies).Returns(new HttpCookieCollection()
+ {
+ new HttpCookie("cookie-name", "valid-value")
+ });
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ CookieName = "cookie-name"
+ };
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>();
+ mockSerializer.Setup(o => o.Deserialize("valid-value")).Returns((object)expectedToken);
+
+ AntiForgeryTokenStore tokenStore = new AntiForgeryTokenStore(
+ config: config,
+ serializer: mockSerializer.Object);
+
+ // Act
+ AntiForgeryToken retVal = tokenStore.GetCookieToken(mockHttpContext.Object);
+
+ // Assert
+ Assert.Same(expectedToken, retVal);
+ }
+
+ [Fact]
+ public void GetFormToken_FormFieldIsEmpty_ReturnsNull()
+ {
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request.Form.Get("form-field-name")).Returns("");
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ FormFieldName = "form-field-name"
+ };
+
+ AntiForgeryTokenStore tokenStore = new AntiForgeryTokenStore(
+ config: config,
+ serializer: null);
+
+ // Act
+ AntiForgeryToken token = tokenStore.GetFormToken(mockHttpContext.Object);
+
+ // Assert
+ Assert.Null(token);
+ }
+
+ [Fact]
+ public void GetFormToken_FormFieldIsInvalid_PropagatesException()
+ {
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request.Form.Get("form-field-name")).Returns("invalid-value");
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ FormFieldName = "form-field-name"
+ };
+
+ HttpAntiForgeryException expectedException = new HttpAntiForgeryException("some exception");
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>();
+ mockSerializer.Setup(o => o.Deserialize("invalid-value")).Throws(expectedException);
+
+ AntiForgeryTokenStore tokenStore = new AntiForgeryTokenStore(
+ config: config,
+ serializer: mockSerializer.Object);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => tokenStore.GetFormToken(mockHttpContext.Object));
+ Assert.Same(expectedException, ex);
+ }
+
+ [Fact]
+ public void GetFormToken_FormFieldIsValid_ReturnsToken()
+ {
+ // Arrange
+ AntiForgeryToken expectedToken = new AntiForgeryToken();
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request.Form.Get("form-field-name")).Returns("valid-value");
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ FormFieldName = "form-field-name"
+ };
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>();
+ mockSerializer.Setup(o => o.Deserialize("valid-value")).Returns((object)expectedToken);
+
+ AntiForgeryTokenStore tokenStore = new AntiForgeryTokenStore(
+ config: config,
+ serializer: mockSerializer.Object);
+
+ // Act
+ AntiForgeryToken retVal = tokenStore.GetFormToken(mockHttpContext.Object);
+
+ // Assert
+ Assert.Same(expectedToken, retVal);
+ }
+
+ [Theory]
+ [InlineData(true, true)]
+ [InlineData(false, null)]
+ public void SaveCookieToken(bool requireSsl, bool? expectedCookieSecureFlag)
+ {
+ // Arrange
+ AntiForgeryToken token = new AntiForgeryToken();
+ HttpCookieCollection cookies = new HttpCookieCollection();
+ bool defaultCookieSecureValue = expectedCookieSecureFlag ?? new HttpCookie("name", "value").Secure; // pulled from config; set by ctor
+
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Response.Cookies).Returns(cookies);
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>();
+ mockSerializer.Setup(o => o.Serialize(token)).Returns("serialized-value");
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ CookieName = "cookie-name",
+ RequireSSL = requireSsl
+ };
+
+ AntiForgeryTokenStore tokenStore = new AntiForgeryTokenStore(
+ config: config,
+ serializer: mockSerializer.Object);
+
+ // Act
+ tokenStore.SaveCookieToken(mockHttpContext.Object, token);
+
+ // Assert
+ Assert.Equal(1, cookies.Count);
+ HttpCookie cookie = cookies["cookie-name"];
+
+ Assert.NotNull(cookie);
+ Assert.Equal("serialized-value", cookie.Value);
+ Assert.True(cookie.HttpOnly);
+ Assert.Equal(defaultCookieSecureValue, cookie.Secure);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenTest.cs
new file mode 100644
index 00000000..8446c5ff
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryTokenTest.cs
@@ -0,0 +1,106 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public class AntiForgeryTokenTest
+ {
+ [Fact]
+ public void AdditionalDataProperty()
+ {
+ // Arrange
+ AntiForgeryToken token = new AntiForgeryToken();
+
+ // Act & assert - 1
+ Assert.Equal("", token.AdditionalData);
+
+ // Act & assert - 2
+ token.AdditionalData = "additional data";
+ Assert.Equal("additional data", token.AdditionalData);
+
+ // Act & assert - 3
+ token.AdditionalData = null;
+ Assert.Equal("", token.AdditionalData);
+ }
+
+ [Fact]
+ public void ClaimUidProperty()
+ {
+ // Arrange
+ AntiForgeryToken token = new AntiForgeryToken();
+
+ // Act & assert - 1
+ Assert.Null(token.ClaimUid);
+
+ // Act & assert - 2
+ BinaryBlob blob = new BinaryBlob(32);
+ token.ClaimUid = blob;
+ Assert.Equal(blob, token.ClaimUid);
+
+ // Act & assert - 3
+ token.ClaimUid = null;
+ Assert.Null(token.ClaimUid);
+ }
+
+ [Fact]
+ public void IsSessionTokenProperty()
+ {
+ // Arrange
+ AntiForgeryToken token = new AntiForgeryToken();
+
+ // Act & assert - 1
+ Assert.False(token.IsSessionToken);
+
+ // Act & assert - 2
+ token.IsSessionToken = true;
+ Assert.True(token.IsSessionToken);
+
+ // Act & assert - 3
+ token.IsSessionToken = false;
+ Assert.False(token.IsSessionToken);
+ }
+
+ [Fact]
+ public void SecurityTokenProperty()
+ {
+ // Arrange
+ AntiForgeryToken token = new AntiForgeryToken();
+
+ // Act & assert - 1
+ BinaryBlob securityToken = token.SecurityToken;
+ Assert.NotNull(securityToken);
+ Assert.Equal(AntiForgeryToken.SecurityTokenBitLength, securityToken.BitLength);
+ Assert.Equal(securityToken, token.SecurityToken); // check that we're not making a new one each property call
+
+ // Act & assert - 2
+ securityToken = new BinaryBlob(64);
+ token.SecurityToken = securityToken;
+ Assert.Equal(securityToken, token.SecurityToken);
+
+ // Act & assert - 3
+ token.SecurityToken = null;
+ securityToken = token.SecurityToken;
+ Assert.NotNull(securityToken);
+ Assert.Equal(AntiForgeryToken.SecurityTokenBitLength, securityToken.BitLength);
+ Assert.Equal(securityToken, token.SecurityToken); // check that we're not making a new one each property call
+ }
+
+ [Fact]
+ public void UsernameProperty()
+ {
+ // Arrange
+ AntiForgeryToken token = new AntiForgeryToken();
+
+ // Act & assert - 1
+ Assert.Equal("", token.Username);
+
+ // Act & assert - 2
+ token.Username = "my username";
+ Assert.Equal("my username", token.Username);
+
+ // Act & assert - 3
+ token.Username = null;
+ Assert.Equal("", token.Username);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryWorkerTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryWorkerTest.cs
new file mode 100644
index 00000000..f7126c2c
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/AntiForgeryWorkerTest.cs
@@ -0,0 +1,401 @@
+using System.Security.Principal;
+using System.Web.Helpers.Test;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public class AntiForgeryWorkerTest
+ {
+ [Fact]
+ public void ChecksSSL()
+ {
+ // Arrange
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.Request.IsSecureConnection).Returns(false);
+
+ IAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ RequireSSL = true
+ };
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: config,
+ serializer: null,
+ tokenStore: null,
+ validator: null);
+
+ // Act & assert
+ var ex = Assert.Throws<InvalidOperationException>(() => worker.Validate(mockHttpContext.Object, "session-token", "field-token"));
+ Assert.Equal(@"The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.", ex.Message);
+
+ ex = Assert.Throws<InvalidOperationException>(() => worker.Validate(mockHttpContext.Object));
+ Assert.Equal(@"The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.", ex.Message);
+
+ ex = Assert.Throws<InvalidOperationException>(() => worker.GetFormInputElement(mockHttpContext.Object));
+ Assert.Equal(@"The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.", ex.Message);
+
+ ex = Assert.Throws<InvalidOperationException>(() => { string dummy1, dummy2; worker.GetTokens(mockHttpContext.Object, "cookie-token", out dummy1, out dummy2); });
+ Assert.Equal(@"The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.", ex.Message);
+ }
+
+ [Fact]
+ public void GetFormInputElement_ExistingInvalidCookieToken()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken oldCookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken newCookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ FormFieldName = "form-field-name"
+ };
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>(MockBehavior.Strict);
+ mockSerializer.Setup(o => o.Serialize(formToken)).Returns("serialized-form-token");
+
+ Mock<MockableTokenStore> mockTokenStore = new Mock<MockableTokenStore>(MockBehavior.Strict);
+ mockTokenStore.Setup(o => o.GetCookieToken(mockHttpContext.Object)).Returns(oldCookieToken);
+ mockTokenStore.Setup(o => o.SaveCookieToken(mockHttpContext.Object, newCookieToken)).Verifiable();
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>(MockBehavior.Strict);
+ mockValidator.Setup(o => o.GenerateFormToken(mockHttpContext.Object, identity, newCookieToken)).Returns(formToken);
+ mockValidator.Setup(o => o.IsCookieTokenValid(oldCookieToken)).Returns(false);
+ mockValidator.Setup(o => o.IsCookieTokenValid(newCookieToken)).Returns(true);
+ mockValidator.Setup(o => o.GenerateCookieToken()).Returns(newCookieToken);
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: config,
+ serializer: mockSerializer.Object,
+ tokenStore: mockTokenStore.Object,
+ validator: mockValidator.Object);
+
+ // Act
+ TagBuilder retVal = worker.GetFormInputElement(mockHttpContext.Object);
+
+ // Assert
+ Assert.Equal(@"<input name=""form-field-name"" type=""hidden"" value=""serialized-form-token"" />", retVal.ToString(TagRenderMode.SelfClosing));
+ mockTokenStore.Verify();
+ }
+
+ [Fact]
+ public void GetFormInputElement_ExistingInvalidCookieToken_SwallowsExceptions()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken oldCookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken newCookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ FormFieldName = "form-field-name"
+ };
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>(MockBehavior.Strict);
+ mockSerializer.Setup(o => o.Serialize(formToken)).Returns("serialized-form-token");
+
+ Mock<MockableTokenStore> mockTokenStore = new Mock<MockableTokenStore>(MockBehavior.Strict);
+ mockTokenStore.Setup(o => o.GetCookieToken(mockHttpContext.Object)).Throws(new Exception("should be swallowed"));
+ mockTokenStore.Setup(o => o.SaveCookieToken(mockHttpContext.Object, newCookieToken)).Verifiable();
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>(MockBehavior.Strict);
+ mockValidator.Setup(o => o.GenerateFormToken(mockHttpContext.Object, identity, newCookieToken)).Returns(formToken);
+ mockValidator.Setup(o => o.IsCookieTokenValid(null)).Returns(false);
+ mockValidator.Setup(o => o.IsCookieTokenValid(newCookieToken)).Returns(true);
+ mockValidator.Setup(o => o.GenerateCookieToken()).Returns(newCookieToken);
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: config,
+ serializer: mockSerializer.Object,
+ tokenStore: mockTokenStore.Object,
+ validator: mockValidator.Object);
+
+ // Act
+ TagBuilder retVal = worker.GetFormInputElement(mockHttpContext.Object);
+
+ // Assert
+ Assert.Equal(@"<input name=""form-field-name"" type=""hidden"" value=""serialized-form-token"" />", retVal.ToString(TagRenderMode.SelfClosing));
+ mockTokenStore.Verify();
+ }
+
+ [Fact]
+ public void GetFormInputElement_ExistingValidCookieToken()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken cookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ FormFieldName = "form-field-name"
+ };
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>(MockBehavior.Strict);
+ mockSerializer.Setup(o => o.Serialize(formToken)).Returns("serialized-form-token");
+
+ Mock<MockableTokenStore> mockTokenStore = new Mock<MockableTokenStore>(MockBehavior.Strict);
+ mockTokenStore.Setup(o => o.GetCookieToken(mockHttpContext.Object)).Returns(cookieToken);
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>(MockBehavior.Strict);
+ mockValidator.Setup(o => o.GenerateFormToken(mockHttpContext.Object, identity, cookieToken)).Returns(formToken);
+ mockValidator.Setup(o => o.IsCookieTokenValid(cookieToken)).Returns(true);
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: config,
+ serializer: mockSerializer.Object,
+ tokenStore: mockTokenStore.Object,
+ validator: mockValidator.Object);
+
+ // Act
+ TagBuilder retVal = worker.GetFormInputElement(mockHttpContext.Object);
+
+ // Assert
+ Assert.Equal(@"<input name=""form-field-name"" type=""hidden"" value=""serialized-form-token"" />", retVal.ToString(TagRenderMode.SelfClosing));
+ }
+
+ [Fact]
+ public void GetTokens_ExistingInvalidCookieToken()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken oldCookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken newCookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>(MockBehavior.Strict);
+ mockSerializer.Setup(o => o.Deserialize("serialized-old-cookie-token")).Returns(oldCookieToken);
+ mockSerializer.Setup(o => o.Serialize(newCookieToken)).Returns("serialized-new-cookie-token");
+ mockSerializer.Setup(o => o.Serialize(formToken)).Returns("serialized-form-token");
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>(MockBehavior.Strict);
+ mockValidator.Setup(o => o.GenerateFormToken(mockHttpContext.Object, identity, newCookieToken)).Returns(formToken);
+ mockValidator.Setup(o => o.IsCookieTokenValid(oldCookieToken)).Returns(false);
+ mockValidator.Setup(o => o.IsCookieTokenValid(newCookieToken)).Returns(true);
+ mockValidator.Setup(o => o.GenerateCookieToken()).Returns(newCookieToken);
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: new MockAntiForgeryConfig(),
+ serializer: mockSerializer.Object,
+ tokenStore: null,
+ validator: mockValidator.Object);
+
+ // Act
+ string serializedNewCookieToken, serializedFormToken;
+ worker.GetTokens(mockHttpContext.Object, "serialized-old-cookie-token", out serializedNewCookieToken, out serializedFormToken);
+
+ // Assert
+ Assert.Equal("serialized-new-cookie-token", serializedNewCookieToken);
+ Assert.Equal("serialized-form-token", serializedFormToken);
+ }
+
+ [Fact]
+ public void GetTokens_ExistingInvalidCookieToken_SwallowsExceptions()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken oldCookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken newCookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>(MockBehavior.Strict);
+ mockSerializer.Setup(o => o.Deserialize("serialized-old-cookie-token")).Throws(new Exception("should be swallowed"));
+ mockSerializer.Setup(o => o.Serialize(newCookieToken)).Returns("serialized-new-cookie-token");
+ mockSerializer.Setup(o => o.Serialize(formToken)).Returns("serialized-form-token");
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>(MockBehavior.Strict);
+ mockValidator.Setup(o => o.GenerateFormToken(mockHttpContext.Object, identity, newCookieToken)).Returns(formToken);
+ mockValidator.Setup(o => o.IsCookieTokenValid(null)).Returns(false);
+ mockValidator.Setup(o => o.IsCookieTokenValid(newCookieToken)).Returns(true);
+ mockValidator.Setup(o => o.GenerateCookieToken()).Returns(newCookieToken);
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: new MockAntiForgeryConfig(),
+ serializer: mockSerializer.Object,
+ tokenStore: null,
+ validator: mockValidator.Object);
+
+ // Act
+ string serializedNewCookieToken, serializedFormToken;
+ worker.GetTokens(mockHttpContext.Object, "serialized-old-cookie-token", out serializedNewCookieToken, out serializedFormToken);
+
+ // Assert
+ Assert.Equal("serialized-new-cookie-token", serializedNewCookieToken);
+ Assert.Equal("serialized-form-token", serializedFormToken);
+ }
+
+ [Fact]
+ public void GetTokens_ExistingValidCookieToken()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken cookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>(MockBehavior.Strict);
+ mockSerializer.Setup(o => o.Deserialize("serialized-old-cookie-token")).Returns(cookieToken);
+ mockSerializer.Setup(o => o.Serialize(formToken)).Returns("serialized-form-token");
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>(MockBehavior.Strict);
+ mockValidator.Setup(o => o.GenerateFormToken(mockHttpContext.Object, identity, cookieToken)).Returns(formToken);
+ mockValidator.Setup(o => o.IsCookieTokenValid(cookieToken)).Returns(true);
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: new MockAntiForgeryConfig(),
+ serializer: mockSerializer.Object,
+ tokenStore: null,
+ validator: mockValidator.Object);
+
+ // Act
+ string serializedNewCookieToken, serializedFormToken;
+ worker.GetTokens(mockHttpContext.Object, "serialized-old-cookie-token", out serializedNewCookieToken, out serializedFormToken);
+
+ // Assert
+ Assert.Null(serializedNewCookieToken);
+ Assert.Equal("serialized-form-token", serializedFormToken);
+ }
+
+ [Fact]
+ public void Validate_FromStrings_Failure()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken cookieToken = new AntiForgeryToken();
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>();
+ mockSerializer.Setup(o => o.Deserialize("cookie-token")).Returns(cookieToken);
+ mockSerializer.Setup(o => o.Deserialize("form-token")).Returns(formToken);
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>();
+ mockValidator.Setup(o => o.ValidateTokens(mockHttpContext.Object, identity, cookieToken, formToken)).Throws(new HttpAntiForgeryException("my-message"));
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: new MockAntiForgeryConfig(),
+ serializer: mockSerializer.Object,
+ tokenStore: null,
+ validator: mockValidator.Object);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => worker.Validate(mockHttpContext.Object, "cookie-token", "form-token"));
+ Assert.Equal("my-message", ex.Message);
+ }
+
+ [Fact]
+ public void Validate_FromStrings_Success()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken cookieToken = new AntiForgeryToken();
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ Mock<MockableAntiForgeryTokenSerializer> mockSerializer = new Mock<MockableAntiForgeryTokenSerializer>();
+ mockSerializer.Setup(o => o.Deserialize("cookie-token")).Returns(cookieToken);
+ mockSerializer.Setup(o => o.Deserialize("form-token")).Returns(formToken);
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>();
+ mockValidator.Setup(o => o.ValidateTokens(mockHttpContext.Object, identity, cookieToken, formToken)).Verifiable();
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: new MockAntiForgeryConfig(),
+ serializer: mockSerializer.Object,
+ tokenStore: null,
+ validator: mockValidator.Object);
+
+ // Act
+ worker.Validate(mockHttpContext.Object, "cookie-token", "form-token");
+
+ // Assert
+ mockValidator.Verify();
+ }
+
+ [Fact]
+ public void Validate_FromStore_Failure()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken cookieToken = new AntiForgeryToken();
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ Mock<MockableTokenStore> mockTokenStore = new Mock<MockableTokenStore>();
+ mockTokenStore.Setup(o => o.GetCookieToken(mockHttpContext.Object)).Returns(cookieToken);
+ mockTokenStore.Setup(o => o.GetFormToken(mockHttpContext.Object)).Returns(formToken);
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>();
+ mockValidator.Setup(o => o.ValidateTokens(mockHttpContext.Object, identity, cookieToken, formToken)).Throws(new HttpAntiForgeryException("my-message"));
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: new MockAntiForgeryConfig(),
+ serializer: null,
+ tokenStore: mockTokenStore.Object,
+ validator: mockValidator.Object);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => worker.Validate(mockHttpContext.Object));
+ Assert.Equal("my-message", ex.Message);
+ }
+
+ [Fact]
+ public void Validate_FromStore_Success()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("some-user");
+ Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
+ mockHttpContext.Setup(o => o.User).Returns(new GenericPrincipal(identity, new string[0]));
+
+ AntiForgeryToken cookieToken = new AntiForgeryToken();
+ AntiForgeryToken formToken = new AntiForgeryToken();
+
+ Mock<MockableTokenStore> mockTokenStore = new Mock<MockableTokenStore>();
+ mockTokenStore.Setup(o => o.GetCookieToken(mockHttpContext.Object)).Returns(cookieToken);
+ mockTokenStore.Setup(o => o.GetFormToken(mockHttpContext.Object)).Returns(formToken);
+
+ Mock<MockableTokenValidator> mockValidator = new Mock<MockableTokenValidator>();
+ mockValidator.Setup(o => o.ValidateTokens(mockHttpContext.Object, identity, cookieToken, formToken)).Verifiable();
+
+ AntiForgeryWorker worker = new AntiForgeryWorker(
+ config: new MockAntiForgeryConfig(),
+ serializer: null,
+ tokenStore: mockTokenStore.Object,
+ validator: mockValidator.Object);
+
+ // Act
+ worker.Validate(mockHttpContext.Object);
+
+ // Assert
+ mockValidator.Verify();
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/BinaryBlobTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/BinaryBlobTest.cs
new file mode 100644
index 00000000..23e509b3
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/BinaryBlobTest.cs
@@ -0,0 +1,127 @@
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public class BinaryBlobTest
+ {
+ [Fact]
+ public void Ctor_BitLength()
+ {
+ // Act
+ BinaryBlob blob = new BinaryBlob(bitLength: 64);
+ byte[] data = blob.GetData();
+
+ // Assert
+ Assert.Equal(64, blob.BitLength);
+ Assert.Equal(64 / 8, data.Length);
+ Assert.NotEqual(new byte[64 / 8], data); // should not be a zero-filled array
+ }
+
+ [Theory]
+ [InlineData(24)]
+ [InlineData(33)]
+ public void Ctor_BitLength_Bad(int bitLength)
+ {
+ // Act & assert
+ var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new BinaryBlob(bitLength));
+ Assert.Equal("bitLength", ex.ParamName);
+ }
+
+ [Fact]
+ public void Ctor_BitLength_ProducesDifferentValues()
+ {
+ // Act
+ BinaryBlob blobA = new BinaryBlob(bitLength: 64);
+ BinaryBlob blobB = new BinaryBlob(bitLength: 64);
+
+ // Assert
+ Assert.NotEqual(blobA.GetData(), blobB.GetData());
+ }
+
+ [Fact]
+ public void Ctor_Data()
+ {
+ // Arrange
+ byte[] expectedData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+
+ // Act
+ BinaryBlob blob = new BinaryBlob(32, expectedData);
+
+ // Assert
+ Assert.Equal(32, blob.BitLength);
+ Assert.Equal(expectedData, blob.GetData());
+ }
+
+ [Theory]
+ [InlineData((object[])null)]
+ [InlineData(new byte[] { 0x01, 0x02, 0x03 })]
+ public void Ctor_Data_Bad(byte[] data)
+ {
+ // Act & assert
+ var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new BinaryBlob(32, data));
+ Assert.Equal("data", ex.ParamName);
+ }
+
+ [Fact]
+ public void Equals_DifferentData_ReturnsFalse()
+ {
+ // Arrange
+ object blobA = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
+ object blobB = new BinaryBlob(32, new byte[] { 0x04, 0x03, 0x02, 0x01 });
+
+ // Act & assert
+ Assert.NotEqual(blobA, blobB);
+ }
+
+ [Fact]
+ public void Equals_NotABlob_ReturnsFalse()
+ {
+ // Arrange
+ object blobA = new BinaryBlob(32);
+ object blobB = "hello";
+
+ // Act & assert
+ Assert.NotEqual(blobA, blobB);
+ }
+
+ [Fact]
+ public void Equals_Null_ReturnsFalse()
+ {
+ // Arrange
+ object blobA = new BinaryBlob(32);
+ object blobB = null;
+
+ // Act & assert
+ Assert.NotEqual(blobA, blobB);
+ }
+
+ [Fact]
+ public void Equals_SameData_ReturnsTrue()
+ {
+ // Arrange
+ object blobA = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
+ object blobB = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
+
+ // Act & assert
+ Assert.Equal(blobA, blobB);
+ }
+
+ [Fact]
+ public void GetHashCodeTest()
+ {
+ // Arrange
+ byte[] blobData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ int expectedHashCode = BitConverter.ToInt32(blobData, 0);
+
+ BinaryBlob blob = new BinaryBlob(32, blobData);
+
+ // Act
+ int actualHashCode = blob.GetHashCode();
+
+ // Assert
+ Assert.Equal(expectedHashCode, actualHashCode);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/ClaimUidExtractorTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/ClaimUidExtractorTest.cs
new file mode 100644
index 00000000..f5262588
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/ClaimUidExtractorTest.cs
@@ -0,0 +1,237 @@
+using System.Collections.Generic;
+using System.Reflection;
+using System.Security.Principal;
+using System.Web.Helpers.Claims;
+using System.Web.Helpers.Claims.Test;
+using System.Web.Helpers.Test;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public class ClaimUidExtractorTest
+ {
+ [Fact]
+ public void ExtractClaimUid_NullIdentity()
+ {
+ // Arrange
+ ClaimUidExtractor extractor = new ClaimUidExtractor(
+ config: null,
+ claimsIdentityConverter: null);
+
+ // Act
+ BinaryBlob retVal = extractor.ExtractClaimUid(null);
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void ExtractClaimUid_Unauthenticated()
+ {
+ // Arrange
+ ClaimUidExtractor extractor = new ClaimUidExtractor(
+ config: null,
+ claimsIdentityConverter: null);
+
+ Mock<IIdentity> mockIdentity = new Mock<IIdentity>();
+ mockIdentity.Setup(o => o.IsAuthenticated).Returns(false);
+
+ // Act
+ BinaryBlob retVal = extractor.ExtractClaimUid(mockIdentity.Object);
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void ExtractClaimUid_ClaimsIdentityHeuristicsSuppressed()
+ {
+ // Arrange
+ GenericIdentity identity = new GenericIdentity("the-user");
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ SuppressIdentityHeuristicChecks = true
+ };
+
+ ClaimUidExtractor extractor = new ClaimUidExtractor(
+ config: config,
+ claimsIdentityConverter: null);
+
+ // Act
+ BinaryBlob retVal = extractor.ExtractClaimUid(identity);
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void ExtractClaimUid_NotAClaimsIdentity()
+ {
+ // Arrange
+ Mock<IIdentity> mockIdentity = new Mock<IIdentity>();
+ mockIdentity.Setup(o => o.IsAuthenticated).Returns(true);
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig();
+ ClaimsIdentityConverter converter = new ClaimsIdentityConverter(new Func<IIdentity, ClaimsIdentity>[0]);
+
+ ClaimUidExtractor extractor = new ClaimUidExtractor(
+ config: config,
+ claimsIdentityConverter: converter);
+
+ // Act
+ BinaryBlob retVal = extractor.ExtractClaimUid(mockIdentity.Object);
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void ExtractClaimUid_ClaimsIdentity()
+ {
+ // Arrange
+ Mock<IIdentity> mockIdentity = new Mock<IIdentity>();
+ mockIdentity.Setup(o => o.IsAuthenticated).Returns(true);
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ UniqueClaimTypeIdentifier = "unique-identifier"
+ };
+ ClaimsIdentityConverter converter = new ClaimsIdentityConverter(new Func<IIdentity, ClaimsIdentity>[] {
+ identity =>
+ {
+ Assert.Equal(mockIdentity.Object, identity);
+ MockClaimsIdentity claimsIdentity = new MockClaimsIdentity();
+ claimsIdentity.AddClaim("unique-identifier", "some-value");
+ return claimsIdentity;
+ }
+ });
+
+ ClaimUidExtractor extractor = new ClaimUidExtractor(
+ config: config,
+ claimsIdentityConverter: converter);
+
+ // Act
+ BinaryBlob retVal = extractor.ExtractClaimUid(mockIdentity.Object);
+
+ // Assert
+ Assert.NotNull(retVal);
+ Assert.Equal("CA9CCFF86F903FBB7505BAAA9F222E49EC2A1E8FAD630AE73DE180BD679751ED", HexUtil.HexEncode(retVal.GetData()));
+ }
+
+ [Theory]
+ [DefaultUniqueClaimTypes_NotPresent_Data]
+ public void DefaultUniqueClaimTypes_NotPresent_Throws(object identity)
+ {
+ // Arrange
+ ClaimsIdentity claimsIdentity = (ClaimsIdentity)identity;
+
+ // Act & assert
+ var ex = Assert.Throws<InvalidOperationException>(() => ClaimUidExtractor.GetUniqueIdentifierParameters(claimsIdentity, null));
+ Assert.Equal(@"A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier.", ex.Message);
+ }
+
+ [Fact]
+ public void DefaultUniqueClaimTypes_Present()
+ {
+ // Arrange
+ MockClaimsIdentity identity = new MockClaimsIdentity();
+ identity.AddClaim("fooClaim", "fooClaimValue");
+ identity.AddClaim(ClaimUidExtractor.NameIdentifierClaimType, "nameIdentifierValue");
+ identity.AddClaim(ClaimUidExtractor.IdentityProviderClaimType, "identityProviderValue");
+
+ // Act
+ var retVal = ClaimUidExtractor.GetUniqueIdentifierParameters(identity, null);
+
+ // Assert
+ Assert.Equal(new string[] {
+ ClaimUidExtractor.NameIdentifierClaimType,
+ "nameIdentifierValue",
+ ClaimUidExtractor.IdentityProviderClaimType,
+ "identityProviderValue"
+ }, retVal);
+ }
+
+ [Fact]
+ public void ExplicitUniqueClaimType_Present()
+ {
+ // Arrange
+ MockClaimsIdentity identity = new MockClaimsIdentity();
+ identity.AddClaim("fooClaim", "fooClaimValue");
+ identity.AddClaim(ClaimUidExtractor.NameIdentifierClaimType, "nameIdentifierValue");
+ identity.AddClaim(ClaimUidExtractor.IdentityProviderClaimType, "identityProviderValue");
+
+ // Act
+ var retVal = ClaimUidExtractor.GetUniqueIdentifierParameters(identity, "fooClaim");
+
+ // Assert
+ Assert.Equal(new string[] {
+ "fooClaim",
+ "fooClaimValue"
+ }, retVal);
+ }
+
+ [Theory]
+ [ExplicitUniqueClaimType_NotPresent_Data]
+ public void ExplicitUniqueClaimType_NotPresent_Throws(object identity)
+ {
+ // Arrange
+ ClaimsIdentity claimsIdentity = (ClaimsIdentity)identity;
+
+ // Act & assert
+ var ex = Assert.Throws<InvalidOperationException>(() => ClaimUidExtractor.GetUniqueIdentifierParameters(claimsIdentity, "fooClaim"));
+ Assert.Equal(@"A claim of type 'fooClaim' was not present on the provided ClaimsIdentity.", ex.Message);
+ }
+
+ private sealed class DefaultUniqueClaimTypes_NotPresent_DataAttribute : DataAttribute
+ {
+ public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest, Type[] parameterTypes)
+ {
+ MockClaimsIdentity identity1 = new MockClaimsIdentity();
+ identity1.AddClaim(ClaimUidExtractor.IdentityProviderClaimType, "identityProviderValue");
+ yield return new object[] { identity1 };
+
+ MockClaimsIdentity identity2 = new MockClaimsIdentity();
+ identity2.AddClaim(ClaimUidExtractor.NameIdentifierClaimType, String.Empty);
+ identity2.AddClaim(ClaimUidExtractor.IdentityProviderClaimType, "identityProviderValue");
+ yield return new object[] { identity2 };
+
+ MockClaimsIdentity identity3 = new MockClaimsIdentity();
+ identity3.AddClaim(ClaimUidExtractor.NameIdentifierClaimType, "nameIdentifierValue");
+ yield return new object[] { identity3 };
+
+ MockClaimsIdentity identity4 = new MockClaimsIdentity();
+ identity4.AddClaim(ClaimUidExtractor.NameIdentifierClaimType, "nameIdentifierValue");
+ identity4.AddClaim(ClaimUidExtractor.IdentityProviderClaimType, String.Empty);
+ yield return new object[] { identity4 };
+
+ MockClaimsIdentity identity5 = new MockClaimsIdentity();
+ identity5.AddClaim(ClaimUidExtractor.NameIdentifierClaimType.ToUpper(), "nameIdentifierValue");
+ identity5.AddClaim(ClaimUidExtractor.IdentityProviderClaimType.ToUpper(), "identityProviderValue");
+ yield return new object[] { identity5 };
+ }
+ }
+
+ private sealed class ExplicitUniqueClaimType_NotPresent_DataAttribute : DataAttribute
+ {
+ public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest, Type[] parameterTypes)
+ {
+ MockClaimsIdentity identity1 = new MockClaimsIdentity();
+ yield return new object[] { identity1 };
+
+ MockClaimsIdentity identity2 = new MockClaimsIdentity();
+ identity2.AddClaim("fooClaim", String.Empty);
+ yield return new object[] { identity2 };
+
+ MockClaimsIdentity identity3 = new MockClaimsIdentity();
+ identity3.AddClaim("FOOCLAIM", "fooClaimValue");
+ yield return new object[] { identity3 };
+
+ MockClaimsIdentity identity4 = new MockClaimsIdentity();
+ identity4.AddClaim(ClaimUidExtractor.NameIdentifierClaimType, "nameIdentifierValue");
+ identity4.AddClaim(ClaimUidExtractor.IdentityProviderClaimType, "identityProviderValue");
+ yield return new object[] { identity4 };
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/HexUtil.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/HexUtil.cs
new file mode 100644
index 00000000..52359eb8
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/HexUtil.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ internal static class HexUtil
+ {
+ public static string HexEncode(byte[] data)
+ {
+ StringBuilder sb = new StringBuilder(data.Length * 2);
+ foreach (byte b in data)
+ {
+ sb.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", b);
+ }
+ return sb.ToString();
+ }
+
+ public static byte[] HexDecode(string input)
+ {
+ List<byte> bytes = new List<byte>(input.Length / 2);
+ for (int i = 0; i < input.Length; i += 2)
+ {
+ bytes.Add(Byte.Parse(input.Substring(i, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture));
+ }
+ return bytes.ToArray();
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MachineKeyCryptoSystemTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MachineKeyCryptoSystemTest.cs
new file mode 100644
index 00000000..9869aea8
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MachineKeyCryptoSystemTest.cs
@@ -0,0 +1,120 @@
+using System.Web.Security;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public class MachineKeyCryptoSystemTest
+ {
+ private static readonly MachineKeyCryptoSystem _dummyCryptoSystem = new MachineKeyCryptoSystem(HexEncoder, HexDecoder);
+
+ [Fact]
+ public void Base64ToHex()
+ {
+ // Arrange
+ string base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_0";
+ string hex = "00108310518720928B30D38F41149351559761969B71D79F8218A39259A7A29AABB2DBAFC31CB3D35DB7E39EBBF3DFBF";
+
+ // Act
+ string retVal = MachineKeyCryptoSystem.Base64ToHex(base64);
+
+ // Assert
+ Assert.Equal(hex, retVal);
+ }
+
+ [Fact]
+ public void Base64ToHex_HexToBase64_RoundTrips()
+ {
+ for (int i = 0; i <= Byte.MaxValue; i++)
+ {
+ // Arrange
+ string hex = String.Format("{0:X2}", i);
+
+ // Act
+ string retVal = MachineKeyCryptoSystem.Base64ToHex(MachineKeyCryptoSystem.HexToBase64(hex));
+
+ // Assert
+ Assert.Equal(hex, retVal);
+ }
+ }
+
+ [Fact]
+ public void HexToBase64()
+ {
+ // Arrange
+ string base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_0";
+ string hex = "00108310518720928B30D38F41149351559761969B71D79F8218A39259A7A29AABB2DBAFC31CB3D35DB7E39EBBF3DFBF";
+
+ // Act
+ string retVal = MachineKeyCryptoSystem.HexToBase64(hex);
+
+ // Assert
+ Assert.Equal(base64, retVal);
+ }
+
+ [Fact]
+ public void Protect()
+ {
+ // Arrange
+ byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ // Act
+ string retVal = _dummyCryptoSystem.Protect(data);
+
+ // Assert
+ Assert.Equal("hYfyZgECAwQF0", retVal);
+ }
+
+ [Theory]
+ [InlineData("hYfyZwECAwQF0")] // bad MagicHeader
+ [InlineData("hYfy0")] // too short to contain MagicHeader
+ public void Unprotect_Failure(string protectedData)
+ {
+ // Act
+ byte[] retVal = _dummyCryptoSystem.Unprotect(protectedData);
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void Unprotect_Success()
+ {
+ // Arrange
+ byte[] expected = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ // Act
+ byte[] retVal = _dummyCryptoSystem.Unprotect("hYfyZgECAwQF0");
+
+ // Assert
+ Assert.Equal(expected, retVal);
+ }
+
+ [Fact]
+ public void Protect_Unprotect_RoundTrips()
+ {
+ // Arrange
+ byte[] data = new byte[1024];
+ new Random().NextBytes(data);
+
+ // Act
+ byte[] roundTripped = _dummyCryptoSystem.Unprotect(_dummyCryptoSystem.Protect(data));
+
+ // Assert
+ Assert.Equal(data, roundTripped);
+ }
+
+ private static string HexEncoder(byte[] data, MachineKeyProtection protection)
+ {
+ Assert.Equal(MachineKeyProtection.All, protection);
+ return HexUtil.HexEncode(data);
+ }
+
+ private static byte[] HexDecoder(string input, MachineKeyProtection protection)
+ {
+ Assert.Equal(MachineKeyProtection.All, protection);
+ return HexUtil.HexDecode(input);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockAntiForgeryConfig.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockAntiForgeryConfig.cs
new file mode 100644
index 00000000..4cf2663d
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockAntiForgeryConfig.cs
@@ -0,0 +1,41 @@
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public sealed class MockAntiForgeryConfig : IAntiForgeryConfig
+ {
+ public IAntiForgeryAdditionalDataProvider AdditionalDataProvider
+ {
+ get;
+ set;
+ }
+
+ public string CookieName
+ {
+ get;
+ set;
+ }
+
+ public string FormFieldName
+ {
+ get;
+ set;
+ }
+
+ public bool RequireSSL
+ {
+ get;
+ set;
+ }
+
+ public bool SuppressIdentityHeuristicChecks
+ {
+ get;
+ set;
+ }
+
+ public string UniqueClaimTypeIdentifier
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableAntiForgeryTokenSerializer.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableAntiForgeryTokenSerializer.cs
new file mode 100644
index 00000000..4e44d905
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableAntiForgeryTokenSerializer.cs
@@ -0,0 +1,19 @@
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ // An IAntiForgeryTokenSerializer that can be passed to MoQ.
+ public abstract class MockableAntiForgeryTokenSerializer : IAntiForgeryTokenSerializer
+ {
+ public abstract object Deserialize(string serializedToken);
+ public abstract string Serialize(object token);
+
+ AntiForgeryToken IAntiForgeryTokenSerializer.Deserialize(string serializedToken)
+ {
+ return (AntiForgeryToken)Deserialize(serializedToken);
+ }
+
+ string IAntiForgeryTokenSerializer.Serialize(AntiForgeryToken token)
+ {
+ return Serialize(token);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableClaimUidExtractor.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableClaimUidExtractor.cs
new file mode 100644
index 00000000..47a309ce
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableClaimUidExtractor.cs
@@ -0,0 +1,15 @@
+using System.Security.Principal;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ // An IClaimUidExtractor that can be passed to MoQ
+ public abstract class MockableClaimUidExtractor : IClaimUidExtractor
+ {
+ public abstract object ExtractClaimUid(IIdentity identity);
+
+ BinaryBlob IClaimUidExtractor.ExtractClaimUid(IIdentity identity)
+ {
+ return (BinaryBlob)ExtractClaimUid(identity);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableCryptoSystem.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableCryptoSystem.cs
new file mode 100644
index 00000000..952e371f
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableCryptoSystem.cs
@@ -0,0 +1,9 @@
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ // An ICryptoSystem that can be passed to MoQ
+ public abstract class MockableCryptoSystem : ICryptoSystem
+ {
+ public abstract string Protect(byte[] data);
+ public abstract byte[] Unprotect(string protectedData);
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableTokenStore.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableTokenStore.cs
new file mode 100644
index 00000000..36efcb18
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableTokenStore.cs
@@ -0,0 +1,27 @@
+using System.Security.Principal;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ // An ITokenStore that can be passed to MoQ
+ public abstract class MockableTokenStore : ITokenStore
+ {
+ public abstract object GetCookieToken(HttpContextBase httpContext);
+ public abstract object GetFormToken(HttpContextBase httpContext);
+ public abstract void SaveCookieToken(HttpContextBase httpContext, object token);
+
+ AntiForgeryToken ITokenStore.GetCookieToken(HttpContextBase httpContext)
+ {
+ return (AntiForgeryToken)GetCookieToken(httpContext);
+ }
+
+ AntiForgeryToken ITokenStore.GetFormToken(HttpContextBase httpContext)
+ {
+ return (AntiForgeryToken)GetFormToken(httpContext);
+ }
+
+ void ITokenStore.SaveCookieToken(HttpContextBase httpContext, AntiForgeryToken token)
+ {
+ SaveCookieToken(httpContext, (AntiForgeryToken)token);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableTokenValidator.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableTokenValidator.cs
new file mode 100644
index 00000000..1f77eef0
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/MockableTokenValidator.cs
@@ -0,0 +1,33 @@
+using System.Security.Principal;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ // An ITokenValidator that can be passed to MoQ
+ public abstract class MockableTokenValidator : ITokenValidator
+ {
+ public abstract object GenerateCookieToken();
+ public abstract object GenerateFormToken(HttpContextBase httpContext, IIdentity identity, object cookieToken);
+ public abstract bool IsCookieTokenValid(object cookieToken);
+ public abstract void ValidateTokens(HttpContextBase httpContext, IIdentity identity, object cookieToken, object formToken);
+
+ AntiForgeryToken ITokenValidator.GenerateCookieToken()
+ {
+ return (AntiForgeryToken)GenerateCookieToken();
+ }
+
+ AntiForgeryToken ITokenValidator.GenerateFormToken(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken)
+ {
+ return (AntiForgeryToken)GenerateFormToken(httpContext, identity, (AntiForgeryToken)cookieToken);
+ }
+
+ bool ITokenValidator.IsCookieTokenValid(AntiForgeryToken cookieToken)
+ {
+ return IsCookieTokenValid((AntiForgeryToken)cookieToken);
+ }
+
+ void ITokenValidator.ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken, AntiForgeryToken formToken)
+ {
+ ValidateTokens(httpContext, identity, (AntiForgeryToken)cookieToken, (AntiForgeryToken)formToken);
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/AntiXsrf/TokenValidatorTest.cs b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/TokenValidatorTest.cs
new file mode 100644
index 00000000..ecd95a21
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/AntiXsrf/TokenValidatorTest.cs
@@ -0,0 +1,515 @@
+using System.Security.Principal;
+using System.Web.Helpers.Test;
+using System.Web.Mvc;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.AntiXsrf.Test
+{
+ public class TokenValidatorTest
+ {
+ [Fact]
+ public void GenerateCookieToken()
+ {
+ // Arrange
+ TokenValidator tokenValidator = new TokenValidator(
+ config: null,
+ claimUidExtractor: null);
+
+ // Act
+ AntiForgeryToken retVal = tokenValidator.GenerateCookieToken();
+
+ // Assert
+ Assert.NotNull(retVal);
+ }
+
+ [Fact]
+ public void GenerateFormToken_AnonymousUser()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ Mock<IIdentity> mockIdentity = new Mock<IIdentity>();
+ mockIdentity.Setup(o => o.IsAuthenticated).Returns(false);
+
+ IAntiForgeryConfig config = new MockAntiForgeryConfig();
+
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: null);
+
+ // Act
+ var fieldToken = validator.GenerateFormToken(httpContext, mockIdentity.Object, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsSessionToken);
+ Assert.Equal("", fieldToken.Username);
+ Assert.Equal(null, fieldToken.ClaimUid);
+ Assert.Equal("", fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void GenerateFormToken_AuthenticatedWithoutUsernameAndNoAdditionalData_NoAdditionalData()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = new AntiForgeryToken()
+ {
+ IsSessionToken = true
+ };
+
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new MyAuthenticatedIdentityWithoutUsername();
+ IAntiForgeryConfig config = new MockAntiForgeryConfig();
+ IClaimUidExtractor claimUidExtractor = new Mock<MockableClaimUidExtractor>().Object;
+
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: claimUidExtractor);
+
+ // Act & assert
+ var ex = Assert.Throws<InvalidOperationException>(() => validator.GenerateFormToken(httpContext, identity, cookieToken));
+ Assert.Equal(@"The provided identity of type 'System.Web.Helpers.AntiXsrf.Test.TokenValidatorTest+MyAuthenticatedIdentityWithoutUsername' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user.", ex.Message);
+ }
+
+ [Fact]
+ public void GenerateFormToken_AuthenticatedWithoutUsernameAndNoAdditionalData_NoAdditionalData_SuppressHeuristics()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new MyAuthenticatedIdentityWithoutUsername();
+
+ IAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ SuppressIdentityHeuristicChecks = true
+ };
+ IClaimUidExtractor claimUidExtractor = new Mock<MockableClaimUidExtractor>().Object;
+
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: claimUidExtractor);
+
+ // Act
+ var fieldToken = validator.GenerateFormToken(httpContext, identity, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsSessionToken);
+ Assert.Equal("", fieldToken.Username);
+ Assert.Equal(null, fieldToken.ClaimUid);
+ Assert.Equal("", fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void GenerateFormToken_AuthenticatedWithoutUsername_WithAdditionalData()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new MyAuthenticatedIdentityWithoutUsername();
+
+ Mock<IAntiForgeryAdditionalDataProvider> mockAdditionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>();
+ mockAdditionalDataProvider.Setup(o => o.GetAdditionalData(httpContext)).Returns("additional-data");
+
+ IAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ AdditionalDataProvider = mockAdditionalDataProvider.Object
+ };
+ IClaimUidExtractor claimUidExtractor = new Mock<MockableClaimUidExtractor>().Object;
+
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: claimUidExtractor);
+
+ // Act
+ var fieldToken = validator.GenerateFormToken(httpContext, identity, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsSessionToken);
+ Assert.Equal("", fieldToken.Username);
+ Assert.Equal(null, fieldToken.ClaimUid);
+ Assert.Equal("additional-data", fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void GenerateFormToken_ClaimsBasedIdentity()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = new AntiForgeryToken() { IsSessionToken = true };
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new GenericIdentity("some-identity");
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ UniqueClaimTypeIdentifier = "unique-identifier"
+ };
+
+ BinaryBlob expectedClaimUid = new BinaryBlob(256);
+ Mock<MockableClaimUidExtractor> mockClaimUidExtractor = new Mock<MockableClaimUidExtractor>();
+ mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity)).Returns((object)expectedClaimUid);
+
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: mockClaimUidExtractor.Object);
+
+ // Act
+ var fieldToken = validator.GenerateFormToken(httpContext, identity, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsSessionToken);
+ Assert.Equal("", fieldToken.Username);
+ Assert.Equal(expectedClaimUid, fieldToken.ClaimUid);
+ Assert.Equal("", fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void GenerateFormToken_RegularUserWithUsername()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = new AntiForgeryToken() { IsSessionToken = true };
+
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ Mock<IIdentity> mockIdentity = new Mock<IIdentity>();
+ mockIdentity.Setup(o => o.IsAuthenticated).Returns(true);
+ mockIdentity.Setup(o => o.Name).Returns("my-username");
+
+ IAntiForgeryConfig config = new MockAntiForgeryConfig();
+ IClaimUidExtractor claimUidExtractor = new Mock<MockableClaimUidExtractor>().Object;
+
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: claimUidExtractor);
+
+ // Act
+ var fieldToken = validator.GenerateFormToken(httpContext, mockIdentity.Object, cookieToken);
+
+ // Assert
+ Assert.NotNull(fieldToken);
+ Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
+ Assert.False(fieldToken.IsSessionToken);
+ Assert.Equal("my-username", fieldToken.Username);
+ Assert.Equal(null, fieldToken.ClaimUid);
+ Assert.Equal("", fieldToken.AdditionalData);
+ }
+
+ [Fact]
+ public void IsCookieTokenValid_FieldToken_ReturnsFalse()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = new AntiForgeryToken()
+ {
+ IsSessionToken = false
+ };
+
+ TokenValidator validator = new TokenValidator(
+ config: null,
+ claimUidExtractor: null);
+
+ // Act
+ bool retVal = validator.IsCookieTokenValid(cookieToken);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void IsCookieTokenValid_NullToken_ReturnsFalse()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = null;
+ TokenValidator validator = new TokenValidator(
+ config: null,
+ claimUidExtractor: null);
+
+ // Act
+ bool retVal = validator.IsCookieTokenValid(cookieToken);
+
+ // Assert
+ Assert.False(retVal);
+ }
+
+ [Fact]
+ public void IsCookieTokenValid_ValidToken_ReturnsTrue()
+ {
+ // Arrange
+ AntiForgeryToken cookieToken = new AntiForgeryToken()
+ {
+ IsSessionToken = true
+ };
+
+ TokenValidator validator = new TokenValidator(
+ config: null,
+ claimUidExtractor: null);
+
+ // Act
+ bool retVal = validator.IsCookieTokenValid(cookieToken);
+
+ // Assert
+ Assert.True(retVal);
+ }
+
+ [Fact]
+ public void ValidateTokens_SessionTokenMissing()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new Mock<IIdentity>().Object;
+ AntiForgeryToken sessionToken = null;
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { IsSessionToken = false };
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ CookieName = "my-cookie-name"
+ };
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: null);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
+ Assert.Equal(@"The required anti-forgery cookie ""my-cookie-name"" is not present.", ex.Message);
+ }
+
+ [Fact]
+ public void ValidateTokens_FieldTokenMissing()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new Mock<IIdentity>().Object;
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = null;
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ FormFieldName = "my-form-field-name"
+ };
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: null);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
+ Assert.Equal(@"The required anti-forgery form field ""my-form-field-name"" is not present.", ex.Message);
+ }
+
+ [Fact]
+ public void ValidateTokens_FieldAndSessionTokensSwapped()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new Mock<IIdentity>().Object;
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { IsSessionToken = false };
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ CookieName = "my-cookie-name",
+ FormFieldName = "my-form-field-name"
+ };
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: null);
+
+ // Act & assert
+ var ex1 = Assert.Throws<HttpAntiForgeryException>(() => validator.ValidateTokens(httpContext, identity, fieldtoken, fieldtoken));
+ Assert.Equal(@"Validation of the provided anti-forgery token failed. The cookie ""my-cookie-name"" and the form field ""my-form-field-name"" were swapped.", ex1.Message);
+
+ var ex2 = Assert.Throws<HttpAntiForgeryException>(() => validator.ValidateTokens(httpContext, identity, sessionToken, sessionToken));
+ Assert.Equal(@"Validation of the provided anti-forgery token failed. The cookie ""my-cookie-name"" and the form field ""my-form-field-name"" were swapped.", ex2.Message);
+ }
+
+ [Fact]
+ public void ValidateTokens_FieldAndSessionTokensHaveDifferentSecurityKeys()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new Mock<IIdentity>().Object;
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { IsSessionToken = false };
+
+ TokenValidator validator = new TokenValidator(
+ config: null,
+ claimUidExtractor: null);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
+ Assert.Equal(@"The anti-forgery cookie token and form field token do not match.", ex.Message);
+ }
+
+ [Theory]
+ [InlineData("the-user", "the-other-user")]
+ [InlineData("http://example.com/uri-casing", "http://example.com/URI-casing")]
+ [InlineData("https://example.com/secure-uri-casing", "https://example.com/secure-URI-casing")]
+ public void ValidateTokens_UsernameMismatch(string identityUsername, string embeddedUsername)
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new GenericIdentity(identityUsername);
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { SecurityToken = sessionToken.SecurityToken, Username = embeddedUsername, IsSessionToken = false };
+
+ Mock<MockableClaimUidExtractor> mockClaimUidExtractor = new Mock<MockableClaimUidExtractor>();
+ mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity)).Returns((object)null);
+
+ TokenValidator validator = new TokenValidator(
+ config: null,
+ claimUidExtractor: mockClaimUidExtractor.Object);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
+ Assert.Equal(@"The provided anti-forgery token was meant for user """ + embeddedUsername + @""", but the current user is """ + identityUsername + @""".", ex.Message);
+ }
+
+ [Fact]
+ public void ValidateTokens_ClaimUidMismatch()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new GenericIdentity("the-user");
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { SecurityToken = sessionToken.SecurityToken, IsSessionToken = false, ClaimUid = new BinaryBlob(256) };
+
+ Mock<MockableClaimUidExtractor> mockClaimUidExtractor = new Mock<MockableClaimUidExtractor>();
+ mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity)).Returns(new BinaryBlob(256));
+
+ TokenValidator validator = new TokenValidator(
+ config: null,
+ claimUidExtractor: mockClaimUidExtractor.Object);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
+ Assert.Equal(@"The provided anti-forgery token was meant for a different claims-based user than the current user.", ex.Message);
+ }
+
+ [Fact]
+ public void ValidateTokens_AdditionalDataRejected()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new GenericIdentity(String.Empty);
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { SecurityToken = sessionToken.SecurityToken, Username = String.Empty, IsSessionToken = false, AdditionalData = "some-additional-data" };
+
+ Mock<IAntiForgeryAdditionalDataProvider> mockAdditionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>();
+ mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data")).Returns(false);
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ AdditionalDataProvider = mockAdditionalDataProvider.Object
+ };
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: null);
+
+ // Act & assert
+ var ex = Assert.Throws<HttpAntiForgeryException>(() => validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
+ Assert.Equal(@"The provided anti-forgery token failed a custom data check.", ex.Message);
+ }
+
+ [Fact]
+ public void ValidateTokens_Success_AnonymousUser()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new GenericIdentity(String.Empty);
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { SecurityToken = sessionToken.SecurityToken, Username = String.Empty, IsSessionToken = false, AdditionalData = "some-additional-data" };
+
+ Mock<IAntiForgeryAdditionalDataProvider> mockAdditionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>();
+ mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data")).Returns(true);
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ AdditionalDataProvider = mockAdditionalDataProvider.Object
+ };
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: null);
+
+ // Act
+ validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken);
+
+ // Assert
+ // Nothing to assert - if we got this far, success!
+ }
+
+ [Fact]
+ public void ValidateTokens_Success_AuthenticatedUserWithUsername()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new GenericIdentity("the-user");
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { SecurityToken = sessionToken.SecurityToken, Username = "THE-USER", IsSessionToken = false, AdditionalData = "some-additional-data" };
+
+ Mock<IAntiForgeryAdditionalDataProvider> mockAdditionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>();
+ mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data")).Returns(true);
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig()
+ {
+ AdditionalDataProvider = mockAdditionalDataProvider.Object
+ };
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: new Mock<MockableClaimUidExtractor>().Object);
+
+ // Act
+ validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken);
+
+ // Assert
+ // Nothing to assert - if we got this far, success!
+ }
+
+ [Fact]
+ public void ValidateTokens_Success_ClaimsBasedUser()
+ {
+ // Arrange
+ HttpContextBase httpContext = new Mock<HttpContextBase>().Object;
+ IIdentity identity = new GenericIdentity("the-user");
+ AntiForgeryToken sessionToken = new AntiForgeryToken() { IsSessionToken = true };
+ AntiForgeryToken fieldtoken = new AntiForgeryToken() { SecurityToken = sessionToken.SecurityToken, IsSessionToken = false, ClaimUid = new BinaryBlob(256) };
+
+ Mock<MockableClaimUidExtractor> mockClaimUidExtractor = new Mock<MockableClaimUidExtractor>();
+ mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity)).Returns(fieldtoken.ClaimUid);
+
+ MockAntiForgeryConfig config = new MockAntiForgeryConfig();
+
+ TokenValidator validator = new TokenValidator(
+ config: config,
+ claimUidExtractor: mockClaimUidExtractor.Object);
+
+ // Act
+ validator.ValidateTokens(httpContext, identity, sessionToken, fieldtoken);
+
+ // Assert
+ // Nothing to assert - if we got this far, success!
+ }
+
+ private sealed class MyAuthenticatedIdentityWithoutUsername : IIdentity
+ {
+ public string AuthenticationType
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public bool IsAuthenticated
+ {
+ get { return true; }
+ }
+
+ public string Name
+ {
+ get { return String.Empty; }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/Claims/ClaimTest.cs b/test/System.Web.WebPages.Test/Helpers/Claims/ClaimTest.cs
new file mode 100644
index 00000000..a8adea4f
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/Claims/ClaimTest.cs
@@ -0,0 +1,62 @@
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Claims.Test
+{
+ public class ClaimTest
+ {
+ [Fact]
+ public void CtorAndProperties()
+ {
+ // Act
+ Claim claim = new Claim("claim-type", "claim-value");
+
+ // Assert
+ Assert.Equal("claim-type", claim.ClaimType);
+ Assert.Equal("claim-value", claim.Value);
+ }
+
+ [Fact]
+ public void Create_WithClaimTypeProperty()
+ {
+ // Act
+ Claim claim = Claim.Create<IClaimType1>(new MyClaimType());
+
+ // Assert
+ Assert.Equal("my-claim-type-1", claim.ClaimType);
+ Assert.Equal("my-claim-value-1", claim.Value);
+ }
+
+ [Fact]
+ public void Create_WithTypeProperty()
+ {
+ // Act
+ Claim claim = Claim.Create<IClaimType2>(new MyClaimType());
+
+ // Assert
+ Assert.Equal("my-claim-type-2", claim.ClaimType);
+ Assert.Equal("my-claim-value-2", claim.Value);
+ }
+
+ private interface IClaimType1
+ {
+ string ClaimType { get; }
+ string Value { get; }
+ }
+
+ private interface IClaimType2
+ {
+ string Type { get; }
+ string Value { get; }
+ }
+
+ private sealed class MyClaimType : IClaimType1, IClaimType2
+ {
+ string IClaimType1.ClaimType { get { return "my-claim-type-1"; } }
+ string IClaimType1.Value { get { return "my-claim-value-1"; } }
+
+ string IClaimType2.Type { get { return "my-claim-type-2"; } }
+ string IClaimType2.Value { get { return "my-claim-value-2"; } }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/Claims/ClaimsIdentityConverterTest.cs b/test/System.Web.WebPages.Test/Helpers/Claims/ClaimsIdentityConverterTest.cs
new file mode 100644
index 00000000..365381e3
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/Claims/ClaimsIdentityConverterTest.cs
@@ -0,0 +1,98 @@
+using System.Collections.Generic;
+using System.Reflection;
+using System.Security.Principal;
+using System.Web.Security;
+using Moq;
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Claims.Test
+{
+ public class ClaimsIdentityConverterTest
+ {
+ [Fact]
+ public void TryConvert_NoMatches_ReturnsNull()
+ {
+ // Arrange
+ IIdentity identity = new Mock<IIdentity>().Object;
+ ClaimsIdentityConverter converter = new ClaimsIdentityConverter(new Func<IIdentity, ClaimsIdentity>[0]);
+
+ // Act
+ ClaimsIdentity retVal = converter.TryConvert(identity);
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ [Fact]
+ public void TryConvert_ReturnsFirstMatch()
+ {
+ // Arrange
+ IIdentity identity = new Mock<IIdentity>().Object;
+ ClaimsIdentity claimsIdentity = new MockClaimsIdentity();
+
+ ClaimsIdentityConverter converter = new ClaimsIdentityConverter(new Func<IIdentity, ClaimsIdentity>[]
+ {
+ _ => null,
+ i => (i == identity) ? claimsIdentity : null
+ });
+
+ // Act
+ ClaimsIdentity retVal = converter.TryConvert(identity);
+
+ // Assert
+ Assert.Same(claimsIdentity, retVal);
+ }
+
+ [Theory]
+ [GrandfatheredTypesData]
+ public void TryConvert_SkipsGrandfatheredTypes(IIdentity identity)
+ {
+ // Arrange
+ ClaimsIdentityConverter converter = new ClaimsIdentityConverter(new Func<IIdentity, ClaimsIdentity>[]
+ {
+ _ => { throw new Exception("Should never be called."); }
+ });
+
+ // Act
+ ClaimsIdentity retVal = converter.TryConvert(identity);
+
+ // Assert
+ Assert.Null(retVal);
+ }
+
+ private sealed class GrandfatheredTypesDataAttribute : DataAttribute
+ {
+ // We need to subclass these types so that they implement the
+ // appropriate interface to be claims-based.
+ public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest, Type[] parameterTypes)
+ {
+ yield return new object[] { new SubclassedFormsIdentity() };
+ yield return new object[] { new SubclassedGenericIdentity() };
+
+ SubclassedWindowsIdentity subclassedWindowsIdentity = null;
+ using (WindowsIdentity originalIdentity = WindowsIdentity.GetCurrent())
+ {
+ subclassedWindowsIdentity = new SubclassedWindowsIdentity(originalIdentity.Token);
+ }
+ yield return new object[] { subclassedWindowsIdentity };
+ }
+ }
+
+ private sealed class SubclassedFormsIdentity : FormsIdentity
+ {
+ public SubclassedFormsIdentity() : base(new FormsAuthenticationTicket("my-name", false, 60)) { }
+ }
+
+ private sealed class SubclassedGenericIdentity : GenericIdentity
+ {
+ public SubclassedGenericIdentity() : base("my-name") { }
+ }
+
+ private sealed class SubclassedWindowsIdentity : WindowsIdentity
+ {
+ public SubclassedWindowsIdentity(IntPtr userToken) : base(userToken) { }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/Claims/ClaimsIdentityTest.cs b/test/System.Web.WebPages.Test/Helpers/Claims/ClaimsIdentityTest.cs
new file mode 100644
index 00000000..36367b66
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/Claims/ClaimsIdentityTest.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Principal;
+using Xunit;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Claims.Test
+{
+ public class ClaimsIdentityTest
+ {
+ [Fact]
+ public void TryConvert_GetClaims()
+ {
+ // Act
+ ClaimsIdentity claimsIdentity = ClaimsIdentity.TryConvert<IClaimsIdentity, IClaim>(new MyClaimsIdentity());
+ var claims = claimsIdentity.GetClaims().ToArray();
+
+ // Assert
+ Assert.Equal(2, claims.Length);
+ Assert.Equal("claim-type-1", claims[0].ClaimType);
+ Assert.Equal("claim-value-1", claims[0].Value);
+ Assert.Equal("claim-type-2", claims[1].ClaimType);
+ Assert.Equal("claim-value-2", claims[1].Value);
+ }
+
+ private interface IClaimsIdentity : IIdentity
+ {
+ IEnumerable<IClaim> Claims { get; }
+ }
+
+ private interface IClaim
+ {
+ string ClaimType { get; }
+ string Value { get; }
+ }
+
+ private sealed class MyClaimsIdentity : IClaimsIdentity, IIdentity
+ {
+ IEnumerable<IClaim> IClaimsIdentity.Claims
+ {
+ get
+ {
+ return new MyClaim[]
+ {
+ new MyClaim() { ClaimType = "claim-type-1", Value = "claim-value-1" },
+ new MyClaim() { ClaimType = "claim-type-2", Value = "claim-value-2" }
+ };
+ }
+ }
+
+ string IIdentity.AuthenticationType
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ bool IIdentity.IsAuthenticated
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ string IIdentity.Name
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ private sealed class MyClaim : IClaim
+ {
+ public string ClaimType { get; set; }
+ public string Value { get; set; }
+ }
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/Claims/MockClaimsIdentity.cs b/test/System.Web.WebPages.Test/Helpers/Claims/MockClaimsIdentity.cs
new file mode 100644
index 00000000..2e52430d
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/Claims/MockClaimsIdentity.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+
+namespace System.Web.Helpers.Claims.Test
+{
+ // Convenient class for mocking a ClaimsIdentity instance given some
+ // prefabricated Claim instances.
+ internal sealed class MockClaimsIdentity : ClaimsIdentity
+ {
+ private readonly List<Claim> _claims = new List<Claim>();
+
+ public void AddClaim(string claimType, string value)
+ {
+ _claims.Add(new Claim(claimType, value));
+ }
+
+ public override IEnumerable<Claim> GetClaims()
+ {
+ return _claims;
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/Helpers/CryptoUtilTest.cs b/test/System.Web.WebPages.Test/Helpers/CryptoUtilTest.cs
new file mode 100644
index 00000000..25a53ea9
--- /dev/null
+++ b/test/System.Web.WebPages.Test/Helpers/CryptoUtilTest.cs
@@ -0,0 +1,57 @@
+using Xunit;
+using Xunit.Extensions;
+using Assert = Microsoft.TestCommon.AssertEx;
+
+namespace System.Web.Helpers.Test
+{
+ public class CryptoUtilTest
+ {
+ [Theory]
+ [InlineData(new byte[0], null)]
+ [InlineData(null, new byte[0])]
+ [InlineData(new byte[0], new byte[] { 0x00 })]
+ [InlineData(new byte[] { 0x01, 0x02 }, new byte[] { 0x02, 0x01 })]
+ public void AreByteArraysEqual_False(byte[] a, byte[] b)
+ {
+ // Act
+ bool retVal = CryptoUtil.AreByteArraysEqual(a, b);
+
+ // Assert
+ Assert.NotEqual(a, b);
+ }
+
+ [Fact]
+ public void AreByteArraysEqual_True()
+ {
+ // Arrange
+ byte[] a = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+ byte[] b = (byte[])a.Clone();
+
+ // Act
+ bool retVal = CryptoUtil.AreByteArraysEqual(a, b);
+
+ // Assert
+ Assert.Equal(a, b);
+ }
+
+ [Fact]
+ public void TestVectors_Empty()
+ {
+ // Act
+ byte[] retVal = CryptoUtil.ComputeSHA256(new string[0]);
+
+ // Assert
+ Assert.Equal("47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", Convert.ToBase64String(retVal));
+ }
+
+ [Fact]
+ public void TestVectors_NonEmpty()
+ {
+ // Act
+ byte[] retVal = CryptoUtil.ComputeSHA256(new string[] { "a parameter", "another parameter" });
+
+ // Assert
+ Assert.Equal("Bez9yYh4Zq9jK1H5jD21wh04HTZi/vgxp6yDE7Y6cfo=", Convert.ToBase64String(retVal));
+ }
+ }
+}
diff --git a/test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj b/test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj
index 6ad4e51d..d3828d3c 100644
--- a/test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj
+++ b/test/System.Web.WebPages.Test/System.Web.WebPages.Test.csproj
@@ -65,6 +65,27 @@
<Compile Include="ApplicationParts\MimeMappingTest.cs" />
<Compile Include="ApplicationParts\ResourceHandlerTest.cs" />
<Compile Include="ApplicationParts\TestResourceAssembly.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryTokenStoreTest.cs" />
+ <Compile Include="Helpers\AntiXsrf\MockableTokenStore.cs" />
+ <Compile Include="Helpers\AntiXsrf\MockableTokenValidator.cs" />
+ <Compile Include="Helpers\AntiXsrf\TokenValidatorTest.cs" />
+ <Compile Include="Helpers\AntiXsrf\MockableClaimUidExtractor.cs" />
+ <Compile Include="Helpers\AntiXsrf\MockableCryptoSystem.cs" />
+ <Compile Include="Helpers\AntiXsrf\HexUtil.cs" />
+ <Compile Include="Helpers\AntiXsrf\MachineKeyCryptoSystemTest.cs" />
+ <Compile Include="Helpers\Claims\ClaimTest.cs" />
+ <Compile Include="Helpers\Claims\ClaimsIdentityTest.cs" />
+ <Compile Include="Helpers\Claims\ClaimsIdentityConverterTest.cs" />
+ <Compile Include="Helpers\AntiXsrf\ClaimUidExtractorTest.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryTokenTest.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryTokenSerializerTest.cs" />
+ <Compile Include="Helpers\AntiXsrf\AntiForgeryWorkerTest.cs" />
+ <Compile Include="Helpers\AntiForgeryConfigTest.cs" />
+ <Compile Include="Helpers\AntiXsrf\MockableAntiForgeryTokenSerializer.cs" />
+ <Compile Include="Helpers\AntiXsrf\MockAntiForgeryConfig.cs" />
+ <Compile Include="Helpers\CryptoUtilTest.cs" />
+ <Compile Include="Helpers\Claims\MockClaimsIdentity.cs" />
+ <Compile Include="Helpers\AntiXsrf\BinaryBlobTest.cs" />
<Compile Include="Utils\SessionStateUtilTest.cs" />
<Compile Include="WebPage\BrowserHelpersTest.cs" />
<Compile Include="WebPage\BrowserOverrideStoresTest.cs" />
@@ -76,10 +97,7 @@
<Compile Include="Extensions\HttpRequestExtensionsTest.cs" />
<Compile Include="Extensions\StringExtensionsTest.cs" />
<Compile Include="Extensions\HttpResponseExtensionsTest.cs" />
- <Compile Include="Helpers\AntiForgeryDataSerializerTest.cs" />
- <Compile Include="Helpers\AntiForgeryDataTest.cs" />
<Compile Include="Helpers\AntiForgeryTest.cs" />
- <Compile Include="Helpers\AntiForgeryWorkerTest.cs" />
<Compile Include="Helpers\UnvalidatedRequestValuesTest.cs" />
<Compile Include="Html\CheckBoxTest.cs" />
<Compile Include="Html\HtmlHelperFactory.cs" />