From 8429dbaca4cd5d76efbd5ad8bcd4d6a2de364460 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 20 May 2026 11:06:39 +0800 Subject: [PATCH] Address Copilot review on #25450 - Use UserManager.SetAuthenticationTokenAsync/GetAuthenticationTokenAsync/ RemoveAuthenticationTokenAsync so the Tokens collection is loaded via IdentityUserStore.EnsureCollectionLoadedAsync instead of relying on the GetByIdAsync(includeDetails) convention. - Add a RemoveLinkUserTokenAsync(manager, user, purpose) overload to invalidate tokens issued for purposes other than LinkUserTokenPurpose. - Add a cross-UnitOfWork persistence test for SetLink/Get/Remove ConsentAsync. - Drop the unused IdentityLinkUserRepository field from LinkUserTokenProvider_Tests. --- ...yUserManagerSingleActiveTokenExtensions.cs | 11 +++++++- .../Abp/Identity/IdentityLinkUserManager.cs | 17 ++++++------ .../AspNetCore/LinkUserTokenProvider_Tests.cs | 2 -- .../Identity/IdentityLinkUserManager_Tests.cs | 27 +++++++++++++++++++ 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions.cs index 6b6f9769de..59d8bd4690 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IdentityUserManagerSingleActiveTokenExtensions.cs @@ -47,7 +47,16 @@ public static class IdentityUserManagerSingleActiveTokenExtensions /// public static Task RemoveLinkUserTokenAsync(this IdentityUserManager manager, IdentityUser user) { - var name = LinkUserTokenProviderConsts.LinkUserTokenProviderName + ":" + LinkUserTokenProviderConsts.LinkUserTokenPurpose; + return RemoveLinkUserTokenAsync(manager, user, LinkUserTokenProviderConsts.LinkUserTokenPurpose); + } + + /// + /// Removes the stored link-user token hash for and the given , + /// immediately invalidating any previously issued link-user token for that purpose. + /// + public static Task RemoveLinkUserTokenAsync(this IdentityUserManager manager, IdentityUser user, string purpose) + { + var name = LinkUserTokenProviderConsts.LinkUserTokenProviderName + ":" + purpose; return manager.RemoveAuthenticationTokenAsync(user, AbpSingleActiveTokenProvider.InternalLoginProvider, name); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityLinkUserManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityLinkUserManager.cs index 08d6204734..f005b52cca 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityLinkUserManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityLinkUserManager.cs @@ -167,11 +167,11 @@ public class IdentityLinkUserManager : DomainService using (CurrentTenant.Change(sourceLinkUser.TenantId)) { var user = await UserManager.GetByIdAsync(sourceLinkUser.UserId); - user.SetToken( + (await UserManager.SetAuthenticationTokenAsync( + user, LinkUserTokenProviderConsts.LinkUserConsentLoginProvider, LinkUserTokenProviderConsts.LinkUserConsentTokenName, - consent); - (await UserManager.UpdateAsync(user)).CheckErrors(); + consent)).CheckErrors(); } } @@ -184,9 +184,10 @@ public class IdentityLinkUserManager : DomainService { return null; } - return user.FindToken( + return await UserManager.GetAuthenticationTokenAsync( + user, LinkUserTokenProviderConsts.LinkUserConsentLoginProvider, - LinkUserTokenProviderConsts.LinkUserConsentTokenName)?.Value; + LinkUserTokenProviderConsts.LinkUserConsentTokenName); } } @@ -199,10 +200,10 @@ public class IdentityLinkUserManager : DomainService { return; } - user.RemoveToken( + (await UserManager.RemoveAuthenticationTokenAsync( + user, LinkUserTokenProviderConsts.LinkUserConsentLoginProvider, - LinkUserTokenProviderConsts.LinkUserConsentTokenName); - (await UserManager.UpdateAsync(user)).CheckErrors(); + LinkUserTokenProviderConsts.LinkUserConsentTokenName)).CheckErrors(); } } } diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider_Tests.cs index 06a90e5410..962538a5ae 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider_Tests.cs @@ -9,12 +9,10 @@ namespace Volo.Abp.Identity.AspNetCore; public class LinkUserTokenProvider_Tests : AbpSingleActiveTokenProviderTestBase { - protected IIdentityLinkUserRepository IdentityLinkUserRepository { get; } protected IdentityLinkUserManager IdentityLinkUserManager { get; } public LinkUserTokenProvider_Tests() { - IdentityLinkUserRepository = GetRequiredService(); IdentityLinkUserManager = GetRequiredService(); } diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityLinkUserManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityLinkUserManager_Tests.cs index 22503ed3ef..f151cc5010 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityLinkUserManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityLinkUserManager_Tests.cs @@ -256,4 +256,31 @@ public class IdentityLinkUserManager_Tests : AbpIdentityDomainTestBase { await IdentityLinkUserManager.RemoveLinkConsentAsync(new IdentityLinkUserInfo(Guid.NewGuid(), null)); } + + [Fact] + public virtual async Task SetLinkConsentAsync_Should_Persist_Across_UnitOfWork() + { + var john = await UserRepository.GetAsync(TestData.UserJohnId); + var source = new IdentityLinkUserInfo(john.Id, john.TenantId); + + await UsingUowAsync(async () => + { + await IdentityLinkUserManager.SetLinkConsentAsync(source, "consent-payload"); + }); + + await UsingUowAsync(async () => + { + (await IdentityLinkUserManager.GetLinkConsentAsync(source)).ShouldBe("consent-payload"); + }); + + await UsingUowAsync(async () => + { + await IdentityLinkUserManager.RemoveLinkConsentAsync(source); + }); + + await UsingUowAsync(async () => + { + (await IdentityLinkUserManager.GetLinkConsentAsync(source)).ShouldBeNull(); + }); + } }