From fe60304df54c43030d3eacfd9073e6a36990db05 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 8 Aug 2016 13:43:30 -0700 Subject: [PATCH] Add CheckPasswordSignIn method --- .../SignInManager.cs | 76 ++++++++++++------- .../SignInManagerTest.cs | 60 +++++++++++++++ 2 files changed, 110 insertions(+), 26 deletions(-) diff --git a/src/Microsoft.AspNetCore.Identity/SignInManager.cs b/src/Microsoft.AspNetCore.Identity/SignInManager.cs index ed7a802947..e67106ab76 100644 --- a/src/Microsoft.AspNetCore.Identity/SignInManager.cs +++ b/src/Microsoft.AspNetCore.Identity/SignInManager.cs @@ -241,32 +241,10 @@ namespace Microsoft.AspNetCore.Identity throw new ArgumentNullException(nameof(user)); } - var error = await PreSignInCheck(user); - if (error != null) - { - return error; - } - if (await IsLockedOut(user)) - { - return await LockedOut(user); - } - if (await UserManager.CheckPasswordAsync(user, password)) - { - await ResetLockout(user); - return await SignInOrTwoFactorAsync(user, isPersistent); - } - Logger.LogWarning(2, "User {userId} failed to provide the correct password.", await UserManager.GetUserIdAsync(user)); - - if (UserManager.SupportsUserLockout && lockoutOnFailure) - { - // If lockout is requested, increment access failed count which might lock out the user - await UserManager.AccessFailedAsync(user); - if (await UserManager.IsLockedOutAsync(user)) - { - return await LockedOut(user); - } - } - return SignInResult.Failed; + var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure); + return attempt.Succeeded + ? await SignInOrTwoFactorAsync(user, isPersistent) + : attempt; } /// @@ -291,6 +269,52 @@ namespace Microsoft.AspNetCore.Identity return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure); } + /// + /// Attempts a password sign in for a user. + /// + /// The user to sign in. + /// The password to attempt to sign in with. + /// Flag indicating if the user account should be locked if the sign in fails. + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + public virtual async Task CheckPasswordSignInAsync(TUser user, string password, bool lockoutOnFailure) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var error = await PreSignInCheck(user); + if (error != null) + { + return error; + } + + if (await IsLockedOut(user)) + { + return await LockedOut(user); + } + if (await UserManager.CheckPasswordAsync(user, password)) + { + await ResetLockout(user); + return SignInResult.Success; + } + Logger.LogWarning(2, "User {userId} failed to provide the correct password.", await UserManager.GetUserIdAsync(user)); + + if (UserManager.SupportsUserLockout && lockoutOnFailure) + { + // If lockout is requested, increment access failed count which might lock out the user + await UserManager.AccessFailedAsync(user); + if (await UserManager.IsLockedOutAsync(user)) + { + return await LockedOut(user); + } + } + return SignInResult.Failed; + } + + /// /// Returns a flag indicating if the current client browser has been remembered by two factor authentication /// for the user attempting to login, as an asynchronous operation. diff --git a/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs index 9fe4297e16..929ec4b437 100644 --- a/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs @@ -136,6 +136,37 @@ namespace Microsoft.AspNetCore.Identity.Test manager.Verify(); } + [Fact] + public async Task CheckPasswordSignInReturnsLockedOutWhenLockedOut() + { + // Setup + var user = new TestUser { UserName = "Foo" }; + var manager = SetupUserManager(user); + manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); + manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(true).Verifiable(); + + var context = new Mock(); + var contextAccessor = new Mock(); + contextAccessor.Setup(a => a.HttpContext).Returns(context.Object); + var roleManager = MockHelpers.MockRoleManager(); + var identityOptions = new IdentityOptions(); + var options = new Mock>(); + options.Setup(a => a.Value).Returns(identityOptions); + var claimsFactory = new UserClaimsPrincipalFactory(manager.Object, roleManager.Object, options.Object); + var logStore = new StringBuilder(); + var logger = MockHelpers.MockILogger>(logStore); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory, options.Object, logger.Object); + + // Act + var result = await helper.CheckPasswordSignInAsync(user, "bogus", false); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.IsLockedOut); + Assert.True(logStore.ToString().Contains($"User {user.Id} is currently locked out.")); + manager.Verify(); + } + private static Mock> SetupUserManager(TestUser user) { var manager = MockHelpers.MockUserManager(); @@ -534,9 +565,11 @@ namespace Microsoft.AspNetCore.Identity.Test var helper = SetupSignInManager(manager.Object, context.Object, logStore); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); + var checkResult = await helper.CheckPasswordSignInAsync(user, "bogus", false); // Assert Assert.False(result.Succeeded); + Assert.False(checkResult.Succeeded); Assert.True(logStore.ToString().Contains($"User {user.Id} failed to provide the correct password.")); manager.Verify(); context.Verify(); @@ -587,6 +620,33 @@ namespace Microsoft.AspNetCore.Identity.Test manager.Verify(); } + [Fact] + public async Task CheckPasswordSignInFailsWithWrongPasswordCanAccessFailedAndLockout() + { + // Setup + var user = new TestUser { UserName = "Foo" }; + var manager = SetupUserManager(user); + var lockedout = false; + manager.Setup(m => m.AccessFailedAsync(user)).Returns(() => + { + lockedout = true; + return Task.FromResult(IdentityResult.Success); + }).Verifiable(); + manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); + manager.Setup(m => m.IsLockedOutAsync(user)).Returns(() => Task.FromResult(lockedout)); + manager.Setup(m => m.CheckPasswordAsync(user, "bogus")).ReturnsAsync(false).Verifiable(); + var context = new Mock(); + var helper = SetupSignInManager(manager.Object, context.Object); + + // Act + var result = await helper.CheckPasswordSignInAsync(user, "bogus", true); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.IsLockedOut); + manager.Verify(); + } + [Theory] [InlineData(true)] [InlineData(false)]