diff --git a/src/Microsoft.AspNetCore.Identity/SignInManager.cs b/src/Microsoft.AspNetCore.Identity/SignInManager.cs index e67106ab76..26840f65d0 100644 --- a/src/Microsoft.AspNetCore.Identity/SignInManager.cs +++ b/src/Microsoft.AspNetCore.Identity/SignInManager.cs @@ -433,7 +433,19 @@ namespace Microsoft.AspNetCore.Identity /// Flag indicating whether the sign-in cookie should persist after the browser is closed. /// The task object representing the asynchronous operation containing the /// for the sign-in attempt. - public virtual async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent) + public virtual Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent) + => ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent, bypassTwoFactor: false); + + /// + /// Signs in a user via a previously registered third party login, as an asynchronous operation. + /// + /// The login provider to use. + /// The unique provider identifier for the user. + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// Flag indicating whether to bypass two factor authentication. + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + public virtual async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor) { var user = await UserManager.FindByLoginAsync(loginProvider, providerKey); if (user == null) @@ -446,7 +458,7 @@ namespace Microsoft.AspNetCore.Identity { return error; } - return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider); + return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); } /// @@ -582,9 +594,10 @@ namespace Microsoft.AspNetCore.Identity } - private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, string loginProvider = null) + private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, string loginProvider = null, bool bypassTwoFactor = false) { - if (UserManager.SupportsUserTwoFactor && + if (!bypassTwoFactor && + UserManager.SupportsUserTwoFactor && await UserManager.GetTwoFactorEnabledAsync(user) && (await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0) { diff --git a/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs index 929ec4b437..df1ae35d79 100644 --- a/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs @@ -314,6 +314,52 @@ namespace Microsoft.AspNetCore.Identity.Test auth.Verify(); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ExternalSignInRequiresVerificationIfNotBypassed(bool bypass) + { + // Setup + var user = new TestUser { UserName = "Foo" }; + const string loginProvider = "login"; + const string providerKey = "fookey"; + var manager = SetupUserManager(user); + manager.Setup(m => m.SupportsUserLockout).Returns(false).Verifiable(); + manager.Setup(m => m.FindByLoginAsync(loginProvider, providerKey)).ReturnsAsync(user).Verifiable(); + if (!bypass) + { + IList providers = new List(); + providers.Add("PhoneNumber"); + manager.Setup(m => m.GetValidTwoFactorProvidersAsync(user)).Returns(Task.FromResult(providers)).Verifiable(); + manager.Setup(m => m.SupportsUserTwoFactor).Returns(true).Verifiable(); + manager.Setup(m => m.GetTwoFactorEnabledAsync(user)).ReturnsAsync(true).Verifiable(); + } + var context = new Mock(); + var auth = new Mock(); + context.Setup(c => c.Authentication).Returns(auth.Object).Verifiable(); + var helper = SetupSignInManager(manager.Object, context.Object); + + if (bypass) + { + SetupSignIn(auth, user.Id, false, loginProvider); + } + else + { + auth.Setup(a => a.SignInAsync(helper.Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme, + It.Is(id => id.FindFirstValue(ClaimTypes.Name) == user.Id))).Returns(Task.FromResult(0)).Verifiable(); + } + + // Act + var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent: false, bypassTwoFactor: bypass); + + // Assert + Assert.Equal(bypass, result.Succeeded); + Assert.Equal(!bypass, result.RequiresTwoFactor); + manager.Verify(); + context.Verify(); + auth.Verify(); + } + [Theory] [InlineData(true, true)] [InlineData(true, false)]