diff --git a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp3.0.cs b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp3.0.cs index d2a9cf1338..2efe1f5634 100644 --- a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp3.0.cs +++ b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp3.0.cs @@ -94,8 +94,9 @@ namespace Microsoft.AspNetCore.Identity } public partial class SecurityStampValidator : Microsoft.AspNetCore.Identity.ISecurityStampValidator where TUser : class { - public SecurityStampValidator(Microsoft.Extensions.Options.IOptions options, Microsoft.AspNetCore.Identity.SignInManager signInManager, Microsoft.AspNetCore.Authentication.ISystemClock clock) { } + public SecurityStampValidator(Microsoft.Extensions.Options.IOptions options, Microsoft.AspNetCore.Identity.SignInManager signInManager, Microsoft.AspNetCore.Authentication.ISystemClock clock, Microsoft.Extensions.Logging.ILoggerFactory logger) { } public Microsoft.AspNetCore.Authentication.ISystemClock Clock { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Identity.SecurityStampValidatorOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Identity.SignInManager SignInManager { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] @@ -171,7 +172,7 @@ namespace Microsoft.AspNetCore.Identity } public partial class TwoFactorSecurityStampValidator : Microsoft.AspNetCore.Identity.SecurityStampValidator, Microsoft.AspNetCore.Identity.ISecurityStampValidator, Microsoft.AspNetCore.Identity.ITwoFactorSecurityStampValidator where TUser : class { - public TwoFactorSecurityStampValidator(Microsoft.Extensions.Options.IOptions options, Microsoft.AspNetCore.Identity.SignInManager signInManager, Microsoft.AspNetCore.Authentication.ISystemClock clock) : base (default(Microsoft.Extensions.Options.IOptions), default(Microsoft.AspNetCore.Identity.SignInManager), default(Microsoft.AspNetCore.Authentication.ISystemClock)) { } + public TwoFactorSecurityStampValidator(Microsoft.Extensions.Options.IOptions options, Microsoft.AspNetCore.Identity.SignInManager signInManager, Microsoft.AspNetCore.Authentication.ISystemClock clock, Microsoft.Extensions.Logging.ILoggerFactory logger) : base (default(Microsoft.Extensions.Options.IOptions), default(Microsoft.AspNetCore.Identity.SignInManager), default(Microsoft.AspNetCore.Authentication.ISystemClock), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } protected override System.Threading.Tasks.Task SecurityStampVerified(TUser user, Microsoft.AspNetCore.Authentication.Cookies.CookieValidatePrincipalContext context) { throw null; } protected override System.Threading.Tasks.Task VerifySecurityStamp(System.Security.Claims.ClaimsPrincipal principal) { throw null; } } diff --git a/src/Identity/Core/src/SecurityStampValidator.cs b/src/Identity/Core/src/SecurityStampValidator.cs index 1766d1b450..726f0097f9 100644 --- a/src/Identity/Core/src/SecurityStampValidator.cs +++ b/src/Identity/Core/src/SecurityStampValidator.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Identity @@ -23,7 +24,8 @@ namespace Microsoft.AspNetCore.Identity /// Used to access the . /// The . /// The system clock. - public SecurityStampValidator(IOptions options, SignInManager signInManager, ISystemClock clock) + /// The logger. + public SecurityStampValidator(IOptions options, SignInManager signInManager, ISystemClock clock, ILoggerFactory logger) { if (options == null) { @@ -36,6 +38,7 @@ namespace Microsoft.AspNetCore.Identity SignInManager = signInManager; Options = options.Value; Clock = clock; + Logger = logger.CreateLogger(this.GetType().FullName); } /// @@ -53,6 +56,14 @@ namespace Microsoft.AspNetCore.Identity /// public ISystemClock Clock { get; } + /// + /// Gets the used to log messages. + /// + /// + /// The used to log messages. + /// + public ILogger Logger { get; set; } + /// /// Called when the security stamp has been verified. /// @@ -121,6 +132,7 @@ namespace Microsoft.AspNetCore.Identity } else { + Logger.LogDebug(0, "Security stamp validation failed, rejecting cookie."); context.RejectPrincipal(); await SignInManager.SignOutAsync(); } @@ -163,4 +175,4 @@ namespace Microsoft.AspNetCore.Identity return validator.ValidateAsync(context); } } -} \ No newline at end of file +} diff --git a/src/Identity/Core/src/SignInManager.cs b/src/Identity/Core/src/SignInManager.cs index 2711de0333..1fa03c3d8a 100644 --- a/src/Identity/Core/src/SignInManager.cs +++ b/src/Identity/Core/src/SignInManager.cs @@ -233,6 +233,7 @@ namespace Microsoft.AspNetCore.Identity { return user; } + Logger.LogDebug(4, "Failed to validate a security stamp."); return null; } @@ -255,6 +256,7 @@ namespace Microsoft.AspNetCore.Identity { return user; } + Logger.LogDebug(5, "Failed to validate a security stamp."); return null; } diff --git a/src/Identity/Core/src/TwoFactorSecurityStampValidator.cs b/src/Identity/Core/src/TwoFactorSecurityStampValidator.cs index 7d0feb7d39..157af01b68 100644 --- a/src/Identity/Core/src/TwoFactorSecurityStampValidator.cs +++ b/src/Identity/Core/src/TwoFactorSecurityStampValidator.cs @@ -5,6 +5,7 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Identity @@ -21,7 +22,8 @@ namespace Microsoft.AspNetCore.Identity /// Used to access the . /// The . /// The system clock. - public TwoFactorSecurityStampValidator(IOptions options, SignInManager signInManager, ISystemClock clock) : base(options, signInManager, clock) + /// The logger. + public TwoFactorSecurityStampValidator(IOptions options, SignInManager signInManager, ISystemClock clock, ILoggerFactory logger) : base(options, signInManager, clock, logger) { } /// @@ -41,4 +43,4 @@ namespace Microsoft.AspNetCore.Identity protected override Task SecurityStampVerified(TUser user, CookieValidatePrincipalContext context) => Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/src/Identity/EntityFrameworkCore/test/EF.Test/SqlStoreTestBase.cs b/src/Identity/EntityFrameworkCore/test/EF.Test/SqlStoreTestBase.cs index 77932fae4c..3c7ae781a4 100644 --- a/src/Identity/EntityFrameworkCore/test/EF.Test/SqlStoreTestBase.cs +++ b/src/Identity/EntityFrameworkCore/test/EF.Test/SqlStoreTestBase.cs @@ -339,6 +339,20 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test Assert.Equal(2, (await manager.GetRolesAsync(userByLogin)).Count); } + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public async Task GetSecurityStampThrowsIfNull() + { + var manager = CreateManager(); + var user = CreateTestUser(); + var result = await manager.CreateAsync(user); + Assert.NotNull(user); + user.SecurityStamp = null; + await Assert.ThrowsAsync(async () => await manager.GetSecurityStampAsync(user)); + } + [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] diff --git a/src/Identity/Extensions.Core/src/UserManager.cs b/src/Identity/Extensions.Core/src/UserManager.cs index 821279b4a7..b785a82019 100644 --- a/src/Identity/Extensions.Core/src/UserManager.cs +++ b/src/Identity/Extensions.Core/src/UserManager.cs @@ -860,7 +860,13 @@ namespace Microsoft.AspNetCore.Identity { throw new ArgumentNullException(nameof(user)); } - return await securityStore.GetSecurityStampAsync(user, CancellationToken); + var stamp = await securityStore.GetSecurityStampAsync(user, CancellationToken); + if (stamp == null) + { + Logger.LogWarning(15, "GetSecurityStampAsync for user {userId} failed because stamp was null.", await GetUserIdAsync(user)); + throw new InvalidOperationException(Resources.NullSecurityStamp); + } + return stamp; } /// diff --git a/src/Identity/Extensions.Stores/src/IdentityUser.cs b/src/Identity/Extensions.Stores/src/IdentityUser.cs index e95bb62366..24541709e8 100644 --- a/src/Identity/Extensions.Stores/src/IdentityUser.cs +++ b/src/Identity/Extensions.Stores/src/IdentityUser.cs @@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Identity public IdentityUser() { Id = Guid.NewGuid().ToString(); + SecurityStamp = Guid.NewGuid().ToString(); } /// diff --git a/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs b/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs index 6681c524b1..bef1c3f047 100644 --- a/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs +++ b/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs @@ -274,25 +274,6 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); } - /// - /// Test. - /// - /// Task - [Fact] - public async Task CreateUpdatesSecurityStamp() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var username = "Create" + Guid.NewGuid().ToString(); - var user = CreateTestUser(username, useNamePrefixAsUserName: true); - var stamp = await manager.GetSecurityStampAsync(user); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - /// /// Test. /// @@ -965,10 +946,8 @@ namespace Microsoft.AspNetCore.Identity.Test } var manager = CreateManager(); var user = CreateTestUser(); - Assert.Null(await manager.GetSecurityStampAsync(user)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/RegisterConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/RegisterConfirmation.cshtml.cs index 5d3b254a84..c361bf7a26 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/RegisterConfirmation.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/RegisterConfirmation.cshtml.cs @@ -4,11 +4,9 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Hosting; namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal @@ -49,13 +47,11 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal internal class RegisterConfirmationModel : RegisterConfirmationModel where TUser : class { private readonly UserManager _userManager; - private readonly IWebHostEnvironment _hostingEnv; private readonly IEmailSender _sender; - public RegisterConfirmationModel(UserManager userManager, IWebHostEnvironment hostingEnv, IEmailSender sender) + public RegisterConfirmationModel(UserManager userManager, IEmailSender sender) { _userManager = userManager; - _hostingEnv = hostingEnv; _sender = sender; } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Email.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Email.cshtml index bc4e8d8ecf..db6f041c03 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Email.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Email.cshtml @@ -31,7 +31,7 @@ - + diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/RegisterConfirmation.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/RegisterConfirmation.cshtml index a8a27df012..4b884db838 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/RegisterConfirmation.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/RegisterConfirmation.cshtml @@ -4,7 +4,7 @@ ViewData["Title"] = "Register confirmation"; } -

@ViewData["Title"]

+

@ViewData["Title"]

@{ if (@Model.DisplayConfirmAccountLink) { diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/RegisterConfirmation.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/RegisterConfirmation.cshtml.cs index d4a5763305..ab07bb4574 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/RegisterConfirmation.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/RegisterConfirmation.cshtml.cs @@ -4,11 +4,9 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Hosting; namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal @@ -49,13 +47,11 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal internal class RegisterConfirmationModel : RegisterConfirmationModel where TUser : class { private readonly UserManager _userManager; - private readonly IWebHostEnvironment _hostingEnv; private readonly IEmailSender _sender; - public RegisterConfirmationModel(UserManager userManager, IWebHostEnvironment hostingEnv, IEmailSender sender) + public RegisterConfirmationModel(UserManager userManager, IEmailSender sender) { _userManager = userManager; - _hostingEnv = hostingEnv; _sender = sender; } @@ -73,7 +69,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal } Email = email; - // This sender is a no-op, so we should auto confirm the account + // If the email sender is a no-op, display the confirm link in the page DisplayConfirmAccountLink = _sender is EmailSender; if (DisplayConfirmAccountLink) { @@ -84,7 +80,6 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal pageHandler: null, values: new { userId = userId, code = code }, protocol: Request.Scheme); - } return Page(); diff --git a/src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs b/src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs index 539fe3679e..0710973b25 100644 --- a/src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs +++ b/src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -117,7 +118,7 @@ namespace Microsoft.AspNetCore.Identity.Test var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); - services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock())); + services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock(), new LoggerFactory())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); await testCode.Invoke(); @@ -171,7 +172,7 @@ namespace Microsoft.AspNetCore.Identity.Test var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager); - services.AddSingleton(new SecurityStampValidator(options.Object, signInManager, new SystemClock())); + services.AddSingleton(new SecurityStampValidator(options.Object, signInManager, new SystemClock(), new LoggerFactory())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); var tid = new ClaimsIdentity(IdentityConstants.ApplicationScheme); @@ -210,7 +211,7 @@ namespace Microsoft.AspNetCore.Identity.Test var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); - services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock())); + services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock(), new LoggerFactory())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); var id = new ClaimsIdentity(IdentityConstants.ApplicationScheme); id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); @@ -247,7 +248,7 @@ namespace Microsoft.AspNetCore.Identity.Test var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); - services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock())); + services.AddSingleton(new SecurityStampValidator(options.Object, signInManager.Object, new SystemClock(), new LoggerFactory())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); var id = new ClaimsIdentity(IdentityConstants.ApplicationScheme); id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); @@ -283,7 +284,7 @@ namespace Microsoft.AspNetCore.Identity.Test var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); - services.AddSingleton(new TwoFactorSecurityStampValidator(options.Object, signInManager.Object, new SystemClock())); + services.AddSingleton(new TwoFactorSecurityStampValidator(options.Object, signInManager.Object, new SystemClock(), new LoggerFactory())); httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider()); var principal = await signInManager.Object.StoreRememberClient(user); diff --git a/src/Identity/test/Identity.Test/UserManagerTest.cs b/src/Identity/test/Identity.Test/UserManagerTest.cs index 91390f6968..9dd93e39e3 100644 --- a/src/Identity/test/Identity.Test/UserManagerTest.cs +++ b/src/Identity/test/Identity.Test/UserManagerTest.cs @@ -84,6 +84,25 @@ namespace Microsoft.AspNetCore.Identity.Test store.VerifyAll(); } + [Fact] + public async Task CreateUpdatesSecurityStampStore() + { + // Setup + var store = new Mock>(); + var user = new PocoUser { UserName = "Foo", SecurityStamp = "sssss" }; + store.Setup(s => s.CreateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + store.Setup(s => s.GetSecurityStampAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.SecurityStamp)).Verifiable(); + store.Setup(s => s.SetSecurityStampAsync(user, It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + var userManager = MockHelpers.TestUserManager(store.Object); + + // Act + var result = await userManager.CreateAsync(user); + + // Assert + Assert.True(result.Succeeded); + store.VerifyAll(); + } + [Fact] public async Task CreateCallsUpdateEmailStore() {