518 lines
23 KiB
C#
Raw Normal View History

// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
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; }
}
}
}
}