From ee3377adf50277241c0697f7cb8889cb7cc5a5fc Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 20 Nov 2023 17:25:40 +0800 Subject: [PATCH] Remove `dynamic claim cache` when deleting organization's role. --- .../Identity/IOrganizationUnitRepository.cs | 6 ++ .../Volo/Abp/Identity/IdentityRoleManager.cs | 11 ++++ .../Abp/Identity/OrganizationUnitManager.cs | 2 +- .../EfCoreOrganizationUnitRepository.cs | 15 +++++ .../MongoOrganizationUnitRepository.cs | 10 ++++ ...DynamicClaimsPrincipalContributor_Tests.cs | 59 +++++++++++++++++++ .../Identity/AbpIdentityTestDataBuilder.cs | 6 ++ .../OrganizationUnitRepository_Tests.cs | 17 ++++++ 8 files changed, 125 insertions(+), 1 deletion(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs index 047e01193a..6a938b34bd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs @@ -41,6 +41,12 @@ public interface IOrganizationUnitRepository : IBasicRepository> GetListByRoleAsync( + Guid roleId, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + Task> GetRolesAsync( OrganizationUnit organizationUnit, string sorting = null, diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs index 3dc8dacc32..c4ad4d2b42 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs @@ -22,6 +22,8 @@ public class IdentityRoleManager : RoleManager, IDomainService protected IStringLocalizer Localizer { get; } protected ICancellationTokenProvider CancellationTokenProvider { get; } protected IIdentityUserRepository UserRepository { get; } + protected IOrganizationUnitRepository OrganizationUnitRepository { get; } + protected OrganizationUnitManager OrganizationUnitManager { get; } protected IDistributedCache DynamicClaimCache { get; } public IdentityRoleManager( @@ -33,6 +35,8 @@ public class IdentityRoleManager : RoleManager, IDomainService IStringLocalizer localizer, ICancellationTokenProvider cancellationTokenProvider, IIdentityUserRepository userRepository, + IOrganizationUnitRepository organizationUnitRepository, + OrganizationUnitManager organizationUnitManager, IDistributedCache dynamicClaimCache) : base( store, @@ -44,6 +48,8 @@ public class IdentityRoleManager : RoleManager, IDomainService Localizer = localizer; CancellationTokenProvider = cancellationTokenProvider; UserRepository = userRepository; + OrganizationUnitRepository = organizationUnitRepository; + OrganizationUnitManager = organizationUnitManager; DynamicClaimCache = dynamicClaimCache; } @@ -84,11 +90,16 @@ public class IdentityRoleManager : RoleManager, IDomainService } var userIdList = await UserRepository.GetUserIdListByRoleIdAsync(role.Id, cancellationToken: CancellationToken); + var orgList = await OrganizationUnitRepository.GetListByRoleAsync(role.Id, includeDetails: false, cancellationToken: CancellationToken); var result = await base.DeleteAsync(role); if (result.Succeeded) { Logger.LogDebug($"Remove dynamic claims cache for users of role: {role.Id}"); await DynamicClaimCache.RemoveManyAsync(userIdList.Select(userId => AbpDynamicClaimCacheItem.CalculateCacheKey(userId, role.TenantId)), token: CancellationToken); + foreach (var organizationUnit in orgList) + { + await OrganizationUnitManager.RemoveDynamicClaimCacheAsync(organizationUnit); + } } return result; diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs index ffca834cde..e8f5703d38 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs @@ -205,7 +205,7 @@ public class OrganizationUnitManager : DomainService await OrganizationUnitRepository.UpdateAsync(organizationUnit); } - protected virtual async Task RemoveDynamicClaimCacheAsync(OrganizationUnit organizationUnit) + public virtual async Task RemoveDynamicClaimCacheAsync(OrganizationUnit organizationUnit) { Logger.LogDebug($"Remove dynamic claims cache for users of organization: {organizationUnit.Id}"); var userIds = await OrganizationUnitRepository.GetMemberIdsAsync(organizationUnit.Id); diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs index fa4b0c7b8d..e356025475 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs @@ -69,6 +69,21 @@ public class EfCoreOrganizationUnitRepository .ToListAsync(GetCancellationToken(cancellationToken)); } + public virtual async Task> GetListByRoleAsync( + Guid roleId, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var dbContext = await GetDbContextAsync(); + + var query = from organizationRole in dbContext.Set() + join organizationUnit in dbContext.OrganizationUnits.IncludeDetails(includeDetails) on organizationRole.OrganizationUnitId equals organizationUnit.Id + where organizationRole.RoleId == roleId + select organizationUnit; + + return await query.ToListAsync(GetCancellationToken(cancellationToken)); + } + public virtual async Task GetAsync( string displayName, bool includeDetails = true, diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs index 77b9b3aa98..58324dc11c 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs @@ -53,6 +53,16 @@ public class MongoOrganizationUnitRepository .ToListAsync(GetCancellationToken(cancellationToken)); } + public virtual async Task> GetListByRoleAsync( + Guid roleId, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await (await GetMongoQueryableAsync(cancellationToken)) + .Where(x => x.Roles.Any(r => r.RoleId == roleId)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + public virtual async Task> GetListAsync( string sorting = null, int maxResultCount = int.MaxValue, diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor_Tests.cs index 0188ce2cc1..f2e6f6b4b6 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor_Tests.cs @@ -209,4 +209,63 @@ public class IdentityDynamicClaimsPrincipalContributor_Tests : AbpIdentityDomain dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "moderator"); dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "manager"); //manager role from OU111 is deleted. } + + [Fact] + public async Task Should_Get_Correct_Claims_After_User_Organization_Role_Or_Member_Changed() + { + ClaimsPrincipal claimsPrincipal = null; + await UsingUowAsync(async () => + { + var user = await _identityUserManager.GetByIdAsync(_testData.UserBobId); + user.ShouldNotBeNull(); + claimsPrincipal = await _abpUserClaimsPrincipalFactory.CreateAsync(user); + claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "supporter"); + claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "moderator"); + claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager"); + var dynamicClaimsPrincipal = await _abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal); + dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "supporter"); + dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "moderator"); + dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager"); + var ou = await _organizationUnitRepository.GetAsync("OU111", true); + ou.ShouldNotBeNull(); + ou.Roles.Count.ShouldBe(2); + ou.Roles.ShouldContain(x => x.RoleId == _testData.RoleModeratorId); + ou.Roles.ShouldContain(x => x.RoleId == _testData.RoleManagerId); + ou.AddRole(_testData.RoleSaleId); + await _identityUserManager.AddToOrganizationUnitAsync(user, ou); + await _organizationUnitManager.UpdateAsync(ou); + }); + var dynamicClaimsPrincipal = await _abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal); + dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "supporter"); + dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "moderator"); + dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager"); + dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "sale"); + + await UsingUowAsync(async () => + { + var saleRole = await _identityRoleRepository.GetAsync(_testData.RoleSaleId); + await _identityRoleManager.DeleteAsync(saleRole); + }); + + dynamicClaimsPrincipal = await _abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal); + dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "supporter"); + dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "moderator"); + dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager"); + dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "sale"); + + await UsingUowAsync(async () => + { + var user = await _identityUserManager.GetByIdAsync(_testData.UserBobId); + user.ShouldNotBeNull(); + var ou = await _organizationUnitRepository.GetAsync("OU111", true); + ou.ShouldNotBeNull(); + await _identityUserManager.RemoveFromOrganizationUnitAsync(user.Id, ou.Id); + }); + + dynamicClaimsPrincipal = await _abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal); + dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "supporter"); + dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "moderator"); + dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager"); + dynamicClaimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "sale"); + } } diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs index e30e1d3bfe..944c6928c1 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs @@ -114,6 +114,12 @@ public class AbpIdentityTestDataBuilder : ITransientDependency _ou111.AddRole(_moderatorRole.Id); _ou111.AddRole(_managerRole.Id); await _organizationUnitRepository.InsertAsync(_ou111); + + var _ou222 = new OrganizationUnit(_guidGenerator.Create(), "OU222"); + _ou222.Code = OrganizationUnit.CreateCode(1, 1, 1); + _ou222.AddRole(_moderatorRole.Id); + _ou222.AddRole(_managerRole.Id); + await _organizationUnitRepository.InsertAsync(_ou222); } private async Task AddUsers() diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs index 8803343d8c..b0daef8c05 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs @@ -66,6 +66,23 @@ public abstract class OrganizationUnitRepository_Tests : AbpIden ous.ShouldContain(ou => ou.Id == ouIds.First()); } + [Fact] + public async Task GetListByRoleAsync() + { + var ous = await _organizationUnitRepository.GetListByRoleAsync(_testData.RoleManagerId); + ous.Count.ShouldBe(2); + ous.ShouldContain(ou => ou.DisplayName == "OU111"); + ous.ShouldContain(ou => ou.DisplayName == "OU222"); + + ous = await _organizationUnitRepository.GetListByRoleAsync(_testData.RoleModeratorId); + ous.Count.ShouldBe(2); + ous.ShouldContain(ou => ou.DisplayName == "OU111"); + ous.ShouldContain(ou => ou.DisplayName == "OU222"); + + ous = await _organizationUnitRepository.GetListByRoleAsync(_testData.RoleSaleId); + ous.Count.ShouldBe(0); + } + [Fact] public async Task AddMemberToOrganizationUnit() {