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..4d37887570 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> GetListByRoleIdAsync( + 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..38de74e800 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.GetListByRoleIdAsync(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..c4a8733787 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 @@ -84,6 +84,7 @@ public class OrganizationUnitManager : DomainService foreach (var child in children) { + await RemoveDynamicClaimCacheAsync(child); await OrganizationUnitRepository.RemoveAllMembersAsync(child); await OrganizationUnitRepository.RemoveAllRolesAsync(child); await OrganizationUnitRepository.DeleteAsync(child); @@ -200,12 +201,12 @@ public class OrganizationUnitManager : DomainService public virtual async Task RemoveRoleFromOrganizationUnitAsync(IdentityRole role, OrganizationUnit organizationUnit) { - await RemoveDynamicClaimCacheAsync(organizationUnit); organizationUnit.RemoveRole(role.Id); await OrganizationUnitRepository.UpdateAsync(organizationUnit); + await RemoveDynamicClaimCacheAsync(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..9086716124 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> GetListByRoleIdAsync( + 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..f085b2eedc 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> GetListByRoleIdAsync( + 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..8746c3a1fd 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 GetListByRoleIdAsync() + { + var ous = await _organizationUnitRepository.GetListByRoleIdAsync(_testData.RoleManagerId); + ous.Count.ShouldBe(2); + ous.ShouldContain(ou => ou.DisplayName == "OU111"); + ous.ShouldContain(ou => ou.DisplayName == "OU222"); + + ous = await _organizationUnitRepository.GetListByRoleIdAsync(_testData.RoleModeratorId); + ous.Count.ShouldBe(2); + ous.ShouldContain(ou => ou.DisplayName == "OU111"); + ous.ShouldContain(ou => ou.DisplayName == "OU222"); + + ous = await _organizationUnitRepository.GetListByRoleIdAsync(_testData.RoleSaleId); + ous.Count.ShouldBe(0); + } + [Fact] public async Task AddMemberToOrganizationUnit() {