diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionDefinitionSerializer.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionDefinitionSerializer.cs index 799896a5d8..ce76afc10c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionDefinitionSerializer.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionDefinitionSerializer.cs @@ -14,6 +14,6 @@ public interface IPermissionDefinitionSerializer Task SerializeAsync( PermissionGroupDefinition permissionGroup); - Task<(List, List)> + Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> SerializeAsync(IEnumerable permissionGroups); } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs index bccfd65a7a..c7a8dac1bb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs @@ -81,7 +81,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I } } - public async Task<(List, List)> + public async Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> SerializeAsync(IEnumerable permissionGroups) { var permissionGroupRecords = new List(); @@ -97,7 +97,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I } } - return (permissionGroupRecords, permissionRecords); + return (permissionGroupRecords.ToArray(), permissionRecords.ToArray()); } public Task DeserializeAsync(PermissionGroupDefinitionRecord permissionGroupRecord) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGroupDefinitionRecord.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGroupDefinitionRecord.cs index ff0c817ad2..c1f9656e2c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGroupDefinitionRecord.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGroupDefinitionRecord.cs @@ -30,4 +30,47 @@ public class PermissionGroupDefinitionRecord : BasicAggregateRoot, IHasExt ExtraProperties = new ExtraPropertyDictionary(); this.SetDefaultsForExtraProperties(); } + + public bool HasSameData(PermissionGroupDefinitionRecord otherRecord) + { + if (Name != otherRecord.Name) + { + return false; + } + + if (DisplayName != otherRecord.DisplayName) + { + return false; + } + + if (!this.HasSameExtraProperties(otherRecord)) + { + return false; + } + + return true; + } + + public void Patch(PermissionGroupDefinitionRecord record) + { + if (Name != record.Name) + { + Name = record.Name; + } + + if (DisplayName != record.DisplayName) + { + DisplayName = record.DisplayName; + } + + if (!this.HasSameExtraProperties(record)) + { + this.ExtraProperties.Clear(); + + foreach (var property in record.ExtraProperties) + { + this.ExtraProperties.Add(property.Key, property.Value); + } + } + } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManagementOptions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManagementOptions.cs index 15da755a33..55137878f3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManagementOptions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManagementOptions.cs @@ -8,10 +8,16 @@ public class PermissionManagementOptions public ITypeList ManagementProviders { get; } public Dictionary ProviderPolicies { get; } + + public HashSet DeletedPermissions { get; } + public HashSet DeletedPermissionGroups { get; } public PermissionManagementOptions() { ManagementProviders = new TypeList(); ProviderPolicies = new Dictionary(); + + DeletedPermissions = new HashSet(); + DeletedPermissionGroups = new HashSet(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs index c3b1e85866..c6ecfa2a2b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs @@ -1,7 +1,15 @@ +using System; using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; using Volo.Abp.Uow; namespace Volo.Abp.PermissionManagement; @@ -12,34 +20,146 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc protected IPermissionGroupDefinitionRecordRepository PermissionGroupRepository { get; } protected IPermissionDefinitionRecordRepository PermissionRepository { get; } protected IPermissionDefinitionSerializer PermissionSerializer { get; } - + protected IDistributedCache Cache { get; } + protected IApplicationNameAccessor ApplicationNameAccessor { get; } + public IAbpDistributedLock DistributedLock { get; } + public PermissionManagementOptions PermissionManagementOptions { get; } + protected AbpDistributedCacheOptions CacheOptions { get; } + public StaticPermissionSaver( IStaticPermissionDefinitionStore staticStore, IPermissionGroupDefinitionRecordRepository permissionGroupRepository, IPermissionDefinitionRecordRepository permissionRepository, - IPermissionDefinitionSerializer permissionSerializer) + IPermissionDefinitionSerializer permissionSerializer, + IDistributedCache cache, + IOptions cacheOptions, + IApplicationNameAccessor applicationNameAccessor, + IAbpDistributedLock distributedLock, + IOptions permissionManagementOptions) { StaticStore = staticStore; PermissionGroupRepository = permissionGroupRepository; PermissionRepository = permissionRepository; PermissionSerializer = permissionSerializer; + Cache = cache; + ApplicationNameAccessor = applicationNameAccessor; + DistributedLock = distributedLock; + PermissionManagementOptions = permissionManagementOptions.Value; + CacheOptions = cacheOptions.Value; } [UnitOfWork] public virtual async Task SaveAsync() { - // TODO: Save only changed permissions & groups - /* + get all groups & perms - * - compare and update or insert groups - * - get all permissions - * - compare and update or insert permissions - * - set in-memory cache using the latest group and permission data - */ + await using var handle = await DistributedLock.TryAcquireAsync(GetDistributedLockKey()); + + if (handle == null) + { + /* Another instance already did it */ + return; + } + + var cacheKey = GetCacheKey(); + var cachedHash = await Cache.GetStringAsync(cacheKey); var (permissionGroupRecords, permissionRecords) = await PermissionSerializer.SerializeAsync( await StaticStore.GetGroupsAsync() ); + + var currentHash = CalculateHash( + permissionGroupRecords, + permissionRecords, + PermissionManagementOptions.DeletedPermissionGroups, + PermissionManagementOptions.DeletedPermissions + ); + + if (cachedHash == currentHash) + { + return; + } + + await UpdateChangedGroupsAsync(permissionGroupRecords); + + await Cache.SetStringAsync( + cacheKey, + currentHash, + new DistributedCacheEntryOptions { + SlidingExpiration = TimeSpan.FromDays(2) + } + ); + } + + private async Task UpdateChangedGroupsAsync(PermissionGroupDefinitionRecord[] permissionGroupRecords) + { + var newRecords = new List(); + var changedRecords = new List(); + + var permissionGroupRecordsInDatabase = (await PermissionGroupRepository.GetListAsync()) + .ToDictionary(x => x.Name); + + foreach (var permissionGroupRecord in permissionGroupRecords) + { + var permissionGroupRecordInDatabase = permissionGroupRecordsInDatabase.GetOrDefault(permissionGroupRecord.Name); + if (permissionGroupRecordInDatabase == null) + { + /* New group */ + newRecords.Add(permissionGroupRecord); + continue; + } + + if (permissionGroupRecord.HasSameData(permissionGroupRecordInDatabase)) + { + /* Not changed */ + continue; + } + + /* Changed */ + permissionGroupRecordInDatabase.Patch(permissionGroupRecord); + changedRecords.Add(permissionGroupRecordInDatabase); + } + + /* Deleted */ + var deletedRecords = permissionGroupRecordsInDatabase.Values + .Where(x => PermissionManagementOptions.DeletedPermissionGroups.Contains(x.Name)) + .ToArray(); + + await PermissionGroupRepository.InsertManyAsync(newRecords); + await PermissionGroupRepository.UpdateManyAsync(changedRecords); + await PermissionGroupRepository.DeleteManyAsync(deletedRecords); + } + + private string GetDistributedLockKey() + { + return $"{ApplicationNameAccessor.ApplicationName}_AbpPermissionUpdateLock"; + } + + private string GetCacheKey() + { + return $"{CacheOptions.KeyPrefix}_{ApplicationNameAccessor.ApplicationName}_AbpPermissionsHash"; + } + + private static string CalculateHash( + PermissionGroupDefinitionRecord[] permissionGroupRecords, + PermissionDefinitionRecord[] permissionRecords, + IEnumerable deletedPermissionGroups, + IEnumerable deletedPermissions) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.Append("PermissionGroupRecords:"); + stringBuilder.AppendLine(JsonSerializer.Serialize(permissionGroupRecords)); + + stringBuilder.Append("PermissionRecords:"); + stringBuilder.AppendLine(JsonSerializer.Serialize(permissionRecords)); + + stringBuilder.Append("DeletedPermissionGroups:"); + stringBuilder.AppendLine(deletedPermissionGroups.JoinAsString(",")); + stringBuilder.Append("DeletedPermission:"); + stringBuilder.Append(deletedPermissions.JoinAsString(",")); + return stringBuilder + .ToString() + .ToMd5(); } } \ No newline at end of file diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs index cac2892838..21f18cca1d 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs @@ -73,41 +73,6 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase permissionRecord.MultiTenancySide.ShouldBe(MultiTenancySides.Tenant); permissionRecord.StateCheckers.ShouldBe("[{\"T\":\"GF\",\"A\":true,\"N\":[\"GlobalFeature1\",\"GlobalFeature2\"]}]"); } - - [Fact(Skip = "Not implemented yet")] - public async Task Deserialize_Complex_Permission_Definition() - { - // Arrange - - var permissionRecord = new PermissionDefinitionRecord - { - Name = "Permission1", - GroupName = "Group1", - DisplayName = "Permission one", - Providers = "ProviderA,ProviderB", - IsEnabled = true, - MultiTenancySide = MultiTenancySides.Tenant, - StateCheckers = "[{\"T\":\"GF\",\"A\":true,\"N\":[\"GlobalFeature1\",\"GlobalFeature2\"]}]" - }; - - // Act - - var permission = await _serializer.DeserializeAsync(permissionRecord); - - //Assert - - permission.Name.ShouldBe("Permission1"); - permission.DisplayName.ShouldBeOfType(); - permission.DisplayName.As().Value.ShouldBe("Permission one"); - permission.Parent.ShouldBeNull(); - permission.MultiTenancySide.ShouldBe(MultiTenancySides.Tenant); - permission.IsEnabled.ShouldBe(true); - permission.Providers.Count.ShouldBe(2); - permission.Providers.ShouldContain("ProviderA"); - permission.Providers.ShouldContain("ProviderB"); - permission.StateCheckers.Count.ShouldBe(1); - permission.StateCheckers[0].ShouldBeOfType>(); - } private static PermissionGroupDefinition CreatePermissionGroup1( IPermissionDefinitionContext context)