Browse Source

Make UserManager.FindByIdAsync shared-aware by default

- Override IdentityUserManager.FindByIdAsync to fall back to a cross-tenant lookup in shared user sharing strategy so any caller that hits FindByIdAsync from a non-matching tenant context (including base SignInManager internals for TwoFactorSignInAsync and TwoFactorRecoveryCodeSignInAsync) can still resolve a tenant user by id
- Drop the now-redundant AbpSignInManager.GetTwoFactorAuthenticationUserAsync override; the base implementation works automatically through the new FindByIdAsync behavior
- Cover the new FindByIdAsync behavior with unit tests
pull/25304/head
maliming 2 weeks ago
parent
commit
db07204b7a
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 20
      modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs
  2. 17
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
  3. 49
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs

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

@ -1,5 +1,4 @@
using System.Security.Claims;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
@ -134,23 +133,6 @@ public class AbpSignInManager : SignInManager<IdentityUser>
return await IdentityUserManager.FindSharedUserByLoginAsync(loginProvider, providerKey);
}
public override async Task<IdentityUser> GetTwoFactorAuthenticationUserAsync()
{
var result = await Context.AuthenticateAsync(IdentityConstants.TwoFactorUserIdScheme);
if (result?.Principal == null)
{
return null;
}
var userId = result.Principal.FindFirstValue(ClaimTypes.Name);
if (string.IsNullOrWhiteSpace(userId))
{
return null;
}
return await IdentityUserManager.FindSharedUserByIdAsync(userId);
}
/// <summary>
/// This is to call the protection method PreSignInCheck
/// </summary>

17
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs

@ -745,4 +745,21 @@ public class IdentityUserManager : UserManager<IdentityUser>, IDomainService
}
}
}
public override async Task<IdentityUser> FindByIdAsync(string userId)
{
// In shared user sharing strategy, a user id is globally unique. Fall back to
// a cross-tenant lookup so callers that hit FindByIdAsync from a non-matching
// tenant context (notably the 2FA mid-flow invoked by ASP.NET Core Identity
// base SignInManager paths) can still resolve the user by id.
// Downstream operations on the returned entity should still be scoped to
// user.TenantId explicitly via CurrentTenant.Change.
var user = await base.FindByIdAsync(userId);
if (user != null || MultiTenancyOptions.Value.UserSharingStrategy == TenantUserSharingStrategy.Isolated)
{
return user;
}
return await FindSharedUserByIdAsync(userId);
}
}

49
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs

@ -678,6 +678,55 @@ public class SharedTenantUserSharingStrategy_IdentityUserManager_Tests : AbpIden
}
}
[Fact]
public async Task FindByIdAsync_Should_Fall_Back_To_Cross_Tenant_Lookup_In_Shared_Mode()
{
// In shared user sharing strategy, UserManager.FindByIdAsync must resolve a
// tenant user even when CurrentTenant is host. This protects any caller that
// hits FindByIdAsync without an explicit shared-aware wrapper (e.g. ASP.NET
// Core Identity SignInManager internals for TwoFactorSignInAsync and
// TwoFactorRecoveryCodeSignInAsync).
var tenantId = Guid.NewGuid();
IdentityUser tenantUser;
using (var uow = _unitOfWorkManager.Begin())
{
tenantUser = await CreateUserAsync(tenantId, "shared-findbyid-fallback", "shared-findbyid-fallback@abp.io");
await uow.CompleteAsync();
}
using (_currentTenant.Change(null))
{
var user = await _identityUserManager.FindByIdAsync(tenantUser.Id.ToString());
user.ShouldNotBeNull();
user.Id.ShouldBe(tenantUser.Id);
user.TenantId.ShouldBe(tenantId);
}
}
[Fact]
public async Task FindByIdAsync_Should_Return_Current_Tenant_Result_Without_Fallback_When_Found()
{
var tenantId = Guid.NewGuid();
IdentityUser tenantUser;
using (var uow = _unitOfWorkManager.Begin())
{
tenantUser = await CreateUserAsync(tenantId, "shared-findbyid-hit", "shared-findbyid-hit@abp.io");
await uow.CompleteAsync();
}
using (_currentTenant.Change(tenantId))
{
var user = await _identityUserManager.FindByIdAsync(tenantUser.Id.ToString());
user.ShouldNotBeNull();
user.Id.ShouldBe(tenantUser.Id);
user.TenantId.ShouldBe(tenantId);
}
}
[Fact]
public async Task Login_Then_TwoFactor_MidFlow_Should_Resolve_Same_Tenant_User_In_Shared_Mode()
{

Loading…
Cancel
Save