aspnetcore/test/Microsoft.AspNet.Mvc.Extens.../AntiXsrf/AntiForgeryWorkerTest.cs

585 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.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.WebEncoders.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Core.Test
{
public class AntiForgeryWorkerTest
{
[Fact]
public async Task ChecksSSL_ValidateAsync_Throws()
{
// Arrange
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(o => o.Request.IsHttps)
.Returns(false);
var config = new AntiForgeryOptions()
{
RequireSSL = true
};
var worker = new AntiForgeryWorker(
config: config,
serializer: null,
tokenStore: null,
generator: null,
validator: null,
htmlEncoder: new CommonTestEncoder());
// Act & assert
var ex =
await
Assert.ThrowsAsync<InvalidOperationException>(
async () => await worker.ValidateAsync(mockHttpContext.Object));
Assert.Equal(
@"The anti-forgery system has the configuration value AntiForgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
ex.Message);
}
[Fact]
public void ChecksSSL_Validate_Throws()
{
// Arrange
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(o => o.Request.IsHttps)
.Returns(false);
var config = new AntiForgeryOptions()
{
RequireSSL = true
};
var worker = new AntiForgeryWorker(
config: config,
serializer: null,
tokenStore: null,
generator: null,
validator: null,
htmlEncoder: new CommonTestEncoder());
// Act & assert
var ex = Assert.Throws<InvalidOperationException>(
() => worker.Validate(mockHttpContext.Object, cookieToken: null, formToken: null));
Assert.Equal(
@"The anti-forgery system has the configuration value AntiForgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
ex.Message);
}
[Fact]
public void ChecksSSL_GetFormInputElement_Throws()
{
// Arrange
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(o => o.Request.IsHttps)
.Returns(false);
var config = new AntiForgeryOptions()
{
RequireSSL = true
};
var worker = new AntiForgeryWorker(
config: config,
serializer: null,
tokenStore: null,
generator: null,
validator: null,
htmlEncoder: new CommonTestEncoder());
// Act & assert
var ex = Assert.Throws<InvalidOperationException>(() => worker.GetFormInputElement(mockHttpContext.Object));
Assert.Equal(
@"The anti-forgery system has the configuration value AntiForgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
ex.Message);
}
[Fact]
public void ChecksSSL_GetTokens_Throws()
{
// Arrange
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(o => o.Request.IsHttps)
.Returns(false);
var config = new AntiForgeryOptions()
{
RequireSSL = true
};
var worker = new AntiForgeryWorker(
config: config,
serializer: null,
tokenStore: null,
generator: null,
validator: null,
htmlEncoder: new CommonTestEncoder());
// Act & assert
var ex = Assert.Throws<InvalidOperationException>(() =>
worker.GetTokens(mockHttpContext.Object, "cookie-token"));
Assert.Equal(
@"The anti-forgery system has the configuration value AntiForgeryOptions.RequireSsl = true, " +
"but the current request is not an SSL request.",
ex.Message);
}
[Fact]
public void GetFormInputElement_ExistingInvalidCookieToken_GeneratesANewCookieAndAnAntiForgeryToken()
{
// Arrange
var config = new AntiForgeryOptions()
{
FormFieldName = "form-field-name"
};
// Make sure the existing cookie is invalid.
var context = GetAntiForgeryWorkerContext(config, isOldCookieValid: false);
var worker = GetAntiForgeryWorker(context);
// Act
var inputElement = worker.GetFormInputElement(context.HttpContext.Object);
// Assert
Assert.Equal(@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[serialized-form-token]]"" />",
inputElement.ToString(TagRenderMode.SelfClosing));
context.TokenStore.Verify();
}
[Fact]
public void GetFormInputElement_ExistingInvalidCookieToken_SwallowsExceptions()
{
// Arrange
var config = new AntiForgeryOptions()
{
FormFieldName = "form-field-name"
};
// Make sure the existing cookie is invalid.
var context = GetAntiForgeryWorkerContext(config, isOldCookieValid: false);
var worker = GetAntiForgeryWorker(context);
// This will cause the cookieToken to be null.
context.TokenStore.Setup(o => o.GetCookieToken(context.HttpContext.Object))
.Throws(new Exception("should be swallowed"));
// Setup so that the null cookie token returned is treated as invalid.
context.TokenProvider.Setup(o => o.IsCookieTokenValid(null))
.Returns(false);
// Act
var inputElement = worker.GetFormInputElement(context.HttpContext.Object);
// Assert
Assert.Equal(@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[serialized-form-token]]"" />",
inputElement.ToString(TagRenderMode.SelfClosing));
context.TokenStore.Verify();
}
[Fact]
public void GetFormInputElement_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 = GetAntiForgeryWorkerContext(options, useOldCookie: true, isOldCookieValid: true);
var worker = GetAntiForgeryWorker(context);
// Act
var inputElement = worker.GetFormInputElement(context.HttpContext.Object);
// Assert
Assert.Equal(@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[serialized-form-token]]"" />",
inputElement.ToString(TagRenderMode.SelfClosing));
}
[Theory]
[InlineData(false, "SAMEORIGIN")]
[InlineData(true, null)]
public void GetFormInputElement_AddsXFrameOptionsHeader(bool suppressXFrameOptions, string expectedHeaderValue)
{
// Arrange
var options = new AntiForgeryOptions()
{
SuppressXFrameOptionsHeader = suppressXFrameOptions
};
// Genreate a new cookie.
var context = GetAntiForgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false);
var worker = GetAntiForgeryWorker(context);
// Act
var inputElement = worker.GetFormInputElement(context.HttpContext.Object);
// Assert
string xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"];
Assert.Equal(expectedHeaderValue, xFrameOptions);
}
[Fact]
public void GetTokens_ExistingInvalidCookieToken_GeneratesANewCookieTokenAndANewFormToken()
{
// Arrange
// Genreate a new cookie.
var context = GetAntiForgeryWorkerContext(
new AntiForgeryOptions(),
useOldCookie: false,
isOldCookieValid: false);
var worker = GetAntiForgeryWorker(context);
// Act
var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token");
// 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 = GetAntiForgeryWorkerContext(
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.TokenProvider.Setup(o => o.IsCookieTokenValid(null))
.Returns(false);
var worker = GetAntiForgeryWorker(context);
// Act
var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token");
// 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 = GetAntiForgeryWorkerContext(
new AntiForgeryOptions(),
useOldCookie: true,
isOldCookieValid: true);
context.TokenStore = null;
var worker = GetAntiForgeryWorker(context);
// Act
var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token");
// Assert
Assert.Null(tokenset.CookieToken);
Assert.Equal("serialized-form-token", tokenset.FormToken);
}
[Fact]
public void Validate_FromInvalidStrings_Throws()
{
// Arrange
var context = GetAntiForgeryWorkerContext(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.TokenProvider.Setup(o => o.ValidateTokens(
context.HttpContext.Object,
context.HttpContext.Object.User.Identity as ClaimsIdentity,
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
.Throws(new InvalidOperationException("my-message"));
context.TokenStore = null;
var worker = GetAntiForgeryWorker(context);
// Act & assert
var ex =
Assert.Throws<InvalidOperationException>(
() => worker.Validate(context.HttpContext.Object, "cookie-token", "form-token"));
Assert.Equal("my-message", ex.Message);
}
[Fact]
public void Validate_FromValidStrings_TokensValidatedSuccessfully()
{
// Arrange
var context = GetAntiForgeryWorkerContext(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.TokenProvider.Setup(o => o.ValidateTokens(
context.HttpContext.Object,
context.HttpContext.Object.User.Identity as ClaimsIdentity,
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
.Verifiable();
context.TokenStore = null;
var worker = GetAntiForgeryWorker(context);
// Act
worker.Validate(context.HttpContext.Object, "cookie-token", "form-token");
// Assert
context.TokenProvider.Verify();
}
[Fact]
public async Task Validate_FromStore_Failure()
{
// Arrange
var context = GetAntiForgeryWorkerContext(new AntiForgeryOptions());
context.TokenProvider.Setup(o => o.ValidateTokens(
context.HttpContext.Object,
context.HttpContext.Object.User.Identity as ClaimsIdentity,
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
.Throws(new InvalidOperationException("my-message"));
context.TokenSerializer = null;
var worker = GetAntiForgeryWorker(context);
// Act & assert
var ex =
await
Assert.ThrowsAsync<InvalidOperationException>(
async () => await worker.ValidateAsync(context.HttpContext.Object));
Assert.Equal("my-message", ex.Message);
}
[Fact]
public async Task Validate_FromStore_Success()
{
// Arrange
var context = GetAntiForgeryWorkerContext(new AntiForgeryOptions());
context.TokenProvider.Setup(o => o.ValidateTokens(
context.HttpContext.Object,
context.HttpContext.Object.User.Identity as ClaimsIdentity,
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
.Verifiable();
context.TokenSerializer = null;
var worker = GetAntiForgeryWorker(context);
// Act
await worker.ValidateAsync(context.HttpContext.Object);
// Assert
context.TokenProvider.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 = GetAntiForgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false);
var worker = GetAntiForgeryWorker(context);
// Act
worker.SetCookieTokenAndHeader(context.HttpContext.Object);
// Assert
var xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"];
Assert.Equal(expectedHeaderValue, xFrameOptions);
}
private AntiForgeryWorker GetAntiForgeryWorker(AntiForgeryWorkerContext context)
{
return new AntiForgeryWorker(
config: context.Options,
serializer: context.TokenSerializer != null ? context.TokenSerializer.Object : null,
tokenStore: context.TokenStore != null ? context.TokenStore.Object : null,
generator: context.TokenProvider != null ? context.TokenProvider.Object : null,
validator: context.TokenProvider != null ? context.TokenProvider.Object : null,
htmlEncoder: new CommonTestEncoder());
}
private Mock<HttpContext> GetHttpContext(bool setupResponse = true)
{
var identity = new ClaimsIdentity("some-auth");
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(o => o.User)
.Returns(new ClaimsPrincipal(identity));
if (setupResponse)
{
var mockResponse = new Mock<HttpResponse>();
mockResponse.Setup(r => r.Headers)
.Returns(new HeaderDictionary(new Dictionary<string, string[]>()));
mockHttpContext.Setup(o => o.Response)
.Returns(mockResponse.Object);
}
return mockHttpContext;
}
private Mock<IAntiForgeryTokenProvider> GetTokenProvider(
HttpContext context,
TestTokenSet testTokenSet,
bool useOldCookie,
bool isOldCookieValid = true,
bool isNewCookieValid = true)
{
var oldCookieToken = testTokenSet.OldCookieToken;
var newCookieToken = testTokenSet.NewCookieToken;
var formToken = testTokenSet.FormToken;
var mockValidator = new Mock<IAntiForgeryTokenProvider>(MockBehavior.Strict);
mockValidator.Setup(o => o.GenerateFormToken(
context,
context.User.Identity as ClaimsIdentity,
useOldCookie ? oldCookieToken : newCookieToken))
.Returns(formToken);
mockValidator.Setup(o => o.IsCookieTokenValid(oldCookieToken))
.Returns(isOldCookieValid);
mockValidator.Setup(o => o.IsCookieTokenValid(newCookieToken))
.Returns(isNewCookieValid);
mockValidator.Setup(o => o.GenerateCookieToken())
.Returns(useOldCookie ? oldCookieToken : newCookieToken);
return mockValidator;
}
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.GetFormTokenAsync(context))
.Returns(Task.FromResult(formToken));
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("serialized-form-token");
mockSerializer.Setup(o => o.Deserialize("serialized-old-cookie-token"))
.Returns(oldCookieToken);
mockSerializer.Setup(o => o.Serialize(newCookieToken))
.Returns("serialized-new-cookie-token");
return mockSerializer;
}
private TestTokenSet GetTokenSet(bool isOldCookieTokenSessionToken = true, bool isNewCookieSessionToken = true)
{
return new TestTokenSet()
{
FormToken = new AntiForgeryToken() { IsSessionToken = false },
OldCookieToken = new AntiForgeryToken() { IsSessionToken = isOldCookieTokenSessionToken },
NewCookieToken = new AntiForgeryToken() { IsSessionToken = isNewCookieSessionToken },
};
}
private AntiForgeryWorkerContext GetAntiForgeryWorkerContext(
AntiForgeryOptions config,
bool useOldCookie = false,
bool isOldCookieValid = true)
{
// Arrange
var mockHttpContext = GetHttpContext();
var testTokenSet = GetTokenSet(isOldCookieTokenSessionToken: true, isNewCookieSessionToken: true);
var mockSerializer = GetTokenSerializer(testTokenSet);
var mockTokenStore = GetTokenStore(mockHttpContext.Object, testTokenSet);
var mockTokenProvider = GetTokenProvider(
mockHttpContext.Object,
testTokenSet,
useOldCookie: useOldCookie,
isOldCookieValid: isOldCookieValid);
return new AntiForgeryWorkerContext()
{
Options = config,
HttpContext = mockHttpContext,
TokenProvider = mockTokenProvider,
TokenSerializer = mockSerializer,
TokenStore = mockTokenStore,
TestTokenSet = testTokenSet
};
}
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 AntiForgeryWorkerContext
{
public AntiForgeryOptions Options { get; set; }
public TestTokenSet TestTokenSet { get; set; }
public Mock<HttpContext> HttpContext { get; set; }
public Mock<IAntiForgeryTokenProvider> TokenProvider { get; set; }
public Mock<IAntiForgeryTokenStore> TokenStore { get; set; }
public Mock<IAntiForgeryTokenSerializer> TokenSerializer { get; set; }
}
}
}