diff --git a/src/Identity/SignInManager.cs b/src/Identity/SignInManager.cs index edef639426..f805e596ab 100644 --- a/src/Identity/SignInManager.cs +++ b/src/Identity/SignInManager.cs @@ -258,8 +258,9 @@ namespace Microsoft.AspNetCore.Identity /// The expected security stamp value. /// True if the stamp matches the persisted value, otherwise it will return false. public virtual async Task ValidateSecurityStampAsync(TUser user, string securityStamp) - => user != null && UserManager.SupportsUserSecurityStamp - && securityStamp == await UserManager.GetSecurityStampAsync(user); + => user != null && + // Only validate the security stamp if the store supports it + (!UserManager.SupportsUserSecurityStamp || securityStamp == await UserManager.GetSecurityStampAsync(user)); /// /// Attempts to sign in the specified and combination diff --git a/test/Identity.Test/SecurityStampValidatorTest.cs b/test/Identity.Test/SecurityStampValidatorTest.cs index fe8d15a274..8bcf985540 100644 --- a/test/Identity.Test/SecurityStampValidatorTest.cs +++ b/test/Identity.Test/SecurityStampValidatorTest.cs @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Identity.Test contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock().Object); - signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).ReturnsAsync(shouldStampValidate ? user : default(PocoUser)).Verifiable(); + signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).ReturnsAsync(shouldStampValidate ? user : default).Verifiable(); if (shouldStampValidate) { @@ -147,6 +147,50 @@ namespace Microsoft.AspNetCore.Identity.Test }); } + [Fact] + public async Task OnValidateIdentityAcceptsWhenStoreDoesNotSupportSecurityStamp() + { + var user = new PocoUser("test"); + var httpContext = new Mock(); + + + var userManager = MockHelpers.MockUserManager(); + + var claimsManager = new Mock>(); + var identityOptions = new Mock>(); + identityOptions.Setup(a => a.Value).Returns(new IdentityOptions()); + var options = new Mock>(); + options.Setup(a => a.Value).Returns(new SecurityStampValidatorOptions { ValidationInterval = TimeSpan.Zero }); + var contextAccessor = new Mock(); + contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object); + var signInManager = new SignInManager(userManager.Object, + contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock().Object); + userManager.Setup(u => u.GetUserAsync(It.IsAny())).ReturnsAsync(user).Verifiable(); + claimsManager.Setup(c => c.CreateAsync(user)).ReturnsAsync(new ClaimsPrincipal()).Verifiable(); + + var services = new ServiceCollection(); + services.AddSingleton(options.Object); + services.AddSingleton(signInManager); + services.AddSingleton(new SecurityStampValidator(options.Object, signInManager, new SystemClock())); + httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); + + var tid = new ClaimsIdentity(IdentityConstants.ApplicationScheme); + tid.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); + var ticket = new AuthenticationTicket(new ClaimsPrincipal(tid), + new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow.AddSeconds(-1) }, + IdentityConstants.ApplicationScheme); + + var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityConstants.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(), new CookieAuthenticationOptions(), ticket); + Assert.NotNull(context.Properties); + Assert.NotNull(context.Options); + Assert.NotNull(context.Principal); + await SecurityStampValidator.ValidatePrincipalAsync(context); + Assert.NotNull(context.Principal); + + userManager.VerifyAll(); + claimsManager.VerifyAll(); + } + [Fact] public async Task OnValidateIdentityRejectsWhenNoIssuedUtc() {