diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs index f2e73cefc0..5245db41ae 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs @@ -23,7 +23,7 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor protected IAbpDistributedLock DistributedLock { get; } public PermissionManagementOptions PermissionManagementOptions { get; } protected AbpDistributedCacheOptions CacheOptions { get; } - + public DynamicPermissionDefinitionStore( IPermissionGroupDefinitionRecordRepository permissionGroupRepository, IPermissionDefinitionRecordRepository permissionRepository, @@ -72,6 +72,34 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor } } + public virtual async Task GetResourcePermissionOrNullAsync(string name) + { + if (!PermissionManagementOptions.IsDynamicPermissionStoreEnabled) + { + return null; + } + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetPermissionOrNull(name); + } + } + + public virtual async Task> GetResourcePermissionsAsync() + { + if (!PermissionManagementOptions.IsDynamicPermissionStoreEnabled) + { + return Array.Empty(); + } + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetPermissions().ToImmutableList(); + } + } + public virtual async Task> GetGroupsAsync() { if (!PermissionManagementOptions.IsDynamicPermissionStoreEnabled) @@ -94,9 +122,9 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor /* We get the latest permission with a small delay for optimization */ return; } - + var stampInDistributedCache = await GetOrSetStampInDistributedCache(); - + if (stampInDistributedCache == StoreCache.CacheStamp) { StoreCache.LastCheckTime = DateTime.Now; @@ -145,7 +173,7 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor } stampInDistributedCache = Guid.NewGuid().ToString(); - + await DistributedCache.SetStringAsync( cacheKey, stampInDistributedCache, @@ -163,9 +191,9 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor { return $"{CacheOptions.KeyPrefix}_AbpInMemoryPermissionCacheStamp"; } - + protected virtual string GetCommonDistributedLockKey() { return $"{CacheOptions.KeyPrefix}_Common_AbpPermissionUpdateLock"; } -} \ No newline at end of file +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreInMemoryCache.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreInMemoryCache.cs index 1040ea84a0..8ce37261af 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreInMemoryCache.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreInMemoryCache.cs @@ -18,6 +18,7 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : protected IDictionary PermissionGroupDefinitions { get; } protected IDictionary PermissionDefinitions { get; } + protected IDictionary ResourcePermissionDefinitions { get; } protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; } protected ILocalizableStringSerializer LocalizableStringSerializer { get; } @@ -34,6 +35,7 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : PermissionGroupDefinitions = new Dictionary(); PermissionDefinitions = new Dictionary(); + ResourcePermissionDefinitions = new Dictionary(); } public Task FillAsync( @@ -42,9 +44,21 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : { PermissionGroupDefinitions.Clear(); PermissionDefinitions.Clear(); + ResourcePermissionDefinitions.Clear(); var context = new PermissionDefinitionContext(null); + var resourcePermissions = permissionRecords.Where(x => !x.ResourceName.IsNullOrWhiteSpace()); + foreach (var resourcePermission in resourcePermissions) + { + context.AddResourcePermission(resourcePermission.Name, + resourcePermission.ResourceName, + resourcePermission.DisplayName != null ? LocalizableStringSerializer.Deserialize(resourcePermission.DisplayName) : null, + resourcePermission.MultiTenancySide, + resourcePermission.IsEnabled); + } + + var permissions = permissionRecords.Where(x => x.ResourceName.IsNullOrWhiteSpace()).ToList(); foreach (var permissionGroupRecord in permissionGroupRecords) { var permissionGroup = context.AddGroup( @@ -59,12 +73,12 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : permissionGroup[property.Key] = property.Value; } - var permissionRecordsInThisGroup = permissionRecords + var permissionRecordsInThisGroup = permissions .Where(p => p.GroupName == permissionGroup.Name); foreach (var permissionRecord in permissionRecordsInThisGroup.Where(x => x.ParentName == null)) { - AddPermissionRecursively(permissionGroup, permissionRecord, permissionRecords); + AddPermissionRecursively(permissionGroup, permissionRecord, permissions); } } @@ -86,6 +100,16 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : return PermissionGroupDefinitions.Values.ToList(); } + public PermissionDefinition GetResourcePermissionOrNull(string name) + { + return ResourcePermissionDefinitions.GetOrDefault(name); + } + + public IReadOnlyList GetResourcePermissions() + { + return ResourcePermissionDefinitions.Values.ToList(); + } + private void AddPermissionRecursively(ICanAddChildPermission permissionContainer, PermissionDefinitionRecord permissionRecord, List allPermissionRecords) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IDynamicPermissionDefinitionStoreInMemoryCache.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IDynamicPermissionDefinitionStoreInMemoryCache.cs index 2dab588ebd..b30baeb1e1 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IDynamicPermissionDefinitionStoreInMemoryCache.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IDynamicPermissionDefinitionStoreInMemoryCache.cs @@ -9,9 +9,9 @@ namespace Volo.Abp.PermissionManagement; public interface IDynamicPermissionDefinitionStoreInMemoryCache { string CacheStamp { get; set; } - + SemaphoreSlim SyncSemaphore { get; } - + DateTime? LastCheckTime { get; set; } Task FillAsync( @@ -19,8 +19,12 @@ public interface IDynamicPermissionDefinitionStoreInMemoryCache List permissionRecords); PermissionDefinition GetPermissionOrNull(string name); - + IReadOnlyList GetPermissions(); - + IReadOnlyList GetGroups(); -} \ No newline at end of file + + PermissionDefinition GetResourcePermissionOrNull(string name); + + IReadOnlyList GetResourcePermissions(); +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionGrantRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionGrantRepository.cs new file mode 100644 index 0000000000..6696b9da75 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionGrantRepository.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.PermissionManagement; + +public interface IResourcePermissionGrantRepository : IBasicRepository +{ + Task FindAsync( + string name, + string resourceName, + string resourceKey, + string providerName, + string providerKey, + CancellationToken cancellationToken = default + ); + + Task> GetListAsync( + string resourceName, + string resourceKey, + string providerName, + string providerKey, + CancellationToken cancellationToken = default + ); + + Task> GetListAsync( + string[] names, + string resourceName, + string resourceKey, + string providerName, + string providerKey, + CancellationToken cancellationToken = default + ); + + Task> GetPermissionsAsync( + string resourceName, + string resourceKey, + CancellationToken cancellationToken = default + ); + + Task> GetResourceKeys( + string resourceName, + string name, + CancellationToken cancellationToken = default + ); +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItem.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItem.cs new file mode 100644 index 0000000000..59362f6f5f --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItem.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using Volo.Abp.Text.Formatting; + +namespace Volo.Abp.PermissionManagement; + +[Serializable] +public class ResourcePermissionGrantCacheItem +{ + private const string CacheKeyFormat = "rn:{0},rk:{1},pn:{2},pk:{3},n:{4}"; + + public bool IsGranted { get; set; } + + public ResourcePermissionGrantCacheItem() + { + + } + + public ResourcePermissionGrantCacheItem(bool isGranted) + { + IsGranted = isGranted; + } + + public static string CalculateCacheKey(string name, string resourceName, string resourceKey, string providerName, string providerKey) + { + return string.Format(CacheKeyFormat, resourceName, resourceKey, providerName, providerKey, name); + } + + public static string GetPermissionNameFormCacheKeyOrNull(string cacheKey) + { + var result = FormattedStringValueExtracter.Extract(cacheKey, CacheKeyFormat, true); + return result.IsMatch ? result.Matches.Last().Value : null; + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionStore.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionStore.cs new file mode 100644 index 0000000000..7942549b73 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionStore.cs @@ -0,0 +1,249 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionStore : IResourcePermissionStore, ITransientDependency +{ + public ILogger Logger { get; set; } + + protected IResourcePermissionGrantRepository ResourcePermissionGrantRepository { get; } + + protected IPermissionDefinitionManager PermissionDefinitionManager { get; } + + protected IDistributedCache Cache { get; } + + public ResourcePermissionStore( + IResourcePermissionGrantRepository resourcePermissionGrantRepository, + IDistributedCache cache, + IPermissionDefinitionManager permissionDefinitionManager) + { + ResourcePermissionGrantRepository = resourcePermissionGrantRepository; + Cache = cache; + PermissionDefinitionManager = permissionDefinitionManager; + Logger = NullLogger.Instance; + } + + public virtual async Task IsGrantedAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) + { + return (await GetCacheItemAsync(name, resourceName, resourceKey, providerName, providerKey)).IsGranted; + } + + protected virtual async Task GetCacheItemAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) + { + var cacheKey = CalculateCacheKey(name, providerName, providerKey, resourceName, resourceKey); + + Logger.LogDebug($"ResourcePermissionStore.GetCacheItemAsync: {cacheKey}"); + + var cacheItem = await Cache.GetAsync(cacheKey); + + if (cacheItem != null) + { + Logger.LogDebug($"Found in the cache: {cacheKey}"); + return cacheItem; + } + + Logger.LogDebug($"Not found in the cache: {cacheKey}"); + + cacheItem = new ResourcePermissionGrantCacheItem(false); + + await SetCacheItemsAsync(resourceName, resourceKey, providerName, providerKey, name, cacheItem); + + return cacheItem; + } + + protected virtual async Task SetCacheItemsAsync(string resourceName, string resourceKey, string providerName, string providerKey, string currentName, ResourcePermissionGrantCacheItem currentCacheItem) + { + using (ResourcePermissionGrantRepository.DisableTracking()) + { + var permissions = await PermissionDefinitionManager.GetResourcePermissionsAsync(); + + Logger.LogDebug($"Getting all granted resource permissions from the repository for resource name,key:{resourceName},{resourceKey} and provider name,key: {providerName},{providerKey}"); + + var grantedPermissionsHashSet = new HashSet( + (await ResourcePermissionGrantRepository.GetListAsync(resourceName, resourceKey, providerName, providerKey)).Select(p => p.Name) + ); + + Logger.LogDebug($"Setting the cache items. Count: {permissions.Count}"); + + var cacheItems = new List>(); + + foreach (var permission in permissions) + { + var isGranted = grantedPermissionsHashSet.Contains(permission.Name); + + cacheItems.Add(new KeyValuePair( + CalculateCacheKey(permission.Name, resourceName, resourceKey, providerName, providerKey), + new ResourcePermissionGrantCacheItem(isGranted)) + ); + + if (permission.Name == currentName) + { + currentCacheItem.IsGranted = isGranted; + } + } + + await Cache.SetManyAsync(cacheItems); + + Logger.LogDebug($"Finished setting the cache items. Count: {permissions.Count}"); + } + } + + public virtual async Task IsGrantedAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) + { + Check.NotNullOrEmpty(names, nameof(names)); + + var result = new MultiplePermissionGrantResult(); + + if (names.Length == 1) + { + var name = names.First(); + result.Result.Add(name, + await IsGrantedAsync(names.First(), resourceName, resourceKey, providerName, providerKey) + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined); + return result; + } + + var cacheItems = await GetCacheItemsAsync(names, resourceName, resourceKey, providerName, providerKey); + foreach (var item in cacheItems) + { + result.Result.Add(GetPermissionNameFormCacheKeyOrNull(item.Key), + item.Value != null && item.Value.IsGranted + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined); + } + + return result; + } + + protected virtual async Task>> GetCacheItemsAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) + { + var cacheKeys = names.Select(x => CalculateCacheKey(x, resourceName, resourceKey, providerName, providerKey)).ToList(); + + Logger.LogDebug($"ResourcePermissionStore.GetCacheItemAsync: {string.Join(",", cacheKeys)}"); + + var cacheItems = (await Cache.GetManyAsync(cacheKeys)).ToList(); + if (cacheItems.All(x => x.Value != null)) + { + Logger.LogDebug($"Found in the cache: {string.Join(",", cacheKeys)}"); + return cacheItems; + } + + var notCacheKeys = cacheItems.Where(x => x.Value == null).Select(x => x.Key).ToList(); + + Logger.LogDebug($"Not found in the cache: {string.Join(",", notCacheKeys)}"); + + var newCacheItems = await SetCacheItemsAsync(resourceName, resourceKey, providerName, providerKey, notCacheKeys); + + var result = new List>(); + foreach (var key in cacheKeys) + { + var item = newCacheItems.FirstOrDefault(x => x.Key == key); + if (item.Value == null) + { + item = cacheItems.FirstOrDefault(x => x.Key == key); + } + + result.Add(new KeyValuePair(key, item.Value)); + } + + return result; + } + + protected virtual async Task>> SetCacheItemsAsync(string resourceName, string resourceKey, string providerName, string providerKey, List notCacheKeys) + { + using (ResourcePermissionGrantRepository.DisableTracking()) + { + var permissionNames = new HashSet(notCacheKeys.Select(GetPermissionNameFormCacheKeyOrNull)); + var permissions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()) + .Where(x => permissionNames.Contains(x.Name)) + .ToList(); + + Logger.LogDebug($"Getting not cache granted permissions from the repository for resource name,key:{resourceName},{resourceKey} and provider name,key: {providerName},{providerKey}"); + + var grantedPermissionsHashSet = new HashSet( + (await ResourcePermissionGrantRepository.GetListAsync(permissionNames.ToArray(), resourceName, resourceKey, providerName, providerKey)).Select(p => p.Name) + ); + + Logger.LogDebug($"Setting the cache items. Count: {permissions.Count}"); + + var cacheItems = new List>(); + + foreach (var permission in permissions) + { + var isGranted = grantedPermissionsHashSet.Contains(permission.Name); + + cacheItems.Add(new KeyValuePair( + CalculateCacheKey(permission.Name, resourceName, resourceKey, providerName, providerKey), + new ResourcePermissionGrantCacheItem(isGranted)) + ); + } + + await Cache.SetManyAsync(cacheItems); + + Logger.LogDebug($"Finished setting the cache items. Count: {permissions.Count}"); + + return cacheItems; + } + } + + public virtual async Task GetPermissionsAsync(string resourceName, string resourceKey) + { + using (ResourcePermissionGrantRepository.DisableTracking()) + { + var result = new MultiplePermissionGrantResult(); + + var resourcePermissions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()).Where(x => x.ResourceName == resourceName).ToList(); + var permissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); + foreach (var resourcePermission in resourcePermissions) + { + var isGranted = permissionGrants.Any(x => x == resourcePermission.Name); + result.Result.Add(resourcePermission.Name, isGranted ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined); + } + + return result; + } + } + + public virtual async Task GetGrantedPermissionsAsync(string resourceName, string resourceKey) + { + var resourcePermissions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()).Where(x => x.ResourceName == resourceName).ToList(); + var grantedPermissions = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); + + var result = new List(); + foreach (var grantedPermission in grantedPermissions) + { + if (resourcePermissions.Any(x => x.Name == grantedPermission)) + { + result.Add(grantedPermission); + } + } + + return result.ToArray(); + } + + public virtual async Task GetGrantedResourceKeysAsync(string resourceName, string name) + { + return (await ResourcePermissionGrantRepository.GetResourceKeys(resourceName, name)).ToArray(); + } + + protected virtual string GetPermissionNameFormCacheKeyOrNull(string key) + { + //TODO: throw ex when name is null? + return ResourcePermissionGrantCacheItem.GetPermissionNameFormCacheKeyOrNull(key); + } + + protected virtual string CalculateCacheKey(string name, string resourceName, string resourceKey, string providerName, string providerKey) + { + return ResourcePermissionGrantCacheItem.CalculateCacheKey(name, resourceName, resourceKey, providerName, providerKey); + } +}