Fix #5 - better error message for missing tokens

This fix changes the model for error messaging in antiforgery. Now only
the token store will report a detailed error message including the names
of form field and cookie. Other components will give more generic errors
and assume that this was handled by the token store.

This way you still see an error if the user creates a token store that
doesn't throw, but it's a generic error that doesn't give incorrect
information.
This commit is contained in:
Ryan Nowak 2015-07-30 15:04:50 -07:00 committed by unknown
parent fbe9dc58e8
commit 9bcecf3994
10 changed files with 239 additions and 211 deletions

View File

@ -76,12 +76,8 @@ namespace Microsoft.AspNet.Antiforgery
{ {
CheckSSLConfig(context); CheckSSLConfig(context);
// Extract cookie & form tokens var tokens = await _tokenStore.GetRequestTokensAsync(context);
var cookieToken = _tokenStore.GetCookieToken(context); ValidateTokens(context, tokens);
var formToken = await _tokenStore.GetFormTokenAsync(context);
// Validate
_tokenGenerator.ValidateTokens(context, cookieToken, formToken);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -89,9 +85,23 @@ namespace Microsoft.AspNet.Antiforgery
{ {
CheckSSLConfig(context); CheckSSLConfig(context);
if (string.IsNullOrEmpty(antiforgeryTokenSet.CookieToken))
{
throw new ArgumentException(
Resources.Antiforgery_CookieToken_MustBeProvided_Generic,
nameof(antiforgeryTokenSet));
}
if (string.IsNullOrEmpty(antiforgeryTokenSet.FormToken))
{
throw new ArgumentException(
Resources.Antiforgery_FormToken_MustBeProvided_Generic,
nameof(antiforgeryTokenSet));
}
// Extract cookie & form tokens // Extract cookie & form tokens
var deserializedCookieToken = DeserializeToken(antiforgeryTokenSet.CookieToken); var deserializedCookieToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.CookieToken);
var deserializedFormToken = DeserializeToken(antiforgeryTokenSet.FormToken); var deserializedFormToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.FormToken);
// Validate // Validate
_tokenGenerator.ValidateTokens( _tokenGenerator.ValidateTokens(
@ -154,26 +164,6 @@ namespace Microsoft.AspNet.Antiforgery
} }
} }
private AntiforgeryToken DeserializeToken(string serializedToken)
{
return (!string.IsNullOrEmpty(serializedToken))
? _tokenSerializer.Deserialize(serializedToken)
: null;
}
private AntiforgeryToken DeserializeTokenDoesNotThrow(string serializedToken)
{
try
{
return DeserializeToken(serializedToken);
}
catch
{
// ignore failures since we'll just generate a new token
return null;
}
}
private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext context) private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext context)
{ {
try try

View File

@ -6,22 +6,18 @@ using System.Diagnostics;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Principal; using System.Security.Principal;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Antiforgery namespace Microsoft.AspNet.Antiforgery
{ {
public class DefaultAntiforgeryTokenGenerator : IAntiforgeryTokenGenerator public class DefaultAntiforgeryTokenGenerator : IAntiforgeryTokenGenerator
{ {
private readonly IClaimUidExtractor _claimUidExtractor; private readonly IClaimUidExtractor _claimUidExtractor;
private readonly AntiforgeryOptions _options;
private readonly IAntiforgeryAdditionalDataProvider _additionalDataProvider; private readonly IAntiforgeryAdditionalDataProvider _additionalDataProvider;
public DefaultAntiforgeryTokenGenerator( public DefaultAntiforgeryTokenGenerator(
IOptions<AntiforgeryOptions> optionsAccessor,
IClaimUidExtractor claimUidExtractor, IClaimUidExtractor claimUidExtractor,
IAntiforgeryAdditionalDataProvider additionalDataProvider) IAntiforgeryAdditionalDataProvider additionalDataProvider)
{ {
_options = optionsAccessor.Options;
_claimUidExtractor = claimUidExtractor; _claimUidExtractor = claimUidExtractor;
_additionalDataProvider = additionalDataProvider; _additionalDataProvider = additionalDataProvider;
} }
@ -96,23 +92,24 @@ namespace Microsoft.AspNet.Antiforgery
AntiforgeryToken sessionToken, AntiforgeryToken sessionToken,
AntiforgeryToken fieldToken) AntiforgeryToken fieldToken)
{ {
// Were the tokens even present at all?
if (sessionToken == null) if (sessionToken == null)
{ {
throw new InvalidOperationException( throw new ArgumentNullException(
Resources.FormatAntiforgeryToken_CookieMissing(_options.CookieName)); nameof(sessionToken),
Resources.Antiforgery_CookieToken_MustBeProvided_Generic);
} }
if (fieldToken == null) if (fieldToken == null)
{ {
throw new InvalidOperationException( throw new ArgumentNullException(
Resources.FormatAntiforgeryToken_FormFieldMissing(_options.FormFieldName)); nameof(fieldToken),
Resources.Antiforgery_FormToken_MustBeProvided_Generic);
} }
// Do the tokens have the correct format? // Do the tokens have the correct format?
if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken) if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(Resources.AntiforgeryToken_TokensSwapped);
Resources.FormatAntiforgeryToken_TokensSwapped(_options.CookieName, _options.FormFieldName));
} }
// Are the security tokens embedded in each incoming token identical? // Are the security tokens embedded in each incoming token identical?

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
@ -43,17 +44,24 @@ namespace Microsoft.AspNet.Antiforgery
return _tokenSerializer.Deserialize(requestCookie); return _tokenSerializer.Deserialize(requestCookie);
} }
public async Task<AntiforgeryToken> GetFormTokenAsync(HttpContext httpContext) public async Task<AntiforgeryTokenSet> GetRequestTokensAsync([NotNull] HttpContext httpContext)
{ {
var form = await httpContext.Request.ReadFormAsync(); var requestCookie = httpContext.Request.Cookies[_options.CookieName];
var value = form[_options.FormFieldName]; if (string.IsNullOrEmpty(requestCookie))
if (string.IsNullOrEmpty(value))
{ {
// did not exist throw new InvalidOperationException(
return null; Resources.FormatAntiforgery_CookieToken_MustBeProvided(_options.CookieName));
} }
return _tokenSerializer.Deserialize(value); var form = await httpContext.Request.ReadFormAsync();
var formField = form[_options.FormFieldName];
if (string.IsNullOrEmpty(formField))
{
throw new InvalidOperationException(
Resources.FormatAntiforgery_FormToken_MustBeProvided(_options.FormFieldName));
}
return new AntiforgeryTokenSet(formField, requestCookie);
} }
public void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token) public void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token)

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Claims;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Antiforgery namespace Microsoft.AspNet.Antiforgery
{ {
@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Antiforgery
// Given a cookie token, generates a corresponding form token. // Given a cookie token, generates a corresponding form token.
// The incoming cookie token must be valid. // The incoming cookie token must be valid.
AntiforgeryToken GenerateFormToken( AntiforgeryToken GenerateFormToken(
HttpContext httpContext, [NotNull] HttpContext httpContext,
AntiforgeryToken cookieToken); AntiforgeryToken cookieToken);
// Determines whether an existing cookie token is valid (well-formed). // Determines whether an existing cookie token is valid (well-formed).
@ -26,8 +26,8 @@ namespace Microsoft.AspNet.Antiforgery
// Validates a (cookie, form) token pair. // Validates a (cookie, form) token pair.
void ValidateTokens( void ValidateTokens(
HttpContext httpContext, [NotNull] HttpContext httpContext,
AntiforgeryToken cookieToken, [NotNull] AntiforgeryToken cookieToken,
AntiforgeryToken formToken); [NotNull] AntiforgeryToken formToken);
} }
} }

View File

@ -3,14 +3,23 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Antiforgery namespace Microsoft.AspNet.Antiforgery
{ {
// Provides an abstraction around how tokens are persisted and retrieved for a request // Provides an abstraction around how tokens are persisted and retrieved for a request
public interface IAntiforgeryTokenStore public interface IAntiforgeryTokenStore
{ {
AntiforgeryToken GetCookieToken(HttpContext httpContext); AntiforgeryToken GetCookieToken([NotNull] HttpContext httpContext);
Task<AntiforgeryToken> GetFormTokenAsync(HttpContext httpContext);
void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token); /// <summary>
/// Gets the cookie and form tokens from the request. Will throw an exception if either token is
/// not present.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
/// <returns>The <see cref="AntiforgeryTokenSet"/>.</returns>
Task<AntiforgeryTokenSet> GetRequestTokensAsync([NotNull] HttpContext httpContext);
void SaveCookieToken([NotNull] HttpContext httpContext, [NotNull] AntiforgeryToken token);
} }
} }

View File

@ -58,22 +58,6 @@ namespace Microsoft.AspNet.Antiforgery
return GetString("AntiforgeryToken_ClaimUidMismatch"); return GetString("AntiforgeryToken_ClaimUidMismatch");
} }
/// <summary>
/// The required antiforgery cookie "{0}" is not present.
/// </summary>
internal static string AntiforgeryToken_CookieMissing
{
get { return GetString("AntiforgeryToken_CookieMissing"); }
}
/// <summary>
/// The required antiforgery cookie "{0}" is not present.
/// </summary>
internal static string FormatAntiforgeryToken_CookieMissing(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryToken_CookieMissing"), p0);
}
/// <summary> /// <summary>
/// The antiforgery token could not be decrypted. /// The antiforgery token could not be decrypted.
/// </summary> /// </summary>
@ -90,22 +74,6 @@ namespace Microsoft.AspNet.Antiforgery
return GetString("AntiforgeryToken_DeserializationFailed"); return GetString("AntiforgeryToken_DeserializationFailed");
} }
/// <summary>
/// The required antiforgery form field "{0}" is not present.
/// </summary>
internal static string AntiforgeryToken_FormFieldMissing
{
get { return GetString("AntiforgeryToken_FormFieldMissing"); }
}
/// <summary>
/// The required antiforgery form field "{0}" is not present.
/// </summary>
internal static string FormatAntiforgeryToken_FormFieldMissing(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryToken_FormFieldMissing"), p0);
}
/// <summary> /// <summary>
/// The antiforgery cookie token and form field token do not match. /// The antiforgery cookie token and form field token do not match.
/// </summary> /// </summary>
@ -123,7 +91,7 @@ namespace Microsoft.AspNet.Antiforgery
} }
/// <summary> /// <summary>
/// Validation of the provided antiforgery token failed. The cookie "{0}" and the form field "{1}" were swapped. /// Validation of the provided antiforgery token failed. The cookie token and the form token were swapped.
/// </summary> /// </summary>
internal static string AntiforgeryToken_TokensSwapped internal static string AntiforgeryToken_TokensSwapped
{ {
@ -131,11 +99,11 @@ namespace Microsoft.AspNet.Antiforgery
} }
/// <summary> /// <summary>
/// Validation of the provided antiforgery token failed. The cookie "{0}" and the form field "{1}" were swapped. /// Validation of the provided antiforgery token failed. The cookie token and the form token were swapped.
/// </summary> /// </summary>
internal static string FormatAntiforgeryToken_TokensSwapped(object p0, object p1) internal static string FormatAntiforgeryToken_TokensSwapped()
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryToken_TokensSwapped"), p0, p1); return GetString("AntiforgeryToken_TokensSwapped");
} }
/// <summary> /// <summary>
@ -170,6 +138,70 @@ namespace Microsoft.AspNet.Antiforgery
return string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryWorker_RequireSSL"), p0, p1, p2); return string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryWorker_RequireSSL"), p0, p1, p2);
} }
/// <summary>
/// The required antiforgery cookie "{0}" is not present.
/// </summary>
internal static string Antiforgery_CookieToken_MustBeProvided
{
get { return GetString("Antiforgery_CookieToken_MustBeProvided"); }
}
/// <summary>
/// The required antiforgery cookie "{0}" is not present.
/// </summary>
internal static string FormatAntiforgery_CookieToken_MustBeProvided(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_CookieToken_MustBeProvided"), p0);
}
/// <summary>
/// The cookie token must be provided.
/// </summary>
internal static string Antiforgery_CookieToken_MustBeProvided_Generic
{
get { return GetString("Antiforgery_CookieToken_MustBeProvided_Generic"); }
}
/// <summary>
/// The cookie token must be provided.
/// </summary>
internal static string FormatAntiforgery_CookieToken_MustBeProvided_Generic()
{
return GetString("Antiforgery_CookieToken_MustBeProvided_Generic");
}
/// <summary>
/// The required antiforgery form field "{0}" is not present.
/// </summary>
internal static string Antiforgery_FormToken_MustBeProvided
{
get { return GetString("Antiforgery_FormToken_MustBeProvided"); }
}
/// <summary>
/// The required antiforgery form field "{0}" is not present.
/// </summary>
internal static string FormatAntiforgery_FormToken_MustBeProvided(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_FormToken_MustBeProvided"), p0);
}
/// <summary>
/// The form token must be provided.
/// </summary>
internal static string Antiforgery_FormToken_MustBeProvided_Generic
{
get { return GetString("Antiforgery_FormToken_MustBeProvided_Generic"); }
}
/// <summary>
/// The form token must be provided.
/// </summary>
internal static string FormatAntiforgery_FormToken_MustBeProvided_Generic()
{
return GetString("Antiforgery_FormToken_MustBeProvided_Generic");
}
/// <summary> /// <summary>
/// Value cannot be null or empty. /// Value cannot be null or empty.
/// </summary> /// </summary>

View File

@ -127,20 +127,14 @@
<data name="AntiforgeryToken_ClaimUidMismatch" xml:space="preserve"> <data name="AntiforgeryToken_ClaimUidMismatch" xml:space="preserve">
<value>The provided antiforgery token was meant for a different claims-based user than the current user.</value> <value>The provided antiforgery token was meant for a different claims-based user than the current user.</value>
</data> </data>
<data name="AntiforgeryToken_CookieMissing" xml:space="preserve">
<value>The required antiforgery cookie "{0}" is not present.</value>
</data>
<data name="AntiforgeryToken_DeserializationFailed" xml:space="preserve"> <data name="AntiforgeryToken_DeserializationFailed" xml:space="preserve">
<value>The antiforgery token could not be decrypted.</value> <value>The antiforgery token could not be decrypted.</value>
</data> </data>
<data name="AntiforgeryToken_FormFieldMissing" xml:space="preserve">
<value>The required antiforgery form field "{0}" is not present.</value>
</data>
<data name="AntiforgeryToken_SecurityTokenMismatch" xml:space="preserve"> <data name="AntiforgeryToken_SecurityTokenMismatch" xml:space="preserve">
<value>The antiforgery cookie token and form field token do not match.</value> <value>The antiforgery cookie token and form field token do not match.</value>
</data> </data>
<data name="AntiforgeryToken_TokensSwapped" xml:space="preserve"> <data name="AntiforgeryToken_TokensSwapped" xml:space="preserve">
<value>Validation of the provided antiforgery token failed. The cookie "{0}" and the form field "{1}" were swapped.</value> <value>Validation of the provided antiforgery token failed. The cookie token and the form token were swapped.</value>
</data> </data>
<data name="AntiforgeryToken_UsernameMismatch" xml:space="preserve"> <data name="AntiforgeryToken_UsernameMismatch" xml:space="preserve">
<value>The provided antiforgery token was meant for user "{0}", but the current user is "{1}".</value> <value>The provided antiforgery token was meant for user "{0}", but the current user is "{1}".</value>
@ -149,6 +143,18 @@
<value>The antiforgery system has the configuration value {0}.{1} = {2}, but the current request is not an SSL request.</value> <value>The antiforgery system has the configuration value {0}.{1} = {2}, but the current request is not an SSL request.</value>
<comment>0 = nameof(AntiforgeryOptions), 1 = nameof(RequireSsl), 2 = bool.TrueString</comment> <comment>0 = nameof(AntiforgeryOptions), 1 = nameof(RequireSsl), 2 = bool.TrueString</comment>
</data> </data>
<data name="Antiforgery_CookieToken_MustBeProvided" xml:space="preserve">
<value>The required antiforgery cookie "{0}" is not present.</value>
</data>
<data name="Antiforgery_CookieToken_MustBeProvided_Generic" xml:space="preserve">
<value>The cookie token must be provided.</value>
</data>
<data name="Antiforgery_FormToken_MustBeProvided" xml:space="preserve">
<value>The required antiforgery form field "{0}" is not present.</value>
</data>
<data name="Antiforgery_FormToken_MustBeProvided_Generic" xml:space="preserve">
<value>The form token must be provided.</value>
</data>
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve"> <data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or empty.</value> <value>Value cannot be null or empty.</value>
</data> </data>

View File

@ -318,7 +318,7 @@ namespace Microsoft.AspNet.Antiforgery
} }
[Fact] [Fact]
public void Validate_FromInvalidStrings_Throws() public void ValidateTokens_FromInvalidStrings_Throws()
{ {
// Arrange // Arrange
var context = CreateMockContext(new AntiforgeryOptions()); var context = CreateMockContext(new AntiforgeryOptions());
@ -344,7 +344,7 @@ namespace Microsoft.AspNet.Antiforgery
} }
[Fact] [Fact]
public void Validate_FromValidStrings_TokensValidatedSuccessfully() public void ValidateTokens_FromValidStrings_TokensValidatedSuccessfully()
{ {
// Arrange // Arrange
var context = CreateMockContext(new AntiforgeryOptions()); var context = CreateMockContext(new AntiforgeryOptions());
@ -369,16 +369,37 @@ namespace Microsoft.AspNet.Antiforgery
} }
[Fact] [Fact]
public async Task Validate_FromStore_Failure() public void ValidateTokens_MissingCookieInTokenSet_Throws()
{
// Arrange
var context = CreateMockContext(new AntiforgeryOptions());
var antiforgery = GetAntiforgery(context);
var tokenSet = new AntiforgeryTokenSet("hi", cookieToken: null);
// Act
var exception = Assert.Throws<ArgumentException>(
() => antiforgery.ValidateTokens(context.HttpContext, tokenSet));
// Assert
var trimmed = exception.Message.Substring(0, exception.Message.IndexOf(Environment.NewLine));
Assert.Equal("The cookie token must be provided.", trimmed);
}
[Fact]
public async Task ValidateRequestAsync_FromStore_Failure()
{ {
// Arrange // Arrange
var context = CreateMockContext(new AntiforgeryOptions()); var context = CreateMockContext(new AntiforgeryOptions());
context.TokenGenerator.Setup(o => o.ValidateTokens( context.TokenGenerator
context.HttpContext, .Setup(o => o.ValidateTokens(
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken)) context.HttpContext,
.Throws(new InvalidOperationException("my-message")); context.TestTokenSet.OldCookieToken,
context.TokenSerializer = null; context.TestTokenSet.FormToken))
.Throws(new InvalidOperationException("my-message"));
var antiforgery = GetAntiforgery(context); var antiforgery = GetAntiforgery(context);
// Act & assert // Act & assert
@ -388,16 +409,18 @@ namespace Microsoft.AspNet.Antiforgery
} }
[Fact] [Fact]
public async Task Validate_FromStore_Success() public async Task ValidateRequestAsync_FromStore_Success()
{ {
// Arrange // Arrange
var context = CreateMockContext(new AntiforgeryOptions()); var context = CreateMockContext(new AntiforgeryOptions());
context.TokenGenerator.Setup(o => o.ValidateTokens( context.TokenGenerator
context.HttpContext, .Setup(o => o.ValidateTokens(
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken)) context.HttpContext,
.Verifiable(); context.TestTokenSet.OldCookieToken,
context.TokenSerializer = null; context.TestTokenSet.FormToken))
.Verifiable();
var antiforgery = GetAntiforgery(context); var antiforgery = GetAntiforgery(context);
// Act // Act
@ -482,8 +505,11 @@ namespace Microsoft.AspNet.Antiforgery
var mockTokenStore = new Mock<IAntiforgeryTokenStore>(MockBehavior.Strict); var mockTokenStore = new Mock<IAntiforgeryTokenStore>(MockBehavior.Strict);
mockTokenStore.Setup(o => o.GetCookieToken(context)) mockTokenStore.Setup(o => o.GetCookieToken(context))
.Returns(oldCookieToken); .Returns(oldCookieToken);
mockTokenStore.Setup(o => o.GetFormTokenAsync(context))
.Returns(Task.FromResult(formToken)); mockTokenStore.Setup(o => o.GetRequestTokensAsync(context))
.Returns(() => Task.FromResult(new AntiforgeryTokenSet(
testTokenSet.FormTokenString,
testTokenSet.OldCookieTokenString)));
if (saveNewCookie) if (saveNewCookie)
{ {
@ -502,11 +528,13 @@ namespace Microsoft.AspNet.Antiforgery
var formToken = testTokenSet.FormToken; var formToken = testTokenSet.FormToken;
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>(MockBehavior.Strict); var mockSerializer = new Mock<IAntiforgeryTokenSerializer>(MockBehavior.Strict);
mockSerializer.Setup(o => o.Serialize(formToken)) mockSerializer.Setup(o => o.Serialize(formToken))
.Returns("serialized-form-token"); .Returns(testTokenSet.FormTokenString);
mockSerializer.Setup(o => o.Deserialize("serialized-old-cookie-token")) mockSerializer.Setup(o => o.Deserialize(testTokenSet.FormTokenString))
.Returns(formToken);
mockSerializer.Setup(o => o.Deserialize(testTokenSet.OldCookieTokenString))
.Returns(oldCookieToken); .Returns(oldCookieToken);
mockSerializer.Setup(o => o.Serialize(newCookieToken)) mockSerializer.Setup(o => o.Serialize(newCookieToken))
.Returns("serialized-new-cookie-token"); .Returns(testTokenSet.NewCookieTokenString);
return mockSerializer; return mockSerializer;
} }
@ -558,8 +586,11 @@ namespace Microsoft.AspNet.Antiforgery
return new TestTokenSet() return new TestTokenSet()
{ {
FormToken = new AntiforgeryToken() { IsSessionToken = false }, FormToken = new AntiforgeryToken() { IsSessionToken = false },
FormTokenString = "serialized-form-token",
OldCookieToken = new AntiforgeryToken() { IsSessionToken = isOldCookieTokenSessionToken }, OldCookieToken = new AntiforgeryToken() { IsSessionToken = isOldCookieTokenSessionToken },
OldCookieTokenString = "serialized-old-cookie-token",
NewCookieToken = new AntiforgeryToken() { IsSessionToken = isNewCookieSessionToken }, NewCookieToken = new AntiforgeryToken() { IsSessionToken = isNewCookieSessionToken },
NewCookieTokenString = "serialized-new-cookie-token",
}; };
} }

View File

@ -19,7 +19,6 @@ namespace Microsoft.AspNet.Antiforgery
{ {
// Arrange // Arrange
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
@ -40,7 +39,6 @@ namespace Microsoft.AspNet.Antiforgery
Assert.False(httpContext.User.Identity.IsAuthenticated); Assert.False(httpContext.User.Identity.IsAuthenticated);
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
@ -74,7 +72,6 @@ namespace Microsoft.AspNet.Antiforgery
var claimUidExtractor = new Mock<IClaimUidExtractor>().Object; var claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: claimUidExtractor, claimUidExtractor: claimUidExtractor,
additionalDataProvider: null); additionalDataProvider: null);
@ -109,7 +106,6 @@ namespace Microsoft.AspNet.Antiforgery
var claimUidExtractor = new Mock<IClaimUidExtractor>().Object; var claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: claimUidExtractor, claimUidExtractor: claimUidExtractor,
additionalDataProvider: mockAdditionalDataProvider.Object); additionalDataProvider: mockAdditionalDataProvider.Object);
@ -148,7 +144,6 @@ namespace Microsoft.AspNet.Antiforgery
.Returns(base64ClaimUId); .Returns(base64ClaimUId);
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: mockClaimUidExtractor.Object, claimUidExtractor: mockClaimUidExtractor.Object,
additionalDataProvider: null); additionalDataProvider: null);
@ -182,7 +177,6 @@ namespace Microsoft.AspNet.Antiforgery
var claimUidExtractor = new Mock<IClaimUidExtractor>().Object; var claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: claimUidExtractor, claimUidExtractor: claimUidExtractor,
additionalDataProvider: null); additionalDataProvider: null);
@ -209,7 +203,6 @@ namespace Microsoft.AspNet.Antiforgery
}; };
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
@ -226,7 +219,6 @@ namespace Microsoft.AspNet.Antiforgery
// Arrange // Arrange
AntiforgeryToken cookieToken = null; AntiforgeryToken cookieToken = null;
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
@ -247,7 +239,6 @@ namespace Microsoft.AspNet.Antiforgery
}; };
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
@ -268,21 +259,16 @@ namespace Microsoft.AspNet.Antiforgery
var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; var fieldtoken = new AntiforgeryToken() { IsSessionToken = false };
var options = new AntiforgeryOptions()
{
CookieName = "my-cookie-name"
};
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(options),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
// Act & assert // Act & assert
var ex = var ex = Assert.Throws<ArgumentNullException>(
Assert.Throws<InvalidOperationException>( () => tokenProvider.ValidateTokens(httpContext, null, fieldtoken));
() => tokenProvider.ValidateTokens(httpContext, null, fieldtoken));
Assert.Equal(@"The required antiforgery cookie ""my-cookie-name"" is not present.", ex.Message); var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine));
Assert.Equal(@"The cookie token must be provided.", trimmed);
} }
[Fact] [Fact]
@ -294,21 +280,16 @@ namespace Microsoft.AspNet.Antiforgery
var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
var options = new AntiforgeryOptions()
{
FormFieldName = "my-form-field-name"
};
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(options),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
// Act & assert // Act & assert
var ex = var ex = Assert.Throws<ArgumentNullException>(
Assert.Throws<InvalidOperationException>( () => tokenProvider.ValidateTokens(httpContext, sessionToken, null));
() => tokenProvider.ValidateTokens(httpContext, sessionToken, null));
Assert.Equal(@"The required antiforgery form field ""my-form-field-name"" is not present.", ex.Message); var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine));
Assert.Equal("The form token must be provided.", trimmed);
} }
[Fact] [Fact]
@ -321,14 +302,7 @@ namespace Microsoft.AspNet.Antiforgery
var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; var fieldtoken = new AntiforgeryToken() { IsSessionToken = false };
var options = new AntiforgeryOptions()
{
CookieName = "my-cookie-name",
FormFieldName = "my-form-field-name"
};
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(options),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
@ -338,7 +312,7 @@ namespace Microsoft.AspNet.Antiforgery
() => tokenProvider.ValidateTokens(httpContext, fieldtoken, fieldtoken)); () => tokenProvider.ValidateTokens(httpContext, fieldtoken, fieldtoken));
Assert.Equal( Assert.Equal(
"Validation of the provided antiforgery token failed. " + "Validation of the provided antiforgery token failed. " +
@"The cookie ""my-cookie-name"" and the form field ""my-form-field-name"" were swapped.", @"The cookie token and the form token were swapped.",
ex1.Message); ex1.Message);
var ex2 = var ex2 =
@ -346,7 +320,7 @@ namespace Microsoft.AspNet.Antiforgery
() => tokenProvider.ValidateTokens(httpContext, sessionToken, sessionToken)); () => tokenProvider.ValidateTokens(httpContext, sessionToken, sessionToken));
Assert.Equal( Assert.Equal(
"Validation of the provided antiforgery token failed. " + "Validation of the provided antiforgery token failed. " +
@"The cookie ""my-cookie-name"" and the form field ""my-form-field-name"" were swapped.", @"The cookie token and the form token were swapped.",
ex2.Message); ex2.Message);
} }
@ -361,7 +335,6 @@ namespace Microsoft.AspNet.Antiforgery
var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; var fieldtoken = new AntiforgeryToken() { IsSessionToken = false };
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: null); additionalDataProvider: null);
@ -399,7 +372,6 @@ namespace Microsoft.AspNet.Antiforgery
.Returns((string)null); .Returns((string)null);
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: mockClaimUidExtractor.Object, claimUidExtractor: mockClaimUidExtractor.Object,
additionalDataProvider: null); additionalDataProvider: null);
@ -434,7 +406,6 @@ namespace Microsoft.AspNet.Antiforgery
.Returns(Convert.ToBase64String(differentToken.GetData())); .Returns(Convert.ToBase64String(differentToken.GetData()));
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: mockClaimUidExtractor.Object, claimUidExtractor: mockClaimUidExtractor.Object,
additionalDataProvider: null); additionalDataProvider: null);
@ -468,7 +439,6 @@ namespace Microsoft.AspNet.Antiforgery
.Returns(false); .Returns(false);
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: mockAdditionalDataProvider.Object); additionalDataProvider: mockAdditionalDataProvider.Object);
@ -500,7 +470,6 @@ namespace Microsoft.AspNet.Antiforgery
.Returns(true); .Returns(true);
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: null, claimUidExtractor: null,
additionalDataProvider: mockAdditionalDataProvider.Object); additionalDataProvider: mockAdditionalDataProvider.Object);
@ -533,7 +502,6 @@ namespace Microsoft.AspNet.Antiforgery
.Returns(true); .Returns(true);
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: new Mock<IClaimUidExtractor>().Object, claimUidExtractor: new Mock<IClaimUidExtractor>().Object,
additionalDataProvider: mockAdditionalDataProvider.Object); additionalDataProvider: mockAdditionalDataProvider.Object);
@ -565,7 +533,6 @@ namespace Microsoft.AspNet.Antiforgery
.Returns(Convert.ToBase64String(fieldtoken.ClaimUid.GetData())); .Returns(Convert.ToBase64String(fieldtoken.ClaimUid.GetData()));
var tokenProvider = new DefaultAntiforgeryTokenGenerator( var tokenProvider = new DefaultAntiforgeryTokenGenerator(
optionsAccessor: new TestOptionsManager(),
claimUidExtractor: mockClaimUidExtractor.Object, claimUidExtractor: mockClaimUidExtractor.Object,
additionalDataProvider: null); additionalDataProvider: null);

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
@ -160,20 +161,16 @@ namespace Microsoft.AspNet.Antiforgery
} }
[Fact] [Fact]
public async Task GetFormToken_FormFieldIsEmpty_ReturnsNull() public async Task GetRequestTokens_CookieIsEmpty_Throws()
{ {
// Arrange // Arrange
var mockHttpContext = new Mock<HttpContext>(); var httpContext = new DefaultHttpContext();
var requestContext = new Mock<HttpRequest>(); httpContext.Request.Form = new FormCollection(new Dictionary<string, string[]>());
var formCollection = new Mock<IFormCollection>(); httpContext.Request.Cookies = new ReadableStringCollection(new Dictionary<string, string[]>());
formCollection.Setup(f => f["form-field-name"]).Returns(string.Empty);
requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None))
.Returns(Task.FromResult(formCollection.Object));
mockHttpContext.Setup(o => o.Request)
.Returns(requestContext.Object);
var options = new AntiforgeryOptions() var options = new AntiforgeryOptions()
{ {
CookieName = "cookie-name",
FormFieldName = "form-field-name", FormFieldName = "form-field-name",
}; };
@ -182,81 +179,72 @@ namespace Microsoft.AspNet.Antiforgery
tokenSerializer: null); tokenSerializer: null);
// Act // Act
var token = await tokenStore.GetFormTokenAsync(mockHttpContext.Object); var exception = await Assert.ThrowsAsync<InvalidOperationException>(
async () => await tokenStore.GetRequestTokensAsync(httpContext));
// Assert // Assert
Assert.Null(token); Assert.Equal("The required antiforgery cookie \"cookie-name\" is not present.", exception.Message);
} }
[Fact] [Fact]
public async Task GetFormToken_FormFieldIsInvalid_PropagatesException() public async Task GetRequestTokens_FormFieldIsEmpty_Throws()
{ {
// Arrange // Arrange
var formCollection = new Mock<IFormCollection>(); var httpContext = new DefaultHttpContext();
formCollection.Setup(f => f["form-field-name"]).Returns("invalid-value"); httpContext.Request.Form = new FormCollection(new Dictionary<string, string[]>());
httpContext.Request.Cookies = new ReadableStringCollection(new Dictionary<string, string[]>()
var requestContext = new Mock<HttpRequest>(); {
requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None)) { "cookie-name", new string[] { "cookie-value" } },
.Returns(Task.FromResult(formCollection.Object)); });
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(o => o.Request)
.Returns(requestContext.Object);
var expectedException = new InvalidOperationException("some exception");
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>();
mockSerializer.Setup(o => o.Deserialize("invalid-value"))
.Throws(expectedException);
var options = new AntiforgeryOptions() var options = new AntiforgeryOptions()
{ {
CookieName = "cookie-name",
FormFieldName = "form-field-name", FormFieldName = "form-field-name",
}; };
var tokenStore = new DefaultAntiforgeryTokenStore( var tokenStore = new DefaultAntiforgeryTokenStore(
optionsAccessor: new TestOptionsManager(options), optionsAccessor: new TestOptionsManager(options),
tokenSerializer: mockSerializer.Object); tokenSerializer: null);
// Act & assert // Act
var exception = await Assert.ThrowsAsync<InvalidOperationException>( var exception = await Assert.ThrowsAsync<InvalidOperationException>(
async () => await tokenStore.GetFormTokenAsync(mockHttpContext.Object)); async () => await tokenStore.GetRequestTokensAsync(httpContext));
Assert.Same(expectedException, exception);
// Assert
Assert.Equal("The required antiforgery form field \"form-field-name\" is not present.", exception.Message);
} }
[Fact] [Fact]
public async Task GetFormToken_FormFieldIsValid_ReturnsToken() public async Task GetFormToken_FormFieldIsValid_ReturnsToken()
{ {
// Arrange // Arrange
var expectedToken = new AntiforgeryToken(); var httpContext = new DefaultHttpContext();
httpContext.Request.Form = new FormCollection(new Dictionary<string, string[]>()
// Arrange {
var mockHttpContext = new Mock<HttpContext>(); { "form-field-name", new string[] { "form-value" } },
var requestContext = new Mock<HttpRequest>(); });
var formCollection = new Mock<IFormCollection>(); httpContext.Request.Cookies = new ReadableStringCollection(new Dictionary<string, string[]>()
formCollection.Setup(f => f["form-field-name"]).Returns("valid-value"); {
requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None)) { "cookie-name", new string[] { "cookie-value" } },
.Returns(Task.FromResult(formCollection.Object)); });
mockHttpContext.Setup(o => o.Request)
.Returns(requestContext.Object);
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>();
mockSerializer.Setup(o => o.Deserialize("valid-value"))
.Returns(expectedToken);
var options = new AntiforgeryOptions() var options = new AntiforgeryOptions()
{ {
CookieName = "cookie-name",
FormFieldName = "form-field-name", FormFieldName = "form-field-name",
}; };
var tokenStore = new DefaultAntiforgeryTokenStore( var tokenStore = new DefaultAntiforgeryTokenStore(
optionsAccessor: new TestOptionsManager(options), optionsAccessor: new TestOptionsManager(options),
tokenSerializer: mockSerializer.Object); tokenSerializer: null);
// Act // Act
var retVal = await tokenStore.GetFormTokenAsync(mockHttpContext.Object); var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
// Assert // Assert
Assert.Same(expectedToken, retVal); Assert.Equal("cookie-value", tokens.CookieToken);
Assert.Equal("form-value", tokens.FormToken);
} }
[Theory] [Theory]