diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider.cs index 6cbdeece99..47607abe7e 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/LinkUserTokenProvider.cs @@ -8,7 +8,7 @@ using Volo.Abp.Threading; namespace Volo.Abp.Identity.AspNetCore; /// -/// Link-user token provider that enforces only the most recently issued +/// Link-user token provider that enforces, per purpose, only the most recently issued /// token to be valid, with a configurable expiration period. /// public class LinkUserTokenProvider : AbpSingleActiveTokenProvider 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 f005b52cca..25a5f299ff 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 @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Volo.Abp.Domain.Services; using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; namespace Volo.Abp.Identity; @@ -162,11 +163,16 @@ public class IdentityLinkUserManager : DomainService } } + [UnitOfWork] public virtual async Task SetLinkConsentAsync(IdentityLinkUserInfo sourceLinkUser, string consent, CancellationToken cancellationToken = default) { using (CurrentTenant.Change(sourceLinkUser.TenantId)) { - var user = await UserManager.GetByIdAsync(sourceLinkUser.UserId); + var user = await UserManager.FindByIdAsync(sourceLinkUser.UserId.ToString()); + if (user == null) + { + return; + } (await UserManager.SetAuthenticationTokenAsync( user, LinkUserTokenProviderConsts.LinkUserConsentLoginProvider, @@ -175,6 +181,7 @@ public class IdentityLinkUserManager : DomainService } } + [UnitOfWork] public virtual async Task GetLinkConsentAsync(IdentityLinkUserInfo sourceLinkUser, CancellationToken cancellationToken = default) { using (CurrentTenant.Change(sourceLinkUser.TenantId)) @@ -191,6 +198,7 @@ public class IdentityLinkUserManager : DomainService } } + [UnitOfWork] public virtual async Task RemoveLinkConsentAsync(IdentityLinkUserInfo sourceLinkUser, CancellationToken cancellationToken = default) { using (CurrentTenant.Change(sourceLinkUser.TenantId)) 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 962538a5ae..7dfca71f92 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 @@ -34,7 +34,7 @@ public class LinkUserTokenProvider_Tests : AbpSingleActiveTokenProviderTestBase => LinkUserTokenProviderConsts.LinkUserTokenPurpose; [Fact] - public void LinkUserTokenProvider_Should_Be_Register() + public void LinkUserTokenProvider_Should_Be_Registered() { var identityOptions = GetRequiredService>().Value; @@ -76,4 +76,50 @@ public class LinkUserTokenProvider_Tests : AbpSingleActiveTokenProviderTestBase await uow.CompleteAsync(); } } + + [Fact] + public async Task RemoveLinkUserTokenAsync_With_Custom_Purpose_Should_Invalidate_Only_That_Purpose() + { + const string customPurpose = "CustomLinkUserPurpose"; + + using (var uow = UnitOfWorkManager.Begin()) + { + var john = await UserRepository.GetAsync(TestData.UserJohnId); + var defaultPurposeToken = await GenerateTokenAsync(john); + var customPurposeToken = await IdentityLinkUserManager.GenerateLinkTokenAsync( + new IdentityLinkUserInfo(john.Id, john.TenantId), + customPurpose); + + john = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.RemoveLinkUserTokenAsync(john, customPurpose)).Succeeded.ShouldBeTrue(); + + john = await UserRepository.GetAsync(TestData.UserJohnId); + (await IdentityLinkUserManager.VerifyLinkTokenAsync(new IdentityLinkUserInfo(john.Id, john.TenantId), customPurposeToken, customPurpose)).ShouldBeFalse(); + (await VerifyTokenAsync(john, defaultPurposeToken)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task GenerateLinkTokenAsync_With_Custom_Purpose_Should_Invalidate_Previous_Token_For_Same_Purpose() + { + const string customPurpose = "CustomLinkUserPurpose"; + + using (var uow = UnitOfWorkManager.Begin()) + { + var john = await UserRepository.GetAsync(TestData.UserJohnId); + var firstToken = await IdentityLinkUserManager.GenerateLinkTokenAsync( + new IdentityLinkUserInfo(john.Id, john.TenantId), + customPurpose); + var secondToken = await IdentityLinkUserManager.GenerateLinkTokenAsync( + new IdentityLinkUserInfo(john.Id, john.TenantId), + customPurpose); + + (await IdentityLinkUserManager.VerifyLinkTokenAsync(new IdentityLinkUserInfo(john.Id, john.TenantId), firstToken, customPurpose)).ShouldBeFalse(); + (await IdentityLinkUserManager.VerifyLinkTokenAsync(new IdentityLinkUserInfo(john.Id, john.TenantId), secondToken, customPurpose)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + } }