// 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.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; using Xunit; namespace Microsoft.AspNetCore.Identity.Test { public class SecurityStampTest { private class NoopHandler : IAuthenticationHandler { public Task AuthenticateAsync() { throw new NotImplementedException(); } public Task ChallengeAsync(ChallengeContext context) { throw new NotImplementedException(); } public Task HandleRequestAsync() { throw new NotImplementedException(); } public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) { throw new NotImplementedException(); } public Task SignInAsync(SignInContext context) { throw new NotImplementedException(); } public Task SignOutAsync(SignOutContext context) { throw new NotImplementedException(); } } [Fact] public async Task OnValidatePrincipalThrowsWithEmptyServiceCollection() { var httpContext = new Mock(); httpContext.Setup(c => c.RequestServices).Returns(new ServiceCollection().BuildServiceProvider()); var id = new ClaimsPrincipal(new ClaimsIdentity(IdentityCookieOptions.ApplicationScheme)); var ticket = new AuthenticationTicket(id, new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow }, IdentityCookieOptions.ApplicationScheme); var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityCookieOptions.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(), ticket, new CookieAuthenticationOptions()); var ex = await Assert.ThrowsAsync(() => SecurityStampValidator.ValidatePrincipalAsync(context)); } [Theory] [InlineData(true)] [InlineData(false)] public async Task OnValidatePrincipalTestSuccess(bool isPersistent) { var user = new TestUser("test"); var userManager = MockHelpers.MockUserManager(); var claimsManager = new Mock>(); var identityOptions = new IdentityOptions { SecurityStampValidationInterval = TimeSpan.Zero }; var options = new Mock>(); options.Setup(a => a.Value).Returns(identityOptions); var httpContext = new Mock(); var contextAccessor = new Mock(); contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object); var id = new ClaimsIdentity(identityOptions.Cookies.ApplicationCookieAuthenticationScheme); id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); var principal = new ClaimsPrincipal(id); var properties = new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow.AddSeconds(-1), IsPersistent = isPersistent }; var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, options.Object, null, new Mock().Object); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).ReturnsAsync(user).Verifiable(); signInManager.Setup(s => s.CreateUserPrincipalAsync(user)).ReturnsAsync(principal).Verifiable(); var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); var ticket = new AuthenticationTicket(principal, properties, identityOptions.Cookies.ApplicationCookieAuthenticationScheme); var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(identityOptions.Cookies.ApplicationCookieAuthenticationScheme) { HandlerType = typeof(NoopHandler) }.Build(), ticket, new CookieAuthenticationOptions()); Assert.NotNull(context.Properties); Assert.NotNull(context.Options); Assert.NotNull(context.Principal); await SecurityStampValidator.ValidatePrincipalAsync(context); Assert.NotNull(context.Principal); signInManager.VerifyAll(); } [Fact] public async Task OnValidateIdentityRejectsWhenValidateSecurityStampFails() { var user = new TestUser("test"); var userManager = MockHelpers.MockUserManager(); var claimsManager = new Mock>(); var identityOptions = new IdentityOptions { SecurityStampValidationInterval = TimeSpan.Zero }; var options = new Mock>(); options.Setup(a => a.Value).Returns(identityOptions); var httpContext = new Mock(); var contextAccessor = new Mock(); contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, options.Object, null, new Mock().Object); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).ReturnsAsync(default(TestUser)).Verifiable(); var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); var id = new ClaimsIdentity(identityOptions.Cookies.ApplicationCookieAuthenticationScheme); id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); var ticket = new AuthenticationTicket(new ClaimsPrincipal(id), new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow.AddSeconds(-1) }, identityOptions.Cookies.ApplicationCookieAuthenticationScheme); var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(identityOptions.Cookies.ApplicationCookieAuthenticationScheme) { HandlerType = typeof(NoopHandler) }.Build(), ticket, new CookieAuthenticationOptions()); Assert.NotNull(context.Properties); Assert.NotNull(context.Options); Assert.NotNull(context.Principal); await SecurityStampValidator.ValidatePrincipalAsync(context); Assert.Null(context.Principal); signInManager.VerifyAll(); } [Fact] public async Task OnValidateIdentityRejectsWhenNoIssuedUtc() { var user = new TestUser("test"); var httpContext = new Mock(); var userManager = MockHelpers.MockUserManager(); var claimsManager = new Mock>(); var identityOptions = new IdentityOptions { SecurityStampValidationInterval = TimeSpan.Zero }; var options = new Mock>(); options.Setup(a => a.Value).Returns(identityOptions); var contextAccessor = new Mock(); contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, options.Object, null, new Mock().Object); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).ReturnsAsync(default(TestUser)).Verifiable(); var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); var id = new ClaimsIdentity(identityOptions.Cookies.ApplicationCookieAuthenticationScheme); id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); var ticket = new AuthenticationTicket(new ClaimsPrincipal(id), new AuthenticationProperties(), identityOptions.Cookies.ApplicationCookieAuthenticationScheme); var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(identityOptions.Cookies.ApplicationCookieAuthenticationScheme) { HandlerType = typeof(NoopHandler) }.Build(), ticket, new CookieAuthenticationOptions()); Assert.NotNull(context.Properties); Assert.NotNull(context.Options); Assert.NotNull(context.Principal); await SecurityStampValidator.ValidatePrincipalAsync(context); Assert.Null(context.Principal); signInManager.VerifyAll(); } [Fact] public async Task OnValidateIdentityDoesNotRejectsWhenNotExpired() { var user = new TestUser("test"); var httpContext = new Mock(); var userManager = MockHelpers.MockUserManager(); var claimsManager = new Mock>(); var identityOptions = new IdentityOptions { SecurityStampValidationInterval = TimeSpan.FromDays(1) }; var options = new Mock>(); options.Setup(a => a.Value).Returns(identityOptions); var contextAccessor = new Mock(); contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, options.Object, null, new Mock().Object); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).Throws(new Exception("Shouldn't be called")); signInManager.Setup(s => s.SignInAsync(user, false, null)).Throws(new Exception("Shouldn't be called")); var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); var id = new ClaimsIdentity(identityOptions.Cookies.ApplicationCookieAuthenticationScheme); id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); var ticket = new AuthenticationTicket(new ClaimsPrincipal(id), new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow }, identityOptions.Cookies.ApplicationCookieAuthenticationScheme); var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(identityOptions.Cookies.ApplicationCookieAuthenticationScheme) { HandlerType = typeof(NoopHandler) }.Build(), ticket, new CookieAuthenticationOptions()); Assert.NotNull(context.Properties); Assert.NotNull(context.Options); Assert.NotNull(context.Principal); await SecurityStampValidator.ValidatePrincipalAsync(context); Assert.NotNull(context.Principal); } } }