[Design] Add antiforgery middleware

This new middleware participates in authentication and acts as a filter
when the request doesn't include a valid CSRF token for a POST.

Any authentication middleware that you want to validate an antiforgery
token should go ahead of this middleware in the pipeline (Cookies,
IISIntegration). This also takes care of automatic auth (Windows) done by
weblistener.

Any authentication middleware that you want to ignore antiforgery should
go after this middleware in the pipeline.

To facilitate this, there are a few changes in the antiforgery API
surface. Namely we can now pass in a principal to validate tokens. You
can't pass in a principal to generate tokens - we expect you to be logged
in at that poing. Also, ValidateRequestAsync(...) now checks the HTTP verb
and won't validate GETs and such.
This commit is contained in:
Ryan Nowak 2016-02-18 12:08:53 -08:00
parent 9783a7c42b
commit 4629148519
13 changed files with 1339 additions and 60 deletions

View File

@ -0,0 +1,29 @@
// 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 Microsoft.AspNetCore.Antiforgery;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Extension methods for configuring the antiforgery middleware.
/// </summary>
public static class AntiforgeryBuilderExtensions
{
/// <summary>
/// Adds the antiforgery middleware.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseAntiforgery(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<AntiforgeryMiddleware>();
}
}
}

View File

@ -0,0 +1,65 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Antiforgery.Internal;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Antiforgery
{
/// <summary>
/// A middleware implementation of antiforgery validation.
/// </summary>
public class AntiforgeryMiddleware
{
private readonly IAntiforgery _antiforgery;
private readonly RequestDelegate _next;
/// <summary>
/// Creates a new <see cref="AntiforgeryMiddleware"/>.
/// </summary>
/// <param name="next">The <see cref="RequestDelegate"/> for the next middleware.</param>
/// <param name="antiforgery">The <see cref="IAntiforgery"/>.</param>
public AntiforgeryMiddleware(RequestDelegate next, IAntiforgery antiforgery)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (antiforgery == null)
{
throw new ArgumentNullException(nameof(antiforgery));
}
_next = next;
_antiforgery = antiforgery;
}
/// <summary>
/// Invokes the middleware for the given <paramref name="httpContext"/>.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <returns>A <see cref="Task"/> which will be completed when execution of the middleware completes.</returns>
public async Task Invoke(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var handler = new AntiforgeryAuthenticationHandler(_antiforgery);
await handler.InitializeAsync(httpContext);
try
{
await _next(httpContext);
}
finally
{
handler.Teardown();
}
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
@ -46,6 +47,18 @@ namespace Microsoft.AspNetCore.Antiforgery
/// </returns>
Task<bool> IsRequestValidAsync(HttpContext httpContext);
/// <summary>
/// Asynchronously returns a value indicating whether the request passes antiforgery validation. If the
/// request uses a safe HTTP method (GET, HEAD, OPTIONS, TRACE), the antiforgery token is not validated.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="principal">The claims-based principal to validate.</param>
/// <returns>
/// A <see cref="Task{Boolean}"/> that, when completed, returns <c>true</c> if the is requst uses a safe HTTP
/// method or contains a value antiforgery token, otherwise returns <c>false</c>.
/// </returns>
Task<bool> IsRequestValidAsync(HttpContext httpContext, ClaimsPrincipal principal);
/// <summary>
/// Validates an antiforgery token that was supplied as part of the request.
/// </summary>
@ -55,6 +68,16 @@ namespace Microsoft.AspNetCore.Antiforgery
/// </exception>
Task ValidateRequestAsync(HttpContext httpContext);
/// <summary>
/// Validates an antiforgery token that was supplied as part of the request.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="principal">The claims-based principal to validate.</param>
/// <exception cref="AntiforgeryValidationException">
/// Thrown when the request does not include a valid antiforgery token.
/// </exception>
Task ValidateRequestAsync(HttpContext httpContext, ClaimsPrincipal principal);
/// <summary>
/// Generates and stores an antiforgery cookie token if one is not available or not valid.
/// </summary>

View File

@ -0,0 +1,161 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.AspNetCore.Http.Features.Authentication.Internal;
namespace Microsoft.AspNetCore.Antiforgery.Internal
{
public class AntiforgeryAuthenticationHandler : IAuthenticationHandler
{
private readonly IAntiforgery _antiforgery;
private HttpContext _httpContext;
private IAuthenticationHandler _priorHandler;
public AntiforgeryAuthenticationHandler(IAntiforgery antiforgery)
{
if (antiforgery == null)
{
throw new ArgumentNullException(nameof(antiforgery));
}
_antiforgery = antiforgery;
}
public async Task InitializeAsync(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
_httpContext = httpContext;
var authentication = GetAuthenticationFeature(_httpContext);
_priorHandler = authentication.Handler;
authentication.Handler = this;
if (authentication.User != null)
{
if (!await _antiforgery.IsRequestValidAsync(_httpContext))
{
// Wipe out any existing principal if we can't validate this request.
authentication.User = null;
return;
}
}
}
public void Teardown()
{
var authentication = GetAuthenticationFeature(_httpContext);
authentication.Handler = _priorHandler;
}
/// <inheritdoc />
public async Task AuthenticateAsync(AuthenticateContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (_priorHandler != null)
{
await _priorHandler.AuthenticateAsync(context);
var authentication = GetAuthenticationFeature(_httpContext);
if (context.Principal != null)
{
try
{
await _antiforgery.ValidateRequestAsync(_httpContext, context.Principal);
}
catch (AntiforgeryValidationException ex)
{
context.Failed(ex);
return;
}
}
}
}
/// <inheritdoc />
public Task ChallengeAsync(ChallengeContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (_priorHandler != null)
{
return _priorHandler.ChallengeAsync(context);
}
return TaskCache.CompletedTask;
}
/// <inheritdoc />
public void GetDescriptions(DescribeSchemesContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (_priorHandler != null)
{
_priorHandler.GetDescriptions(context);
}
}
/// <inheritdoc />
public Task SignInAsync(SignInContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (_priorHandler != null)
{
return _priorHandler.SignInAsync(context);
}
return TaskCache.CompletedTask;
}
/// <inheritdoc />
public Task SignOutAsync(SignOutContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (_priorHandler != null)
{
return _priorHandler.SignOutAsync(context);
}
return TaskCache.CompletedTask;
}
private static IHttpAuthenticationFeature GetAuthenticationFeature(HttpContext httpContext)
{
var authentication = httpContext.Features.Get<IHttpAuthenticationFeature>();
if (authentication == null)
{
authentication = new HttpAuthenticationFeature();
httpContext.Features.Set(authentication);
}
return authentication;
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
@ -85,7 +86,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
/// <inheritdoc />
public async Task<bool> IsRequestValidAsync(HttpContext httpContext)
public Task<bool> IsRequestValidAsync(HttpContext httpContext)
{
if (httpContext == null)
{
@ -94,13 +95,26 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
CheckSSLConfig(httpContext);
var method = httpContext.Request.Method;
if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) ||
string.Equals(method, "HEAD", StringComparison.OrdinalIgnoreCase) ||
string.Equals(method, "OPTIONS", StringComparison.OrdinalIgnoreCase) ||
string.Equals(method, "TRACE", StringComparison.OrdinalIgnoreCase))
return IsRequestValidAsync(httpContext, httpContext.User);
}
/// <inheritdoc />
public async Task<bool> IsRequestValidAsync(HttpContext httpContext, ClaimsPrincipal principal)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
CheckSSLConfig(httpContext);
if (!IsValidationRequired(httpContext))
{
// Validation not needed for these request types.
return true;
}
@ -126,6 +140,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
string message;
var result = _tokenGenerator.TryValidateTokenSet(
httpContext,
principal,
deserializedCookieToken,
deserializedRequestToken,
out message);
@ -143,7 +158,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
/// <inheritdoc />
public async Task ValidateRequestAsync(HttpContext httpContext)
public Task ValidateRequestAsync(HttpContext httpContext)
{
if (httpContext == null)
{
@ -152,6 +167,29 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
CheckSSLConfig(httpContext);
return ValidateRequestAsync(httpContext, httpContext.User);
}
/// <inheritdoc />
public async Task ValidateRequestAsync(HttpContext httpContext, ClaimsPrincipal principal)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
CheckSSLConfig(httpContext);
if (!IsValidationRequired(httpContext))
{
return;
}
var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
if (tokens.CookieToken == null)
{
@ -180,12 +218,15 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
}
ValidateTokens(httpContext, tokens);
ValidateTokens(httpContext, principal, tokens);
_logger.ValidatedAntiforgeryToken();
}
private void ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
private void ValidateTokens(
HttpContext httpContext,
ClaimsPrincipal principal,
AntiforgeryTokenSet antiforgeryTokenSet)
{
Debug.Assert(!string.IsNullOrEmpty(antiforgeryTokenSet.CookieToken));
Debug.Assert(!string.IsNullOrEmpty(antiforgeryTokenSet.RequestToken));
@ -203,6 +244,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
string message;
if (!_tokenGenerator.TryValidateTokenSet(
httpContext,
principal,
deserializedCookieToken,
deserializedRequestToken,
out message))
@ -268,6 +310,21 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
}
private bool IsValidationRequired(HttpContext httpContext)
{
var method = httpContext.Request.Method;
if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) ||
string.Equals(method, "HEAD", StringComparison.OrdinalIgnoreCase) ||
string.Equals(method, "OPTIONS", StringComparison.OrdinalIgnoreCase) ||
string.Equals(method, "TRACE", StringComparison.OrdinalIgnoreCase))
{
// Validation not needed for HTTP methods that don't mutate any state.
return false;
}
return true;
}
private AntiforgeryContext GetCookieTokens(HttpContext httpContext)
{
var services = httpContext.RequestServices;
@ -340,7 +397,10 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
if (antiforgeryContext.NewRequestToken == null)
{
var cookieToken = antiforgeryContext.NewCookieToken ?? antiforgeryContext.CookieToken;
antiforgeryContext.NewRequestToken = _tokenGenerator.GenerateRequestToken(httpContext, cookieToken);
antiforgeryContext.NewRequestToken = _tokenGenerator.GenerateRequestToken(
httpContext,
httpContext.User,
cookieToken);
}
return antiforgeryContext;

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
/// <inheritdoc />
public AntiforgeryToken GenerateRequestToken(
HttpContext httpContext,
ClaimsPrincipal principal,
AntiforgeryToken cookieToken)
{
if (httpContext == null)
@ -63,11 +64,11 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
var isIdentityAuthenticated = false;
// populate Username and ClaimUid
var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
var authenticatedIdentity = GetAuthenticatedIdentity(principal);
if (authenticatedIdentity != null)
{
isIdentityAuthenticated = true;
requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(principal));
if (requestToken.ClaimUid == null)
{
@ -109,6 +110,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
/// <inheritdoc />
public bool TryValidateTokenSet(
HttpContext httpContext,
ClaimsPrincipal principal,
AntiforgeryToken cookieToken,
AntiforgeryToken requestToken,
out string message)
@ -150,10 +152,10 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
var currentUsername = string.Empty;
BinaryBlob currentClaimUid = null;
var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
var authenticatedIdentity = GetAuthenticatedIdentity(principal);
if (authenticatedIdentity != null)
{
currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(principal));
if (currentClaimUid == null)
{
currentUsername = authenticatedIdentity.Name ?? string.Empty;

View File

@ -1,6 +1,7 @@
// 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.Security.Claims;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Antiforgery.Internal
@ -20,9 +21,13 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
/// Generates a request token corresponding to <paramref name="cookieToken"/>.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="principal">The claims-based principal to use for token generation.</param>
/// <param name="cookieToken">A valid cookie token.</param>
/// <returns>An <see cref="AntiforgeryToken"/>.</returns>
AntiforgeryToken GenerateRequestToken(HttpContext httpContext, AntiforgeryToken cookieToken);
AntiforgeryToken GenerateRequestToken(
HttpContext httpContext,
ClaimsPrincipal principal,
AntiforgeryToken cookieToken);
/// <summary>
/// Attempts to validate a cookie token.
@ -35,6 +40,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
/// Attempts to validate a cookie and request token set for the given <paramref name="httpContext"/>.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <param name="principal">The claims-based principal to use for token validation.</param>
/// <param name="cookieToken">A cookie token.</param>
/// <param name="requestToken">A request token.</param>
/// <param name="message">
@ -43,6 +49,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
/// <returns><c>true</c> if the tokens are valid, otherwise <c>false</c>.</returns>
bool TryValidateTokenSet(
HttpContext httpContext,
ClaimsPrincipal principal,
AntiforgeryToken cookieToken,
AntiforgeryToken requestToken,
out string message);

View File

@ -0,0 +1,12 @@
// 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.Threading.Tasks;
namespace Microsoft.AspNetCore.Antiforgery.Internal
{
public static class TaskCache
{
public static readonly Task CompletedTask = Task.FromResult(0);
}
}

View File

@ -13,8 +13,7 @@
},
"dependencies": {
"Microsoft.AspNetCore.DataProtection": "1.0.0-*",
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0-*",
"Microsoft.AspNetCore.WebUtilities": "1.0.0-*",
"Microsoft.AspNetCore.Http": "1.0.0-*",
"Microsoft.Extensions.ObjectPool": "1.0.0-*"
},
"frameworks": {

View File

@ -0,0 +1,382 @@
// 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.AspNetCore.Antiforgery.Internal;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.AspNetCore.Http.Features.Authentication.Internal;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Antiforgery
{
// These are really more like integration tests and just verify a bunch of different
// reasonable combinations of authN middleware.
public class AntiforgeryMiddlewareTest
{
private readonly ClaimsPrincipal LoggedInUser = new ClaimsPrincipal(new ClaimsIdentity[]
{
new ClaimsIdentity("Test"),
});
private readonly ClaimsPrincipal LoggedInUser2 = new ClaimsPrincipal(new ClaimsIdentity[]
{
new ClaimsIdentity("Test"),
});
[Fact]
public async Task AutomaticAuthentication_Anonymous()
{
// Arrange
var context = Setup((app) =>
{
app.Use(next => new AutomaticAuthenticationMiddleware(next, null).Invoke);
app.UseAntiforgery();
});
var httpContext = new DefaultHttpContext();
await context.AppFunc(httpContext);
Assert.Null(context.Principal);
}
[Fact]
public async Task AutomaticAuthentication_LoggedIn_WithoutToken()
{
// Arrange
var context = Setup((app) =>
{
app.UseMiddleware<AutomaticAuthenticationMiddleware>(LoggedInUser);
app.UseAntiforgery();
});
context.Antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false);
var httpContext = new DefaultHttpContext();
await context.AppFunc(httpContext);
Assert.Null(context.Principal);
}
[Fact]
public async Task AutomaticAuthentication_LoggedIn_WithValidToken()
{
// Arrange
var context = Setup((app) =>
{
app.UseMiddleware<AutomaticAuthenticationMiddleware>(LoggedInUser);
app.UseAntiforgery();
});
context.Antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(true);
var httpContext = new DefaultHttpContext();
await context.AppFunc(httpContext);
Assert.Same(LoggedInUser, context.Principal);
}
// A middleware after antiforgery in the pipeline can authenticate without going through token
// validation.
[Fact]
public async Task AutomaticAuthentication_LoggedIn_WithoutToken_AuthenticatedBySubsequentMiddleware()
{
// Arrange
var context = Setup((app) =>
{
app.UseMiddleware<AutomaticAuthenticationMiddleware>(LoggedInUser);
app.UseAntiforgery();
app.UseMiddleware<AutomaticAuthenticationMiddleware>(LoggedInUser2);
});
context.Antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false);
var httpContext = new DefaultHttpContext();
await context.AppFunc(httpContext);
Assert.Same(LoggedInUser2, context.Principal);
}
[Fact]
public async Task PasiveAuthentication_Anonymous()
{
// Arrange
var context = Setup((app) =>
{
app.Use(next => new AuthenticationHandlerMiddleware(next, null).Invoke);
app.UseAntiforgery();
app.UseMiddleware<CallAuthenticateMiddleware>();
});
var httpContext = new DefaultHttpContext();
await context.AppFunc(httpContext);
Assert.Null(context.Principal);
}
[Fact]
public async Task PassiveAuthentication_LoggedIn_WithoutToken()
{
// Arrange
var context = Setup((app) =>
{
app.UseMiddleware<AuthenticationHandlerMiddleware>(LoggedInUser);
app.UseAntiforgery();
app.UseMiddleware<CallAuthenticateMiddleware>();
});
context.Antiforgery
.Setup(a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), LoggedInUser))
.Throws(new AntiforgeryValidationException("error"));
var httpContext = new DefaultHttpContext();
await context.AppFunc(httpContext);
Assert.Null(context.Principal);
}
[Fact]
public async Task PassiveAuthentication_LoggedIn_WithValidToken()
{
// Arrange
var context = Setup((app) =>
{
app.UseMiddleware<AuthenticationHandlerMiddleware>(LoggedInUser);
app.UseAntiforgery();
app.UseMiddleware<CallAuthenticateMiddleware>();
});
context.Antiforgery
.Setup(a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), LoggedInUser))
.Returns(TaskCache.CompletedTask);
var httpContext = new DefaultHttpContext();
await context.AppFunc(httpContext);
Assert.Same(LoggedInUser, context.Principal);
}
// A middleware after antiforgery in the pipeline can authenticate without going through token
// validation.
[Fact]
public async Task PassiveAuthentication_LoggedIn_WithoutToken_AuthenticatedBySubsequentMiddleware()
{
// Arrange
var context = Setup((app) =>
{
app.UseMiddleware<AuthenticationHandlerMiddleware>(LoggedInUser);
app.UseAntiforgery();
app.UseMiddleware<AuthenticationHandlerMiddleware>(LoggedInUser2);
app.UseMiddleware<CallAuthenticateMiddleware>();
});
var httpContext = new DefaultHttpContext();
await context.AppFunc(httpContext);
Assert.Same(LoggedInUser2, context.Principal);
}
private static IHttpAuthenticationFeature GetAuthenticationFeature(HttpContext httpContext)
{
var authentication = httpContext.Features.Get<IHttpAuthenticationFeature>();
if (authentication == null)
{
authentication = new HttpAuthenticationFeature();
httpContext.Features.Set(authentication);
}
return authentication;
}
private static TestContext Setup(Action<IApplicationBuilder> action)
{
var services = new ServiceCollection();
services.AddLogging();
services.AddOptions();
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
services.AddSingleton(antiforgery.Object);
var result = new TestContext();
result.Antiforgery = antiforgery;
var app = new ApplicationBuilder(services.BuildServiceProvider());
action(app);
// Capture the logged in user 'after' the middleware so we can validate it.
app.Run(c =>
{
result.Principal = GetAuthenticationFeature(c).User;
return TaskCache.CompletedTask;
});
result.AppFunc = app.Build();
return result;
}
private class TestContext
{
public Mock<IAntiforgery> Antiforgery { get; set; }
public RequestDelegate AppFunc { get; set; }
public ClaimsPrincipal Principal { get; set; }
}
private class AutomaticAuthenticationMiddleware
{
private readonly RequestDelegate _next;
private readonly ClaimsPrincipal _principal;
public AutomaticAuthenticationMiddleware(RequestDelegate next, ClaimsPrincipal principal)
{
_next = next;
_principal = principal;
}
public Task Invoke(HttpContext httpContext)
{
GetAuthenticationFeature(httpContext).User = _principal;
return _next(httpContext);
}
}
private class AuthenticationHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ClaimsPrincipal _principal;
public AuthenticationHandlerMiddleware(RequestDelegate next, ClaimsPrincipal principal)
{
_next = next;
_principal = principal;
}
public async Task Invoke(HttpContext httpContext)
{
var handler = new AuthenticationHandler(_principal);
await handler.InitializeAsync(httpContext);
try
{
await _next(httpContext);
}
finally
{
await handler.TeardownAsync();
}
}
}
private class AuthenticationHandler : IAuthenticationHandler
{
private readonly ClaimsPrincipal _principal;
private IAuthenticationHandler _priorHandler;
private HttpContext _httpContext;
public AuthenticationHandler(ClaimsPrincipal principal)
{
_principal = principal;
}
public Task InitializeAsync(HttpContext httpContext)
{
_httpContext = httpContext;
var authenticationFeature = GetAuthenticationFeature(_httpContext);
_priorHandler = authenticationFeature.Handler;
authenticationFeature.Handler = this;
return TaskCache.CompletedTask;
}
public Task TeardownAsync()
{
var authenticationFeature = GetAuthenticationFeature(_httpContext);
authenticationFeature.Handler = _priorHandler;
return TaskCache.CompletedTask;
}
public Task AuthenticateAsync(AuthenticateContext context)
{
if (_principal == null)
{
context.NotAuthenticated();
}
else
{
context.Authenticated(_principal, null, null);
}
return TaskCache.CompletedTask;
}
public Task ChallengeAsync(ChallengeContext context)
{
throw new NotImplementedException();
}
public void GetDescriptions(DescribeSchemesContext context)
{
throw new NotImplementedException();
}
public Task SignInAsync(SignInContext context)
{
throw new NotImplementedException();
}
public Task SignOutAsync(SignOutContext context)
{
throw new NotImplementedException();
}
}
private class CallAuthenticateMiddleware
{
private readonly RequestDelegate _next;
public CallAuthenticateMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var authenticationFeature = GetAuthenticationFeature(httpContext);
var authenticateContext = new AuthenticateContext("Test");
await httpContext.Authentication.AuthenticateAsync(authenticateContext);
if (authenticateContext.Accepted)
{
authenticationFeature.User = authenticateContext.Principal;
}
await _next(httpContext);
}
}
}
}

View File

@ -0,0 +1,293 @@
// 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.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.AspNetCore.Http.Features.Authentication.Internal;
using Microsoft.AspNetCore.Http.Internal;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Antiforgery.Internal
{
public class AntiforgeryAuthenticationHandlerTest
{
[Fact]
public async Task IntializeAsync_NoOp_WhenAnonymous()
{
// Arrange
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false)
.Verifiable();
var httpContext = new DefaultHttpContext();
// Act
await handler.InitializeAsync(httpContext);
// Assert
antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()), Times.Never());
}
[Fact]
public async Task IntializeAsync_ValidatesRequest_WhenLoggedIn()
{
// Arrange
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(true)
.Verifiable();
var httpContext = new DefaultHttpContext();
var authenticationFeature = new HttpAuthenticationFeature();
httpContext.Features.Set<IHttpAuthenticationFeature>(authenticationFeature);
authenticationFeature.User = new ClaimsPrincipal();
// Act
await handler.InitializeAsync(httpContext);
// Assert
antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()), Times.Once());
}
[Fact]
public async Task IntializeAsync_ClearsUser_WhenInvalid()
{
// Arrange
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false)
.Verifiable();
var httpContext = new DefaultHttpContext();
var authenticationFeature = new HttpAuthenticationFeature();
httpContext.Features.Set<IHttpAuthenticationFeature>(authenticationFeature);
authenticationFeature.User = new ClaimsPrincipal();
// Act
await handler.InitializeAsync(httpContext);
// Assert
Assert.Null(authenticationFeature.User);
antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()), Times.Once());
}
[Fact]
public async Task IntializeAsync_AttachesAuthorizationHandler()
{
// Arrange
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false)
.Verifiable();
var httpContext = new DefaultHttpContext();
var authenticationFeature = new HttpAuthenticationFeature();
httpContext.Features.Set<IHttpAuthenticationFeature>(authenticationFeature);
// Act
await handler.InitializeAsync(httpContext);
// Assert
Assert.Same(handler, authenticationFeature.Handler);
antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()), Times.Never());
}
[Fact]
public async Task AuthenticateAsync_NoPriorHandler_NoOp()
{
// Arrange
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false)
.Verifiable();
antiforgery
.Setup(a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), It.IsAny<ClaimsPrincipal>()))
.Verifiable();
var httpContext = new DefaultHttpContext();
var authenticationFeature = new HttpAuthenticationFeature();
httpContext.Features.Set<IHttpAuthenticationFeature>(authenticationFeature);
await handler.InitializeAsync(httpContext);
var authenticateContext = new AuthenticateContext("Test");
// Act
await handler.AuthenticateAsync(authenticateContext);
// Assert
Assert.False(authenticateContext.Accepted);
antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()), Times.Never());
antiforgery.Verify(
a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), It.IsAny<ClaimsPrincipal>()),
Times.Never());
}
[Fact]
public async Task AuthenticateAsync_PriorHandlerDoesNotAuthenticate_NoOp()
{
// Arrange
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false)
.Verifiable();
antiforgery
.Setup(a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), It.IsAny<ClaimsPrincipal>()))
.Verifiable();
var httpContext = new DefaultHttpContext();
var authenticationFeature = new HttpAuthenticationFeature();
httpContext.Features.Set<IHttpAuthenticationFeature>(authenticationFeature);
var priorHandler = new Mock<IAuthenticationHandler>(MockBehavior.Strict);
authenticationFeature.Handler = priorHandler.Object;
priorHandler
.Setup(h => h.AuthenticateAsync(It.IsAny<AuthenticateContext>()))
.Returns(TaskCache.CompletedTask)
.Callback<AuthenticateContext>(c => c.NotAuthenticated());
await handler.InitializeAsync(httpContext);
var authenticateContext = new AuthenticateContext("Test");
// Act
await handler.AuthenticateAsync(authenticateContext);
// Assert
Assert.True(authenticateContext.Accepted);
Assert.Null(authenticateContext.Principal);
antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()), Times.Never());
antiforgery.Verify(
a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), It.IsAny<ClaimsPrincipal>()),
Times.Never());
}
[Fact]
public async Task AuthenticateAsync_PriorHandlerSetsPrincipal_Valid()
{
// Arrange
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
var principal = new ClaimsPrincipal();
antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false)
.Verifiable();
antiforgery
.Setup(a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), principal))
.Returns(TaskCache.CompletedTask)
.Verifiable();
var httpContext = new DefaultHttpContext();
var authenticationFeature = new HttpAuthenticationFeature();
httpContext.Features.Set<IHttpAuthenticationFeature>(authenticationFeature);
var priorHandler = new Mock<IAuthenticationHandler>(MockBehavior.Strict);
authenticationFeature.Handler = priorHandler.Object;
priorHandler
.Setup(h => h.AuthenticateAsync(It.IsAny<AuthenticateContext>()))
.Returns(TaskCache.CompletedTask)
.Callback<AuthenticateContext>(c => c.Authenticated(principal, null, null));
await handler.InitializeAsync(httpContext);
var authenticateContext = new AuthenticateContext("Test");
// Act
await handler.AuthenticateAsync(authenticateContext);
// Assert
Assert.True(authenticateContext.Accepted);
Assert.Same(principal, authenticateContext.Principal);
antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()), Times.Never());
antiforgery.Verify(
a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), principal),
Times.Once());
}
[Fact]
public async Task AuthenticateAsync_PriorHandlerSetsPrincipal_Invalid()
{
// Arrange
var antiforgery = new Mock<IAntiforgery>(MockBehavior.Strict);
var handler = new AntiforgeryAuthenticationHandler(antiforgery.Object);
var principal = new ClaimsPrincipal();
antiforgery
.Setup(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()))
.ReturnsAsync(false)
.Verifiable();
antiforgery
.Setup(a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), principal))
.Throws(new AntiforgeryValidationException("invalid"))
.Verifiable();
var httpContext = new DefaultHttpContext();
var authenticationFeature = new HttpAuthenticationFeature();
httpContext.Features.Set<IHttpAuthenticationFeature>(authenticationFeature);
var priorHandler = new Mock<IAuthenticationHandler>(MockBehavior.Strict);
authenticationFeature.Handler = priorHandler.Object;
priorHandler
.Setup(h => h.AuthenticateAsync(It.IsAny<AuthenticateContext>()))
.Returns(TaskCache.CompletedTask)
.Callback<AuthenticateContext>(c => c.Authenticated(principal, null, null));
await handler.InitializeAsync(httpContext);
var authenticateContext = new AuthenticateContext("Test");
// Act
await handler.AuthenticateAsync(authenticateContext);
// Assert
Assert.True(authenticateContext.Accepted);
Assert.Null(authenticateContext.Principal);
Assert.NotNull(authenticateContext.Error);
antiforgery.Verify(a => a.IsRequestValidAsync(It.IsAny<HttpContext>()), Times.Never());
antiforgery.Verify(
a => a.ValidateRequestAsync(It.IsAny<HttpContext>(), principal),
Times.Once());
}
}
}

View File

@ -16,6 +16,22 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
public class DefaultAntiforgeryTest
{
public static TheoryData<string> SafeHttpMethods => new TheoryData<string>()
{
"GeT",
"HEAD",
"options",
"TrAcE",
};
public static TheoryData<string> UnsafeHttpMethods => new TheoryData<string>()
{
"PUT",
"post",
"Delete",
"Custom",
};
[Fact]
public async Task ChecksSSL_ValidateRequestAsync_Throws()
{
@ -36,6 +52,26 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
exception.Message);
}
[Fact]
public async Task ChecksSSL_ValidateRequestAsync_WithPrincipal_Throws()
{
// Arrange
var httpContext = GetHttpContext();
var options = new AntiforgeryOptions()
{
RequireSsl = true
};
var antiforgery = GetAntiforgery(httpContext, options);
// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => antiforgery.ValidateRequestAsync(httpContext, new ClaimsPrincipal()));
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 async Task ChecksSSL_IsRequestValidAsync_Throws()
{
@ -57,6 +93,27 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
exception.Message);
}
[Fact]
public async Task ChecksSSL_IsRequestValidAsync_WithPrincipal_Throws()
{
// Arrange
var httpContext = GetHttpContext();
var options = new AntiforgeryOptions()
{
RequireSsl = true
};
var antiforgery = GetAntiforgery(httpContext, options);
// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => antiforgery.IsRequestValidAsync(httpContext, new ClaimsPrincipal()));
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()
{
@ -420,6 +477,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.RequestToken,
out message))
@ -454,6 +512,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.RequestToken,
out message))
@ -497,6 +556,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
contextAccessor.Value.CookieToken,
contextAccessor.Value.RequestToken,
out message))
@ -522,10 +582,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
[Theory]
[InlineData("GeT")]
[InlineData("HEAD")]
[InlineData("options")]
[InlineData("TrAcE")]
[MemberData(nameof(SafeHttpMethods))]
public async Task IsRequestValidAsync_SkipsAntiforgery_ForSafeHttpMethods(string httpMethod)
{
// Arrange
@ -536,6 +593,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message))
@ -552,6 +610,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Verify(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message),
@ -559,10 +618,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
}
[Theory]
[InlineData("PUT")]
[InlineData("post")]
[InlineData("Delete")]
[InlineData("Custom")]
[MemberData(nameof(UnsafeHttpMethods))]
public async Task IsRequestValidAsync_ValidatesAntiforgery_ForNonSafeHttpMethods(string httpMethod)
{
// Arrange
@ -573,6 +629,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message))
@ -589,6 +646,68 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator.Verify();
}
[Fact]
public async Task IsRequestValidAsync_UsesPrincipalFromHttpContext()
{
// Arrange
var context = CreateMockContext(new AntiforgeryOptions());
context.HttpContext.Request.Method = "POST";
var principal = new ClaimsPrincipal();
context.HttpContext.User = principal;
string message;
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
principal,
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 IsRequestValidAsync_UsesPassedInPrincipal()
{
// Arrange
var context = CreateMockContext(new AntiforgeryOptions());
context.HttpContext.Request.Method = "POST";
var principal = new ClaimsPrincipal();
context.HttpContext.User = new ClaimsPrincipal(); // This should be ignored.
string message;
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
principal,
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message))
.Returns(true)
.Verifiable();
var antiforgery = GetAntiforgery(context);
// Act
var result = await antiforgery.IsRequestValidAsync(context.HttpContext, principal);
// Assert
Assert.True(result);
context.TokenGenerator.Verify();
}
[Fact]
public async Task ValidateRequestAsync_FromStore_Failure()
{
@ -600,6 +719,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.RequestToken,
out message))
@ -632,6 +752,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
context.TestTokenSet.OldCookieToken,
context.TestTokenSet.RequestToken,
out message))
@ -776,6 +897,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
contextAccessor.Value.CookieToken,
contextAccessor.Value.RequestToken,
out message))
@ -799,6 +921,129 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
Times.Never);
}
[Theory]
[MemberData(nameof(SafeHttpMethods))]
public async Task ValidateRequestAsync_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<ClaimsPrincipal>(),
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message))
.Returns(false)
.Verifiable();
var antiforgery = GetAntiforgery(context);
// Act
await antiforgery.ValidateRequestAsync(context.HttpContext);
// Assert
context.TokenGenerator
.Verify(o => o.TryValidateTokenSet(
context.HttpContext,
It.IsAny<ClaimsPrincipal>(),
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message),
Times.Never);
}
[Theory]
[MemberData(nameof(UnsafeHttpMethods))]
public async Task ValidateRequestAsync_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<ClaimsPrincipal>(),
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message))
.Returns(true)
.Verifiable();
var antiforgery = GetAntiforgery(context);
// Act
await antiforgery.ValidateRequestAsync(context.HttpContext);
// Assert
context.TokenGenerator.Verify();
}
[Fact]
public async Task ValidateRequestAsync_UsesPrincipalFromHttpContext()
{
// Arrange
var context = CreateMockContext(new AntiforgeryOptions());
context.HttpContext.Request.Method = "POST";
var principal = new ClaimsPrincipal();
context.HttpContext.User = principal;
string message;
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
principal,
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message))
.Returns(true)
.Verifiable();
var antiforgery = GetAntiforgery(context);
// Act
await antiforgery.ValidateRequestAsync(context.HttpContext);
// Assert
context.TokenGenerator.Verify();
}
[Fact]
public async Task ValidateRequestAsync_UsesPassedInPrincipal()
{
// Arrange
var context = CreateMockContext(new AntiforgeryOptions());
context.HttpContext.Request.Method = "POST";
var principal = new ClaimsPrincipal();
context.HttpContext.User = new ClaimsPrincipal(); // This should be ignored.
string message;
context.TokenGenerator
.Setup(o => o.TryValidateTokenSet(
context.HttpContext,
principal,
It.IsAny<AntiforgeryToken>(),
It.IsAny<AntiforgeryToken>(),
out message))
.Returns(true)
.Verifiable();
var antiforgery = GetAntiforgery(context);
// Act
await antiforgery.ValidateRequestAsync(context.HttpContext, principal);
// Assert
context.TokenGenerator.Verify();
}
[Theory]
[InlineData(false, "SAMEORIGIN")]
[InlineData(true, null)]
@ -1045,6 +1290,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
mockGenerator
.Setup(o => o.GenerateRequestToken(
httpContext,
It.IsAny<ClaimsPrincipal>(),
useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken))
.Returns(testTokenSet.RequestToken);

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var cookieToken = new AntiforgeryToken() { IsCookieToken = false };
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
var principal = new ClaimsPrincipal(new ClaimsIdentity());
Assert.False(httpContext.User.Identity.IsAuthenticated);
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act & Assert
ExceptionAssert.ThrowsArgument(
() => tokenProvider.GenerateRequestToken(httpContext, cookieToken),
() => tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken),
"cookieToken",
"The antiforgery cookie token is invalid.");
}
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
var principal = new ClaimsPrincipal(new ClaimsIdentity());
Assert.False(httpContext.User.Identity.IsAuthenticated);
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
additionalDataProvider: null);
// Act
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken);
// Assert
Assert.NotNull(fieldToken);
@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
};
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
var principal = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
var options = new AntiforgeryOptions();
var claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act & assert
var exception = Assert.Throws<InvalidOperationException>(
() => tokenProvider.GenerateRequestToken(httpContext, cookieToken));
() => tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken));
Assert.Equal(
"The provided identity of type " +
$"'{typeof(MyAuthenticatedIdentityWithoutUsername).FullName}' " +
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
var principal = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
mockAdditionalDataProvider.Setup(o => o.GetAdditionalData(httpContext))
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
additionalDataProvider: mockAdditionalDataProvider.Object);
// Act
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken);
// Assert
Assert.NotNull(fieldToken);
@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
var identity = GetAuthenticatedIdentity("some-identity");
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(identity);
var principal = new ClaimsPrincipal(identity);
byte[] data = new byte[256 / 8];
using (var rng = RandomNumberGenerator.Create())
@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
additionalDataProvider: null);
// Act
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken);
// Assert
Assert.NotNull(fieldToken);
@ -190,7 +190,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
mockIdentity.Setup(o => o.Name)
.Returns("my-username");
httpContext.User = new ClaimsPrincipal(mockIdentity.Object);
var principal = new ClaimsPrincipal(mockIdentity.Object);
var claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
additionalDataProvider: null);
// Act
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, principal, cookieToken);
// Assert
Assert.NotNull(fieldToken);
@ -272,7 +272,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
var principal = new ClaimsPrincipal(new ClaimsIdentity());
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act & Assert
string message;
var ex = Assert.Throws<ArgumentNullException>(
() => tokenProvider.TryValidateTokenSet(httpContext, null, fieldtoken, out message));
() => tokenProvider.TryValidateTokenSet(httpContext, principal, null, fieldtoken, out message));
var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine));
Assert.Equal(@"The required antiforgery cookie token must be provided.", trimmed);
@ -294,7 +294,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
var principal = new ClaimsPrincipal(new ClaimsIdentity());
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
@ -306,7 +306,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act & Assert
string message;
var ex = Assert.Throws<ArgumentNullException>(
() => tokenProvider.TryValidateTokenSet(httpContext, cookieToken, null, out message));
() => tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, null, out message));
var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine));
Assert.Equal("The required antiforgery request token must be provided.", trimmed);
@ -317,7 +317,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
var principal = new ClaimsPrincipal(new ClaimsIdentity());
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
@ -332,7 +332,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, fieldtoken, fieldtoken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, fieldtoken, fieldtoken, out message);
// Assert
Assert.False(result);
@ -344,7 +344,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
var principal = new ClaimsPrincipal(new ClaimsIdentity());
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
@ -359,7 +359,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, cookieToken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, cookieToken, out message);
// Assert
Assert.False(result);
@ -371,7 +371,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
var principal = new ClaimsPrincipal(new ClaimsIdentity());
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
@ -384,7 +384,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.False(result);
@ -400,7 +400,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = GetAuthenticatedIdentity(identityUsername);
httpContext.User = new ClaimsPrincipal(identity);
var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@ -424,7 +424,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.False(result);
@ -437,7 +437,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = GetAuthenticatedIdentity("the-user");
httpContext.User = new ClaimsPrincipal(identity);
var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@ -462,7 +462,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.False(result);
@ -475,7 +475,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = new ClaimsIdentity();
httpContext.User = new ClaimsPrincipal(identity);
var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@ -499,7 +499,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.False(result);
@ -512,7 +512,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = new ClaimsIdentity();
httpContext.User = new ClaimsPrincipal(identity);
var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@ -533,7 +533,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.True(result);
@ -546,7 +546,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = GetAuthenticatedIdentity("the-user");
httpContext.User = new ClaimsPrincipal(identity);
var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@ -567,7 +567,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.True(result);
@ -580,7 +580,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Arrange
var httpContext = new DefaultHttpContext();
var identity = GetAuthenticatedIdentity("the-user");
httpContext.User = new ClaimsPrincipal(identity);
var principal = new ClaimsPrincipal(identity);
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
var fieldtoken = new AntiforgeryToken()
@ -600,7 +600,7 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal
// Act
string message;
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
var result = tokenProvider.TryValidateTokenSet(httpContext, principal, cookieToken, fieldtoken, out message);
// Assert
Assert.True(result);