1498 lines
60 KiB
C#
1498 lines
60 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.Linq;
|
|
using System.Security.Claims;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Testing;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.Net.Http.Headers;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
|
{
|
|
public class DefaultAntiforgeryTest
|
|
{
|
|
private const string ResponseCacheHeadersOverrideWarningMessage =
|
|
"The 'Cache-Control' and 'Pragma' headers have been overridden and set to 'no-cache, no-store' and " +
|
|
"'no-cache' respectively to prevent caching of this response. Any response that uses antiforgery " +
|
|
"should not be cached.";
|
|
|
|
[Fact]
|
|
public async Task ChecksSSL_ValidateRequestAsync_Throws()
|
|
{
|
|
// Arrange
|
|
var httpContext = GetHttpContext();
|
|
var options = new AntiforgeryOptions
|
|
{
|
|
Cookie = new CookieBuilder
|
|
{
|
|
SecurePolicy = CookieSecurePolicy.Always
|
|
}
|
|
};
|
|
var antiforgery = GetAntiforgery(httpContext, options);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => antiforgery.ValidateRequestAsync(httpContext));
|
|
Assert.Equal(
|
|
@"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
|
|
"but the current request is not an SSL request.",
|
|
exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ChecksSSL_IsRequestValidAsync_Throws()
|
|
{
|
|
// Arrange
|
|
var httpContext = GetHttpContext();
|
|
var options = new AntiforgeryOptions()
|
|
{
|
|
Cookie = { SecurePolicy = CookieSecurePolicy.Always }
|
|
};
|
|
|
|
var antiforgery = GetAntiforgery(httpContext, options);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => antiforgery.IsRequestValidAsync(httpContext));
|
|
Assert.Equal(
|
|
@"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
|
|
"but the current request is not an SSL request.",
|
|
exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void ChecksSSL_GetAndStoreTokens_Throws()
|
|
{
|
|
// Arrange
|
|
var httpContext = GetHttpContext();
|
|
var options = new AntiforgeryOptions()
|
|
{
|
|
Cookie = { SecurePolicy = CookieSecurePolicy.Always }
|
|
};
|
|
|
|
var antiforgery = GetAntiforgery(httpContext, options);
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(
|
|
() => antiforgery.GetAndStoreTokens(httpContext));
|
|
Assert.Equal(
|
|
@"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
|
|
"but the current request is not an SSL request.",
|
|
exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void ChecksSSL_GetTokens_Throws()
|
|
{
|
|
// Arrange
|
|
var httpContext = GetHttpContext();
|
|
var options = new AntiforgeryOptions()
|
|
{
|
|
Cookie = { SecurePolicy = CookieSecurePolicy.Always }
|
|
};
|
|
|
|
var antiforgery = GetAntiforgery(httpContext, options);
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(
|
|
() => antiforgery.GetTokens(httpContext));
|
|
Assert.Equal(
|
|
@"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
|
|
"but the current request is not an SSL request.",
|
|
exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void ChecksSSL_SetCookieTokenAndHeader_Throws()
|
|
{
|
|
// Arrange
|
|
var httpContext = GetHttpContext();
|
|
var options = new AntiforgeryOptions()
|
|
{
|
|
Cookie = { SecurePolicy = CookieSecurePolicy.Always }
|
|
};
|
|
|
|
var antiforgery = GetAntiforgery(httpContext, options);
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(
|
|
() => antiforgery.SetCookieTokenAndHeader(httpContext));
|
|
Assert.Equal(
|
|
@"The antiforgery system has the configuration value AntiforgeryOptions.Cookie.SecurePolicy = Always, " +
|
|
"but the current request is not an SSL request.",
|
|
exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetTokens_ExistingInvalidCookieToken_GeneratesANewCookieTokenAndANewFormToken()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
// Generate a new cookie.
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenset = antiforgery.GetTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.Equal(context.TestTokenSet.NewCookieTokenString, tokenset.CookieToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, tokenset.RequestToken);
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
|
|
Assert.Equal(context.TestTokenSet.NewCookieToken, antiforgeryFeature.NewCookieToken);
|
|
Assert.Equal(context.TestTokenSet.NewCookieTokenString, antiforgeryFeature.NewCookieTokenString);
|
|
Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.NewRequestToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, antiforgeryFeature.NewRequestTokenString);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetTokens_ExistingInvalidCookieToken_SwallowsExceptions()
|
|
{
|
|
// Arrange
|
|
// Make sure the existing cookie is invalid.
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false);
|
|
|
|
// Exception will cause the cookieToken to be null.
|
|
context.TokenSerializer
|
|
.Setup(o => o.Deserialize(context.TestTokenSet.OldCookieTokenString))
|
|
.Throws(new Exception("should be swallowed"));
|
|
context.TokenGenerator
|
|
.Setup(o => o.IsCookieTokenValid(null))
|
|
.Returns(false);
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenset = antiforgery.GetTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.Equal(context.TestTokenSet.NewCookieTokenString, tokenset.CookieToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, tokenset.RequestToken);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetTokens_ExistingValidCookieToken_GeneratesANewFormToken()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenset = antiforgery.GetTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.Null(tokenset.CookieToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, tokenset.RequestToken);
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
|
|
Assert.Null(antiforgeryFeature.NewCookieToken);
|
|
Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.NewRequestToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, antiforgeryFeature.NewRequestTokenString);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetTokens_DoesNotSerializeTwice()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = true,
|
|
HaveGeneratedNewCookieToken = true,
|
|
NewRequestToken = new AntiforgeryToken(),
|
|
NewRequestTokenString = "serialized-form-token-from-context",
|
|
};
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenset = antiforgery.GetTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.Null(tokenset.CookieToken);
|
|
Assert.Equal("serialized-form-token-from-context", tokenset.RequestToken);
|
|
|
|
Assert.Null(antiforgeryFeature.NewCookieToken);
|
|
|
|
// Token serializer not used.
|
|
context.TokenSerializer.Verify(
|
|
o => o.Deserialize(It.IsAny<string>()),
|
|
Times.Never);
|
|
context.TokenSerializer.Verify(
|
|
o => o.Serialize(It.IsAny<AntiforgeryToken>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAndStoreTokens_ExistingValidCookieToken_NotOverriden()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
// We shouldn't have saved the cookie because it already existed.
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(It.IsAny<HttpContext>(), It.IsAny<string>()),
|
|
Times.Never);
|
|
|
|
Assert.Null(tokenSet.CookieToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
|
|
Assert.Null(antiforgeryFeature.NewCookieToken);
|
|
Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.NewRequestToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, antiforgeryFeature.NewRequestTokenString);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAndStoreTokens_ExistingValidCookieToken_NotOverriden_AndSetsDoNotCacheHeaders()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
// We shouldn't have saved the cookie because it already existed.
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(It.IsAny<HttpContext>(), It.IsAny<string>()),
|
|
Times.Never);
|
|
|
|
Assert.Null(tokenSet.CookieToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
|
|
Assert.Equal("no-cache", context.HttpContext.Response.Headers[HeaderNames.Pragma]);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAndStoreTokens_ExistingCachingHeaders_Overriden()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
context.HttpContext.Response.Headers["Cache-Control"] = "public";
|
|
|
|
// Act
|
|
var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
// We shouldn't have saved the cookie because it already existed.
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(It.IsAny<HttpContext>(), It.IsAny<string>()),
|
|
Times.Never);
|
|
|
|
Assert.Null(tokenSet.CookieToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
|
|
Assert.Equal("no-cache", context.HttpContext.Response.Headers[HeaderNames.Pragma]);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAndStoreTokens_NoExistingCookieToken_Saved()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(It.IsAny<HttpContext>(), context.TestTokenSet.NewCookieTokenString),
|
|
Times.Once);
|
|
|
|
Assert.Equal(context.TestTokenSet.NewCookieTokenString, tokenSet.CookieToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
|
|
Assert.Equal(context.TestTokenSet.NewCookieToken, antiforgeryFeature.NewCookieToken);
|
|
Assert.Equal(context.TestTokenSet.NewCookieTokenString, antiforgeryFeature.NewCookieTokenString);
|
|
Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.NewRequestToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, antiforgeryFeature.NewRequestTokenString);
|
|
Assert.True(antiforgeryFeature.HaveStoredNewCookieToken);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAndStoreTokens_NoExistingCookieToken_Saved_AndSetsDoNotCacheHeaders()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(It.IsAny<HttpContext>(), context.TestTokenSet.NewCookieTokenString),
|
|
Times.Once);
|
|
|
|
Assert.Equal(context.TestTokenSet.NewCookieTokenString, tokenSet.CookieToken);
|
|
Assert.Equal(context.TestTokenSet.FormTokenString, tokenSet.RequestToken);
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
|
|
Assert.Equal("no-cache", context.HttpContext.Response.Headers[HeaderNames.Pragma]);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAndStoreTokens_DoesNotSerializeTwice()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = true,
|
|
HaveGeneratedNewCookieToken = true,
|
|
NewCookieToken = new AntiforgeryToken(),
|
|
NewCookieTokenString = "serialized-cookie-token-from-context",
|
|
NewRequestToken = new AntiforgeryToken(),
|
|
NewRequestTokenString = "serialized-form-token-from-context",
|
|
};
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
context.TokenStore
|
|
.Setup(t => t.SaveCookieToken(context.HttpContext, "serialized-cookie-token-from-context"))
|
|
.Verifiable();
|
|
|
|
// Act
|
|
var tokenset = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
// Token store used once, with expected arguments.
|
|
// Passed context's cookie token though request's cookie token was valid.
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(context.HttpContext, "serialized-cookie-token-from-context"),
|
|
Times.Once);
|
|
|
|
// Token serializer not used.
|
|
context.TokenSerializer.Verify(
|
|
o => o.Deserialize(It.IsAny<string>()),
|
|
Times.Never);
|
|
context.TokenSerializer.Verify(
|
|
o => o.Serialize(It.IsAny<AntiforgeryToken>()),
|
|
Times.Never);
|
|
|
|
Assert.Equal("serialized-cookie-token-from-context", tokenset.CookieToken);
|
|
Assert.Equal("serialized-form-token-from-context", tokenset.RequestToken);
|
|
|
|
Assert.True(antiforgeryFeature.HaveStoredNewCookieToken);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAndStoreTokens_DoesNotStoreTwice()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = true,
|
|
HaveGeneratedNewCookieToken = true,
|
|
HaveStoredNewCookieToken = true,
|
|
NewCookieToken = new AntiforgeryToken(),
|
|
NewCookieTokenString = "serialized-cookie-token-from-context",
|
|
NewRequestToken = new AntiforgeryToken(),
|
|
NewRequestTokenString = "serialized-form-token-from-context",
|
|
};
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenset = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
// Token store not used.
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(It.IsAny<HttpContext>(), It.IsAny<string>()),
|
|
Times.Never);
|
|
|
|
// Token serializer not used.
|
|
context.TokenSerializer.Verify(
|
|
o => o.Deserialize(It.IsAny<string>()),
|
|
Times.Never);
|
|
context.TokenSerializer.Verify(
|
|
o => o.Serialize(It.IsAny<AntiforgeryToken>()),
|
|
Times.Never);
|
|
|
|
Assert.Equal("serialized-cookie-token-from-context", tokenset.CookieToken);
|
|
Assert.Equal("serialized-form-token-from-context", tokenset.RequestToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task IsRequestValidAsync_FromStore_Failure()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
|
|
|
|
string message;
|
|
context.TokenGenerator
|
|
.Setup(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
context.TestTokenSet.OldCookieToken,
|
|
context.TestTokenSet.RequestToken,
|
|
out message))
|
|
.Returns(false);
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.False(result);
|
|
context.TokenGenerator.Verify();
|
|
|
|
// Failed _after_ updating the AntiforgeryContext.
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedRequestToken);
|
|
Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.RequestToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task IsRequestValidAsync_FromStore_Success()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
|
|
context.HttpContext.Request.Method = "POST";
|
|
|
|
string message;
|
|
context.TokenGenerator
|
|
.Setup(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
context.TestTokenSet.OldCookieToken,
|
|
context.TestTokenSet.RequestToken,
|
|
out message))
|
|
.Returns(true)
|
|
.Verifiable();
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.True(result);
|
|
context.TokenGenerator.Verify();
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedRequestToken);
|
|
Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.RequestToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task IsRequestValidAsync_DoesNotDeserializeTwice()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = true,
|
|
CookieToken = new AntiforgeryToken(),
|
|
HaveDeserializedRequestToken = true,
|
|
RequestToken = new AntiforgeryToken(),
|
|
};
|
|
var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
|
|
context.HttpContext.Request.Method = "POST";
|
|
|
|
string message;
|
|
context.TokenGenerator
|
|
.Setup(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
antiforgeryFeature.CookieToken,
|
|
antiforgeryFeature.RequestToken,
|
|
out message))
|
|
.Returns(true)
|
|
.Verifiable();
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.True(result);
|
|
context.TokenGenerator.Verify();
|
|
|
|
// Token serializer not used.
|
|
context.TokenSerializer.Verify(
|
|
o => o.Deserialize(It.IsAny<string>()),
|
|
Times.Never);
|
|
context.TokenSerializer.Verify(
|
|
o => o.Serialize(It.IsAny<AntiforgeryToken>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("GeT")]
|
|
[InlineData("HEAD")]
|
|
[InlineData("options")]
|
|
[InlineData("TrAcE")]
|
|
public async Task IsRequestValidAsync_SkipsAntiforgery_ForSafeHttpMethods(string httpMethod)
|
|
{
|
|
// Arrange
|
|
var context = CreateMockContext(new AntiforgeryOptions());
|
|
context.HttpContext.Request.Method = httpMethod;
|
|
|
|
string message;
|
|
context.TokenGenerator
|
|
.Setup(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
It.IsAny<AntiforgeryToken>(),
|
|
It.IsAny<AntiforgeryToken>(),
|
|
out message))
|
|
.Returns(false)
|
|
.Verifiable();
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.True(result);
|
|
context.TokenGenerator
|
|
.Verify(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
It.IsAny<AntiforgeryToken>(),
|
|
It.IsAny<AntiforgeryToken>(),
|
|
out message),
|
|
Times.Never);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("PUT")]
|
|
[InlineData("post")]
|
|
[InlineData("Delete")]
|
|
[InlineData("Custom")]
|
|
public async Task IsRequestValidAsync_ValidatesAntiforgery_ForNonSafeHttpMethods(string httpMethod)
|
|
{
|
|
// Arrange
|
|
var context = CreateMockContext(new AntiforgeryOptions());
|
|
context.HttpContext.Request.Method = httpMethod;
|
|
|
|
string message;
|
|
context.TokenGenerator
|
|
.Setup(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
It.IsAny<AntiforgeryToken>(),
|
|
It.IsAny<AntiforgeryToken>(),
|
|
out message))
|
|
.Returns(true)
|
|
.Verifiable();
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var result = await antiforgery.IsRequestValidAsync(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.True(result);
|
|
context.TokenGenerator.Verify();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateRequestAsync_FromStore_Failure()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
|
|
|
|
var message = "my-message";
|
|
context.TokenGenerator
|
|
.Setup(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
context.TestTokenSet.OldCookieToken,
|
|
context.TestTokenSet.RequestToken,
|
|
out message))
|
|
.Returns(false)
|
|
.Verifiable();
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act & assert
|
|
var exception = await Assert.ThrowsAsync<AntiforgeryValidationException>(
|
|
() => antiforgery.ValidateRequestAsync(context.HttpContext));
|
|
Assert.Equal("my-message", exception.Message);
|
|
context.TokenGenerator.Verify();
|
|
|
|
// Failed _after_ updating the AntiforgeryContext.
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedRequestToken);
|
|
Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.RequestToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateRequestAsync_FromStore_Success()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
|
|
|
|
string message;
|
|
context.TokenGenerator
|
|
.Setup(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
context.TestTokenSet.OldCookieToken,
|
|
context.TestTokenSet.RequestToken,
|
|
out message))
|
|
.Returns(true)
|
|
.Verifiable();
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
await antiforgery.ValidateRequestAsync(context.HttpContext);
|
|
|
|
// Assert
|
|
context.TokenGenerator.Verify();
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedRequestToken);
|
|
Assert.Equal(context.TestTokenSet.RequestToken, antiforgeryFeature.RequestToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateRequestAsync_NoCookieToken_Throws()
|
|
{
|
|
// Arrange
|
|
var context = CreateMockContext(new AntiforgeryOptions()
|
|
{
|
|
Cookie = { Name = "cookie-name" },
|
|
FormFieldName = "form-field-name",
|
|
HeaderName = null,
|
|
});
|
|
|
|
var tokenSet = new AntiforgeryTokenSet(null, null, "form-field-name", null);
|
|
context.TokenStore
|
|
.Setup(s => s.GetRequestTokensAsync(context.HttpContext))
|
|
.Returns(Task.FromResult(tokenSet));
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<AntiforgeryValidationException>(
|
|
() => antiforgery.ValidateRequestAsync(context.HttpContext));
|
|
Assert.Equal("The required antiforgery cookie \"cookie-name\" is not present.", exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateRequestAsync_NonFormRequest_HeaderDisabled_Throws()
|
|
{
|
|
// Arrange
|
|
var context = CreateMockContext(new AntiforgeryOptions()
|
|
{
|
|
Cookie = { Name = "cookie-name" },
|
|
FormFieldName = "form-field-name",
|
|
HeaderName = null,
|
|
});
|
|
|
|
var tokenSet = new AntiforgeryTokenSet(null, "cookie-token", "form-field-name", null);
|
|
context.TokenStore
|
|
.Setup(s => s.GetRequestTokensAsync(context.HttpContext))
|
|
.Returns(Task.FromResult(tokenSet));
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<AntiforgeryValidationException>(
|
|
() => antiforgery.ValidateRequestAsync(context.HttpContext));
|
|
Assert.Equal("The required antiforgery form field \"form-field-name\" is not present.", exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateRequestAsync_NonFormRequest_NoHeaderValue_Throws()
|
|
{
|
|
// Arrange
|
|
var context = CreateMockContext(new AntiforgeryOptions()
|
|
{
|
|
Cookie = { Name = "cookie-name" },
|
|
FormFieldName = "form-field-name",
|
|
HeaderName = "header-name",
|
|
});
|
|
|
|
context.HttpContext.Request.ContentType = "application/json";
|
|
|
|
var tokenSet = new AntiforgeryTokenSet(null, "cookie-token", "form-field-name", "header-name");
|
|
context.TokenStore
|
|
.Setup(s => s.GetRequestTokensAsync(context.HttpContext))
|
|
.Returns(Task.FromResult(tokenSet));
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<AntiforgeryValidationException>(
|
|
() => antiforgery.ValidateRequestAsync(context.HttpContext));
|
|
Assert.Equal("The required antiforgery header value \"header-name\" is not present.", exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateRequestAsync_FormRequest_NoRequestTokenValue_Throws()
|
|
{
|
|
// Arrange
|
|
var context = CreateMockContext(new AntiforgeryOptions()
|
|
{
|
|
Cookie = { Name = "cookie-name" },
|
|
FormFieldName = "form-field-name",
|
|
HeaderName = "header-name",
|
|
});
|
|
|
|
context.HttpContext.Request.ContentType = "application/x-www-form-urlencoded";
|
|
|
|
var tokenSet = new AntiforgeryTokenSet(null, "cookie-token", "form-field-name", "header-name");
|
|
context.TokenStore
|
|
.Setup(s => s.GetRequestTokensAsync(context.HttpContext))
|
|
.Returns(Task.FromResult(tokenSet));
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<AntiforgeryValidationException>(
|
|
() => antiforgery.ValidateRequestAsync(context.HttpContext));
|
|
Assert.Equal(
|
|
"The required antiforgery request token was not provided in either form field \"form-field-name\" " +
|
|
"or header value \"header-name\".",
|
|
exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateRequestAsync_DoesNotDeserializeTwice()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = true,
|
|
CookieToken = new AntiforgeryToken(),
|
|
HaveDeserializedRequestToken = true,
|
|
RequestToken = new AntiforgeryToken(),
|
|
};
|
|
var context = CreateMockContext(new AntiforgeryOptions(), antiforgeryFeature: antiforgeryFeature);
|
|
|
|
string message;
|
|
context.TokenGenerator
|
|
.Setup(o => o.TryValidateTokenSet(
|
|
context.HttpContext,
|
|
antiforgeryFeature.CookieToken,
|
|
antiforgeryFeature.RequestToken,
|
|
out message))
|
|
.Returns(true)
|
|
.Verifiable();
|
|
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
await antiforgery.ValidateRequestAsync(context.HttpContext);
|
|
|
|
// Assert (does not throw)
|
|
context.TokenGenerator.Verify();
|
|
|
|
// Token serializer not used.
|
|
context.TokenSerializer.Verify(
|
|
o => o.Deserialize(It.IsAny<string>()),
|
|
Times.Never);
|
|
context.TokenSerializer.Verify(
|
|
o => o.Serialize(It.IsAny<AntiforgeryToken>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetCookieTokenAndHeader_PreserveXFrameOptionsHeader()
|
|
{
|
|
// Arrange
|
|
var options = new AntiforgeryOptions();
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var expectedHeaderValue = "DIFFERENTORIGIN";
|
|
|
|
// Generate a new cookie.
|
|
var context = CreateMockContext(
|
|
options,
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
context.HttpContext.Response.Headers["X-Frame-Options"] = expectedHeaderValue;
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
// Assert
|
|
var xFrameOptions = context.HttpContext.Response.Headers["X-Frame-Options"];
|
|
Assert.Equal(expectedHeaderValue, xFrameOptions);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetCookieTokenAndHeader_NewCookieToken_SetsDoNotCacheHeaders()
|
|
{
|
|
// Arrange
|
|
var options = new AntiforgeryOptions();
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
|
|
// Generate a new cookie.
|
|
var context = CreateMockContext(
|
|
options,
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers["Cache-Control"]);
|
|
Assert.Equal("no-cache", context.HttpContext.Response.Headers["Pragma"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetCookieTokenAndHeader_ValidOldCookieToken_SetsDoNotCacheHeaders()
|
|
{
|
|
// Arrange
|
|
var options = new AntiforgeryOptions();
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
|
|
// Generate a new cookie.
|
|
var context = CreateMockContext(
|
|
options,
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers["Cache-Control"]);
|
|
Assert.Equal("no-cache", context.HttpContext.Response.Headers["Pragma"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetCookieTokenAndHeader_OverridesExistingCachingHeaders()
|
|
{
|
|
// Arrange
|
|
var options = new AntiforgeryOptions();
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
|
|
// Generate a new cookie.
|
|
var context = CreateMockContext(
|
|
options,
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
context.HttpContext.Response.Headers["Cache-Control"] = "public";
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
// Assert
|
|
Assert.Equal("no-cache, no-store", context.HttpContext.Response.Headers["Cache-Control"]);
|
|
Assert.Equal("no-cache", context.HttpContext.Response.Headers["Pragma"]);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false, "SAMEORIGIN")]
|
|
[InlineData(true, null)]
|
|
public void SetCookieTokenAndHeader_AddsXFrameOptionsHeader(
|
|
bool suppressXFrameOptions,
|
|
string expectedHeaderValue)
|
|
{
|
|
// Arrange
|
|
var options = new AntiforgeryOptions()
|
|
{
|
|
SuppressXFrameOptionsHeader = suppressXFrameOptions
|
|
};
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
|
|
// Generate a new cookie.
|
|
var context = CreateMockContext(
|
|
options,
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
// Assert
|
|
var xFrameOptions = context.HttpContext.Response.Headers["X-Frame-Options"];
|
|
Assert.Equal(expectedHeaderValue, xFrameOptions);
|
|
|
|
Assert.NotNull(antiforgeryFeature);
|
|
Assert.True(antiforgeryFeature.HaveDeserializedCookieToken);
|
|
Assert.Equal(context.TestTokenSet.OldCookieToken, antiforgeryFeature.CookieToken);
|
|
Assert.True(antiforgeryFeature.HaveGeneratedNewCookieToken);
|
|
Assert.Equal(context.TestTokenSet.NewCookieToken, antiforgeryFeature.NewCookieToken);
|
|
Assert.Equal(context.TestTokenSet.NewCookieTokenString, antiforgeryFeature.NewCookieTokenString);
|
|
Assert.True(antiforgeryFeature.HaveStoredNewCookieToken);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetCookieTokenAndHeader_DoesNotDeserializeTwice()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = true,
|
|
HaveGeneratedNewCookieToken = true,
|
|
NewCookieToken = new AntiforgeryToken(),
|
|
NewCookieTokenString = "serialized-cookie-token-from-context",
|
|
NewRequestToken = new AntiforgeryToken(),
|
|
NewRequestTokenString = "serialized-form-token-from-context",
|
|
};
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
context.TokenStore
|
|
.Setup(t => t.SaveCookieToken(context.HttpContext, "serialized-cookie-token-from-context"))
|
|
.Verifiable();
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
// Assert
|
|
// Token store used once, with expected arguments.
|
|
// Passed context's cookie token though request's cookie token was valid.
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(context.HttpContext, "serialized-cookie-token-from-context"),
|
|
Times.Once);
|
|
|
|
// Token serializer not used.
|
|
context.TokenSerializer.Verify(
|
|
o => o.Deserialize(It.IsAny<string>()),
|
|
Times.Never);
|
|
context.TokenSerializer.Verify(
|
|
o => o.Serialize(It.IsAny<AntiforgeryToken>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetCookieTokenAndHeader_DoesNotStoreTwice()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = true,
|
|
HaveGeneratedNewCookieToken = true,
|
|
HaveStoredNewCookieToken = true,
|
|
NewCookieToken = new AntiforgeryToken(),
|
|
NewCookieTokenString = "serialized-cookie-token-from-context",
|
|
NewRequestToken = new AntiforgeryToken(),
|
|
NewRequestTokenString = "serialized-form-token-from-context",
|
|
};
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: true,
|
|
isOldCookieValid: true,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
// Assert
|
|
// Token serializer not used.
|
|
context.TokenSerializer.Verify(
|
|
o => o.Deserialize(It.IsAny<string>()),
|
|
Times.Never);
|
|
context.TokenSerializer.Verify(
|
|
o => o.Serialize(It.IsAny<AntiforgeryToken>()),
|
|
Times.Never);
|
|
|
|
// Token store not used.
|
|
context.TokenStore.Verify(
|
|
t => t.SaveCookieToken(It.IsAny<HttpContext>(), It.IsAny<string>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetCookieTokenAndHeader_NullCookieToken()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = false,
|
|
HaveGeneratedNewCookieToken = false,
|
|
HaveStoredNewCookieToken = true,
|
|
NewCookieToken = new AntiforgeryToken(),
|
|
NewCookieTokenString = "serialized-cookie-token-from-context",
|
|
NewRequestToken = new AntiforgeryToken(),
|
|
NewRequestTokenString = "serialized-form-token-from-context",
|
|
};
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var testTokenSet = new TestTokenSet
|
|
{
|
|
OldCookieTokenString = null
|
|
};
|
|
|
|
var nullTokenStore = GetTokenStore(context.HttpContext, testTokenSet, false);
|
|
var antiforgery = GetAntiforgery(
|
|
context.HttpContext,
|
|
tokenGenerator: context.TokenGenerator.Object,
|
|
tokenStore: nullTokenStore.Object);
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
// Assert
|
|
context.TokenSerializer.Verify(s => s.Deserialize(null), Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetCookieTokenAndHeader_DoesNotModifyHeadersAfterResponseHasStarted()
|
|
{
|
|
// Arrange
|
|
var antiforgeryFeature = new AntiforgeryFeature
|
|
{
|
|
HaveDeserializedCookieToken = false,
|
|
HaveGeneratedNewCookieToken = false,
|
|
HaveStoredNewCookieToken = true,
|
|
NewCookieToken = new AntiforgeryToken(),
|
|
NewCookieTokenString = "serialized-cookie-token-from-context",
|
|
NewRequestToken = new AntiforgeryToken(),
|
|
NewRequestTokenString = "serialized-form-token-from-context",
|
|
};
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
var testTokenSet = new TestTokenSet
|
|
{
|
|
OldCookieTokenString = null
|
|
};
|
|
|
|
var nullTokenStore = GetTokenStore(context.HttpContext, testTokenSet, false);
|
|
var antiforgery = GetAntiforgery(
|
|
context.HttpContext,
|
|
tokenGenerator: context.TokenGenerator.Object,
|
|
tokenStore: nullTokenStore.Object);
|
|
|
|
TestResponseFeature testResponse = new TestResponseFeature();
|
|
context.HttpContext.Features.Set<IHttpResponseFeature>(testResponse);
|
|
context.HttpContext.Response.Headers["Cache-Control"] = "public";
|
|
testResponse.StartResponse();
|
|
|
|
// Act
|
|
antiforgery.SetCookieTokenAndHeader(context.HttpContext);
|
|
|
|
Assert.Equal("public", context.HttpContext.Response.Headers["Cache-Control"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAndStoreTokens_DoesNotLogWarning_IfNoExistingCacheHeadersPresent()
|
|
{
|
|
// Arrange
|
|
var testSink = new TestSink();
|
|
var loggerFactory = new Mock<ILoggerFactory>();
|
|
loggerFactory
|
|
.Setup(lf => lf.CreateLogger(typeof(DefaultAntiforgery).FullName))
|
|
.Returns(new TestLogger("test logger", testSink, enabled: true));
|
|
var services = new ServiceCollection();
|
|
services.AddSingleton(loggerFactory.Object);
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
context.HttpContext.RequestServices = services.BuildServiceProvider();
|
|
var antiforgery = GetAntiforgery(context);
|
|
|
|
// Act
|
|
var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
var hasWarningMessage = testSink.Writes
|
|
.Where(wc => wc.LogLevel == LogLevel.Warning)
|
|
.Select(wc => wc.State?.ToString())
|
|
.Contains(ResponseCacheHeadersOverrideWarningMessage);
|
|
Assert.False(hasWarningMessage);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Cache-Control", "Public")]
|
|
[InlineData("Cache-Control", "PuBlic")]
|
|
[InlineData("Cache-Control", "Private")]
|
|
[InlineData("Cache-Control", "PriVate")]
|
|
[InlineData("Cache-Control", "No-Store")]
|
|
[InlineData("Cache-Control", "No-store")]
|
|
[InlineData("Pragma", "Foo")]
|
|
public void GetAndStoreTokens_LogsWarning_NonNoCacheHeadersAlreadyPresent(string headerName, string headerValue)
|
|
{
|
|
// Arrange
|
|
var testSink = new TestSink();
|
|
var loggerFactory = new Mock<ILoggerFactory>();
|
|
loggerFactory
|
|
.Setup(lf => lf.CreateLogger(typeof(DefaultAntiforgery).FullName))
|
|
.Returns(new TestLogger("test logger", testSink, enabled: true));
|
|
var services = new ServiceCollection();
|
|
services.AddSingleton(loggerFactory.Object);
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
context.HttpContext.RequestServices = services.BuildServiceProvider();
|
|
var antiforgery = GetAntiforgery(context);
|
|
context.HttpContext.Response.Headers[headerName] = headerValue;
|
|
|
|
// Act
|
|
var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
var hasWarningMessage = testSink.Writes
|
|
.Where(wc => wc.LogLevel == LogLevel.Warning)
|
|
.Select(wc => wc.State?.ToString())
|
|
.Contains(ResponseCacheHeadersOverrideWarningMessage);
|
|
Assert.True(hasWarningMessage);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Cache-Control", "no-cache")]
|
|
[InlineData("Pragma", "no-cache")]
|
|
public void GetAndStoreTokens_DoesNotLogsWarning_ForNoCacheHeaders_AlreadyPresent(string headerName, string headerValue)
|
|
{
|
|
// Arrange
|
|
var testSink = new TestSink();
|
|
var loggerFactory = new Mock<ILoggerFactory>();
|
|
loggerFactory
|
|
.Setup(lf => lf.CreateLogger(typeof(DefaultAntiforgery).FullName))
|
|
.Returns(new TestLogger("test logger", testSink, enabled: true));
|
|
var services = new ServiceCollection();
|
|
services.AddSingleton(loggerFactory.Object);
|
|
var antiforgeryFeature = new AntiforgeryFeature();
|
|
var context = CreateMockContext(
|
|
new AntiforgeryOptions(),
|
|
useOldCookie: false,
|
|
isOldCookieValid: false,
|
|
antiforgeryFeature: antiforgeryFeature);
|
|
context.HttpContext.RequestServices = services.BuildServiceProvider();
|
|
var antiforgery = GetAntiforgery(context);
|
|
context.HttpContext.Response.Headers[headerName] = headerValue;
|
|
|
|
// Act
|
|
var tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
|
|
|
|
// Assert
|
|
var hasWarningMessage = testSink.Writes
|
|
.Where(wc => wc.LogLevel == LogLevel.Warning)
|
|
.Select(wc => wc.State?.ToString())
|
|
.Contains(ResponseCacheHeadersOverrideWarningMessage);
|
|
Assert.False(hasWarningMessage);
|
|
}
|
|
|
|
private DefaultAntiforgery GetAntiforgery(
|
|
HttpContext httpContext,
|
|
AntiforgeryOptions options = null,
|
|
IAntiforgeryTokenGenerator tokenGenerator = null,
|
|
IAntiforgeryTokenSerializer tokenSerializer = null,
|
|
IAntiforgeryTokenStore tokenStore = null)
|
|
{
|
|
var optionsManager = new TestOptionsManager();
|
|
if (options != null)
|
|
{
|
|
optionsManager.Value = options;
|
|
}
|
|
|
|
var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
|
|
return new DefaultAntiforgery(
|
|
antiforgeryOptionsAccessor: optionsManager,
|
|
tokenGenerator: tokenGenerator,
|
|
tokenSerializer: tokenSerializer,
|
|
tokenStore: tokenStore,
|
|
loggerFactory: loggerFactory);
|
|
}
|
|
|
|
private IServiceProvider GetServices()
|
|
{
|
|
var builder = new ServiceCollection();
|
|
builder.AddSingleton<ILoggerFactory>(new LoggerFactory());
|
|
|
|
return builder.BuildServiceProvider();
|
|
}
|
|
|
|
private HttpContext GetHttpContext(IAntiforgeryFeature antiforgeryFeature = null)
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
antiforgeryFeature = antiforgeryFeature ?? new AntiforgeryFeature();
|
|
httpContext.Features.Set(antiforgeryFeature);
|
|
httpContext.RequestServices = GetServices();
|
|
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity("some-auth"));
|
|
|
|
return httpContext;
|
|
}
|
|
|
|
private DefaultAntiforgery GetAntiforgery(AntiforgeryMockContext context)
|
|
{
|
|
return GetAntiforgery(
|
|
context.HttpContext,
|
|
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.OldCookieTokenString;
|
|
var formToken = testTokenSet.FormTokenString;
|
|
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(
|
|
formToken,
|
|
oldCookieToken,
|
|
"form",
|
|
"header")));
|
|
|
|
if (saveNewCookie)
|
|
{
|
|
var newCookieToken = testTokenSet.NewCookieTokenString;
|
|
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.RequestToken;
|
|
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(oldCookieToken))
|
|
.Returns(testTokenSet.OldCookieTokenString);
|
|
mockSerializer.Setup(o => o.Serialize(newCookieToken))
|
|
.Returns(testTokenSet.NewCookieTokenString);
|
|
return mockSerializer;
|
|
}
|
|
|
|
private AntiforgeryMockContext CreateMockContext(
|
|
AntiforgeryOptions options,
|
|
bool useOldCookie = false,
|
|
bool isOldCookieValid = true,
|
|
IAntiforgeryFeature antiforgeryFeature = null)
|
|
{
|
|
// Arrange
|
|
var httpContext = GetHttpContext(antiforgeryFeature);
|
|
var testTokenSet = GetTokenSet();
|
|
|
|
var mockSerializer = GetTokenSerializer(testTokenSet);
|
|
|
|
var mockTokenStore = GetTokenStore(httpContext, testTokenSet, !useOldCookie);
|
|
|
|
var mockGenerator = new Mock<IAntiforgeryTokenGenerator>(MockBehavior.Strict);
|
|
mockGenerator
|
|
.Setup(o => o.GenerateRequestToken(
|
|
httpContext,
|
|
useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken))
|
|
.Returns(testTokenSet.RequestToken);
|
|
|
|
mockGenerator
|
|
.Setup(o => o.GenerateCookieToken())
|
|
.Returns(useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken);
|
|
mockGenerator
|
|
.Setup(o => o.IsCookieTokenValid(null))
|
|
.Returns(false);
|
|
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()
|
|
{
|
|
return new TestTokenSet()
|
|
{
|
|
RequestToken = new AntiforgeryToken() { IsCookieToken = false },
|
|
FormTokenString = "serialized-form-token",
|
|
OldCookieToken = new AntiforgeryToken() { IsCookieToken = true },
|
|
OldCookieTokenString = "serialized-old-cookie-token",
|
|
NewCookieToken = new AntiforgeryToken() { IsCookieToken = true },
|
|
NewCookieTokenString = "serialized-new-cookie-token",
|
|
};
|
|
}
|
|
|
|
private class TestTokenSet
|
|
{
|
|
public AntiforgeryToken RequestToken { 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; }
|
|
}
|
|
|
|
private class TestOptionsManager : IOptions<AntiforgeryOptions>
|
|
{
|
|
public AntiforgeryOptions Value { get; set; } = new AntiforgeryOptions();
|
|
}
|
|
|
|
private class TestResponseFeature : HttpResponseFeature
|
|
{
|
|
private bool _hasStarted = false;
|
|
|
|
public override bool HasStarted { get => _hasStarted; }
|
|
|
|
public TestResponseFeature()
|
|
{
|
|
}
|
|
|
|
public void StartResponse()
|
|
{
|
|
_hasStarted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|