Browse Source

Add shared-user 2FA integration tests

pull/25304/head
maliming 4 months ago
parent
commit
8b48eb7f98
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 16
      modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs
  2. 52
      modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Isolated_TwoFactor_Tests.cs
  3. 114
      modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Shared_SignIn_Tests.cs
  4. 7
      modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs

16
modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs

@ -115,12 +115,17 @@ public class AbpSignInManager : SignInManager<IdentityUser>
return SignInResult.Failed;
}
var error = await PreSignInCheck(user);
if (error != null)
using (CurrentTenant.Change(user.TenantId))
{
return error;
await IdentityOptionsAccessor.SetAsync();
var error = await PreSignInCheck(user);
if (error != null)
{
return error;
}
return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor);
}
return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor);
}
public virtual async Task<IdentityUser> FindByEmailAsync(string email)
@ -165,6 +170,7 @@ public class AbpSignInManager : SignInManager<IdentityUser>
using (CurrentTenant.Change(user.TenantId))
{
await IdentityOptionsAccessor.SetAsync();
return await base.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient);
}
}
@ -179,6 +185,8 @@ public class AbpSignInManager : SignInManager<IdentityUser>
using (CurrentTenant.Change(user.TenantId))
{
await IdentityOptionsAccessor.SetAsync();
// Base TwoFactorRecoveryCodeSignInAsync does not invoke PreSignInCheck, which means
// AbpSignInManager's IsActive / ShouldChangePassword checks would be bypassed. Run the
// same pre-sign-in checks here so recovery-code sign-in has the same gating as the

52
modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Isolated_TwoFactor_Tests.cs

@ -0,0 +1,52 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.Identity.AspNetCore;
public class Isolated_TwoFactor_Tests : AbpIdentityAspNetCoreTestBase
{
[Fact]
public async Task TwoFactorRecoveryCodeSignInAsync_Should_Return_NotAllowed_For_Inactive_User()
{
// The AbpSignInManager override adds PreSignInCheck to the recovery-code path (the base
// AspNetCore Identity implementation does not). This test asserts that behavior also works
// in the default (isolated) configuration so the new invariant is protected across modes.
var userManager = GetRequiredService<IdentityUserManager>();
var userRepository = GetRequiredService<IIdentityUserRepository>();
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
Guid userId;
using (var uow = unitOfWorkManager.Begin())
{
var user = new IdentityUser(Guid.NewGuid(), "iso-recovery-inactive", "iso-recovery-inactive@abp.io");
(await userManager.CreateAsync(user, "Iso!9Aa")).Succeeded.ShouldBeTrue();
user.SetIsActive(false);
await userRepository.UpdateAsync(user);
userId = user.Id;
await uow.CompleteAsync();
}
var writeResponse = await Client.GetAsync($"/api/signin-test/write-two-factor-cookie?userId={userId}");
writeResponse.EnsureSuccessStatusCode();
if (writeResponse.Headers.TryGetValues("Set-Cookie", out var setCookies))
{
Client.DefaultRequestHeaders.Remove("Cookie");
foreach (var cookie in setCookies)
{
Client.DefaultRequestHeaders.Add("Cookie", cookie.Split(';').First());
}
}
var response = await Client.GetAsync("/api/signin-test/two-factor-recovery-signin?recoveryCode=invalid");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
result.ShouldBe("NotAllowed");
}
}

114
modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Shared_SignIn_Tests.cs

@ -0,0 +1,114 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Shouldly;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.Identity.AspNetCore;
public class Shared_SignIn_Tests : SharedAbpIdentityAspNetCoreTestBase
{
[Fact]
public async Task PasswordSignInAsync_Should_Sign_In_Tenant_User_From_Host_Context()
{
// In shared mode, calling PasswordSignInAsync with a username while CurrentTenant is host
// must resolve the tenant user via FindSharedUserByNameAsync, apply the user's tenant
// IdentityOptions (the fix added in AbpSignInManager), and complete the sign-in.
var userManager = GetRequiredService<IdentityUserManager>();
var currentTenant = GetRequiredService<ICurrentTenant>();
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
var tenantId = Guid.NewGuid();
const string userName = "shared-password-signin";
const string password = "Shared!9Aa";
using (var uow = unitOfWorkManager.Begin())
{
using (currentTenant.Change(tenantId))
{
var user = new IdentityUser(Guid.NewGuid(), userName, userName + "@abp.io", tenantId);
(await userManager.CreateAsync(user, password)).Succeeded.ShouldBeTrue();
}
await uow.CompleteAsync();
}
var response = await Client.GetAsync($"/api/signin-test/password?userName={userName}&password={password}");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
result.ShouldBe("Succeeded");
}
[Fact]
public async Task PasswordSignInAsync_Should_Fail_For_Wrong_Password_In_Shared_Mode()
{
var userManager = GetRequiredService<IdentityUserManager>();
var currentTenant = GetRequiredService<ICurrentTenant>();
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
var tenantId = Guid.NewGuid();
const string userName = "shared-password-wrong";
using (var uow = unitOfWorkManager.Begin())
{
using (currentTenant.Change(tenantId))
{
var user = new IdentityUser(Guid.NewGuid(), userName, userName + "@abp.io", tenantId);
(await userManager.CreateAsync(user, "Shared!9Aa")).Succeeded.ShouldBeTrue();
}
await uow.CompleteAsync();
}
var response = await Client.GetAsync($"/api/signin-test/password?userName={userName}&password=wrong");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
result.ShouldBe("Failed");
}
[Fact]
public async Task ExternalLoginSignInAsync_Should_Sign_In_Tenant_User_From_Host_Context()
{
// Covers the AbpSignInManager.ExternalLoginSignInAsync override: finds the user via
// FindSharedUserByLoginAsync, switches CurrentTenant to user.TenantId, applies tenant
// IdentityOptions, then calls PreSignInCheck + SignInOrTwoFactorAsync.
const string loginProvider = "test-provider";
var providerKey = "ext-" + Guid.NewGuid().ToString("N").Substring(0, 8);
var userManager = GetRequiredService<IdentityUserManager>();
var currentTenant = GetRequiredService<ICurrentTenant>();
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
var tenantId = Guid.NewGuid();
using (var uow = unitOfWorkManager.Begin())
{
using (currentTenant.Change(tenantId))
{
var user = new IdentityUser(Guid.NewGuid(), "shared-external-signin", "shared-external-signin@abp.io", tenantId);
(await userManager.CreateAsync(user, "Shared!9Aa")).Succeeded.ShouldBeTrue();
(await userManager.AddLoginAsync(user, new UserLoginInfo(loginProvider, providerKey, "Test Provider"))).Succeeded.ShouldBeTrue();
}
await uow.CompleteAsync();
}
var response = await Client.GetAsync($"/api/signin-test/external-login-signin?loginProvider={loginProvider}&providerKey={providerKey}");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
result.ShouldBe("Succeeded");
}
[Fact]
public async Task ExternalLoginSignInAsync_Should_Fail_For_Unknown_Provider_Key_In_Shared_Mode()
{
var response = await Client.GetAsync($"/api/signin-test/external-login-signin?loginProvider=unknown&providerKey=none");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
result.ShouldBe("Failed");
}
}

7
modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs

@ -59,4 +59,11 @@ public class SignInTestController : AbpController
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
return Content(result.ToString());
}
[Route("external-login-signin")]
public async Task<ActionResult> ExternalLoginSignIn(string loginProvider, string providerKey)
{
var result = await _signInManager.ExternalLoginSignInAsync(loginProvider, providerKey, false, false);
return Content(result.ToString());
}
}

Loading…
Cancel
Save