diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs index df1e0144ad..a2ad4a591e 100644 --- a/src/Microsoft.AspNet.Identity/SignInManager.cs +++ b/src/Microsoft.AspNet.Identity/SignInManager.cs @@ -58,19 +58,19 @@ namespace Microsoft.AspNet.Identity return await ClaimsFactory.CreateAsync(user); } - //public virtual async Task CanSignInAsync(TUser user, - // CancellationToken cancellationToken = default(CancellationToken)) - //{ - // if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user, cancellationToken))) - // { - // return false; - // } - // if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user, cancellationToken))) - // { - // return false; - // } - // return true; - //} + public virtual async Task CanSignInAsync(TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user, cancellationToken))) + { + return false; + } + if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user, cancellationToken))) + { + return false; + } + return true; + } public virtual async Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null, CancellationToken cancellationToken = default(CancellationToken)) @@ -94,6 +94,19 @@ namespace Microsoft.AspNet.Identity return UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user, token); } + private async Task PreSignInCheck(TUser user, CancellationToken token) + { + if (!await CanSignInAsync(user, token)) + { + return SignInStatus.NotAllowed; + } + if (await IsLockedOut(user, token)) + { + return SignInStatus.LockedOut; + } + return null; + } + private Task ResetLockout(TUser user, CancellationToken token) { if (UserManager.SupportsUserLockout) @@ -129,9 +142,10 @@ namespace Microsoft.AspNet.Identity public virtual async Task PasswordSignInAsync(TUser user, string password, bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken)) { - if (await IsLockedOut(user, cancellationToken)) + var error = await PreSignInCheck(user, cancellationToken); + if (error != null) { - return SignInStatus.LockedOut; + return error.Value; } if (await UserManager.CheckPasswordAsync(user, password, cancellationToken)) { @@ -233,9 +247,10 @@ namespace Microsoft.AspNet.Identity { return SignInStatus.Failure; } - if (await IsLockedOut(user, cancellationToken)) + var error = await PreSignInCheck(user, cancellationToken); + if (error != null) { - return SignInStatus.LockedOut; + return error.Value; } if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code, cancellationToken)) { @@ -283,9 +298,10 @@ namespace Microsoft.AspNet.Identity { return SignInStatus.Failure; } - if (await IsLockedOut(user, cancellationToken)) + var error = await PreSignInCheck(user, cancellationToken); + if (error != null) { - return SignInStatus.LockedOut; + return error.Value; } return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider); } diff --git a/src/Microsoft.AspNet.Identity/SignInStatus.cs b/src/Microsoft.AspNet.Identity/SignInStatus.cs index 6cb7e20c5d..3247a7dd39 100644 --- a/src/Microsoft.AspNet.Identity/SignInStatus.cs +++ b/src/Microsoft.AspNet.Identity/SignInStatus.cs @@ -8,6 +8,7 @@ namespace Microsoft.AspNet.Identity Success, LockedOut, RequiresVerification, + NotAllowed, Failure } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs index 96d6c3e14b..fcb4c5302a 100644 --- a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs @@ -501,6 +501,8 @@ namespace Microsoft.AspNet.Identity.Test // Assert context.VerifyAll(); response.VerifyAll(); + contextAccessor.VerifyAll(); + claimsFactory.VerifyAll(); } [Fact] @@ -528,6 +530,8 @@ namespace Microsoft.AspNet.Identity.Test // Assert Assert.Equal(SignInStatus.Failure, result); manager.VerifyAll(); + context.VerifyAll(); + contextAccessor.VerifyAll(); } [Fact] @@ -552,6 +556,8 @@ namespace Microsoft.AspNet.Identity.Test // Assert Assert.Equal(SignInStatus.Failure, result); manager.VerifyAll(); + context.VerifyAll(); + contextAccessor.VerifyAll(); } [Fact] @@ -588,5 +594,86 @@ namespace Microsoft.AspNet.Identity.Test manager.VerifyAll(); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CanRequireConfirmedEmailForPasswordSignIn(bool confirmed) + { + // Setup + var user = new TestUser { UserName = "Foo" }; + var manager = MockHelpers.MockUserManager(); + manager.Setup(m => m.IsEmailConfirmedAsync(user, CancellationToken.None)).ReturnsAsync(confirmed).Verifiable(); + if (confirmed) + { + manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); + } + var context = new Mock(); + var response = new Mock(); + if (confirmed) + { + manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); + context.Setup(c => c.Response).Returns(response.Object).Verifiable(); + response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == false), It.IsAny())).Verifiable(); + } + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var roleManager = MockHelpers.MockRoleManager(); + var identityOptions = new IdentityOptions(); + identityOptions.SignIn.RequireConfirmedEmail = true; + var options = new Mock>(); + options.Setup(a => a.Options).Returns(identityOptions); + var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + + // Act + var result = await helper.PasswordSignInAsync(user, "password", false, false); + + // Assert + Assert.Equal(confirmed ? SignInStatus.Success : SignInStatus.NotAllowed, result); + manager.VerifyAll(); + context.VerifyAll(); + response.VerifyAll(); + contextAccessor.VerifyAll(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CanRequireConfirmedPhoneNumberForPasswordSignIn(bool confirmed) + { + // Setup + var user = new TestUser { UserName = "Foo" }; + var manager = MockHelpers.MockUserManager(); + manager.Setup(m => m.IsEmailConfirmedAsync(user, CancellationToken.None)).ReturnsAsync(confirmed).Verifiable(); + var context = new Mock(); + var response = new Mock(); + if (confirmed) + { + manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); + context.Setup(c => c.Response).Returns(response.Object).Verifiable(); + response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == false), It.IsAny())).Verifiable(); + } + + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var roleManager = MockHelpers.MockRoleManager(); + var identityOptions = new IdentityOptions(); + identityOptions.SignIn.RequireConfirmedEmail = true; + var options = new Mock>(); + options.Setup(a => a.Options).Returns(identityOptions); + var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + + // Act + var result = await helper.PasswordSignInAsync(user, "password", false, false); + + // Assert + Assert.Equal(confirmed ? SignInStatus.Success : SignInStatus.NotAllowed, result); + manager.VerifyAll(); + context.VerifyAll(); + response.VerifyAll(); + contextAccessor.VerifyAll(); + } + } }