aspnetcore/test/Microsoft.AspNet.Antiforger.../DefaultAntiforgeryTest.cs

634 lines
23 KiB
C#

// 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.
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.Extensions.OptionsModel;
using Microsoft.Extensions.WebEncoders.Testing;
#if DNX451
using Moq;
#endif
using Xunit;
namespace Microsoft.AspNet.Antiforgery
{
public class DefaultAntiforgeryTest
{
[Fact]
public async Task ChecksSSL_ValidateRequestAsync_Throws()
{
// Arrange
var httpContext = new DefaultHttpContext();
var options = new AntiforgeryOptions()
{
RequireSsl = true
};
var antiforgery = GetAntiforgery(options);
// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
async () => await antiforgery.ValidateRequestAsync(httpContext));
Assert.Equal(
@"The antiforgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
exception.Message);
}
[Fact]
public void ChecksSSL_ValidateTokens_Throws()
{
// Arrange
var httpContext = new DefaultHttpContext();
var options = new AntiforgeryOptions()
{
RequireSsl = true
};
var antiforgery = GetAntiforgery(options);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => antiforgery.ValidateTokens(httpContext, new AntiforgeryTokenSet("hello", "world")));
Assert.Equal(
@"The antiforgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
exception.Message);
}
[Fact]
public void ChecksSSL_GetHtml_Throws()
{
// Arrange
var httpContext = new DefaultHttpContext();
var options = new AntiforgeryOptions()
{
RequireSsl = true
};
var antiforgery = GetAntiforgery(options);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => antiforgery.GetHtml(httpContext));
Assert.Equal(
@"The antiforgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
exception.Message);
}
[Fact]
public void ChecksSSL_GetAndStoreTokens_Throws()
{
// Arrange
var httpContext = new DefaultHttpContext();
var options = new AntiforgeryOptions()
{
RequireSsl = true
};
var antiforgery = GetAntiforgery(options);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => antiforgery.GetAndStoreTokens(httpContext));
Assert.Equal(
@"The antiforgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
exception.Message);
}
[Fact]
public void ChecksSSL_GetTokens_Throws()
{
// Arrange
var httpContext = new DefaultHttpContext();
var options = new AntiforgeryOptions()
{
RequireSsl = true
};
var antiforgery = GetAntiforgery(options);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => antiforgery.GetTokens(httpContext));
Assert.Equal(
@"The antiforgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
exception.Message);
}
[Fact]
public void ChecksSSL_SetCookieTokenAndHeader_Throws()
{
// Arrange
var httpContext = new DefaultHttpContext();
var options = new AntiforgeryOptions()
{
RequireSsl = true
};
var antiforgery = GetAntiforgery(options);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => antiforgery.SetCookieTokenAndHeader(httpContext));
Assert.Equal(
@"The antiforgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
exception.Message);
}
#if DNX451
[Fact]
public void GetHtml_ExistingInvalidCookieToken_GeneratesANewCookieAndAnAntiforgeryToken()
{
// Arrange
var options = new AntiforgeryOptions()
{
FormFieldName = "form-field-name"
};
// Make sure the existing cookie is invalid.
var context = CreateMockContext(options, isOldCookieValid: false);
var antiforgery = GetAntiforgery(context);
// Act
var inputElement = antiforgery.GetHtml(context.HttpContext);
// Assert
Assert.Equal(
@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[serialized-form-token]]"" />",
inputElement);
context.TokenStore.Verify();
}
[Fact]
public void GetHtml_ExistingInvalidCookieToken_SwallowsExceptions()
{
// Arrange
var options = new AntiforgeryOptions()
{
FormFieldName = "form-field-name"
};
// Make sure the existing cookie is invalid.
var context = CreateMockContext(options, isOldCookieValid: false);
var antiforgery = GetAntiforgery(context);
// This will cause the cookieToken to be null.
context.TokenStore.Setup(o => o.GetCookieToken(context.HttpContext))
.Throws(new Exception("should be swallowed"));
// Setup so that the null cookie token returned is treated as invalid.
context.TokenGenerator.Setup(o => o.IsCookieTokenValid(null))
.Returns(false);
// Act
var inputElement = antiforgery.GetHtml(context.HttpContext);
// Assert
Assert.Equal(
@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[serialized-form-token]]"" />",
inputElement);
context.TokenStore.Verify();
}
[Fact]
public void GetHtml_ExistingValidCookieToken_GeneratesAnAntiforgeryToken()
{
// Arrange
var options = new AntiforgeryOptions()
{
FormFieldName = "form-field-name"
};
// Make sure the existing cookie is valid and use the same cookie for the mock Token Provider.
var context = CreateMockContext(options, useOldCookie: true, isOldCookieValid: true);
var antiforgery = GetAntiforgery(context);
// Act
var inputElement = antiforgery.GetHtml(context.HttpContext);
// Assert
Assert.Equal(
@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[serialized-form-token]]"" />",
inputElement);
}
[Theory]
[InlineData(false, "SAMEORIGIN")]
[InlineData(true, null)]
public void GetHtml_AddsXFrameOptionsHeader(bool suppressXFrameOptions, string expectedHeaderValue)
{
// Arrange
var options = new AntiforgeryOptions()
{
SuppressXFrameOptionsHeader = suppressXFrameOptions
};
// Genreate a new cookie.
var context = CreateMockContext(options, useOldCookie: false, isOldCookieValid: false);
var antiforgery = GetAntiforgery(context);
// Act
var inputElement = antiforgery.GetHtml(context.HttpContext);
// Assert
string xFrameOptions = context.HttpContext.Response.Headers["X-Frame-Options"];
Assert.Equal(expectedHeaderValue, xFrameOptions);
}
[Fact]
public void GetTokens_ExistingInvalidCookieToken_GeneratesANewCookieTokenAndANewFormToken()
{
// Arrange
// Genreate a new cookie.
var context = CreateMockContext(
new AntiforgeryOptions(),
useOldCookie: false,
isOldCookieValid: false);
var antiforgery = GetAntiforgery(context);
// Act
var tokenset = antiforgery.GetTokens(context.HttpContext);
// Assert
Assert.Equal("serialized-new-cookie-token", tokenset.CookieToken);
Assert.Equal("serialized-form-token", tokenset.FormToken);
}
[Fact]
public void GetTokens_ExistingInvalidCookieToken_SwallowsExceptions()
{
// Arrange
// Make sure the existing cookie is invalid.
var context = CreateMockContext(
new AntiforgeryOptions(),
useOldCookie: false,
isOldCookieValid: false);
// This will cause the cookieToken to be null.
context.TokenSerializer.Setup(o => o.Deserialize("serialized-old-cookie-token"))
.Throws(new Exception("should be swallowed"));
// Setup so that the null cookie token returned is treated as invalid.
context.TokenGenerator.Setup(o => o.IsCookieTokenValid(null))
.Returns(false);
var antiforgery = GetAntiforgery(context);
// Act
var tokenset = antiforgery.GetTokens(context.HttpContext);
// Assert
Assert.Equal("serialized-new-cookie-token", tokenset.CookieToken);
Assert.Equal("serialized-form-token", tokenset.FormToken);
}
[Fact]
public void GetTokens_ExistingValidCookieToken_GeneratesANewFormToken()
{
// Arrange
var context = CreateMockContext(
new AntiforgeryOptions(),
useOldCookie: true,
isOldCookieValid: true);
var antiforgery = GetAntiforgery(context);
// Act
var tokenset = antiforgery.GetTokens(context.HttpContext);
// Assert
Assert.Null(tokenset.CookieToken);
Assert.Equal("serialized-form-token", tokenset.FormToken);
}
[Fact]
public void ValidateTokens_FromInvalidStrings_Throws()
{
// Arrange
var context = CreateMockContext(new AntiforgeryOptions());
context.TokenSerializer.Setup(o => o.Deserialize("cookie-token"))
.Returns(context.TestTokenSet.OldCookieToken);
context.TokenSerializer.Setup(o => o.Deserialize("form-token"))
.Returns(context.TestTokenSet.FormToken);
context.TokenGenerator.Setup(o => o.ValidateTokens(
context.HttpContext,
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
.Throws(new InvalidOperationException("my-message"));
context.TokenStore = null;
var antiforgery = GetAntiforgery(context);
// Act & assert
var exception = Assert.Throws<InvalidOperationException>(
() => antiforgery.ValidateTokens(
context.HttpContext,
new AntiforgeryTokenSet("form-token", "cookie-token")));
Assert.Equal("my-message", exception.Message);
}
[Fact]
public void ValidateTokens_FromValidStrings_TokensValidatedSuccessfully()
{
// Arrange
var context = CreateMockContext(new AntiforgeryOptions());
context.TokenSerializer.Setup(o => o.Deserialize("cookie-token"))
.Returns(context.TestTokenSet.OldCookieToken);
context.TokenSerializer.Setup(o => o.Deserialize("form-token"))
.Returns(context.TestTokenSet.FormToken);
context.TokenGenerator.Setup(o => o.ValidateTokens(
context.HttpContext,
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
.Verifiable();
context.TokenStore = null;
var antiforgery = GetAntiforgery(context);
// Act
antiforgery.ValidateTokens(context.HttpContext, new AntiforgeryTokenSet("form-token", "cookie-token"));
// Assert
context.TokenGenerator.Verify();
}
[Fact]
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
var context = CreateMockContext(new AntiforgeryOptions());
context.TokenGenerator
.Setup(o => o.ValidateTokens(
context.HttpContext,
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.FormToken))
.Throws(new InvalidOperationException("my-message"));
var antiforgery = GetAntiforgery(context);
// Act & assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
async () => await antiforgery.ValidateRequestAsync(context.HttpContext));
Assert.Equal("my-message", exception.Message);
}
[Fact]
public async Task ValidateRequestAsync_FromStore_Success()
{
// Arrange
var context = CreateMockContext(new AntiforgeryOptions());
context.TokenGenerator
.Setup(o => o.ValidateTokens(
context.HttpContext,
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.FormToken))
.Verifiable();
var antiforgery = GetAntiforgery(context);
// Act
await antiforgery.ValidateRequestAsync(context.HttpContext);
// Assert
context.TokenGenerator.Verify();
}
[Theory]
[InlineData(false, "SAMEORIGIN")]
[InlineData(true, null)]
public void SetCookieTokenAndHeader_AddsXFrameOptionsHeader(
bool suppressXFrameOptions,
string expectedHeaderValue)
{
// Arrange
var options = new AntiforgeryOptions()
{
SuppressXFrameOptionsHeader = suppressXFrameOptions
};
// Genreate a new cookie.
var context = CreateMockContext(options, useOldCookie: false, isOldCookieValid: false);
var antiforgery = GetAntiforgery(context);
// Act
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
// Assert
var xFrameOptions = context.HttpContext.Response.Headers["X-Frame-Options"];
Assert.Equal(expectedHeaderValue, xFrameOptions);
}
#endif
private DefaultAntiforgery GetAntiforgery(
AntiforgeryOptions options = null,
IAntiforgeryTokenGenerator tokenGenerator = null,
IAntiforgeryTokenSerializer tokenSerializer = null,
IAntiforgeryTokenStore tokenStore = null)
{
var optionsManager = new TestOptionsManager();
if (options != null)
{
optionsManager.Value = options;
}
return new DefaultAntiforgery(
antiforgeryOptionsAccessor: optionsManager,
tokenGenerator: tokenGenerator,
tokenSerializer: tokenSerializer,
tokenStore: tokenStore,
htmlEncoder: new HtmlTestEncoder());
}
private HttpContext GetHttpContext()
{
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity("some-auth"));
return httpContext;
}
#if DNX451
private DefaultAntiforgery GetAntiforgery(AntiforgeryMockContext context)
{
return GetAntiforgery(
context.Options,
context.TokenGenerator?.Object,
context.TokenSerializer?.Object,
context.TokenStore?.Object);
}
private Mock<IAntiforgeryTokenStore> GetTokenStore(
HttpContext context,
TestTokenSet testTokenSet,
bool saveNewCookie = true)
{
var oldCookieToken = testTokenSet.OldCookieToken;
var formToken = testTokenSet.FormToken;
var mockTokenStore = new Mock<IAntiforgeryTokenStore>(MockBehavior.Strict);
mockTokenStore.Setup(o => o.GetCookieToken(context))
.Returns(oldCookieToken);
mockTokenStore.Setup(o => o.GetRequestTokensAsync(context))
.Returns(() => Task.FromResult(new AntiforgeryTokenSet(
testTokenSet.FormTokenString,
testTokenSet.OldCookieTokenString)));
if (saveNewCookie)
{
var newCookieToken = testTokenSet.NewCookieToken;
mockTokenStore.Setup(o => o.SaveCookieToken(context, newCookieToken))
.Verifiable();
}
return mockTokenStore;
}
private Mock<IAntiforgeryTokenSerializer> GetTokenSerializer(TestTokenSet testTokenSet)
{
var oldCookieToken = testTokenSet.OldCookieToken;
var newCookieToken = testTokenSet.NewCookieToken;
var formToken = testTokenSet.FormToken;
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>(MockBehavior.Strict);
mockSerializer.Setup(o => o.Serialize(formToken))
.Returns(testTokenSet.FormTokenString);
mockSerializer.Setup(o => o.Deserialize(testTokenSet.FormTokenString))
.Returns(formToken);
mockSerializer.Setup(o => o.Deserialize(testTokenSet.OldCookieTokenString))
.Returns(oldCookieToken);
mockSerializer.Setup(o => o.Serialize(newCookieToken))
.Returns(testTokenSet.NewCookieTokenString);
return mockSerializer;
}
private AntiforgeryMockContext CreateMockContext(
AntiforgeryOptions options,
bool useOldCookie = false,
bool isOldCookieValid = true)
{
// Arrange
var httpContext = GetHttpContext();
var testTokenSet = GetTokenSet(isOldCookieTokenSessionToken: true, isNewCookieSessionToken: true);
var mockSerializer = GetTokenSerializer(testTokenSet);
var mockTokenStore = GetTokenStore(httpContext, testTokenSet);
var mockGenerator = new Mock<IAntiforgeryTokenGenerator>(MockBehavior.Strict);
mockGenerator
.Setup(o => o.GenerateFormToken(
httpContext,
useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken))
.Returns(testTokenSet.FormToken);
mockGenerator
.Setup(o => o.GenerateCookieToken())
.Returns(useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken);
mockGenerator
.Setup(o => o.IsCookieTokenValid(testTokenSet.OldCookieToken))
.Returns(isOldCookieValid);
mockGenerator
.Setup(o => o.IsCookieTokenValid(testTokenSet.NewCookieToken))
.Returns(!isOldCookieValid);
return new AntiforgeryMockContext()
{
Options = options,
HttpContext = httpContext,
TokenGenerator = mockGenerator,
TokenSerializer = mockSerializer,
TokenStore = mockTokenStore,
TestTokenSet = testTokenSet
};
}
private TestTokenSet GetTokenSet(bool isOldCookieTokenSessionToken = true, bool isNewCookieSessionToken = true)
{
return new TestTokenSet()
{
FormToken = new AntiforgeryToken() { IsSessionToken = false },
FormTokenString = "serialized-form-token",
OldCookieToken = new AntiforgeryToken() { IsSessionToken = isOldCookieTokenSessionToken },
OldCookieTokenString = "serialized-old-cookie-token",
NewCookieToken = new AntiforgeryToken() { IsSessionToken = isNewCookieSessionToken },
NewCookieTokenString = "serialized-new-cookie-token",
};
}
private class TestTokenSet
{
public AntiforgeryToken FormToken { get; set; }
public string FormTokenString { get; set; }
public AntiforgeryToken OldCookieToken { get; set; }
public string OldCookieTokenString { get; set; }
public AntiforgeryToken NewCookieToken { get; set; }
public string NewCookieTokenString { get; set; }
}
private class AntiforgeryMockContext
{
public AntiforgeryOptions Options { get; set; }
public TestTokenSet TestTokenSet { get; set; }
public HttpContext HttpContext { get; set; }
public Mock<IAntiforgeryTokenGenerator> TokenGenerator { get; set; }
public Mock<IAntiforgeryTokenStore> TokenStore { get; set; }
public Mock<IAntiforgeryTokenSerializer> TokenSerializer { get; set; }
}
#endif
private class TestOptionsManager : IOptions<AntiforgeryOptions>
{
public AntiforgeryOptions Value { get; set; } = new AntiforgeryOptions();
}
}
}