diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs index ddedc15c3..87cf6d257 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs @@ -1247,6 +1247,10 @@ public async virtual Task FindByLoginAsync(string loginProvider, string p { throw new ArgumentNullException(nameof(user)); } + if (stamp == null) + { + throw new ArgumentNullException(nameof(stamp)); + } user.SecurityStamp = stamp; return TaskCache.CompletedTask; } diff --git a/src/Microsoft.AspNetCore.Identity/IdentityErrorDescriber.cs b/src/Microsoft.AspNetCore.Identity/IdentityErrorDescriber.cs index 54c024c79..914e82e5e 100644 --- a/src/Microsoft.AspNetCore.Identity/IdentityErrorDescriber.cs +++ b/src/Microsoft.AspNetCore.Identity/IdentityErrorDescriber.cs @@ -14,7 +14,7 @@ public class IdentityErrorDescriber /// /// Returns the default . /// - /// The default , + /// The default . public virtual IdentityError DefaultError() { return new IdentityError diff --git a/src/Microsoft.AspNetCore.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Identity/Properties/Resources.Designer.cs index 002925879..fa00dea7f 100644 --- a/src/Microsoft.AspNetCore.Identity/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Identity/Properties/Resources.Designer.cs @@ -250,6 +250,22 @@ internal static string FormatNoTokenProvider(object p0) return string.Format(CultureInfo.CurrentCulture, GetString("NoTokenProvider"), p0); } + /// + /// User security stamp cannot be null. + /// + internal static string NullSecurityStamp + { + get { return GetString("NullSecurityStamp"); } + } + + /// + /// User security stamp cannot be null. + /// + internal static string FormatNullSecurityStamp() + { + return GetString("NullSecurityStamp"); + } + /// /// Incorrect password. /// diff --git a/src/Microsoft.AspNetCore.Identity/Resources.resx b/src/Microsoft.AspNetCore.Identity/Resources.resx index 85e16fc30..3e0512b16 100644 --- a/src/Microsoft.AspNetCore.Identity/Resources.resx +++ b/src/Microsoft.AspNetCore.Identity/Resources.resx @@ -177,6 +177,10 @@ No IUserTokenProvider named '{0}' is registered. Error when there is no IUserTokenProvider + + User security stamp cannot be null. + Error when a user's security stamp is null. + Incorrect password. Error when a password doesn't match diff --git a/src/Microsoft.AspNetCore.Identity/UserManager.cs b/src/Microsoft.AspNetCore.Identity/UserManager.cs index d3eff1b91..ffd67bd86 100644 --- a/src/Microsoft.AspNetCore.Identity/UserManager.cs +++ b/src/Microsoft.AspNetCore.Identity/UserManager.cs @@ -2236,6 +2236,14 @@ protected static string GetChangeEmailTokenPurpose(string newEmail) private async Task ValidateUserInternal(TUser user) { + if (SupportsUserSecurityStamp) + { + var stamp = await GetSecurityStampAsync(user); + if (stamp == null) + { + throw new InvalidOperationException(Resources.NullSecurityStamp); + } + } var errors = new List(); foreach (var v in UserValidators) { diff --git a/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs index 664bc14f9..61a0e1e0e 100644 --- a/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs @@ -483,6 +483,42 @@ public async Task CheckPasswordWillRehashPasswordWhenNeeded() hasher.VerifyAll(); } + [Fact] + public async Task CreateFailsWithNullSecurityStamp() + { + // Setup + var store = new Mock>(); + var manager = MockHelpers.TestUserManager(store.Object); + var user = new TestUser { UserName = "nulldude" }; + store.Setup(s => s.GetSecurityStampAsync(user, It.IsAny())).ReturnsAsync(null).Verifiable(); + + // Act + // Assert + var ex = await Assert.ThrowsAsync(() => manager.CreateAsync(user)); + Assert.Contains(Resources.NullSecurityStamp, ex.Message); + + store.VerifyAll(); + } + + [Fact] + public async Task UpdateFailsWithNullSecurityStamp() + { + // Setup + var store = new Mock>(); + var manager = MockHelpers.TestUserManager(store.Object); + var user = new TestUser { UserName = "nulldude" }; + store.Setup(s => s.GetSecurityStampAsync(user, It.IsAny())).ReturnsAsync(null).Verifiable(); + + // Act + // Assert + var ex = await Assert.ThrowsAsync(() => manager.UpdateAsync(user)); + Assert.Contains(Resources.NullSecurityStamp, ex.Message); + + store.VerifyAll(); + } + + + [Fact] public async Task RemoveClaimsCallsStore() {