From b535f429636bd183930ee5a500c0b53f006778af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 7 Nov 2025 16:30:47 +0300 Subject: [PATCH 001/130] Introduce IResourcePermissionChecker --- .../Resources/IResourcePermissionChecker.cs | 23 ++++++++ .../ResourcePermissionCheckerExtensions.cs | 58 +++++++++++++++++++ .../Volo.Abp.Ddd.Domain.csproj | 1 + ...tityResourcePermissionCheckerExtensions.cs | 56 ++++++++++++++++++ .../Volo/Abp/Domain/AbpDddDomainModule.cs | 4 +- 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs new file mode 100644 index 0000000000..0bf3b23389 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public interface IResourcePermissionChecker +{ + Task IsGrantedAsync( + string permissionName, + string resourceName, + string resourceKey + ); + + Task> GetPermissionsAsync( + string resourceName, + string resourceKey + ); + + Task GetGrantedPermissionsAsync( + string resourceName, + string resourceKey + ); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs new file mode 100644 index 0000000000..c294b7d75c --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public static class ResourcePermissionCheckerExtensions +{ + public static Task IsGrantedAsync( + this IResourcePermissionChecker resourcePermissionChecker, + string permissionName, + TResource resource, + object resourceKey + ) + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); + Check.NotNull(resource, nameof(resource)); + Check.NotNull(resourceKey, nameof(resourceKey)); + + return resourcePermissionChecker.IsGrantedAsync( + permissionName, + typeof(TResource).FullName!, + resourceKey.ToString()! + ); + } + + public static Task> GetPermissionsAsync( + this IResourcePermissionChecker resourcePermissionChecker, + TResource resource, + object resourceKey + ) + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resource, nameof(resource)); + Check.NotNull(resourceKey, nameof(resourceKey)); + + return resourcePermissionChecker.GetPermissionsAsync( + typeof(TResource).FullName!, + resourceKey.ToString()! + ); + } + + public static Task GetGrantedPermissionsAsync( + this IResourcePermissionChecker resourcePermissionChecker, + TResource resource, + object resourceKey + ) + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resource, nameof(resource)); + Check.NotNull(resourceKey, nameof(resourceKey)); + + return resourcePermissionChecker.GetGrantedPermissionsAsync( + typeof(TResource).FullName!, + resourceKey.ToString()! + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj b/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj index 6506d8fe7b..8666c1faa2 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj @@ -17,6 +17,7 @@ + diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs new file mode 100644 index 0000000000..810c6eade2 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public static class EntityResourcePermissionCheckerExtensions +{ + public static Task IsGrantedAsync( + this IResourcePermissionChecker resourcePermissionChecker, + string permissionName, + TEntity entity + ) + where TEntity : class, IEntity + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); + Check.NotNull(entity, nameof(entity)); + + return resourcePermissionChecker.IsGrantedAsync( + permissionName, + typeof(TEntity).FullName!, + entity.GetKeys().JoinAsString(",") + ); + } + + public static Task> GetPermissionsAsync( + this IResourcePermissionChecker resourcePermissionChecker, + TEntity entity + ) + where TEntity : class, IEntity + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(entity, nameof(entity)); + + return resourcePermissionChecker.GetPermissionsAsync( + typeof(TEntity).FullName!, + entity.GetKeys().JoinAsString(",") + ); + } + + public static Task GetGrantedPermissionsAsync( + this IResourcePermissionChecker resourcePermissionChecker, + TEntity entity + ) + where TEntity : class, IEntity + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(entity, nameof(entity)); + + return resourcePermissionChecker.GetGrantedPermissionsAsync( + typeof(TEntity).FullName!, + entity.GetKeys().JoinAsString(",") + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs index de6632704d..cf3ba5fea8 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Auditing; +using Volo.Abp.Authorization; using Volo.Abp.Caching; using Volo.Abp.Data; using Volo.Abp.Domain.ChangeTracking; @@ -24,7 +25,8 @@ namespace Volo.Abp.Domain; typeof(AbpExceptionHandlingModule), typeof(AbpSpecificationsModule), typeof(AbpCachingModule), - typeof(AbpDddDomainSharedModule) + typeof(AbpDddDomainSharedModule), + typeof(AbpAuthorizationAbstractionsModule) )] public class AbpDddDomainModule : AbpModule { From e5e25c5e5f9c8a1e7f8d644599f164212281b0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 10 Nov 2025 14:01:29 +0300 Subject: [PATCH 002/130] Added GetGrantedResourceKeysAsync --- .../Resources/IResourcePermissionChecker.cs | 5 ++++ .../ResourcePermissionCheckerExtensions.cs | 16 +++++++++++++ ...tityResourcePermissionCheckerExtensions.cs | 24 ++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs index 0bf3b23389..383e49c527 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs @@ -20,4 +20,9 @@ public interface IResourcePermissionChecker string resourceName, string resourceKey ); + + Task GetGrantedResourceKeysAsync( + string resourceName, + string permissionName + ); } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs index c294b7d75c..bd54ef5eee 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs @@ -55,4 +55,20 @@ public static class ResourcePermissionCheckerExtensions resourceKey.ToString()! ); } + + public static Task GetGrantedResourceKeysAsync( + this IResourcePermissionChecker resourcePermissionChecker, + TResource resource, + string permissionName + ) + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resource, nameof(resource)); + Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); + + return resourcePermissionChecker.GetGrantedResourceKeysAsync( + typeof(TResource).FullName!, + permissionName + ); + } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs index 810c6eade2..9867bb68cb 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Volo.Abp.Domain.Entities; @@ -53,4 +55,24 @@ public static class EntityResourcePermissionCheckerExtensions entity.GetKeys().JoinAsString(",") ); } + + public async static Task GetGrantedEntityIdsAsync( + this IResourcePermissionChecker resourcePermissionChecker, + string permissionName + ) + where TEntity : class, IEntity + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); + + var keys = await resourcePermissionChecker.GetGrantedResourceKeysAsync( + typeof(TEntity).FullName!, + permissionName + ); + + return keys + .Select(x => Convert.ChangeType(x, typeof(TKey))) + .Cast() + .ToArray(); + } } \ No newline at end of file From 87e41a616635ae096151b010fcd1c114531dc9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 10 Nov 2025 14:06:32 +0300 Subject: [PATCH 003/130] Added summaries --- .../Resources/IResourcePermissionChecker.cs | 35 ++++++++++++++++- .../ResourcePermissionCheckerExtensions.cs | 39 +++++++++++++++++-- ...tityResourcePermissionCheckerExtensions.cs | 36 +++++++++++++++-- 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs index 383e49c527..4e8229d605 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs @@ -5,22 +5,55 @@ namespace Volo.Abp.Authorization.Permissions.Resources; public interface IResourcePermissionChecker { + /// + /// Checks if the given permission is granted for the given resource. + /// + /// The name of the permission. + /// The name of the resource. + /// Resource key + /// + /// True if the permission is granted. + /// Task IsGrantedAsync( string permissionName, string resourceName, string resourceKey ); + /// + /// Gets all permissions for the given resource. + /// + /// Resource name + /// Resource key + /// + /// A dictionary of permission names and their states. + /// Task> GetPermissionsAsync( string resourceName, string resourceKey ); + /// + /// Gets all granted permissions for the given resource. + /// + /// Resource name + /// Resource key + /// + /// An array of granted permission names. + /// Task GetGrantedPermissionsAsync( string resourceName, string resourceKey ); - + + /// + /// Retrieves the keys of resources for which the specified permission is granted. + /// + /// The name of the resource. + /// The name of the permission. + /// + /// An array of resource keys where the specified permission is granted. + /// Task GetGrantedResourceKeysAsync( string resourceName, string permissionName diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs index bd54ef5eee..fa73aa428f 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs @@ -5,6 +5,15 @@ namespace Volo.Abp.Authorization.Permissions.Resources; public static class ResourcePermissionCheckerExtensions { + /// + /// Checks if a specific permission is granted for a resource with a given key. + /// + /// The type of the resource. + /// The resource permission checker instance. + /// The name of the permission to check. + /// The resource instance to check permission for. + /// The unique key identifying the resource instance. + /// A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the permission is granted. public static Task IsGrantedAsync( this IResourcePermissionChecker resourcePermissionChecker, string permissionName, @@ -23,7 +32,15 @@ public static class ResourcePermissionCheckerExtensions resourceKey.ToString()! ); } - + + /// + /// Retrieves a dictionary of permissions and their granted statuses for a specific resource. + /// + /// The type of the resource. + /// The resource permission checker instance. + /// The resource instance for which permissions are being checked. + /// The unique key identifying the resource instance. + /// A task that represents the asynchronous operation. The task result contains a dictionary where keys are permission names and values indicate whether the corresponding permission is granted. public static Task> GetPermissionsAsync( this IResourcePermissionChecker resourcePermissionChecker, TResource resource, @@ -39,7 +56,15 @@ public static class ResourcePermissionCheckerExtensions resourceKey.ToString()! ); } - + + /// + /// Retrieves the list of granted permissions for a specific resource with a given key. + /// + /// The type of the resource. + /// The resource permission checker instance. + /// The resource instance to retrieve permissions for. + /// The unique key identifying the resource instance. + /// A task that represents the asynchronous operation. The task result contains an array of strings representing the granted permissions. public static Task GetGrantedPermissionsAsync( this IResourcePermissionChecker resourcePermissionChecker, TResource resource, @@ -55,7 +80,15 @@ public static class ResourcePermissionCheckerExtensions resourceKey.ToString()! ); } - + + /// + /// Retrieves the keys of the resources granted a specific permission. + /// + /// The type of the resource. + /// The resource permission checker instance. + /// The resource instance to check granted permissions for. + /// The name of the permission to check. + /// A task that represents the asynchronous operation. The task result contains an array of strings representing the granted resource keys. public static Task GetGrantedResourceKeysAsync( this IResourcePermissionChecker resourcePermissionChecker, TResource resource, diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs index 9867bb68cb..6a3ff5e6b1 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs @@ -8,6 +8,14 @@ namespace Volo.Abp.Authorization.Permissions.Resources; public static class EntityResourcePermissionCheckerExtensions { + /// + /// Checks if the specified permission is granted for the given entity. + /// + /// The type of the entity. + /// The resource permission checker instance. + /// The name of the permission to check. + /// The entity for which the permission is being checked. + /// A task that represents the asynchronous operation. The task result is a boolean indicating whether the permission is granted. public static Task IsGrantedAsync( this IResourcePermissionChecker resourcePermissionChecker, string permissionName, @@ -25,7 +33,14 @@ public static class EntityResourcePermissionCheckerExtensions entity.GetKeys().JoinAsString(",") ); } - + + /// + /// Retrieves a dictionary of permissions and their granted status for the specified entity. + /// + /// The type of the entity. + /// The resource permission checker instance. + /// The entity for which the permissions are being retrieved. + /// A dictionary where the keys are permission names and the values are booleans indicating whether the permission is granted. public static Task> GetPermissionsAsync( this IResourcePermissionChecker resourcePermissionChecker, TEntity entity @@ -40,7 +55,14 @@ public static class EntityResourcePermissionCheckerExtensions entity.GetKeys().JoinAsString(",") ); } - + + /// + /// Retrieves an array of granted permissions for a specific entity. + /// + /// The type of the entity. + /// The resource permission checker instance. + /// The entity for which the permissions are being checked. + /// An array of granted permission names as strings. public static Task GetGrantedPermissionsAsync( this IResourcePermissionChecker resourcePermissionChecker, TEntity entity @@ -55,7 +77,15 @@ public static class EntityResourcePermissionCheckerExtensions entity.GetKeys().JoinAsString(",") ); } - + + /// + /// Retrieves an array of granted entity IDs for a specific permission. + /// + /// The type of the entity. + /// The type of the entity's primary key. + /// The resource permission checker instance. + /// The name of the permission to check. + /// An array of entity IDs (of type ) for which the permission is granted. public async static Task GetGrantedEntityIdsAsync( this IResourcePermissionChecker resourcePermissionChecker, string permissionName From ca61e7d67f59fc3fddaadf9d5ab32989a66be3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 10 Nov 2025 14:12:47 +0300 Subject: [PATCH 004/130] Reformat IPermissionManager --- .../IPermissionManager.cs | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionManager.cs index 7ec69ad100..bee98c55f9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionManager.cs @@ -8,15 +8,37 @@ namespace Volo.Abp.PermissionManagement; public interface IPermissionManager { - Task GetAsync(string permissionName, string providerName, string providerKey); - - Task GetAsync(string[] permissionNames, string provideName, string providerKey); - - Task> GetAllAsync([NotNull] string providerName, [NotNull] string providerKey); - - Task SetAsync(string permissionName, string providerName, string providerKey, bool isGranted); - - Task UpdateProviderKeyAsync(PermissionGrant permissionGrant, string providerKey); - - Task DeleteAsync(string providerName, string providerKey); -} + Task GetAsync( + string permissionName, + string providerName, + string providerKey + ); + + Task GetAsync( + string[] permissionNames, + string provideName, + string providerKey + ); + + Task> GetAllAsync( + [NotNull] string providerName, + [NotNull] string providerKey + ); + + Task SetAsync( + string permissionName, + string providerName, + string providerKey, + bool isGranted + ); + + Task UpdateProviderKeyAsync( + PermissionGrant permissionGrant, + string providerKey + ); + + Task DeleteAsync( + string providerName, + string providerKey + ); +} \ No newline at end of file From 8448bdda1d6848aab7e794e409924375e1b8ae00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 10 Nov 2025 14:14:46 +0300 Subject: [PATCH 005/130] Remove outdated comment --- .../Volo/Abp/PermissionManagement/IPermissionManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionManager.cs index bee98c55f9..ffb934321b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IPermissionManager.cs @@ -4,8 +4,6 @@ using JetBrains.Annotations; namespace Volo.Abp.PermissionManagement; -//TODO: Write extension methods for simple IsGranted check - public interface IPermissionManager { Task GetAsync( From e662b62ec308deadf403eb13557152050e5713f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 10 Nov 2025 14:35:48 +0300 Subject: [PATCH 006/130] Create IResourcePermissionManager.cs --- .../IResourcePermissionManager.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs new file mode 100644 index 0000000000..f58fcdc584 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.PermissionManagement; + +public interface IResourcePermissionManager +{ + Task GetAsync( + string permissionName, + string providerName, + string providerKey, + string resourceName, + string resourceKey + ); + + Task GetAsync( + string[] permissionNames, + string provideName, + string providerKey, + string resourceName, + string resourceKey + ); + + Task> GetAllAsync( + string providerName, + string providerKey, + string resourceName, + string resourceKey + ); + + Task SetAsync( + string permissionName, + string providerName, + string providerKey, + string resourceName, + string resourceKey, + bool isGranted + ); +} \ No newline at end of file From 74d6bdbe2cca4d8b5f9c8b54c5acfbf77a509796 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 11 Nov 2025 15:13:06 +0800 Subject: [PATCH 007/130] Add resource permission management interfaces and implementations --- .../Permissions/AbpPermissionOptions.cs | 4 + .../Resources/IResourcePermissionChecker.cs | 63 +------ .../Resources/IResourcePermissionStore.cs | 67 +++++++ .../IResourcePermissionValueProvider.cs | 13 ++ ...IResourcePermissionValueProviderManager.cs | 8 + .../Resources/NullResourcePermissionStore.cs | 44 +++++ .../Resources/ResourcePermissionGrantInfo.cs | 17 ++ ...s => ResourcePermissionStoreExtensions.cs} | 36 ++-- .../ResourcePermissionValueCheckContext.cs | 25 +++ .../ResourcePermissionValueProvider.cs | 20 +++ .../ResourcePermissionValuesCheckContext.cs | 41 +++++ .../Resources/ResourcePermissionChecker.cs | 170 ++++++++++++++++++ ...ntityResourcePermissionStoreExtensions.cs} | 40 ++--- 13 files changed, 456 insertions(+), 92 deletions(-) create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProvider.cs create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProviderManager.cs create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionGrantInfo.cs rename framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/{ResourcePermissionCheckerExtensions.cs => ResourcePermissionStoreExtensions.cs} (75%) create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueCheckContext.cs create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProvider.cs create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValuesCheckContext.cs create mode 100644 framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs rename framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/{EntityResourcePermissionCheckerExtensions.cs => EntityResourcePermissionStoreExtensions.cs} (70%) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/AbpPermissionOptions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/AbpPermissionOptions.cs index 4bdd7c0bb8..c240e1ac07 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/AbpPermissionOptions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/AbpPermissionOptions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Volo.Abp.Authorization.Permissions.Resources; using Volo.Abp.Collections; namespace Volo.Abp.Authorization.Permissions; @@ -9,6 +10,8 @@ public class AbpPermissionOptions public ITypeList ValueProviders { get; } + public ITypeList ResourceValueProviders { get; } + public HashSet DeletedPermissions { get; } public HashSet DeletedPermissionGroups { get; } @@ -17,6 +20,7 @@ public class AbpPermissionOptions { DefinitionProviders = new TypeList(); ValueProviders = new TypeList(); + ResourceValueProviders = new TypeList(); DeletedPermissions = new HashSet(); DeletedPermissionGroups = new HashSet(); diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs index 4e8229d605..5b3bf1eee1 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs @@ -1,61 +1,16 @@ -using System.Collections.Generic; +using System.Security.Claims; using System.Threading.Tasks; +using JetBrains.Annotations; namespace Volo.Abp.Authorization.Permissions.Resources; public interface IResourcePermissionChecker { - /// - /// Checks if the given permission is granted for the given resource. - /// - /// The name of the permission. - /// The name of the resource. - /// Resource key - /// - /// True if the permission is granted. - /// - Task IsGrantedAsync( - string permissionName, - string resourceName, - string resourceKey - ); - - /// - /// Gets all permissions for the given resource. - /// - /// Resource name - /// Resource key - /// - /// A dictionary of permission names and their states. - /// - Task> GetPermissionsAsync( - string resourceName, - string resourceKey - ); - - /// - /// Gets all granted permissions for the given resource. - /// - /// Resource name - /// Resource key - /// - /// An array of granted permission names. - /// - Task GetGrantedPermissionsAsync( - string resourceName, - string resourceKey - ); + Task IsGrantedAsync([NotNull] string name, string resourceName, string resourceKey); - /// - /// Retrieves the keys of resources for which the specified permission is granted. - /// - /// The name of the resource. - /// The name of the permission. - /// - /// An array of resource keys where the specified permission is granted. - /// - Task GetGrantedResourceKeysAsync( - string resourceName, - string permissionName - ); -} \ No newline at end of file + Task IsGrantedAsync(ClaimsPrincipal? claimsPrincipal, [NotNull] string name, string resourceName, string resourceKey); + + Task IsGrantedAsync([NotNull] string[] names, string resourceName, string resourceKey); + + Task IsGrantedAsync(ClaimsPrincipal? claimsPrincipal, [NotNull] string[] names, string resourceName, string resourceKey); +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs new file mode 100644 index 0000000000..94ae94c5db --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public interface IResourcePermissionStore +{ + /// + /// Checks if the given permission is granted for the given resource. + /// + /// The name of the permission. + /// The name of the resource. + /// Resource key + /// + /// True if the permission is granted. + /// + Task IsGrantedAsync( + string name, + string resourceName, + string resourceKey + ); + + Task IsGrantedAsync( + string[] names, + string resourceName, + string resourceKey + ); + + /// + /// Gets all permissions for the given resource. + /// + /// Resource name + /// Resource key + /// + /// A dictionary of permission names and their states. + /// + Task> GetPermissionsAsync( + string resourceName, + string resourceKey + ); + + /// + /// Gets all granted permissions for the given resource. + /// + /// Resource name + /// Resource key + /// + /// An array of granted permission names. + /// + Task GetGrantedPermissionsAsync( + string resourceName, + string resourceKey + ); + + /// + /// Retrieves the keys of resources for which the specified permission is granted. + /// + /// The name of the resource. + /// The name of the permission. + /// + /// An array of resource keys where the specified permission is granted. + /// + Task GetGrantedResourceKeysAsync( + string resourceName, + string name + ); +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProvider.cs new file mode 100644 index 0000000000..655b2cda6e --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProvider.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public interface IResourcePermissionValueProvider +{ + string Name { get; } + + //TODO: Rename to GetResult? (CheckAsync throws exception by naming convention) + Task CheckAsync(ResourcePermissionValueCheckContext context); + + Task CheckAsync(ResourcePermissionValuesCheckContext context); +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProviderManager.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProviderManager.cs new file mode 100644 index 0000000000..e07e63d06a --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProviderManager.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public interface IResourcePermissionValueProviderManager +{ + IReadOnlyList ValueProviders { get; } +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs new file mode 100644 index 0000000000..0b5ce51d2e --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class NullResourcePermissionStore : IResourcePermissionStore, ISingletonDependency +{ + public ILogger Logger { get; set; } + + public NullResourcePermissionStore() + { + Logger = NullLogger.Instance; + } + + public Task IsGrantedAsync(string name, string resourceName, string resourceKey) + { + return TaskCache.FalseResult; + } + + public Task IsGrantedAsync(string[] names, string resourceName, string resourceKey) + { + return Task.FromResult(new MultiplePermissionGrantResult(names, PermissionGrantResult.Prohibited)); + } + + public Task> GetPermissionsAsync(string resourceName, string resourceKey) + { + return Task.FromResult((IDictionary)new Dictionary()); + } + + public Task GetGrantedPermissionsAsync(string resourceName, string resourceKey) + { + return Task.FromResult(Array.Empty()); + } + + public Task GetGrantedResourceKeysAsync(string resourceName, string name) + { + return Task.FromResult(Array.Empty()); + } +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionGrantInfo.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionGrantInfo.cs new file mode 100644 index 0000000000..9977c5eb2a --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionGrantInfo.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class ResourcePermissionGrantInfo : PermissionGrantInfo +{ + public string ResourceName { get; } + + public string ResourceKey { get; } + + public ResourcePermissionGrantInfo([NotNull] string name, bool isGranted, string resourceName, string resourceKey, string? providerName = null, string? providerKey = null) + : base(name, isGranted, providerName, providerKey) + { + ResourceName = resourceName; + ResourceKey = resourceKey; + } +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs similarity index 75% rename from framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs rename to framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs index fa73aa428f..2333737595 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs @@ -3,30 +3,30 @@ using System.Threading.Tasks; namespace Volo.Abp.Authorization.Permissions.Resources; -public static class ResourcePermissionCheckerExtensions +public static class ResourcePermissionStoreExtensions { /// /// Checks if a specific permission is granted for a resource with a given key. /// /// The type of the resource. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The name of the permission to check. /// The resource instance to check permission for. /// The unique key identifying the resource instance. /// A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the permission is granted. public static Task IsGrantedAsync( - this IResourcePermissionChecker resourcePermissionChecker, + this IResourcePermissionStore resourcePermissionStore, string permissionName, TResource resource, object resourceKey ) { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); Check.NotNull(resource, nameof(resource)); Check.NotNull(resourceKey, nameof(resourceKey)); - return resourcePermissionChecker.IsGrantedAsync( + return resourcePermissionStore.IsGrantedAsync( permissionName, typeof(TResource).FullName!, resourceKey.ToString()! @@ -37,21 +37,21 @@ public static class ResourcePermissionCheckerExtensions /// Retrieves a dictionary of permissions and their granted statuses for a specific resource. /// /// The type of the resource. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The resource instance for which permissions are being checked. /// The unique key identifying the resource instance. /// A task that represents the asynchronous operation. The task result contains a dictionary where keys are permission names and values indicate whether the corresponding permission is granted. public static Task> GetPermissionsAsync( - this IResourcePermissionChecker resourcePermissionChecker, + this IResourcePermissionStore resourcePermissionStore, TResource resource, object resourceKey ) { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNull(resource, nameof(resource)); Check.NotNull(resourceKey, nameof(resourceKey)); - return resourcePermissionChecker.GetPermissionsAsync( + return resourcePermissionStore.GetPermissionsAsync( typeof(TResource).FullName!, resourceKey.ToString()! ); @@ -61,21 +61,21 @@ public static class ResourcePermissionCheckerExtensions /// Retrieves the list of granted permissions for a specific resource with a given key. /// /// The type of the resource. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The resource instance to retrieve permissions for. /// The unique key identifying the resource instance. /// A task that represents the asynchronous operation. The task result contains an array of strings representing the granted permissions. public static Task GetGrantedPermissionsAsync( - this IResourcePermissionChecker resourcePermissionChecker, + this IResourcePermissionStore resourcePermissionStore, TResource resource, object resourceKey ) { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNull(resource, nameof(resource)); Check.NotNull(resourceKey, nameof(resourceKey)); - return resourcePermissionChecker.GetGrantedPermissionsAsync( + return resourcePermissionStore.GetGrantedPermissionsAsync( typeof(TResource).FullName!, resourceKey.ToString()! ); @@ -85,23 +85,23 @@ public static class ResourcePermissionCheckerExtensions /// Retrieves the keys of the resources granted a specific permission. /// /// The type of the resource. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The resource instance to check granted permissions for. /// The name of the permission to check. /// A task that represents the asynchronous operation. The task result contains an array of strings representing the granted resource keys. public static Task GetGrantedResourceKeysAsync( - this IResourcePermissionChecker resourcePermissionChecker, + this IResourcePermissionStore resourcePermissionStore, TResource resource, string permissionName ) { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNull(resource, nameof(resource)); Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); - return resourcePermissionChecker.GetGrantedResourceKeysAsync( + return resourcePermissionStore.GetGrantedResourceKeysAsync( typeof(TResource).FullName!, permissionName ); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueCheckContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueCheckContext.cs new file mode 100644 index 0000000000..54e7f80e35 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueCheckContext.cs @@ -0,0 +1,25 @@ +using System.Security.Claims; +using JetBrains.Annotations; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class ResourcePermissionValueCheckContext : PermissionValueCheckContext +{ + [NotNull] + public string ResourceName { get; } + + [NotNull] + public string ResourceKey { get; } + + public ResourcePermissionValueCheckContext([NotNull] PermissionDefinition permission, string resourceName, string resourceKey) + : this(permission, null, resourceName, resourceKey) + { + } + + public ResourcePermissionValueCheckContext([NotNull] PermissionDefinition permission, ClaimsPrincipal? principal, string resourceName, string resourceKey) + : base(permission, principal) + { + ResourceName = resourceName; + ResourceKey = resourceKey; + } +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProvider.cs new file mode 100644 index 0000000000..9275502696 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProvider.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public abstract class ResourcePermissionValueProvider : IResourcePermissionValueProvider, ITransientDependency +{ + public abstract string Name { get; } + + protected IResourcePermissionStore PermissionStore { get; } + + protected ResourcePermissionValueProvider(IResourcePermissionStore permissionStore) + { + PermissionStore = permissionStore; + } + + public abstract Task CheckAsync(ResourcePermissionValueCheckContext context); + + public abstract Task CheckAsync(ResourcePermissionValuesCheckContext context); +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValuesCheckContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValuesCheckContext.cs new file mode 100644 index 0000000000..7de44b1a0d --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValuesCheckContext.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Security.Claims; +using JetBrains.Annotations; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class ResourcePermissionValuesCheckContext : PermissionValuesCheckContext +{ + [NotNull] + public string ResourceName { get; } + + [NotNull] + public string ResourceKey { get; } + + public ResourcePermissionValuesCheckContext([NotNull] PermissionDefinition permission,string resourceName, string resourceKey) + : this([permission], null, resourceName, resourceKey) + { + + } + + + public ResourcePermissionValuesCheckContext([NotNull] PermissionDefinition permission, ClaimsPrincipal? principal, string resourceName, string resourceKey) + : this([permission], principal, resourceName, resourceKey) + { + + } + + public ResourcePermissionValuesCheckContext([NotNull] List permissions, string resourceName, string resourceKey) + : this(permissions, null, resourceName, resourceKey) + { + ResourceName = resourceName; + ResourceKey = resourceKey; + } + + public ResourcePermissionValuesCheckContext([NotNull] List permissions, ClaimsPrincipal? principal, string resourceName, string resourceKey) + : base(permissions, principal) + { + ResourceName = resourceName; + ResourceKey = resourceKey; + } +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs new file mode 100644 index 0000000000..09b2e7c3bc --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Security.Claims; +using Volo.Abp.SimpleStateChecking; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientDependency +{ + protected IPermissionDefinitionManager PermissionDefinitionManager { get; } + protected ICurrentPrincipalAccessor PrincipalAccessor { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IResourcePermissionValueProviderManager PermissionValueProviderManager { get; } + protected ISimpleStateCheckerManager StateCheckerManager { get; } + + public ResourcePermissionChecker( + ICurrentPrincipalAccessor principalAccessor, + IPermissionDefinitionManager permissionDefinitionManager, + ICurrentTenant currentTenant, + IResourcePermissionValueProviderManager permissionValueProviderManager, + ISimpleStateCheckerManager stateCheckerManager) + { + PrincipalAccessor = principalAccessor; + PermissionDefinitionManager = permissionDefinitionManager; + CurrentTenant = currentTenant; + PermissionValueProviderManager = permissionValueProviderManager; + StateCheckerManager = stateCheckerManager; + } + + public virtual async Task IsGrantedAsync(string name, string resourceName, string resourceKey) + { + return await IsGrantedAsync(PrincipalAccessor.Principal, name, resourceName, resourceKey); + } + + public virtual async Task IsGrantedAsync( + ClaimsPrincipal? claimsPrincipal, + string name, + string resourceName, + string resourceKey) + { + Check.NotNull(name, nameof(name)); + + var permission = await PermissionDefinitionManager.GetOrNullAsync(name); + if (permission == null) + { + return false; + } + + if (!permission.IsEnabled) + { + return false; + } + + if (!await StateCheckerManager.IsEnabledAsync(permission)) + { + return false; + } + + var multiTenancySide = claimsPrincipal?.GetMultiTenancySide() + ?? CurrentTenant.GetMultiTenancySide(); + + if (!permission.MultiTenancySide.HasFlag(multiTenancySide)) + { + return false; + } + + var isGranted = false; + var context = new ResourcePermissionValueCheckContext(permission, claimsPrincipal, resourceName, resourceKey); + foreach (var provider in PermissionValueProviderManager.ValueProviders) + { + if (context.Permission.Providers.Any() && + !context.Permission.Providers.Contains(provider.Name)) + { + continue; + } + + var result = await provider.CheckAsync(context); + + if (result == PermissionGrantResult.Granted) + { + isGranted = true; + } + else if (result == PermissionGrantResult.Prohibited) + { + return false; + } + } + + return isGranted; + } + + public async Task IsGrantedAsync(string[] names, string resourceName, string resourceKey) + { + return await IsGrantedAsync(PrincipalAccessor.Principal, names, resourceName,resourceKey); + } + + public async Task IsGrantedAsync(ClaimsPrincipal? claimsPrincipal, string[] names, string resourceName, string resourceKey) + { + Check.NotNull(names, nameof(names)); + + var result = new MultiplePermissionGrantResult(); + if (!names.Any()) + { + return result; + } + + var multiTenancySide = claimsPrincipal?.GetMultiTenancySide() ?? + CurrentTenant.GetMultiTenancySide(); + + var permissionDefinitions = new List(); + foreach (var name in names) + { + var permission = await PermissionDefinitionManager.GetOrNullAsync(name); + if (permission == null) + { + result.Result.Add(name, PermissionGrantResult.Prohibited); + continue; + } + + result.Result.Add(name, PermissionGrantResult.Undefined); + + if (permission.IsEnabled && + await StateCheckerManager.IsEnabledAsync(permission) && + permission.MultiTenancySide.HasFlag(multiTenancySide)) + { + permissionDefinitions.Add(permission); + } + } + + foreach (var provider in PermissionValueProviderManager.ValueProviders) + { + var permissions = permissionDefinitions + .Where(x => !x.Providers.Any() || x.Providers.Contains(provider.Name)) + .ToList(); + + if (permissions.IsNullOrEmpty()) + { + continue; + } + + var context = new ResourcePermissionValuesCheckContext( + permissions, + claimsPrincipal, + resourceName, + resourceKey); + + var multipleResult = await provider.CheckAsync(context); + foreach (var grantResult in multipleResult.Result.Where(grantResult => + result.Result.ContainsKey(grantResult.Key) && + result.Result[grantResult.Key] == PermissionGrantResult.Undefined && + grantResult.Value != PermissionGrantResult.Undefined)) + { + result.Result[grantResult.Key] = grantResult.Value; + permissionDefinitions.RemoveAll(x => x.Name == grantResult.Key); + } + + if (result.AllGranted || result.AllProhibited) + { + break; + } + } + + return result; + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs similarity index 70% rename from framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs rename to framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs index 6a3ff5e6b1..67be57a747 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs @@ -6,28 +6,28 @@ using Volo.Abp.Domain.Entities; namespace Volo.Abp.Authorization.Permissions.Resources; -public static class EntityResourcePermissionCheckerExtensions +public static class EntityResourcePermissionStoreExtensions { /// /// Checks if the specified permission is granted for the given entity. /// /// The type of the entity. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The name of the permission to check. /// The entity for which the permission is being checked. /// A task that represents the asynchronous operation. The task result is a boolean indicating whether the permission is granted. public static Task IsGrantedAsync( - this IResourcePermissionChecker resourcePermissionChecker, + this IResourcePermissionStore resourcePermissionStore, string permissionName, TEntity entity ) where TEntity : class, IEntity { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); Check.NotNull(entity, nameof(entity)); - return resourcePermissionChecker.IsGrantedAsync( + return resourcePermissionStore.IsGrantedAsync( permissionName, typeof(TEntity).FullName!, entity.GetKeys().JoinAsString(",") @@ -38,19 +38,19 @@ public static class EntityResourcePermissionCheckerExtensions /// Retrieves a dictionary of permissions and their granted status for the specified entity. /// /// The type of the entity. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The entity for which the permissions are being retrieved. /// A dictionary where the keys are permission names and the values are booleans indicating whether the permission is granted. public static Task> GetPermissionsAsync( - this IResourcePermissionChecker resourcePermissionChecker, + this IResourcePermissionStore resourcePermissionStore, TEntity entity ) where TEntity : class, IEntity { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNull(entity, nameof(entity)); - return resourcePermissionChecker.GetPermissionsAsync( + return resourcePermissionStore.GetPermissionsAsync( typeof(TEntity).FullName!, entity.GetKeys().JoinAsString(",") ); @@ -60,19 +60,19 @@ public static class EntityResourcePermissionCheckerExtensions /// Retrieves an array of granted permissions for a specific entity. /// /// The type of the entity. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The entity for which the permissions are being checked. /// An array of granted permission names as strings. public static Task GetGrantedPermissionsAsync( - this IResourcePermissionChecker resourcePermissionChecker, + this IResourcePermissionStore resourcePermissionStore, TEntity entity ) where TEntity : class, IEntity { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNull(entity, nameof(entity)); - return resourcePermissionChecker.GetGrantedPermissionsAsync( + return resourcePermissionStore.GetGrantedPermissionsAsync( typeof(TEntity).FullName!, entity.GetKeys().JoinAsString(",") ); @@ -83,26 +83,26 @@ public static class EntityResourcePermissionCheckerExtensions /// /// The type of the entity. /// The type of the entity's primary key. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The name of the permission to check. /// An array of entity IDs (of type ) for which the permission is granted. - public async static Task GetGrantedEntityIdsAsync( - this IResourcePermissionChecker resourcePermissionChecker, + public static async Task GetGrantedEntityIdsAsync( + this IResourcePermissionStore resourcePermissionStore, string permissionName ) where TEntity : class, IEntity { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); - var keys = await resourcePermissionChecker.GetGrantedResourceKeysAsync( + var keys = await resourcePermissionStore.GetGrantedResourceKeysAsync( typeof(TEntity).FullName!, permissionName ); - + return keys .Select(x => Convert.ChangeType(x, typeof(TKey))) .Cast() .ToArray(); } -} \ No newline at end of file +} From 4c35fc97592d25468618bd1ba54da7c216434ff8 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 11 Nov 2025 16:13:12 +0800 Subject: [PATCH 008/130] Add resource-based permission to PermissionDefinition system. --- .../Permissions/ICanAddChildPermission.cs | 8 ++- .../Permissions/PermissionDefinition.cs | 39 ++++++++++++-- .../Permissions/PermissionGroupDefinition.cs | 30 +++++++++++ .../Permissions/PermissionType.cs | 14 +++++ .../PermissionGrantConsts.cs | 10 ++++ .../PermissionDefinitionRecord.cs | 5 ++ .../PermissionDefinitionSerializer.cs | 21 ++++---- .../ResourcePermissionGrant.cs | 52 +++++++++++++++++++ ...nagementDbContextModelBuilderExtensions.cs | 17 ++++++ .../IPermissionManagementDbContext.cs | 6 ++- .../PermissionManagementDbContext.cs | 1 + ...ssionManagementMongoDbContextExtensions.cs | 9 +++- .../IPermissionManagementMongoDbContext.cs | 6 ++- .../PermissionManagementMongoDbContext.cs | 1 + .../CalculateHash_Tests.cs | 3 +- 15 files changed, 200 insertions(+), 22 deletions(-) create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionType.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/ICanAddChildPermission.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/ICanAddChildPermission.cs index 828e4cbd01..97d214d8b4 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/ICanAddChildPermission.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/ICanAddChildPermission.cs @@ -11,4 +11,10 @@ public interface ICanAddChildPermission ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true); -} \ No newline at end of file + + PermissionDefinition AddResourcePermission( + [NotNull] string name, + ILocalizableString? displayName = null, + MultiTenancySides multiTenancySide = MultiTenancySides.Both, + bool isEnabled = true); +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index 0f5d713e2b..a6c48381d9 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -7,7 +7,7 @@ using Volo.Abp.SimpleStateChecking; namespace Volo.Abp.Authorization.Permissions; -public class PermissionDefinition : +public class PermissionDefinition : IHasSimpleStateCheckers, ICanAddChildPermission { @@ -16,6 +16,11 @@ public class PermissionDefinition : /// public string Name { get; } + /// + /// Type of the permission. + /// + public PermissionType Type { get; set; } + /// /// Parent of this permission if one exists. /// If set, this permission can be granted only if parent is granted. @@ -78,11 +83,13 @@ public class PermissionDefinition : protected internal PermissionDefinition( [NotNull] string name, + PermissionType type = PermissionType.UserBased, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { Name = Check.NotNull(name, nameof(name)); + Type = type; DisplayName = displayName ?? new FixedLocalizableString(name); MultiTenancySide = multiTenancySide; IsEnabled = isEnabled; @@ -98,9 +105,25 @@ public class PermissionDefinition : ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) + { + return AddChild( + name, + PermissionType.UserBased, + displayName, + multiTenancySide, + isEnabled); + } + + public virtual PermissionDefinition AddChild( + [NotNull] string name, + PermissionType type, + ILocalizableString? displayName = null, + MultiTenancySides multiTenancySide = MultiTenancySides.Both, + bool isEnabled = true) { var child = new PermissionDefinition( name, + type, displayName, multiTenancySide, isEnabled) @@ -109,21 +132,29 @@ public class PermissionDefinition : }; child[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName] = this[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName]; - + _children.Add(child); return child; } - + PermissionDefinition ICanAddChildPermission.AddPermission( string name, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { - return this.AddChild(name, displayName, multiTenancySide, isEnabled); + return this.AddChild(name, PermissionType.ResourceBased, displayName, multiTenancySide, isEnabled); } + PermissionDefinition ICanAddChildPermission.AddResourcePermission( + string name, + ILocalizableString? displayName = null, + MultiTenancySides multiTenancySide = MultiTenancySides.Both, + bool isEnabled = true) + { + return this.AddChild(name, PermissionType.ResourceBased, displayName, multiTenancySide, isEnabled); + } /// /// Sets a property in the dictionary. diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs index 0ad2dca099..9d7b295aba 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs @@ -53,9 +53,39 @@ public class PermissionGroupDefinition : ICanAddChildPermission ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) + { + return AddPermission( + name, + PermissionType.UserBased, + displayName, + multiTenancySide, + isEnabled); + } + + public virtual PermissionDefinition AddResourcePermission( + [NotNull] string name, + ILocalizableString? displayName = null, + MultiTenancySides multiTenancySide = MultiTenancySides.Both, + bool isEnabled = true) + { + return AddPermission( + name, + PermissionType.ResourceBased, + displayName, + multiTenancySide, + isEnabled); + } + + protected virtual PermissionDefinition AddPermission( + [NotNull] string name, + PermissionType type, + ILocalizableString? displayName = null, + MultiTenancySides multiTenancySide = MultiTenancySides.Both, + bool isEnabled = true) { var permission = new PermissionDefinition( name, + type, displayName, multiTenancySide, isEnabled diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionType.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionType.cs new file mode 100644 index 0000000000..c572489001 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionType.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.Authorization.Permissions; + +public enum PermissionType +{ + /// + /// Based on user(roles/claims). + /// + UserBased = 0, + + /// + /// Based on resource(entities). + /// + ResourceBased = 1 +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionGrantConsts.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionGrantConsts.cs index 630f5dc72e..d1008bcbdb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionGrantConsts.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionGrantConsts.cs @@ -11,4 +11,14 @@ public static class PermissionGrantConsts /// Default value: 64 /// public static int MaxProviderKeyLength { get; set; } = 64; + + /// + /// Default value: 256 + /// + public static int MaxResourceNameLength { get; set; } = 256; + + /// + /// Default value: 256 + /// + public static int MaxResourceKeyLength { get; set; } = 256; } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs index 7473eb2589..6b1b865045 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs @@ -1,4 +1,5 @@ using System; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Data; using Volo.Abp.Domain.Entities; using Volo.Abp.MultiTenancy; @@ -11,6 +12,8 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro public string Name { get; set; } + public PermissionType Type { get; set; } + public string ParentName { get; set; } public string DisplayName { get; set; } @@ -41,6 +44,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro Guid id, string groupName, string name, + PermissionType type, string parentName, string displayName, bool isEnabled = true, @@ -51,6 +55,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro { GroupName = Check.NotNullOrWhiteSpace(groupName, nameof(groupName), PermissionGroupDefinitionRecordConsts.MaxNameLength); Name = Check.NotNullOrWhiteSpace(name, nameof(name), PermissionDefinitionRecordConsts.MaxNameLength); + Type = type; ParentName = Check.Length(parentName, nameof(parentName), PermissionDefinitionRecordConsts.MaxNameLength); DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), PermissionDefinitionRecordConsts.MaxDisplayNameLength); IsEnabled = isEnabled; 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 8e63342502..8e8c4afde4 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 @@ -19,7 +19,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I public PermissionDefinitionSerializer( IGuidGenerator guidGenerator, - ISimpleStateCheckerSerializer stateCheckerSerializer, + ISimpleStateCheckerSerializer stateCheckerSerializer, ILocalizableStringSerializer localizableStringSerializer) { StateCheckerSerializer = stateCheckerSerializer; @@ -27,16 +27,16 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I GuidGenerator = guidGenerator; } - public async Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> + public async Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> SerializeAsync(IEnumerable permissionGroups) { var permissionGroupRecords = new List(); var permissionRecords = new List(); - + foreach (var permissionGroup in permissionGroups) { permissionGroupRecords.Add(await SerializeAsync(permissionGroup)); - + foreach (var permission in permissionGroup.GetPermissionsWithChildren()) { permissionRecords.Add(await SerializeAsync(permission, permissionGroup)); @@ -45,7 +45,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I return (permissionGroupRecords.ToArray(), permissionRecords.ToArray()); } - + public Task SerializeAsync(PermissionGroupDefinition permissionGroup) { using (CultureHelper.Use(CultureInfo.InvariantCulture)) @@ -60,11 +60,11 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I { permissionGroupRecord.SetProperty(property.Key, property.Value); } - + return Task.FromResult(permissionGroupRecord); } } - + public Task SerializeAsync( PermissionDefinition permission, PermissionGroupDefinition permissionGroup) @@ -75,6 +75,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I GuidGenerator.Create(), permissionGroup?.Name, permission.Name, + permission.Type, permission.Parent?.Name, LocalizableStringSerializer.Serialize(permission.DisplayName), permission.IsEnabled, @@ -87,11 +88,11 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I { permissionRecord.SetProperty(property.Key, property.Value); } - + return Task.FromResult(permissionRecord); } } - + protected virtual string SerializeProviders(ICollection providers) { return providers.Any() @@ -103,4 +104,4 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I { return StateCheckerSerializer.Serialize(stateCheckers); } -} \ No newline at end of file +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs new file mode 100644 index 0000000000..9a68ef4481 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs @@ -0,0 +1,52 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.PermissionManagement; + +//TODO: To aggregate root? +public class ResourcePermissionGrant : Entity, IMultiTenant +{ + public virtual Guid? TenantId { get; protected set; } + + [NotNull] + public virtual string Name { get; protected set; } + + [NotNull] + public virtual string ProviderName { get; protected set; } + + [CanBeNull] + public virtual string ProviderKey { get; protected internal set; } + + [NotNull] + public virtual string ResourceName { get; protected set; } + + [NotNull] + public virtual string ResourceKey { get; protected set; } + + protected ResourcePermissionGrant() + { + + } + + public ResourcePermissionGrant( + Guid id, + [NotNull] string name, + [NotNull] string providerName, + [CanBeNull] string providerKey, + [NotNull] string resourceName, + [NotNull] string resourceKey, + Guid? tenantId = null) + { + Check.NotNull(name, nameof(name)); + + Id = id; + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + ProviderName = Check.NotNullOrWhiteSpace(providerName, nameof(providerName)); + ProviderKey = providerKey; + ResourceName = Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName)); + ResourceKey = Check.NotNullOrWhiteSpace(resourceKey, nameof(resourceKey)); + TenantId = tenantId; + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs index fa6862abf6..5b36cf1dc3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs @@ -27,6 +27,23 @@ public static class AbpPermissionManagementDbContextModelBuilderExtensions b.ApplyObjectExtensionMappings(); }); + builder.Entity(b => + { + b.ToTable(AbpPermissionManagementDbProperties.DbTablePrefix + "ResourcePermissionGrants", AbpPermissionManagementDbProperties.DbSchema); + + b.ConfigureByConvention(); + + b.Property(x => x.Name).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength).IsRequired(); + b.Property(x => x.ProviderName).HasMaxLength(PermissionGrantConsts.MaxProviderNameLength).IsRequired(); + b.Property(x => x.ProviderKey).HasMaxLength(PermissionGrantConsts.MaxProviderKeyLength).IsRequired(); + b.Property(x => x.ResourceName).HasMaxLength(PermissionGrantConsts.MaxResourceNameLength).IsRequired(); + b.Property(x => x.ResourceKey).HasMaxLength(PermissionGrantConsts.MaxResourceKeyLength).IsRequired(); + + b.HasIndex(x => new { x.TenantId, x.Name, x.ProviderName, x.ProviderKey, x.ResourceName, x.ResourceKey }).IsUnique(); + + b.ApplyObjectExtensionMappings(); + }); + if (builder.IsHostDatabase()) { builder.Entity(b => diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/IPermissionManagementDbContext.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/IPermissionManagementDbContext.cs index c294c1b248..de02ab70ec 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/IPermissionManagementDbContext.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/IPermissionManagementDbContext.cs @@ -8,8 +8,10 @@ namespace Volo.Abp.PermissionManagement.EntityFrameworkCore; public interface IPermissionManagementDbContext : IEfCoreDbContext { DbSet PermissionGroups { get; } - + DbSet Permissions { get; } - + DbSet PermissionGrants { get; } + + DbSet ResourcePermissionGrants { get; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/PermissionManagementDbContext.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/PermissionManagementDbContext.cs index fe143fa8ae..59c724bfb7 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/PermissionManagementDbContext.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/PermissionManagementDbContext.cs @@ -10,6 +10,7 @@ public class PermissionManagementDbContext : AbpDbContext PermissionGroups { get; set; } public DbSet Permissions { get; set; } public DbSet PermissionGrants { get; set; } + public DbSet ResourcePermissionGrants { get; set; } public PermissionManagementDbContext(DbContextOptions options) : base(options) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbContextExtensions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbContextExtensions.cs index 9c7c90af58..4eb8eea3b6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbContextExtensions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbContextExtensions.cs @@ -13,15 +13,20 @@ public static class AbpPermissionManagementMongoDbContextExtensions { b.CollectionName = AbpPermissionManagementDbProperties.DbTablePrefix + "PermissionGroups"; }); - + builder.Entity(b => { b.CollectionName = AbpPermissionManagementDbProperties.DbTablePrefix + "Permissions"; }); - + builder.Entity(b => { b.CollectionName = AbpPermissionManagementDbProperties.DbTablePrefix + "PermissionGrants"; }); + + builder.Entity(b => + { + b.CollectionName = AbpPermissionManagementDbProperties.DbTablePrefix + "ResourcePermissionGrants"; + }); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/IPermissionManagementMongoDbContext.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/IPermissionManagementMongoDbContext.cs index 98015484fb..454875f517 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/IPermissionManagementMongoDbContext.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/IPermissionManagementMongoDbContext.cs @@ -8,8 +8,10 @@ namespace Volo.Abp.PermissionManagement.MongoDB; public interface IPermissionManagementMongoDbContext : IAbpMongoDbContext { IMongoCollection PermissionGroups { get; } - + IMongoCollection Permissions { get; } - + IMongoCollection PermissionGrants { get; } + + IMongoCollection ResourcePermissionGrants { get; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/PermissionManagementMongoDbContext.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/PermissionManagementMongoDbContext.cs index c62db52c11..c9ea6ea984 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/PermissionManagementMongoDbContext.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/PermissionManagementMongoDbContext.cs @@ -10,6 +10,7 @@ public class PermissionManagementMongoDbContext : AbpMongoDbContext, IPermission public IMongoCollection PermissionGroups => Collection(); public IMongoCollection Permissions => Collection(); public IMongoCollection PermissionGrants => Collection(); + public IMongoCollection ResourcePermissionGrants => Collection(); protected override void CreateModel(IMongoModelBuilder modelBuilder) { diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs index 2e862c8f85..783ba1f10a 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization.Metadata; using Shouldly; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Json.SystemTextJson.Modifiers; using Xunit; @@ -34,7 +35,7 @@ public class CalculateHash_Tests: PermissionTestBase json.ShouldNotContain(id.ToString("D")); json = JsonSerializer.Serialize(new List() { - new PermissionDefinitionRecord(id, "Test", "Test", "Test", "Test") + new PermissionDefinitionRecord(id, "Test", "Test", PermissionType.UserBased, "Test", "Test") }, jsonSerializerOptions); json.ShouldNotContain("\"Id\""); From fccc3f20f6b19d7bdeaaff55f0bd4de277ba4151 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 11 Nov 2025 19:53:48 +0800 Subject: [PATCH 009/130] Add resource-based permission value providers --- .../ResourcePermissionValueProvider.cs | 6 +- .../Authorization/AbpAuthorizationModule.cs | 4 + .../ResourcePermissionValueProviderManager.cs | 43 ++++++++++ .../RoleResourcePermissionValueProvider.cs | 79 +++++++++++++++++++ .../UserResourcePermissionValueProvider.cs | 46 +++++++++++ 5 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProviderManager.cs create mode 100644 framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/RoleResourcePermissionValueProvider.cs create mode 100644 framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/UserResourcePermissionValueProvider.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProvider.cs index 9275502696..ff53f274ad 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProvider.cs @@ -7,11 +7,11 @@ public abstract class ResourcePermissionValueProvider : IResourcePermissionValue { public abstract string Name { get; } - protected IResourcePermissionStore PermissionStore { get; } + protected IResourcePermissionStore ResourcePermissionStore { get; } - protected ResourcePermissionValueProvider(IResourcePermissionStore permissionStore) + protected ResourcePermissionValueProvider(IResourcePermissionStore resourcePermissionStore) { - PermissionStore = permissionStore; + ResourcePermissionStore = resourcePermissionStore; } public abstract Task CheckAsync(ResourcePermissionValueCheckContext context); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs index d8226bdee5..ec2a2ddd82 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Authorization.Localization; using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; @@ -42,6 +43,9 @@ public class AbpAuthorizationModule : AbpModule options.ValueProviders.Add(); options.ValueProviders.Add(); options.ValueProviders.Add(); + + options.ResourceValueProviders.Add(); + options.ResourceValueProviders.Add(); }); Configure(options => diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProviderManager.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProviderManager.cs new file mode 100644 index 0000000000..628893397e --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProviderManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class ResourcePermissionValueProviderManager : IResourcePermissionValueProviderManager, ISingletonDependency +{ + public IReadOnlyList ValueProviders => _lazyProviders.Value; + private readonly Lazy> _lazyProviders; + + protected AbpPermissionOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + + public ResourcePermissionValueProviderManager( + IServiceProvider serviceProvider, + IOptions options) + { + Options = options.Value; + ServiceProvider = serviceProvider; + + _lazyProviders = new Lazy>(GetProviders, true); + } + + protected virtual List GetProviders() + { + var providers = Options + .ValueProviders + .Select(type => (ServiceProvider.GetRequiredService(type) as IResourcePermissionValueProvider)!) + .ToList(); + + var multipleProviders = providers.GroupBy(p => p.Name).FirstOrDefault(x => x.Count() > 1); + if(multipleProviders != null) + { + throw new AbpException($"Duplicate permission value provider name detected: {multipleProviders.Key}. Providers:{Environment.NewLine}{multipleProviders.Select(p => p.GetType().FullName!).JoinAsString(Environment.NewLine)}"); + } + + return providers; + } +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/RoleResourcePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/RoleResourcePermissionValueProvider.cs new file mode 100644 index 0000000000..8a32da5f2c --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/RoleResourcePermissionValueProvider.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class RoleResourcePermissionValueProvider : ResourcePermissionValueProvider +{ + public const string ProviderName = "R"; + + public override string Name => ProviderName; + + public RoleResourcePermissionValueProvider(IResourcePermissionStore resourcePermissionStore) + : base(resourcePermissionStore) + { + + } + + public override async Task CheckAsync(ResourcePermissionValueCheckContext context) + { + var roles = context.Principal?.FindAll(AbpClaimTypes.Role).Select(c => c.Value).ToArray(); + + if (roles == null || !roles.Any()) + { + return PermissionGrantResult.Undefined; + } + + foreach (var role in roles.Distinct()) + { + if (await ResourcePermissionStore.IsGrantedAsync(context.Permission.Name, Name, role)) + { + return PermissionGrantResult.Granted; + } + } + + return PermissionGrantResult.Undefined; + } + + public override async Task CheckAsync(ResourcePermissionValuesCheckContext context) + { + var permissionNames = context.Permissions.Select(x => x.Name).Distinct().ToList(); + Check.NotNullOrEmpty(permissionNames, nameof(permissionNames)); + + var result = new MultiplePermissionGrantResult(permissionNames.ToArray()); + + var roles = context.Principal?.FindAll(AbpClaimTypes.Role).Select(c => c.Value).ToArray(); + if (roles == null || !roles.Any()) + { + return result; + } + + foreach (var role in roles.Distinct()) + { + var multipleResult = await ResourcePermissionStore.IsGrantedAsync(permissionNames.ToArray(), Name, role); + + foreach (var grantResult in multipleResult.Result.Where(grantResult => + result.Result.ContainsKey(grantResult.Key) && + result.Result[grantResult.Key] == PermissionGrantResult.Undefined && + grantResult.Value != PermissionGrantResult.Undefined)) + { + result.Result[grantResult.Key] = grantResult.Value; + permissionNames.RemoveAll(x => x == grantResult.Key); + } + + if (result.AllGranted || result.AllProhibited) + { + break; + } + + if (permissionNames.IsNullOrEmpty()) + { + break; + } + } + + return result; + } +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/UserResourcePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/UserResourcePermissionValueProvider.cs new file mode 100644 index 0000000000..0597616709 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/UserResourcePermissionValueProvider.cs @@ -0,0 +1,46 @@ +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class UserResourcePermissionValueProvider : ResourcePermissionValueProvider +{ + public const string ProviderName = "U"; + + public override string Name => ProviderName; + + public UserResourcePermissionValueProvider(IResourcePermissionStore resourcePermissionStore) + : base(resourcePermissionStore) + { + + } + + public override async Task CheckAsync(ResourcePermissionValueCheckContext context) + { + var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value; + + if (userId == null) + { + return PermissionGrantResult.Undefined; + } + + return await ResourcePermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId) + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined; + } + + public override async Task CheckAsync(ResourcePermissionValuesCheckContext context) + { + var permissionNames = context.Permissions.Select(x => x.Name).Distinct().ToArray(); + Check.NotNullOrEmpty(permissionNames, nameof(permissionNames)); + + var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value; + if (userId == null) + { + return new MultiplePermissionGrantResult(permissionNames); + } + + return await ResourcePermissionStore.IsGrantedAsync(permissionNames, Name, userId); + } +} From 22cfc5b5d135831bd88e5e0bd82279eb0b65f0ad Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 11 Nov 2025 20:07:07 +0800 Subject: [PATCH 010/130] Refactor permission type to resource name --- .../Permissions/ICanAddChildPermission.cs | 6 ---- .../Permissions/PermissionDefinition.cs | 36 ++++--------------- .../Permissions/PermissionGroupDefinition.cs | 9 ++--- .../Permissions/PermissionType.cs | 14 -------- .../PermissionDefinitionRecordConsts.cs | 8 +++-- .../PermissionDefinitionRecord.cs | 6 ++-- .../PermissionDefinitionSerializer.cs | 2 +- ...nagementDbContextModelBuilderExtensions.cs | 1 + .../CalculateHash_Tests.cs | 2 +- 9 files changed, 22 insertions(+), 62 deletions(-) delete mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionType.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/ICanAddChildPermission.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/ICanAddChildPermission.cs index 97d214d8b4..2bcf9ba10d 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/ICanAddChildPermission.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/ICanAddChildPermission.cs @@ -11,10 +11,4 @@ public interface ICanAddChildPermission ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true); - - PermissionDefinition AddResourcePermission( - [NotNull] string name, - ILocalizableString? displayName = null, - MultiTenancySides multiTenancySide = MultiTenancySides.Both, - bool isEnabled = true); } diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index a6c48381d9..9f527487e0 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -17,9 +17,9 @@ public class PermissionDefinition : public string Name { get; } /// - /// Type of the permission. + /// Resource name of the permission. /// - public PermissionType Type { get; set; } + public string? ResourceName { get; set; } /// /// Parent of this permission if one exists. @@ -83,13 +83,13 @@ public class PermissionDefinition : protected internal PermissionDefinition( [NotNull] string name, - PermissionType type = PermissionType.UserBased, + string? resourceName = null, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { Name = Check.NotNull(name, nameof(name)); - Type = type; + ResourceName = resourceName; DisplayName = displayName ?? new FixedLocalizableString(name); MultiTenancySide = multiTenancySide; IsEnabled = isEnabled; @@ -105,25 +105,10 @@ public class PermissionDefinition : ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) - { - return AddChild( - name, - PermissionType.UserBased, - displayName, - multiTenancySide, - isEnabled); - } - - public virtual PermissionDefinition AddChild( - [NotNull] string name, - PermissionType type, - ILocalizableString? displayName = null, - MultiTenancySides multiTenancySide = MultiTenancySides.Both, - bool isEnabled = true) { var child = new PermissionDefinition( name, - type, + null, displayName, multiTenancySide, isEnabled) @@ -144,16 +129,7 @@ public class PermissionDefinition : MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { - return this.AddChild(name, PermissionType.ResourceBased, displayName, multiTenancySide, isEnabled); - } - - PermissionDefinition ICanAddChildPermission.AddResourcePermission( - string name, - ILocalizableString? displayName = null, - MultiTenancySides multiTenancySide = MultiTenancySides.Both, - bool isEnabled = true) - { - return this.AddChild(name, PermissionType.ResourceBased, displayName, multiTenancySide, isEnabled); + return this.AddChild(name, displayName, multiTenancySide, isEnabled); } /// diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs index 9d7b295aba..a064245488 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs @@ -56,7 +56,7 @@ public class PermissionGroupDefinition : ICanAddChildPermission { return AddPermission( name, - PermissionType.UserBased, + null, displayName, multiTenancySide, isEnabled); @@ -64,13 +64,14 @@ public class PermissionGroupDefinition : ICanAddChildPermission public virtual PermissionDefinition AddResourcePermission( [NotNull] string name, + [NotNull] string resourceName, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { return AddPermission( name, - PermissionType.ResourceBased, + resourceName, displayName, multiTenancySide, isEnabled); @@ -78,14 +79,14 @@ public class PermissionGroupDefinition : ICanAddChildPermission protected virtual PermissionDefinition AddPermission( [NotNull] string name, - PermissionType type, + string? resourceName, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { var permission = new PermissionDefinition( name, - type, + resourceName, displayName, multiTenancySide, isEnabled diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionType.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionType.cs deleted file mode 100644 index c572489001..0000000000 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionType.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Volo.Abp.Authorization.Permissions; - -public enum PermissionType -{ - /// - /// Based on user(roles/claims). - /// - UserBased = 0, - - /// - /// Based on resource(entities). - /// - ResourceBased = 1 -} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs index 93b1b465c6..7d449a2f3b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs @@ -6,10 +6,12 @@ public class PermissionDefinitionRecordConsts /// Default value: 128 /// public static int MaxNameLength { get; set; } = 128; - + public static int MaxDisplayNameLength { get; set; } = 256; public static int MaxProvidersLength { get; set; } = 128; - + public static int MaxStateCheckersLength { get; set; } = 256; -} \ No newline at end of file + + public static int MaxResourceNameLength { get; set; } = 256; +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs index 6b1b865045..a513beafe9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs @@ -12,7 +12,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro public string Name { get; set; } - public PermissionType Type { get; set; } + public string ResourceName { get; set; } public string ParentName { get; set; } @@ -44,7 +44,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro Guid id, string groupName, string name, - PermissionType type, + string resourceName, string parentName, string displayName, bool isEnabled = true, @@ -55,7 +55,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro { GroupName = Check.NotNullOrWhiteSpace(groupName, nameof(groupName), PermissionGroupDefinitionRecordConsts.MaxNameLength); Name = Check.NotNullOrWhiteSpace(name, nameof(name), PermissionDefinitionRecordConsts.MaxNameLength); - Type = type; + ResourceName = resourceName; ParentName = Check.Length(parentName, nameof(parentName), PermissionDefinitionRecordConsts.MaxNameLength); DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), PermissionDefinitionRecordConsts.MaxDisplayNameLength); IsEnabled = isEnabled; 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 8e8c4afde4..5d07e81a94 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 @@ -75,7 +75,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I GuidGenerator.Create(), permissionGroup?.Name, permission.Name, - permission.Type, + permission.ResourceName, permission.Parent?.Name, LocalizableStringSerializer.Serialize(permission.DisplayName), permission.IsEnabled, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs index 5b36cf1dc3..19d848b9db 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs @@ -72,6 +72,7 @@ public static class AbpPermissionManagementDbContextModelBuilderExtensions b.Property(x => x.GroupName).HasMaxLength(PermissionGroupDefinitionRecordConsts.MaxNameLength) .IsRequired(); b.Property(x => x.Name).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength).IsRequired(); + b.Property(x => x.ResourceName).HasMaxLength(PermissionDefinitionRecordConsts.MaxResourceNameLength); b.Property(x => x.ParentName).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength); b.Property(x => x.DisplayName).HasMaxLength(PermissionDefinitionRecordConsts.MaxDisplayNameLength) .IsRequired(); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs index 783ba1f10a..5d7833ad0f 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs @@ -35,7 +35,7 @@ public class CalculateHash_Tests: PermissionTestBase json.ShouldNotContain(id.ToString("D")); json = JsonSerializer.Serialize(new List() { - new PermissionDefinitionRecord(id, "Test", "Test", PermissionType.UserBased, "Test", "Test") + new PermissionDefinitionRecord(id, "Test", "Test", "Test", "Test", "Test") }, jsonSerializerOptions); json.ShouldNotContain("\"Id\""); From a2d39ad8b3070b89ba6f0bcab1673907731c71dc Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 10:00:57 +0800 Subject: [PATCH 011/130] Implement resource-based permission management with new methods and classes --- .../IPermissionDefinitionContext.cs | 13 ++++ .../IPermissionDefinitionManager.cs | 7 ++ .../Permissions/PermissionDefinition.cs | 14 +++- .../PermissionDefinitionContext.cs | 73 ++++++++++++++----- .../Permissions/PermissionGroupDefinition.cs | 31 -------- .../ResourcePermissionRequirement.cs | 21 ++++++ .../IDynamicPermissionDefinitionStore.cs | 8 +- .../IStaticPermissionDefinitionStore.cs | 8 +- .../NullDynamicPermissionDefinitionStore.cs | 19 ++++- .../PermissionDefinitionManager.cs | 44 +++++++++-- .../StaticPermissionDefinitionStore.cs | 32 +++++--- ...sourcePermissionAuthorizationExtensions.cs | 13 ++++ ...ityResourcePermissionRequirementHandler.cs | 30 ++++++++ 13 files changed, 240 insertions(+), 73 deletions(-) create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/ResourcePermissionRequirement.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs index 7ff890bac7..e6c949429d 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs @@ -1,5 +1,7 @@ using System; +using JetBrains.Annotations; using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Authorization.Permissions; @@ -46,4 +48,15 @@ public interface IPermissionDefinitionContext /// Name of the permission /// PermissionDefinition? GetPermissionOrNull(string name); + + PermissionDefinition AddResourcePermission( + string name, + string resourceName, + ILocalizableString? displayName = null, + MultiTenancySides multiTenancySide = MultiTenancySides.Both, + bool isEnabled = true); + + PermissionDefinition? GetResourcePermissionOrNull([NotNull] string name); + + void RemoveResourcePermission([NotNull] string name); } diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionManager.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionManager.cs index ca04b110f7..52afcbe6eb 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionManager.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionManager.cs @@ -11,7 +11,14 @@ public interface IPermissionDefinitionManager Task GetOrNullAsync([NotNull] string name); + [ItemNotNull] + Task GetResourcePermissionAsync([NotNull] string name); + + Task GetResourcePermissionOrNullAsync([NotNull] string name); + Task> GetPermissionsAsync(); + Task> GetResourcePermissionsAsync(); + Task> GetGroupsAsync(); } diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index 9f527487e0..5e4cdac85a 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -83,13 +83,22 @@ public class PermissionDefinition : protected internal PermissionDefinition( [NotNull] string name, - string? resourceName = null, + string resourceName, + ILocalizableString? displayName = null, + MultiTenancySides multiTenancySide = MultiTenancySides.Both, + bool isEnabled = true) + : this(name, displayName, multiTenancySide, isEnabled) + { + ResourceName = Check.NotNull(resourceName, nameof(resourceName)); + } + + protected internal PermissionDefinition( + [NotNull] string name, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { Name = Check.NotNull(name, nameof(name)); - ResourceName = resourceName; DisplayName = displayName ?? new FixedLocalizableString(name); MultiTenancySide = multiTenancySide; IsEnabled = isEnabled; @@ -108,7 +117,6 @@ public class PermissionDefinition : { var child = new PermissionDefinition( name, - null, displayName, multiTenancySide, isEnabled) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs index 394cdb9d82..83ffda1f80 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using JetBrains.Annotations; using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Authorization.Permissions; @@ -11,17 +12,20 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext public Dictionary Groups { get; } + public Dictionary ResourcePermissions { get; } + internal IPermissionDefinitionProvider? CurrentProvider { get; set; } public static class KnownPropertyNames { public const string CurrentProviderName = "_CurrentProviderName"; } - + public PermissionDefinitionContext(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; Groups = new Dictionary(); + ResourcePermissions = new Dictionary(); } public virtual PermissionGroupDefinition AddGroup( @@ -43,7 +47,7 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext } Groups[name] = group; - + return group; } @@ -51,37 +55,23 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext public virtual PermissionGroupDefinition GetGroup([NotNull] string name) { var group = GetGroupOrNull(name); - - if (group == null) - { - throw new AbpException($"Could not find a permission definition group with the given name: {name}"); - } - - return group; + return group ?? throw new AbpException($"Could not find a permission definition group with the given name: {name}"); } public virtual PermissionGroupDefinition? GetGroupOrNull([NotNull] string name) { Check.NotNull(name, nameof(name)); - - if (!Groups.ContainsKey(name)) - { - return null; - } - - return Groups[name]; + return Groups.GetOrDefault(name); } public virtual void RemoveGroup(string name) { Check.NotNull(name, nameof(name)); - if (!Groups.ContainsKey(name)) + if (!Groups.Remove(name)) { throw new AbpException($"Not found permission group with name: {name}"); } - - Groups.Remove(name); } public virtual PermissionDefinition? GetPermissionOrNull([NotNull] string name) @@ -100,4 +90,49 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext return null; } + + public virtual PermissionDefinition AddResourcePermission( + string name, + string resourceName, + ILocalizableString? displayName = null, + MultiTenancySides multiTenancySide = MultiTenancySides.Both, + bool isEnabled = true) + { + Check.NotNull(name, nameof(name)); + + if (ResourcePermissions.ContainsKey(name)) + { + throw new AbpException($"There is already an existing resource permission with name: {name}"); + } + + var permission = new PermissionDefinition( + name, + resourceName, + displayName, + multiTenancySide, + isEnabled) + { + [KnownPropertyNames.CurrentProviderName] = CurrentProvider?.GetType().FullName + }; + + ResourcePermissions[name] = permission; + + return permission; + } + + public virtual PermissionDefinition? GetResourcePermissionOrNull([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + return ResourcePermissions.GetOrDefault(name); + } + + public virtual void RemoveResourcePermission([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + + if (!ResourcePermissions.Remove(name)) + { + throw new AbpException($"Not found resource permission with name: {name}"); + } + } } diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs index a064245488..0ad2dca099 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs @@ -53,40 +53,9 @@ public class PermissionGroupDefinition : ICanAddChildPermission ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) - { - return AddPermission( - name, - null, - displayName, - multiTenancySide, - isEnabled); - } - - public virtual PermissionDefinition AddResourcePermission( - [NotNull] string name, - [NotNull] string resourceName, - ILocalizableString? displayName = null, - MultiTenancySides multiTenancySide = MultiTenancySides.Both, - bool isEnabled = true) - { - return AddPermission( - name, - resourceName, - displayName, - multiTenancySide, - isEnabled); - } - - protected virtual PermissionDefinition AddPermission( - [NotNull] string name, - string? resourceName, - ILocalizableString? displayName = null, - MultiTenancySides multiTenancySide = MultiTenancySides.Both, - bool isEnabled = true) { var permission = new PermissionDefinition( name, - resourceName, displayName, multiTenancySide, isEnabled diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/ResourcePermissionRequirement.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/ResourcePermissionRequirement.cs new file mode 100644 index 0000000000..03658f3e90 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/ResourcePermissionRequirement.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.Authorization; + +namespace Volo.Abp.Authorization; + +public class ResourcePermissionRequirement : IAuthorizationRequirement +{ + public string PermissionName { get; } + + public ResourcePermissionRequirement([NotNull] string permissionName) + { + Check.NotNull(permissionName, nameof(permissionName)); + + PermissionName = permissionName; + } + + public override string ToString() + { + return $"ResourcePermissionRequirement: {PermissionName}"; + } +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IDynamicPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IDynamicPermissionDefinitionStore.cs index 366ae2e58a..49f798675a 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IDynamicPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IDynamicPermissionDefinitionStore.cs @@ -8,6 +8,10 @@ public interface IDynamicPermissionDefinitionStore Task GetOrNullAsync(string name); Task> GetPermissionsAsync(); - + + Task GetResourcePermissionOrNullAsync(string name); + + Task> GetResourcePermissionsAsync(); + Task> GetGroupsAsync(); -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IStaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IStaticPermissionDefinitionStore.cs index 4da8423dd3..74899574e7 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IStaticPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IStaticPermissionDefinitionStore.cs @@ -8,6 +8,10 @@ public interface IStaticPermissionDefinitionStore Task GetOrNullAsync(string name); Task> GetPermissionsAsync(); - + + Task GetResourcePermissionOrNullAsync(string name); + + Task> GetResourcePermissionsAsync(); + Task> GetGroupsAsync(); -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullDynamicPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullDynamicPermissionDefinitionStore.cs index 5b13288153..4ea7990abd 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullDynamicPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullDynamicPermissionDefinitionStore.cs @@ -9,10 +9,15 @@ namespace Volo.Abp.Authorization.Permissions; public class NullDynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStore, ISingletonDependency { private readonly static Task CachedPermissionResult = Task.FromResult((PermissionDefinition?)null); - + private readonly static Task> CachedPermissionsResult = Task.FromResult((IReadOnlyList)Array.Empty().ToImmutableList()); + private readonly static Task CachedResourcePermissionResult = Task.FromResult((PermissionDefinition?)null); + + private readonly static Task> CachedResourcePermissionsResult = + Task.FromResult((IReadOnlyList)Array.Empty().ToImmutableList()); + private readonly static Task> CachedGroupsResult = Task.FromResult((IReadOnlyList)Array.Empty().ToImmutableList()); @@ -26,8 +31,18 @@ public class NullDynamicPermissionDefinitionStore : IDynamicPermissionDefinition return CachedPermissionsResult; } + public Task GetResourcePermissionOrNullAsync(string name) + { + return CachedResourcePermissionResult; + } + + public Task> GetResourcePermissionsAsync() + { + return CachedResourcePermissionsResult; + } + public Task> GetGroupsAsync() { return CachedGroupsResult; } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs index 23f9e7883e..41c261de49 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs @@ -34,17 +34,36 @@ public class PermissionDefinitionManager : IPermissionDefinitionManager, ITransi { Check.NotNull(name, nameof(name)); - return await _staticStore.GetOrNullAsync(name) ?? + return await _staticStore.GetOrNullAsync(name) ?? await _dynamicStore.GetOrNullAsync(name); } + public virtual async Task GetResourcePermissionAsync(string name) + { + var permission = await GetResourcePermissionOrNullAsync(name); + if (permission == null) + { + throw new AbpException("Undefined permission: " + name); + } + + return permission; + } + + public virtual async Task GetResourcePermissionOrNullAsync(string name) + { + Check.NotNull(name, nameof(name)); + + return await _staticStore.GetResourcePermissionOrNullAsync(name) ?? + await _dynamicStore.GetResourcePermissionOrNullAsync(name); + } + public virtual async Task> GetPermissionsAsync() { var staticPermissions = await _staticStore.GetPermissionsAsync(); var staticPermissionNames = staticPermissions .Select(p => p.Name) .ToImmutableHashSet(); - + var dynamicPermissions = await _dynamicStore.GetPermissionsAsync(); /* We prefer static permissions over dynamics */ @@ -53,13 +72,28 @@ public class PermissionDefinitionManager : IPermissionDefinitionManager, ITransi ).ToImmutableList(); } - public async Task> GetGroupsAsync() + public virtual async Task> GetResourcePermissionsAsync() + { + var staticResourcePermissions = await _staticStore.GetResourcePermissionsAsync(); + var staticResourcePermissionNames = staticResourcePermissions + .Select(p => p.Name) + .ToImmutableHashSet(); + + var dynamicResourcePermissions = await _dynamicStore.GetResourcePermissionsAsync(); + + /* We prefer static permissions over dynamics */ + return staticResourcePermissions.Concat( + dynamicResourcePermissions.Where(d => !staticResourcePermissionNames.Contains(d.Name)) + ).ToImmutableList(); + } + + public virtual async Task> GetGroupsAsync() { var staticGroups = await _staticStore.GetGroupsAsync(); var staticGroupNames = staticGroups .Select(p => p.Name) .ToImmutableHashSet(); - + var dynamicGroups = await _dynamicStore.GetGroupsAsync(); /* We prefer static groups over dynamics */ @@ -67,4 +101,4 @@ public class PermissionDefinitionManager : IPermissionDefinitionManager, ITransi dynamicGroups.Where(d => !staticGroupNames.Contains(d.Name)) ).ToImmutableList(); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs index 4e6ff0d11c..938aaf3440 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs @@ -11,12 +11,14 @@ namespace Volo.Abp.Authorization.Permissions; public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, ISingletonDependency { - protected IDictionary PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value; - private readonly Lazy> _lazyPermissionGroupDefinitions; + protected IDictionary PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value.Item1; + private readonly Lazy<(Dictionary, Dictionary)> _lazyPermissionGroupDefinitions; protected IDictionary PermissionDefinitions => _lazyPermissionDefinitions.Value; private readonly Lazy> _lazyPermissionDefinitions; + protected IDictionary ResourcePermissionDefinitions => _lazyPermissionGroupDefinitions.Value.Item2; + protected AbpPermissionOptions Options { get; } private readonly IServiceProvider _serviceProvider; @@ -33,12 +35,12 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, isThreadSafe: true ); - _lazyPermissionGroupDefinitions = new Lazy>( + _lazyPermissionGroupDefinitions = new Lazy<(Dictionary, Dictionary)>( CreatePermissionGroupDefinitions, isThreadSafe: true ); } - + protected virtual Dictionary CreatePermissionDefinitions() { var permissions = new Dictionary(); @@ -71,7 +73,7 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, } } - protected virtual Dictionary CreatePermissionGroupDefinitions() + protected virtual (Dictionary, Dictionary) CreatePermissionGroupDefinitions() { using (var scope = _serviceProvider.CreateScope()) { @@ -99,10 +101,10 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, context.CurrentProvider = provider; provider.PostDefine(context); } - + context.CurrentProvider = null; - return context.Groups; + return (context.Groups, context.ResourcePermissions); } } @@ -110,7 +112,7 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, { return Task.FromResult(PermissionDefinitions.GetOrDefault(name)); } - + public virtual Task> GetPermissionsAsync() { return Task.FromResult>( @@ -118,10 +120,22 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, ); } + public virtual Task GetResourcePermissionOrNullAsync(string name) + { + return Task.FromResult(ResourcePermissionDefinitions.GetOrDefault(name)); + } + + public virtual Task> GetResourcePermissionsAsync() + { + return Task.FromResult>( + ResourcePermissionDefinitions.Values.ToImmutableList() + ); + } + public Task> GetGroupsAsync() { return Task.FromResult>( PermissionGroupDefinitions.Values.ToImmutableList() ); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs new file mode 100644 index 0000000000..e392d6378d --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Authorization.Permissions.Resources; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ResourcePermissionExtenstions +{ + public static IServiceCollection AddEntityResourcePermissionAuthorization(this IServiceCollection services) + { + services.AddSingleton(); + return services; + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs new file mode 100644 index 0000000000..aff721d90e --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class EntityResourcePermissionRequirementHandler : AuthorizationHandler +{ + protected readonly IResourcePermissionChecker PermissionChecker; + + protected EntityResourcePermissionRequirementHandler(IResourcePermissionChecker permissionChecker) + { + PermissionChecker = permissionChecker; + } + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ResourcePermissionRequirement requirement, IEntity? resource) + { + if (resource == null) + { + return; + } + + var resourceName = resource.GetType().FullName!; + var resourceKey = resource.GetKeys().JoinAsString(","); + if (await PermissionChecker.IsGrantedAsync(context.User, requirement.PermissionName, resourceName, resourceKey)) + { + context.Succeed(requirement); + } + } +} From f2e5cb75b0fcc279645622226a1f9141b65a8c91 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 10:47:24 +0800 Subject: [PATCH 012/130] Enhance resource permission management by adding provider name and key parameters to permission checks --- .../Resources/IResourcePermissionStore.cs | 21 +++++++++++++++++-- .../Resources/NullResourcePermissionStore.cs | 4 ++-- .../ResourcePermissionStoreExtensions.cs | 16 +++++++------- .../RoleResourcePermissionValueProvider.cs | 4 ++-- .../UserResourcePermissionValueProvider.cs | 4 ++-- ...EntityResourcePermissionStoreExtensions.cs | 16 +++++++------- .../Volo/Abp/Domain/AbpDddDomainModule.cs | 5 +++++ 7 files changed, 46 insertions(+), 24 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs index 94ae94c5db..334bda81f0 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs @@ -11,19 +11,36 @@ public interface IResourcePermissionStore /// The name of the permission. /// The name of the resource. /// Resource key + /// The name of the provider. + /// The key of the provider. /// /// True if the permission is granted. /// Task IsGrantedAsync( string name, string resourceName, - string resourceKey + string resourceKey, + string providerName, + string providerKey ); + /// + /// Checks if the given permissions are granted for the given resource. + /// + /// The name of the permissions. + /// The name of the resource. + /// Resource key + /// The name of the provider. + /// The key of the provider. + /// + /// A object containing the grant results for each permission. + /// Task IsGrantedAsync( string[] names, string resourceName, - string resourceKey + string resourceKey, + string providerName, + string providerKey ); /// diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs index 0b5ce51d2e..72fd84ec02 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs @@ -17,12 +17,12 @@ public class NullResourcePermissionStore : IResourcePermissionStore, ISingletonD Logger = NullLogger.Instance; } - public Task IsGrantedAsync(string name, string resourceName, string resourceKey) + public Task IsGrantedAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) { return TaskCache.FalseResult; } - public Task IsGrantedAsync(string[] names, string resourceName, string resourceKey) + public Task IsGrantedAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) { return Task.FromResult(new MultiplePermissionGrantResult(names, PermissionGrantResult.Prohibited)); } diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs index 2333737595..ad2c033755 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs @@ -9,24 +9,24 @@ public static class ResourcePermissionStoreExtensions /// Checks if a specific permission is granted for a resource with a given key. /// /// The type of the resource. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The name of the permission to check. /// The resource instance to check permission for. /// The unique key identifying the resource instance. /// A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the permission is granted. public static Task IsGrantedAsync( - this IResourcePermissionStore resourcePermissionStore, + this IResourcePermissionChecker resourcePermissionChecker, string permissionName, TResource resource, object resourceKey ) { - Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); Check.NotNull(resource, nameof(resource)); Check.NotNull(resourceKey, nameof(resourceKey)); - return resourcePermissionStore.IsGrantedAsync( + return resourcePermissionChecker.IsGrantedAsync( permissionName, typeof(TResource).FullName!, resourceKey.ToString()! @@ -37,21 +37,21 @@ public static class ResourcePermissionStoreExtensions /// Retrieves a dictionary of permissions and their granted statuses for a specific resource. /// /// The type of the resource. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The resource instance for which permissions are being checked. /// The unique key identifying the resource instance. /// A task that represents the asynchronous operation. The task result contains a dictionary where keys are permission names and values indicate whether the corresponding permission is granted. public static Task> GetPermissionsAsync( - this IResourcePermissionStore resourcePermissionStore, + this IResourcePermissionChecker resourcePermissionChecker, TResource resource, object resourceKey ) { - Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); Check.NotNull(resource, nameof(resource)); Check.NotNull(resourceKey, nameof(resourceKey)); - return resourcePermissionStore.GetPermissionsAsync( + return resourcePermissionChecker.GetPermissionsAsync( typeof(TResource).FullName!, resourceKey.ToString()! ); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/RoleResourcePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/RoleResourcePermissionValueProvider.cs index 8a32da5f2c..fc8e91e794 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/RoleResourcePermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/RoleResourcePermissionValueProvider.cs @@ -28,7 +28,7 @@ public class RoleResourcePermissionValueProvider : ResourcePermissionValueProvid foreach (var role in roles.Distinct()) { - if (await ResourcePermissionStore.IsGrantedAsync(context.Permission.Name, Name, role)) + if (await ResourcePermissionStore.IsGrantedAsync(context.Permission.Name, context.ResourceName, context.ResourceKey, Name, role)) { return PermissionGrantResult.Granted; } @@ -52,7 +52,7 @@ public class RoleResourcePermissionValueProvider : ResourcePermissionValueProvid foreach (var role in roles.Distinct()) { - var multipleResult = await ResourcePermissionStore.IsGrantedAsync(permissionNames.ToArray(), Name, role); + var multipleResult = await ResourcePermissionStore.IsGrantedAsync(permissionNames.ToArray(), context.ResourceName, context.ResourceKey, Name, role); foreach (var grantResult in multipleResult.Result.Where(grantResult => result.Result.ContainsKey(grantResult.Key) && diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/UserResourcePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/UserResourcePermissionValueProvider.cs index 0597616709..8cfcb8ca6f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/UserResourcePermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/UserResourcePermissionValueProvider.cs @@ -25,7 +25,7 @@ public class UserResourcePermissionValueProvider : ResourcePermissionValueProvid return PermissionGrantResult.Undefined; } - return await ResourcePermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId) + return await ResourcePermissionStore.IsGrantedAsync(context.Permission.Name, context.ResourceName, context.ResourceKey, Name, userId) ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined; } @@ -41,6 +41,6 @@ public class UserResourcePermissionValueProvider : ResourcePermissionValueProvid return new MultiplePermissionGrantResult(permissionNames); } - return await ResourcePermissionStore.IsGrantedAsync(permissionNames, Name, userId); + return await ResourcePermissionStore.IsGrantedAsync(permissionNames, context.ResourceName, context.ResourceKey, Name, userId); } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs index 67be57a747..b326df7cb4 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs @@ -12,22 +12,22 @@ public static class EntityResourcePermissionStoreExtensions /// Checks if the specified permission is granted for the given entity. /// /// The type of the entity. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The name of the permission to check. /// The entity for which the permission is being checked. /// A task that represents the asynchronous operation. The task result is a boolean indicating whether the permission is granted. public static Task IsGrantedAsync( - this IResourcePermissionStore resourcePermissionStore, + this IResourcePermissionChecker resourcePermissionChecker, string permissionName, TEntity entity ) where TEntity : class, IEntity { - Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); Check.NotNull(entity, nameof(entity)); - return resourcePermissionStore.IsGrantedAsync( + return resourcePermissionChecker.IsGrantedAsync( permissionName, typeof(TEntity).FullName!, entity.GetKeys().JoinAsString(",") @@ -38,19 +38,19 @@ public static class EntityResourcePermissionStoreExtensions /// Retrieves a dictionary of permissions and their granted status for the specified entity. /// /// The type of the entity. - /// The resource permission checker instance. + /// The resource permission checker instance. /// The entity for which the permissions are being retrieved. /// A dictionary where the keys are permission names and the values are booleans indicating whether the permission is granted. public static Task> GetPermissionsAsync( - this IResourcePermissionStore resourcePermissionStore, + this IResourcePermissionChecker resourcePermissionChecker, TEntity entity ) where TEntity : class, IEntity { - Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); Check.NotNull(entity, nameof(entity)); - return resourcePermissionStore.GetPermissionsAsync( + return resourcePermissionChecker.GetPermissionsAsync( typeof(TEntity).FullName!, entity.GetKeys().JoinAsString(",") ); diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs index cf3ba5fea8..5efa873226 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs @@ -35,4 +35,9 @@ public class AbpDddDomainModule : AbpModule context.Services.AddConventionalRegistrar(new AbpRepositoryConventionalRegistrar()); context.Services.OnRegistered(ChangeTrackingInterceptorRegistrar.RegisterIfNeeded); } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddEntityResourcePermissionAuthorization(); + } } From 09675ebe04817005d15fc5513c11723b80dc6c97 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 11:17:16 +0800 Subject: [PATCH 013/130] Refactor GetPermissionsAsync method to return MultiplePermissionGrantResult instead of IDictionary --- .../Permissions/Resources/IResourcePermissionStore.cs | 2 +- .../Permissions/Resources/NullResourcePermissionStore.cs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs index 334bda81f0..5da0462433 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs @@ -51,7 +51,7 @@ public interface IResourcePermissionStore /// /// A dictionary of permission names and their states. /// - Task> GetPermissionsAsync( + Task GetPermissionsAsync( string resourceName, string resourceKey ); diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs index 72fd84ec02..84bf38ef42 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/NullResourcePermissionStore.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -27,9 +26,9 @@ public class NullResourcePermissionStore : IResourcePermissionStore, ISingletonD return Task.FromResult(new MultiplePermissionGrantResult(names, PermissionGrantResult.Prohibited)); } - public Task> GetPermissionsAsync(string resourceName, string resourceKey) + public Task GetPermissionsAsync(string resourceName, string resourceKey) { - return Task.FromResult((IDictionary)new Dictionary()); + return Task.FromResult(new MultiplePermissionGrantResult()); } public Task GetGrantedPermissionsAsync(string resourceName, string resourceKey) From cdc3fdd00847588688711aef8f3ba71610745bc9 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 12:56:56 +0800 Subject: [PATCH 014/130] Implement `ResourcePermissionStore`. --- .../DynamicPermissionDefinitionStore.cs | 40 ++- ...cPermissionDefinitionStoreInMemoryCache.cs | 28 +- ...cPermissionDefinitionStoreInMemoryCache.cs | 14 +- .../IResourcePermissionGrantRepository.cs | 48 ++++ .../ResourcePermissionGrantCacheItem.cs | 34 +++ .../ResourcePermissionStore.cs | 249 ++++++++++++++++++ 6 files changed, 400 insertions(+), 13 deletions(-) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionGrantRepository.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItem.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionStore.cs 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); + } +} From 0c0cff768b2ecc216b8ffa4fc946b87d6870a7e6 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 13:25:45 +0800 Subject: [PATCH 015/130] Add resource permission grant repositories for EF Core and MongoDB --- ...sionManagementEntityFrameworkCoreModule.cs | 1 + ...EfCoreResourcePermissionGrantRepository.cs | 74 +++++++++++++++++ .../AbpPermissionManagementMongoDbModule.cs | 1 + .../MongoResourcePermissionGrantRepository.cs | 79 +++++++++++++++++++ 4 files changed, 155 insertions(+) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreModule.cs index ebcc8527c1..da6dfe8b06 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreModule.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreModule.cs @@ -17,6 +17,7 @@ public class AbpPermissionManagementEntityFrameworkCoreModule : AbpModule options.AddRepository(); options.AddRepository(); options.AddRepository(); + options.AddRepository(); }); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs new file mode 100644 index 0000000000..0e096389ff --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.PermissionManagement.EntityFrameworkCore; + +public class EfCoreResourcePermissionGrantRepository : EfCoreRepository, IResourcePermissionGrantRepository +{ + public EfCoreResourcePermissionGrantRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + + } + + public virtual async Task FindAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .OrderBy(x => x.Id) + .FirstOrDefaultAsync(s => + s.Name == name && + s.ResourceName == resourceName && + s.ResourceKey == resourceKey && + s.ProviderName == providerName && + s.ProviderKey == providerKey, + GetCancellationToken(cancellationToken) + ); + } + + public virtual async Task> GetListAsync(string resourceName, string resourceKey, string providerName, string providerKey, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(s => + s.ResourceName == resourceName && + s.ResourceKey == resourceKey && + s.ProviderName == providerName && + s.ProviderKey == providerKey + ).ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetListAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(s => + names.Contains(s.Name) && + s.ResourceName == resourceName && + s.ResourceKey == resourceKey && + s.ProviderName == providerName && + s.ProviderKey == providerKey + ).ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetPermissionsAsync(string resourceName, string resourceKey, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(s => + s.ResourceName == resourceName && + s.ResourceKey == resourceKey + ).Select(x => x.Name).ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetResourceKeys(string resourceName, string name, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(s => + s.ResourceName == resourceName && + s.Name == name + ).Select(x => x.ResourceKey).ToListAsync(GetCancellationToken(cancellationToken)); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbModule.cs index 80831660f8..686d219295 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbModule.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbModule.cs @@ -19,6 +19,7 @@ public class AbpPermissionManagementMongoDbModule : AbpModule options.AddRepository(); options.AddRepository(); options.AddRepository(); + options.AddRepository(); }); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs new file mode 100644 index 0000000000..5b96ac536f --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver.Linq; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.PermissionManagement.MongoDB; + +public class MongoResourcePermissionGrantRepository : MongoDbRepository, IResourcePermissionGrantRepository +{ + public MongoResourcePermissionGrantRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + + } + + public virtual async Task FindAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey, CancellationToken cancellationToken = default) + { + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)) + .OrderBy(x => x.Id) + .FirstOrDefaultAsync(s => + s.Name == name && + s.ResourceName == resourceName && + s.ResourceKey == resourceKey && + s.ProviderName == providerName && + s.ProviderKey == providerKey, + cancellationToken + ); + } + + public virtual async Task> GetListAsync(string resourceName, string resourceKey, string providerName, string providerKey, CancellationToken cancellationToken = default) + { + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)) + .Where(s => + s.ResourceName == resourceName && + s.ResourceKey == resourceKey && + s.ProviderName == providerName && + s.ProviderKey == providerKey + ).ToListAsync(cancellationToken); + } + + public virtual async Task> GetListAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey, CancellationToken cancellationToken = default) + { + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)) + .Where(s => + names.AsEnumerable().Contains(s.Name) && + s.ResourceName == resourceName && + s.ResourceKey == resourceKey && + s.ProviderName == providerName && + s.ProviderKey == providerKey + ).ToListAsync(cancellationToken); + } + + public virtual async Task> GetPermissionsAsync(string resourceName, string resourceKey, CancellationToken cancellationToken = default) + { + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)) + .Where(s => + s.ResourceName == resourceName && + s.ResourceKey == resourceKey + ).Select(x => x.Name).ToListAsync(cancellationToken); + } + + public virtual async Task> GetResourceKeys(string resourceName, string name, CancellationToken cancellationToken = default) + { + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)) + .Where(s => + s.ResourceName == resourceName && + s.Name == name + ).Select(x => x.ResourceKey).ToListAsync(cancellationToken); + } +} From 90bf895cacc6f9c9fc7677429e2149b58d7f9ecb Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 14:26:44 +0800 Subject: [PATCH 016/130] Implement `ResourcePermissionManager`. --- ...ermissionManagementDomainIdentityModule.cs | 5 + ...oleResourcePermissionManagementProvider.cs | 80 ++++++ ...serResourcePermissionManagementProvider.cs | 22 ++ .../IResourcePermissionManagementProvider.cs | 34 +++ .../IResourcePermissionManager.cs | 30 +- .../PermissionManagementOptions.cs | 3 + .../ResourcePermissionManagementProvider.cs | 87 ++++++ .../ResourcePermissionManager.cs | 257 ++++++++++++++++++ 8 files changed, 503 insertions(+), 15 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs create mode 100644 modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionManagementProvider.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManagementProvider.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs index 973577ccff..1eec9d44c8 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs @@ -1,4 +1,5 @@ using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; using Volo.Abp.Identity; using Volo.Abp.Modularity; using Volo.Abp.Users; @@ -18,10 +19,14 @@ public class AbpPermissionManagementDomainIdentityModule : AbpModule { options.ManagementProviders.Add(); options.ManagementProviders.Add(); + options.ResourceManagementProviders.Add(); + options.ResourceManagementProviders.Add(); //TODO: Can we prevent duplication of permission names without breaking the design and making the system complicated options.ProviderPolicies[UserPermissionValueProvider.ProviderName] = "AbpIdentity.Users.ManagePermissions"; options.ProviderPolicies[RolePermissionValueProvider.ProviderName] = "AbpIdentity.Roles.ManagePermissions"; + options.ProviderPolicies[UserResourcePermissionValueProvider.ProviderName] = "AbpIdentity.Users.ManagePermissions"; + options.ProviderPolicies[RoleResourcePermissionValueProvider.ProviderName] = "AbpIdentity.Roles.ManagePermissions"; }); } } diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs new file mode 100644 index 0000000000..8b77face53 --- /dev/null +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; +using Volo.Abp.Identity; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.PermissionManagement.Identity; + +public class RoleResourcePermissionManagementProvider : ResourcePermissionManagementProvider +{ + public override string Name => RoleResourcePermissionValueProvider.ProviderName; + + protected IUserRoleFinder UserRoleFinder { get; } + + public RoleResourcePermissionManagementProvider( + IResourcePermissionGrantRepository resourcepPrmissionGrantRepository, + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant, + IUserRoleFinder userRoleFinder) + : base( + resourcepPrmissionGrantRepository, + guidGenerator, + currentTenant) + { + UserRoleFinder = userRoleFinder; + } + + public override async Task CheckAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) + { + var multipleGrantInfo = await CheckAsync(new[] { name }, resourceName, resourceKey, providerName, providerKey); + + return multipleGrantInfo.Result.Values.First(); + } + + public override async Task CheckAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) + { + using (ResourcePermissionGrantRepository.DisableTracking()) + { + var multiplePermissionValueProviderGrantInfo = new MultiplePermissionValueProviderGrantInfo(names); + var resourcePermissionGrants = new List(); + + if (providerName == Name) + { + resourcePermissionGrants.AddRange(await ResourcePermissionGrantRepository.GetListAsync(names, resourceName, resourceKey, providerName, providerKey)); + } + + if (providerName == UserResourcePermissionValueProvider.ProviderName) + { + var userId = Guid.Parse(providerKey); + var roleNames = await UserRoleFinder.GetRoleNamesAsync(userId); + + foreach (var roleName in roleNames) + { + resourcePermissionGrants.AddRange(await ResourcePermissionGrantRepository.GetListAsync(names, resourceName, resourceKey, Name, roleName)); + } + } + + resourcePermissionGrants = resourcePermissionGrants.Distinct().ToList(); + if (!resourcePermissionGrants.Any()) + { + return multiplePermissionValueProviderGrantInfo; + } + + foreach (var permissionName in names) + { + var resourcePermissionGrant = resourcePermissionGrants.FirstOrDefault(x => x.Name == permissionName); + if (resourcePermissionGrant != null) + { + multiplePermissionValueProviderGrantInfo.Result[permissionName] = new PermissionValueProviderGrantInfo(true, resourcePermissionGrant.ProviderKey); + } + } + + return multiplePermissionValueProviderGrantInfo; + } + } +} diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionManagementProvider.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionManagementProvider.cs new file mode 100644 index 0000000000..ef165d422d --- /dev/null +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionManagementProvider.cs @@ -0,0 +1,22 @@ +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.PermissionManagement.Identity; + +public class UserResourcePermissionManagementProvider : ResourcePermissionManagementProvider +{ + public override string Name => UserResourcePermissionValueProvider.ProviderName; + + public UserResourcePermissionManagementProvider( + IResourcePermissionGrantRepository resourcePermissionGrantRepository, + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) + : base( + resourcePermissionGrantRepository, + guidGenerator, + currentTenant) + { + + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManagementProvider.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManagementProvider.cs new file mode 100644 index 0000000000..e5f1fe78ac --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManagementProvider.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.PermissionManagement; + +public interface IResourcePermissionManagementProvider : ISingletonDependency //TODO: Consider to remove this pre-assumption +{ + string Name { get; } + + Task CheckAsync( + [NotNull] string name, + [NotNull] string resourceName, + [NotNull] string resourceKey, + [NotNull] string providerName, + [NotNull] string providerKey + ); + + Task CheckAsync( + [NotNull] string[] names, + [NotNull] string resourceName, + [NotNull] string resourceKey, + [NotNull] string providerName, + [NotNull] string providerKey + ); + + Task SetAsync( + [NotNull] string name, + [NotNull] string resourceName, + [NotNull] string resourceKey, + [NotNull] string providerKey, + bool isGranted + ); +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs index f58fcdc584..0ca47706d5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs @@ -7,33 +7,33 @@ public interface IResourcePermissionManager { Task GetAsync( string permissionName, - string providerName, - string providerKey, string resourceName, - string resourceKey + string resourceKey, + string providerName, + string providerKey ); - + Task GetAsync( string[] permissionNames, - string provideName, - string providerKey, string resourceName, - string resourceKey + string resourceKey, + string providerName, + string providerKey ); - + Task> GetAllAsync( - string providerName, - string providerKey, string resourceName, - string resourceKey + string resourceKey, + string providerName, + string providerKey ); - + Task SetAsync( string permissionName, - string providerName, - string providerKey, string resourceName, string resourceKey, + string providerName, + string providerKey, bool isGranted ); -} \ 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 489ae26e3d..e917a3fea6 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 @@ -7,6 +7,8 @@ public class PermissionManagementOptions { public ITypeList ManagementProviders { get; } + public ITypeList ResourceManagementProviders { get; } + public Dictionary ProviderPolicies { get; } /// @@ -22,6 +24,7 @@ public class PermissionManagementOptions public PermissionManagementOptions() { ManagementProviders = new TypeList(); + ResourceManagementProviders = new TypeList(); ProviderPolicies = new Dictionary(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider.cs new file mode 100644 index 0000000000..3344995b07 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider.cs @@ -0,0 +1,87 @@ +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.PermissionManagement; + +public abstract class ResourcePermissionManagementProvider : IResourcePermissionManagementProvider +{ + public abstract string Name { get; } + + protected IResourcePermissionGrantRepository ResourcePermissionGrantRepository { get; } + + protected IGuidGenerator GuidGenerator { get; } + + protected ICurrentTenant CurrentTenant { get; } + + protected ResourcePermissionManagementProvider( + IResourcePermissionGrantRepository resourcePermissionGrantRepository, + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) + { + ResourcePermissionGrantRepository = resourcePermissionGrantRepository; + GuidGenerator = guidGenerator; + CurrentTenant = currentTenant; + } + + public virtual async Task CheckAsync(string name, string resourceName,string resourceKey, string providerName, string providerKey) + { + var multiplePermissionValueProviderGrantInfo = await CheckAsync(new[] { name }, resourceName, resourceKey, providerName, providerKey); + + return multiplePermissionValueProviderGrantInfo.Result.First().Value; + } + + public virtual async Task CheckAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) + { + using (ResourcePermissionGrantRepository.DisableTracking()) + { + var multiplePermissionValueProviderGrantInfo = new MultiplePermissionValueProviderGrantInfo(names); + if (providerName != Name) + { + return multiplePermissionValueProviderGrantInfo; + } + + var resourcePermissionGrants = await ResourcePermissionGrantRepository.GetListAsync(names, resourceName, resourceKey, providerName, providerKey); + + foreach (var permissionName in names) + { + var isGrant = resourcePermissionGrants.Any(x => x.Name == permissionName); + multiplePermissionValueProviderGrantInfo.Result[permissionName] = new PermissionValueProviderGrantInfo(isGrant, providerKey); + } + + return multiplePermissionValueProviderGrantInfo; + } + } + + public virtual Task SetAsync(string name, string resourceName,string resourceKey, string providerKey, bool isGranted) + { + return isGranted + ? GrantAsync(name, resourceName, resourceKey, providerKey) + : RevokeAsync(name, resourceName, resourceKey, providerKey); + } + + protected virtual async Task GrantAsync(string name, string resourceName, string resourceKey, string providerKey) + { + var resourcePermissionGrants = await ResourcePermissionGrantRepository.FindAsync(name, resourceName, resourceKey, Name, providerKey); + if (resourcePermissionGrants != null) + { + return; + } + + resourcePermissionGrants = new ResourcePermissionGrant(GuidGenerator.Create(), name, resourceName, resourceKey, Name, providerKey, CurrentTenant.Id); + await ResourcePermissionGrantRepository.InsertAsync(resourcePermissionGrants, true); + } + + protected virtual async Task RevokeAsync(string name, string resourceName,string resourceKey, string providerKey) + { + var resourcePermissionGrants = await ResourcePermissionGrantRepository.FindAsync(name, resourceName, resourceKey, Name, providerKey); + if (resourcePermissionGrants == null) + { + return; + } + + await ResourcePermissionGrantRepository.DeleteAsync(resourcePermissionGrants, true); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs new file mode 100644 index 0000000000..f22ebb40e3 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; +using Volo.Abp.SimpleStateChecking; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionManager : IResourcePermissionManager, ISingletonDependency +{ + protected IResourcePermissionGrantRepository ResourcePermissionGrantRepository { get; } + + protected IPermissionDefinitionManager PermissionDefinitionManager { get; } + + protected ISimpleStateCheckerManager SimpleStateCheckerManager { get; } + + protected IGuidGenerator GuidGenerator { get; } + + protected ICurrentTenant CurrentTenant { get; } + + protected IReadOnlyList ManagementProviders => _lazyProviders.Value; + + protected PermissionManagementOptions Options { get; } + + protected IDistributedCache Cache { get; } + + private readonly Lazy> _lazyProviders; + + public ResourcePermissionManager( + IPermissionDefinitionManager permissionDefinitionManager, + ISimpleStateCheckerManager simpleStateCheckerManager, + IResourcePermissionGrantRepository resourcePermissionGrantRepository, + IServiceProvider serviceProvider, + IGuidGenerator guidGenerator, + IOptions options, + ICurrentTenant currentTenant, + IDistributedCache cache) + { + GuidGenerator = guidGenerator; + CurrentTenant = currentTenant; + Cache = cache; + SimpleStateCheckerManager = simpleStateCheckerManager; + ResourcePermissionGrantRepository = resourcePermissionGrantRepository; + PermissionDefinitionManager = permissionDefinitionManager; + Options = options.Value; + + _lazyProviders = new Lazy>( + () => Options + .ResourceManagementProviders + .Select(c => serviceProvider.GetRequiredService(c) as IResourcePermissionManagementProvider) + .ToList(), + true + ); + } + + public virtual async Task GetAsync(string permissionName, string resourceName, string resourceKey, string providerName, string providerKey) + { + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); + if (permission == null) + { + return new PermissionWithGrantedProviders(permissionName, false); + } + + return await GetInternalAsync( + permission, + resourceName, + resourceKey, + providerName, + providerKey + ); + } + + public virtual async Task GetAsync(string[] permissionNames, string resourceName, string resourceKey, string providerName, string providerKey) + { + var permissions = new List(); + var undefinedPermissions = new List(); + + foreach (var permissionName in permissionNames) + { + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); + if (permission != null) + { + permissions.Add(permission); + } + else + { + undefinedPermissions.Add(permissionName); + } + } + + if (!permissions.Any()) + { + return new MultiplePermissionWithGrantedProviders(undefinedPermissions.ToArray()); + } + + var result = await GetInternalAsync( + permissions.ToArray(), + resourceName, + resourceKey, + providerName, + providerKey + ); + + foreach (var undefinedPermission in undefinedPermissions) + { + result.Result.Add(new PermissionWithGrantedProviders(undefinedPermission, false)); + } + + return result; + } + + public virtual async Task> GetAllAsync(string resourceName, string resourceKey, string providerName, string providerKey) + { + var permissionDefinitions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()).ToArray(); + + var multiplePermissionWithGrantedProviders = await GetInternalAsync(permissionDefinitions, resourceName, resourceKey, providerName, providerKey); + + return multiplePermissionWithGrantedProviders.Result; + } + + public virtual async Task SetAsync(string permissionName, string resourceName, string resourceKey, string providerName, string providerKey, bool isGranted) + { + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); + if (permission == null) + { + /* Silently ignore undefined permissions, + maybe they were removed from dynamic permission definition store */ + return; + } + + if (!permission.IsEnabled || !await SimpleStateCheckerManager.IsEnabledAsync(permission)) + { + //TODO: BusinessException + throw new ApplicationException($"The resource permission named '{permission.Name}' is disabled!"); + } + + if (permission.Providers.Any() && !permission.Providers.Contains(providerName)) + { + //TODO: BusinessException + throw new ApplicationException($"The resource permission named '{permission.Name}' has not compatible with the provider named '{providerName}'"); + } + + if (!permission.MultiTenancySide.HasFlag(CurrentTenant.GetMultiTenancySide())) + { + //TODO: BusinessException + throw new ApplicationException($"The resource permission named '{permission.Name}' has multitenancy side '{permission.MultiTenancySide}' which is not compatible with the current multitenancy side '{CurrentTenant.GetMultiTenancySide()}'"); + } + + var currentGrantInfo = await GetInternalAsync(permission, resourceName, resourceKey, providerName, providerKey); + if (currentGrantInfo.IsGranted == isGranted) + { + return; + } + + var provider = ManagementProviders.FirstOrDefault(m => m.Name == providerName); + if (provider == null) + { + //TODO: BusinessException + throw new AbpException("Unknown permission management provider: " + providerName); + } + + await provider.SetAsync(permissionName, resourceName, resourceKey, providerKey, isGranted); + } + + public virtual async Task UpdateProviderKeyAsync(ResourcePermissionGrant resourcePermissionGrant, string resourceName, string resourceKey, string providerKey) + { + using (CurrentTenant.Change(resourcePermissionGrant.TenantId)) + { + //Invalidating the cache for the old key + await Cache.RemoveAsync( + ResourcePermissionGrantCacheItem.CalculateCacheKey( + resourcePermissionGrant.Name, + resourcePermissionGrant.ResourceName, + resourcePermissionGrant.ResourceKey, + resourcePermissionGrant.ProviderName, + resourcePermissionGrant.ProviderKey + ) + ); + } + + resourcePermissionGrant.ProviderKey = providerKey; + return await ResourcePermissionGrantRepository.UpdateAsync(resourcePermissionGrant, true); + } + + public virtual async Task DeleteAsync(string providerName, string resourceName, string resourceKey, string providerKey) + { + var permissionGrants = await ResourcePermissionGrantRepository.GetListAsync(providerName, resourceName, resourceKey, providerKey); + foreach (var permissionGrant in permissionGrants) + { + await ResourcePermissionGrantRepository.DeleteAsync(permissionGrant, true); + } + } + + protected virtual async Task GetInternalAsync(PermissionDefinition permission, string resourceName, string resourceKey, string providerName, string providerKey) + { + var multiplePermissionWithGrantedProviders = await GetInternalAsync( + new[] { permission }, + resourceName, + resourceKey, + providerName, + providerKey + ); + + return multiplePermissionWithGrantedProviders.Result.First(); + } + + protected virtual async Task GetInternalAsync(PermissionDefinition[] permissions, string resourceName, string resourceKey, string providerName, string providerKey) + { + var permissionNames = permissions.Select(x => x.Name).ToArray(); + var multiplePermissionWithGrantedProviders = new MultiplePermissionWithGrantedProviders(permissionNames); + + var neededCheckPermissions = new List(); + + foreach (var permission in permissions + .Where(x => x.IsEnabled) + .Where(x => x.MultiTenancySide.HasFlag(CurrentTenant.GetMultiTenancySide())) + .Where(x => !x.Providers.Any() || x.Providers.Contains(providerName))) + { + if (await SimpleStateCheckerManager.IsEnabledAsync(permission)) + { + neededCheckPermissions.Add(permission); + } + } + + if (!neededCheckPermissions.Any()) + { + return multiplePermissionWithGrantedProviders; + } + + foreach (var provider in ManagementProviders) + { + permissionNames = neededCheckPermissions.Select(x => x.Name).ToArray(); + var multiplePermissionValueProviderGrantInfo = await provider.CheckAsync(permissionNames, resourceName, resourceKey, providerName, providerKey); + + foreach (var providerResultDict in multiplePermissionValueProviderGrantInfo.Result) + { + if (providerResultDict.Value.IsGranted) + { + var permissionWithGrantedProvider = multiplePermissionWithGrantedProviders.Result + .First(x => x.Name == providerResultDict.Key); + + permissionWithGrantedProvider.IsGranted = true; + permissionWithGrantedProvider.Providers.Add(new PermissionValueProviderInfo(provider.Name, providerResultDict.Value.ProviderKey)); + } + } + } + + return multiplePermissionWithGrantedProviders; + } +} From 2f56610c15db5ba50be5b2f1da0fb5cebae835af Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 15:31:47 +0800 Subject: [PATCH 017/130] Add resource permission checker and store extension methods for entity permissions --- .../ResourcePermissionCheckerExtensions.cs | 59 +++++++++++++++++++ .../ResourcePermissionStoreExtensions.cs | 56 +----------------- ...tityResourcePermissionCheckerExtensions.cs | 57 ++++++++++++++++++ ...EntityResourcePermissionStoreExtensions.cs | 52 +--------------- 4 files changed, 120 insertions(+), 104 deletions(-) create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs new file mode 100644 index 0000000000..3a5fad9d7d --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public static class ResourcePermissionCheckerExtensions +{ + /// + /// Checks if a specific permission is granted for a resource with a given key. + /// + /// The type of the resource. + /// The resource permission checker instance. + /// The name of the permission to check. + /// The resource instance to check permission for. + /// The unique key identifying the resource instance. + /// A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the permission is granted. + public static Task IsGrantedAsync( + this IResourcePermissionChecker resourcePermissionChecker, + string permissionName, + TResource resource, + object resourceKey + ) + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); + Check.NotNull(resource, nameof(resource)); + Check.NotNull(resourceKey, nameof(resourceKey)); + + return resourcePermissionChecker.IsGrantedAsync( + permissionName, + typeof(TResource).FullName!, + resourceKey.ToString()! + ); + } + + /// + /// Retrieves a dictionary of permissions and their granted statuses for a specific resource. + /// + /// The type of the resource. + /// The resource permission checker instance. + /// The resource instance for which permissions are being checked. + /// The unique key identifying the resource instance. + /// A task that represents the asynchronous operation. The task result contains a dictionary where keys are permission names and values indicate whether the corresponding permission is granted. + public static Task> GetPermissionsAsync( + this IResourcePermissionChecker resourcePermissionChecker, + TResource resource, + object resourceKey + ) + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(resource, nameof(resource)); + Check.NotNull(resourceKey, nameof(resourceKey)); + + return resourcePermissionChecker.GetPermissionsAsync( + typeof(TResource).FullName!, + resourceKey.ToString()! + ); + } +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs index ad2c033755..6ed817c232 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs @@ -5,63 +5,11 @@ namespace Volo.Abp.Authorization.Permissions.Resources; public static class ResourcePermissionStoreExtensions { - /// - /// Checks if a specific permission is granted for a resource with a given key. - /// - /// The type of the resource. - /// The resource permission checker instance. - /// The name of the permission to check. - /// The resource instance to check permission for. - /// The unique key identifying the resource instance. - /// A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the permission is granted. - public static Task IsGrantedAsync( - this IResourcePermissionChecker resourcePermissionChecker, - string permissionName, - TResource resource, - object resourceKey - ) - { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); - Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); - Check.NotNull(resource, nameof(resource)); - Check.NotNull(resourceKey, nameof(resourceKey)); - - return resourcePermissionChecker.IsGrantedAsync( - permissionName, - typeof(TResource).FullName!, - resourceKey.ToString()! - ); - } - - /// - /// Retrieves a dictionary of permissions and their granted statuses for a specific resource. - /// - /// The type of the resource. - /// The resource permission checker instance. - /// The resource instance for which permissions are being checked. - /// The unique key identifying the resource instance. - /// A task that represents the asynchronous operation. The task result contains a dictionary where keys are permission names and values indicate whether the corresponding permission is granted. - public static Task> GetPermissionsAsync( - this IResourcePermissionChecker resourcePermissionChecker, - TResource resource, - object resourceKey - ) - { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); - Check.NotNull(resource, nameof(resource)); - Check.NotNull(resourceKey, nameof(resourceKey)); - - return resourcePermissionChecker.GetPermissionsAsync( - typeof(TResource).FullName!, - resourceKey.ToString()! - ); - } - /// /// Retrieves the list of granted permissions for a specific resource with a given key. /// /// The type of the resource. - /// The resource permission checker instance. + /// The resource permission store instance. /// The resource instance to retrieve permissions for. /// The unique key identifying the resource instance. /// A task that represents the asynchronous operation. The task result contains an array of strings representing the granted permissions. @@ -85,7 +33,7 @@ public static class ResourcePermissionStoreExtensions /// Retrieves the keys of the resources granted a specific permission. /// /// The type of the resource. - /// The resource permission checker instance. + /// The resource permission store instance. /// The resource instance to check granted permissions for. /// The name of the permission to check. /// A task that represents the asynchronous operation. The task result contains an array of strings representing the granted resource keys. diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs new file mode 100644 index 0000000000..28f72f2abe --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public static class EntityResourcePermissionCheckerExtensions +{ + /// + /// Checks if the specified permission is granted for the given entity. + /// + /// The type of the entity. + /// The resource permission checker instance. + /// The name of the permission to check. + /// The entity for which the permission is being checked. + /// A task that represents the asynchronous operation. The task result is a boolean indicating whether the permission is granted. + public static Task IsGrantedAsync( + this IResourcePermissionChecker resourcePermissionChecker, + string permissionName, + TEntity entity + ) + where TEntity : class, IEntity + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); + Check.NotNull(entity, nameof(entity)); + + return resourcePermissionChecker.IsGrantedAsync( + permissionName, + typeof(TEntity).FullName!, + entity.GetKeys().JoinAsString(",") + ); + } + + /// + /// Retrieves a dictionary of permissions and their granted status for the specified entity. + /// + /// The type of the entity. + /// The resource permission checker instance. + /// The entity for which the permissions are being retrieved. + /// A dictionary where the keys are permission names and the values are booleans indicating whether the permission is granted. + public static Task> GetPermissionsAsync( + this IResourcePermissionChecker resourcePermissionChecker, + TEntity entity + ) + where TEntity : class, IEntity + { + Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); + Check.NotNull(entity, nameof(entity)); + + return resourcePermissionChecker.GetPermissionsAsync( + typeof(TEntity).FullName!, + entity.GetKeys().JoinAsString(",") + ); + } + +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs index b326df7cb4..90a27a246b 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs @@ -8,59 +8,11 @@ namespace Volo.Abp.Authorization.Permissions.Resources; public static class EntityResourcePermissionStoreExtensions { - /// - /// Checks if the specified permission is granted for the given entity. - /// - /// The type of the entity. - /// The resource permission checker instance. - /// The name of the permission to check. - /// The entity for which the permission is being checked. - /// A task that represents the asynchronous operation. The task result is a boolean indicating whether the permission is granted. - public static Task IsGrantedAsync( - this IResourcePermissionChecker resourcePermissionChecker, - string permissionName, - TEntity entity - ) - where TEntity : class, IEntity - { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); - Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); - Check.NotNull(entity, nameof(entity)); - - return resourcePermissionChecker.IsGrantedAsync( - permissionName, - typeof(TEntity).FullName!, - entity.GetKeys().JoinAsString(",") - ); - } - - /// - /// Retrieves a dictionary of permissions and their granted status for the specified entity. - /// - /// The type of the entity. - /// The resource permission checker instance. - /// The entity for which the permissions are being retrieved. - /// A dictionary where the keys are permission names and the values are booleans indicating whether the permission is granted. - public static Task> GetPermissionsAsync( - this IResourcePermissionChecker resourcePermissionChecker, - TEntity entity - ) - where TEntity : class, IEntity - { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); - Check.NotNull(entity, nameof(entity)); - - return resourcePermissionChecker.GetPermissionsAsync( - typeof(TEntity).FullName!, - entity.GetKeys().JoinAsString(",") - ); - } - /// /// Retrieves an array of granted permissions for a specific entity. /// /// The type of the entity. - /// The resource permission checker instance. + /// The resource permission store instance. /// The entity for which the permissions are being checked. /// An array of granted permission names as strings. public static Task GetGrantedPermissionsAsync( @@ -83,7 +35,7 @@ public static class EntityResourcePermissionStoreExtensions /// /// The type of the entity. /// The type of the entity's primary key. - /// The resource permission checker instance. + /// The resource permission store instance. /// The name of the permission to check. /// An array of entity IDs (of type ) for which the permission is granted. public static async Task GetGrantedEntityIdsAsync( From b3706cf23a1aacc9cb02bfb124be7ef4a648cf29 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 16:36:02 +0800 Subject: [PATCH 018/130] Add resource permission management features and update API methods --- ...ermissionManagementDomainIdentityModule.cs | 11 +-- .../GetResourcePermissionListResultDto.cs | 10 +++ .../IPermissionAppService.cs | 4 ++ .../ResourcePermissionGrantInfoDto.cs | 16 +++++ .../PermissionAppService.cs | 68 ++++++++++++++++++- .../PermissionManagementOptions.cs | 8 ++- .../PermissionsController.cs | 12 ++++ 7 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionListResultDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs index 1eec9d44c8..483fae7f20 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs @@ -19,14 +19,17 @@ public class AbpPermissionManagementDomainIdentityModule : AbpModule { options.ManagementProviders.Add(); options.ManagementProviders.Add(); - options.ResourceManagementProviders.Add(); - options.ResourceManagementProviders.Add(); //TODO: Can we prevent duplication of permission names without breaking the design and making the system complicated options.ProviderPolicies[UserPermissionValueProvider.ProviderName] = "AbpIdentity.Users.ManagePermissions"; options.ProviderPolicies[RolePermissionValueProvider.ProviderName] = "AbpIdentity.Roles.ManagePermissions"; - options.ProviderPolicies[UserResourcePermissionValueProvider.ProviderName] = "AbpIdentity.Users.ManagePermissions"; - options.ProviderPolicies[RoleResourcePermissionValueProvider.ProviderName] = "AbpIdentity.Roles.ManagePermissions"; + + options.ResourceManagementProviders.Add(); + options.ResourceManagementProviders.Add(); + + //TODO: Can we prevent duplication of permission names without breaking the design and making the system complicated + options.ResourceProviderPolicies[UserResourcePermissionValueProvider.ProviderName] = "AbpIdentity.Users.ManagePermissions"; + options.ResourceProviderPolicies[RoleResourcePermissionValueProvider.ProviderName] = "AbpIdentity.Roles.ManagePermissions"; }); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionListResultDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionListResultDto.cs new file mode 100644 index 0000000000..25381fc014 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionListResultDto.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Volo.Abp.PermissionManagement; + +public class GetResourcePermissionListResultDto +{ + public string EntityDisplayName { get; set; } + + public List Permissions { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs index 819d643a21..43b10c9de6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs @@ -11,4 +11,8 @@ public interface IPermissionAppService : IApplicationService Task GetByGroupAsync([NotNull] string groupName, [NotNull] string providerName, [NotNull] string providerKey); Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdatePermissionsDto input); + + Task GetAsync([NotNull] string resourceName, [NotNull] string resourceKey, [NotNull] string providerName, [NotNull] string providerKey); + + Task UpdateAsync([NotNull] string resourceName, [NotNull] string resourceKey, [NotNull] string providerName, [NotNull] string providerKey, UpdatePermissionsDto input); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs new file mode 100644 index 0000000000..f3931872f9 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionGrantInfoDto +{ + public string Name { get; set; } + + public string DisplayName { get; set; } + + public bool IsGranted { get; set; } + + public List AllowedProviders { get; set; } + + public List GrantedProviders { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index e628c39644..04cfac28b8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -18,12 +18,14 @@ public class PermissionAppService : ApplicationService, IPermissionAppService { protected PermissionManagementOptions Options { get; } protected IPermissionManager PermissionManager { get; } + protected IResourcePermissionManager ResourcePermissionManager { get; } protected IPermissionDefinitionManager PermissionDefinitionManager { get; } protected ISimpleStateCheckerManager SimpleStateCheckerManager { get; } public PermissionAppService( IPermissionManager permissionManager, IPermissionDefinitionManager permissionDefinitionManager, + IResourcePermissionManager resourcePermissionManager, IOptions options, ISimpleStateCheckerManager simpleStateCheckerManager) { @@ -32,6 +34,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService Options = options.Value; PermissionManager = permissionManager; + ResourcePermissionManager = resourcePermissionManager; PermissionDefinitionManager = permissionDefinitionManager; SimpleStateCheckerManager = simpleStateCheckerManager; } @@ -160,14 +163,73 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } - protected virtual async Task CheckProviderPolicy(string providerName) + public virtual async Task GetAsync(string resourceName, string resourceKey, string providerName, string providerKey) { - var policyName = Options.ProviderPolicies.GetOrDefault(providerName); + await CheckProviderPolicy(providerName, true); + + var result = new GetResourcePermissionListResultDto + { + EntityDisplayName = providerKey, + Permissions = new List() + }; + + var multiTenancySide = CurrentTenant.GetMultiTenancySide(); + + var resourcePermissions = new List(); + foreach (var resourcePermission in (await PermissionDefinitionManager.GetResourcePermissionsAsync()) + .Where(x => x.IsEnabled && (!x.Providers.Any() || x.Providers.Contains(providerName)) && x.MultiTenancySide.HasFlag(multiTenancySide))) + { + if (await SimpleStateCheckerManager.IsEnabledAsync(resourcePermission)) + { + resourcePermissions.Add(resourcePermission); + } + } + + var multipleGrantInfo = await ResourcePermissionManager.GetAsync(resourcePermissions.Select(x => x.Name).ToArray(), resourceName, resourceKey, providerName, providerKey); + foreach (var resourcePermission in resourcePermissions) + { + var grantInfo = multipleGrantInfo.Result.FirstOrDefault(x => x.Name == resourcePermission.Name); + if (grantInfo == null) + { + continue; + } + + result.Permissions.Add(new ResourcePermissionGrantInfoDto() + { + Name = resourcePermission.Name, + DisplayName = resourcePermission.DisplayName?.Localize(StringLocalizerFactory), + IsGranted = grantInfo.IsGranted, + AllowedProviders = resourcePermission.Providers, + GrantedProviders = grantInfo.Providers.Select(x => new ProviderInfoDto + { + ProviderName = x.Name, + ProviderKey = x.Key, + }).ToList() + }); + } + + return result; + } + + public virtual async Task UpdateAsync(string resourceName, string resourceKey, string providerName, string providerKey, UpdatePermissionsDto input) + { + await CheckProviderPolicy(providerName, true); + + foreach (var permissionDto in input.Permissions) + { + await ResourcePermissionManager.SetAsync(permissionDto.Name, resourceName, resourceKey, providerName, providerKey, permissionDto.IsGranted); + } + } + + protected virtual async Task CheckProviderPolicy(string providerName, bool isResourcePermission = false) + { + var policyName = isResourcePermission + ? Options.ResourceProviderPolicies.GetOrDefault(providerName) + : Options.ProviderPolicies.GetOrDefault(providerName); if (policyName.IsNullOrEmpty()) { throw new AbpException($"No policy defined to get/set permissions for the provider '{providerName}'. Use {nameof(PermissionManagementOptions)} to map the policy."); } - await AuthorizationService.CheckAsync(policyName); } } 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 e917a3fea6..58278d575a 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 @@ -7,9 +7,11 @@ public class PermissionManagementOptions { public ITypeList ManagementProviders { get; } + public Dictionary ProviderPolicies { get; } + public ITypeList ResourceManagementProviders { get; } - public Dictionary ProviderPolicies { get; } + public Dictionary ResourceProviderPolicies { get; } /// /// Default: true. @@ -24,7 +26,9 @@ public class PermissionManagementOptions public PermissionManagementOptions() { ManagementProviders = new TypeList(); - ResourceManagementProviders = new TypeList(); ProviderPolicies = new Dictionary(); + + ResourceManagementProviders = new TypeList(); + ResourceProviderPolicies = new Dictionary(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs index ff6d7e6128..5a27663ffa 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs @@ -34,4 +34,16 @@ public class PermissionsController : AbpControllerBase, IPermissionAppService { return PermissionAppService.UpdateAsync(providerName, providerKey, input); } + + [HttpGet("resource")] + public virtual Task GetAsync(string resourceName, string resourceKey, string providerName, string providerKey) + { + return PermissionAppService.GetAsync(resourceName, resourceKey, providerName, providerKey); + } + + [HttpPut("resource")] + public virtual Task UpdateAsync(string resourceName, string resourceKey, string providerName, string providerKey, UpdatePermissionsDto input) + { + return PermissionAppService.UpdateAsync(resourceName, resourceKey, providerName, providerKey, input); + } } From 8bd08b9df894c83e45f7e8154baf398a9c632231 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Nov 2025 17:06:43 +0800 Subject: [PATCH 019/130] Make constructor public in permission handler --- .../Resources/EntityResourcePermissionRequirementHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs index aff721d90e..f97ddbafef 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs @@ -9,10 +9,11 @@ public class EntityResourcePermissionRequirementHandler : AuthorizationHandler Date: Sat, 15 Nov 2025 14:31:07 +0800 Subject: [PATCH 020/130] Refactor permission management module --- .../GetResourcePermissionListResultDto.cs | 2 - .../GrantedResourcePermissionDto.cs | 8 + .../IPermissionAppService.cs | 4 +- .../ResourcePermissionGrantInfoDto.cs | 10 +- .../Volo/Abp/PermissionManagement/Test.cs | 18 ++ .../UpdateResourcePermissionDto.cs | 12 ++ .../UpdateResourcePermissionsDto.cs | 6 + .../PermissionAppService.cs | 55 +++-- .../IResourcePermissionGrantRepository.cs | 4 +- .../IResourcePermissionManagementProvider.cs | 4 +- .../IResourcePermissionManager.cs | 10 + ...esourcePermissionValueProviderGrantInfo.cs | 25 +++ .../PermissionManagementOptions.cs | 3 - .../PermissionProviderWithPermissions.cs | 20 ++ .../ResourcePermissionGrant.cs | 4 +- .../ResourcePermissionManagementProvider.cs | 8 +- .../ResourcePermissionManager.cs | 53 ++++- .../ResourcePermissionStore.cs | 8 +- ...esourcePermissionValueProviderGrantInfo.cs | 18 ++ ...EfCoreResourcePermissionGrantRepository.cs | 8 +- .../PermissionsClientProxy.Generated.cs | 19 ++ .../permissionManagement-generate-proxy.json | 196 +++++++++++++++++- .../PermissionsController.cs | 8 +- .../MongoResourcePermissionGrantRepository.cs | 8 +- 24 files changed, 433 insertions(+), 78 deletions(-) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GrantedResourcePermissionDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/Test.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionsDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/MultipleResourcePermissionValueProviderGrantInfo.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionValueProviderGrantInfo.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionListResultDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionListResultDto.cs index 25381fc014..b9c4db2190 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionListResultDto.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionListResultDto.cs @@ -4,7 +4,5 @@ namespace Volo.Abp.PermissionManagement; public class GetResourcePermissionListResultDto { - public string EntityDisplayName { get; set; } - public List Permissions { get; set; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GrantedResourcePermissionDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GrantedResourcePermissionDto.cs new file mode 100644 index 0000000000..bbb1de112a --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GrantedResourcePermissionDto.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.PermissionManagement; + +public class GrantedResourcePermissionDto +{ + public string Name { get; set; } + + public string DisplayName { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs index 43b10c9de6..4344f0d662 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs @@ -12,7 +12,7 @@ public interface IPermissionAppService : IApplicationService Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdatePermissionsDto input); - Task GetAsync([NotNull] string resourceName, [NotNull] string resourceKey, [NotNull] string providerName, [NotNull] string providerKey); + Task GetResourceAsync([NotNull] string resourceName, [NotNull] string resourceKey); - Task UpdateAsync([NotNull] string resourceName, [NotNull] string resourceKey, [NotNull] string providerName, [NotNull] string providerKey, UpdatePermissionsDto input); + Task UpdateResourceAsync([NotNull] string resourceName, [NotNull] string resourceKey, UpdateResourcePermissionsDto input); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs index f3931872f9..9e38d1e257 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs @@ -4,13 +4,9 @@ namespace Volo.Abp.PermissionManagement; public class ResourcePermissionGrantInfoDto { - public string Name { get; set; } + public string ProviderName { get; set; } - public string DisplayName { get; set; } + public string ProviderKey { get; set; } - public bool IsGranted { get; set; } - - public List AllowedProviders { get; set; } - - public List GrantedProviders { get; set; } + public List Permissions { get; set; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/Test.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/Test.cs new file mode 100644 index 0000000000..deec5ed657 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/Test.cs @@ -0,0 +1,18 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; +using Volo.Abp.PermissionManagement.Localization; + +namespace Volo.Abp.PermissionManagement; + +public class TestPermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + context.AddResourcePermission("AbpIdentity.Resource.ChangeName", "Volo.Abp.IdentityUser", L("OnlyProviderPermissons")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionDto.cs new file mode 100644 index 0000000000..e6ca8fff20 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionDto.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.PermissionManagement; + +public class UpdateResourcePermissionDto +{ + public string ProviderName { get; set; } + + public string ProviderKey { get; set; } + + public string Name { get; set; } + + public bool IsGranted { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionsDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionsDto.cs new file mode 100644 index 0000000000..3ca137d367 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionsDto.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.PermissionManagement; + +public class UpdateResourcePermissionsDto +{ + public UpdateResourcePermissionDto[] Permissions { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 04cfac28b8..a85b9f5e1a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -19,6 +19,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService protected PermissionManagementOptions Options { get; } protected IPermissionManager PermissionManager { get; } protected IResourcePermissionManager ResourcePermissionManager { get; } + protected IResourcePermissionGrantRepository ResourcePermissionGrantRepository { get; } protected IPermissionDefinitionManager PermissionDefinitionManager { get; } protected ISimpleStateCheckerManager SimpleStateCheckerManager { get; } @@ -26,6 +27,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService IPermissionManager permissionManager, IPermissionDefinitionManager permissionDefinitionManager, IResourcePermissionManager resourcePermissionManager, + IResourcePermissionGrantRepository resourcePermissionGrantRepository, IOptions options, ISimpleStateCheckerManager simpleStateCheckerManager) { @@ -35,6 +37,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService Options = options.Value; PermissionManager = permissionManager; ResourcePermissionManager = resourcePermissionManager; + ResourcePermissionGrantRepository = resourcePermissionGrantRepository; PermissionDefinitionManager = permissionDefinitionManager; SimpleStateCheckerManager = simpleStateCheckerManager; } @@ -163,21 +166,17 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } - public virtual async Task GetAsync(string resourceName, string resourceKey, string providerName, string providerKey) + public virtual async Task GetResourceAsync(string resourceName, string resourceKey) { - await CheckProviderPolicy(providerName, true); - var result = new GetResourcePermissionListResultDto { - EntityDisplayName = providerKey, Permissions = new List() }; var multiTenancySide = CurrentTenant.GetMultiTenancySide(); - var resourcePermissions = new List(); foreach (var resourcePermission in (await PermissionDefinitionManager.GetResourcePermissionsAsync()) - .Where(x => x.IsEnabled && (!x.Providers.Any() || x.Providers.Contains(providerName)) && x.MultiTenancySide.HasFlag(multiTenancySide))) + .Where(x => x.IsEnabled && x.MultiTenancySide.HasFlag(multiTenancySide) && x.ResourceName == resourceName)) { if (await SimpleStateCheckerManager.IsEnabledAsync(resourcePermission)) { @@ -185,51 +184,47 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } - var multipleGrantInfo = await ResourcePermissionManager.GetAsync(resourcePermissions.Select(x => x.Name).ToArray(), resourceName, resourceKey, providerName, providerKey); - foreach (var resourcePermission in resourcePermissions) + var resourcePermissionGrantsGroup = await ResourcePermissionManager.GetAllGroupAsync(resourceName, resourceKey); + foreach (var resourcePermissionGrant in resourcePermissionGrantsGroup) { - var grantInfo = multipleGrantInfo.Result.FirstOrDefault(x => x.Name == resourcePermission.Name); - if (grantInfo == null) + var resourcePermissionGrantInfoDto = new ResourcePermissionGrantInfoDto() { - continue; - } + ProviderName = resourcePermissionGrant.ProviderName, + ProviderKey = resourcePermissionGrant.ProviderKey, + Permissions = new List() + }; - result.Permissions.Add(new ResourcePermissionGrantInfoDto() + foreach (var permission in resourcePermissionGrant.Permissions) { - Name = resourcePermission.Name, - DisplayName = resourcePermission.DisplayName?.Localize(StringLocalizerFactory), - IsGranted = grantInfo.IsGranted, - AllowedProviders = resourcePermission.Providers, - GrantedProviders = grantInfo.Providers.Select(x => new ProviderInfoDto + resourcePermissionGrantInfoDto.Permissions.Add(new GrantedResourcePermissionDto() { - ProviderName = x.Name, - ProviderKey = x.Key, - }).ToList() - }); + Name = permission, + DisplayName = resourcePermissions.FirstOrDefault(x => x.Name == permission)?.DisplayName?.Localize(StringLocalizerFactory), + }); + } + + result.Permissions.Add(resourcePermissionGrantInfoDto); } return result; } - public virtual async Task UpdateAsync(string resourceName, string resourceKey, string providerName, string providerKey, UpdatePermissionsDto input) + public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { - await CheckProviderPolicy(providerName, true); - foreach (var permissionDto in input.Permissions) { - await ResourcePermissionManager.SetAsync(permissionDto.Name, resourceName, resourceKey, providerName, providerKey, permissionDto.IsGranted); + await ResourcePermissionManager.SetAsync(permissionDto.Name, resourceName, resourceKey, permissionDto.ProviderName, permissionDto.ProviderKey, permissionDto.IsGranted); } } - protected virtual async Task CheckProviderPolicy(string providerName, bool isResourcePermission = false) + protected virtual async Task CheckProviderPolicy(string providerName) { - var policyName = isResourcePermission - ? Options.ResourceProviderPolicies.GetOrDefault(providerName) - : Options.ProviderPolicies.GetOrDefault(providerName); + var policyName = Options.ProviderPolicies.GetOrDefault(providerName); if (policyName.IsNullOrEmpty()) { throw new AbpException($"No policy defined to get/set permissions for the provider '{providerName}'. Use {nameof(PermissionManagementOptions)} to map the policy."); } + await AuthorizationService.CheckAsync(policyName); } } 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 index 6696b9da75..62fb51fbb8 100644 --- 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 @@ -34,13 +34,13 @@ public interface IResourcePermissionGrantRepository : IBasicRepository> GetPermissionsAsync( + Task> GetPermissionsAsync( string resourceName, string resourceKey, CancellationToken cancellationToken = default ); - Task> GetResourceKeys( + Task> GetResourceKeys( string resourceName, string name, CancellationToken cancellationToken = default diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManagementProvider.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManagementProvider.cs index e5f1fe78ac..429e6f6e85 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManagementProvider.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManagementProvider.cs @@ -8,7 +8,7 @@ public interface IResourcePermissionManagementProvider : ISingletonDependency // { string Name { get; } - Task CheckAsync( + Task CheckAsync( [NotNull] string name, [NotNull] string resourceName, [NotNull] string resourceKey, @@ -16,7 +16,7 @@ public interface IResourcePermissionManagementProvider : ISingletonDependency // [NotNull] string providerKey ); - Task CheckAsync( + Task CheckAsync( [NotNull] string[] names, [NotNull] string resourceName, [NotNull] string resourceKey, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs index 0ca47706d5..b8d8ae91a7 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs @@ -21,6 +21,11 @@ public interface IResourcePermissionManager string providerKey ); + Task> GetAllAsync( + string resourceName, + string resourceKey + ); + Task> GetAllAsync( string resourceName, string resourceKey, @@ -28,6 +33,11 @@ public interface IResourcePermissionManager string providerKey ); + Task> GetAllGroupAsync( + string resourceName, + string resourceKey + ); + Task SetAsync( string permissionName, string resourceName, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/MultipleResourcePermissionValueProviderGrantInfo.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/MultipleResourcePermissionValueProviderGrantInfo.cs new file mode 100644 index 0000000000..4ff1790488 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/MultipleResourcePermissionValueProviderGrantInfo.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Volo.Abp.PermissionManagement; + +public class MultipleResourcePermissionValueProviderGrantInfo +{ + public Dictionary Result { get; } + + public MultipleResourcePermissionValueProviderGrantInfo() + { + Result = new Dictionary(); + } + + public MultipleResourcePermissionValueProviderGrantInfo(string[] names) + { + Check.NotNull(names, nameof(names)); + + Result = new Dictionary(); + + foreach (var name in names) + { + Result.Add(name, ResourcePermissionValueProviderGrantInfo.NonGranted); + } + } +} 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 58278d575a..cf5ff73115 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 @@ -11,8 +11,6 @@ public class PermissionManagementOptions public ITypeList ResourceManagementProviders { get; } - public Dictionary ResourceProviderPolicies { get; } - /// /// Default: true. /// @@ -29,6 +27,5 @@ public class PermissionManagementOptions ProviderPolicies = new Dictionary(); ResourceManagementProviders = new TypeList(); - ResourceProviderPolicies = new Dictionary(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs new file mode 100644 index 0000000000..2e53a23956 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.PermissionManagement; + +public class PermissionProviderWithPermissions +{ + public string ProviderName { get; } + + public string ProviderKey { get; } + + public List Permissions { get; set; } + + public PermissionProviderWithPermissions(string providerName, string providerKey) + { + ProviderName = providerName; + ProviderKey = providerKey; + Permissions = new List(); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs index 9a68ef4481..3d67c2bc53 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs @@ -16,7 +16,7 @@ public class ResourcePermissionGrant : Entity, IMultiTenant [NotNull] public virtual string ProviderName { get; protected set; } - [CanBeNull] + [NotNull] public virtual string ProviderKey { get; protected internal set; } [NotNull] @@ -44,7 +44,7 @@ public class ResourcePermissionGrant : Entity, IMultiTenant Id = id; Name = Check.NotNullOrWhiteSpace(name, nameof(name)); ProviderName = Check.NotNullOrWhiteSpace(providerName, nameof(providerName)); - ProviderKey = providerKey; + ProviderKey = Check.NotNullOrWhiteSpace(providerKey, nameof(providerKey)); ResourceName = Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName)); ResourceKey = Check.NotNullOrWhiteSpace(resourceKey, nameof(resourceKey)); TenantId = tenantId; diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider.cs index 3344995b07..90d4e176d3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider.cs @@ -26,18 +26,18 @@ public abstract class ResourcePermissionManagementProvider : IResourcePermission CurrentTenant = currentTenant; } - public virtual async Task CheckAsync(string name, string resourceName,string resourceKey, string providerName, string providerKey) + public virtual async Task CheckAsync(string name, string resourceName,string resourceKey, string providerName, string providerKey) { var multiplePermissionValueProviderGrantInfo = await CheckAsync(new[] { name }, resourceName, resourceKey, providerName, providerKey); return multiplePermissionValueProviderGrantInfo.Result.First().Value; } - public virtual async Task CheckAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) + public virtual async Task CheckAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) { using (ResourcePermissionGrantRepository.DisableTracking()) { - var multiplePermissionValueProviderGrantInfo = new MultiplePermissionValueProviderGrantInfo(names); + var multiplePermissionValueProviderGrantInfo = new MultipleResourcePermissionValueProviderGrantInfo(names); if (providerName != Name) { return multiplePermissionValueProviderGrantInfo; @@ -48,7 +48,7 @@ public abstract class ResourcePermissionManagementProvider : IResourcePermission foreach (var permissionName in names) { var isGrant = resourcePermissionGrants.Any(x => x.Name == permissionName); - multiplePermissionValueProviderGrantInfo.Result[permissionName] = new PermissionValueProviderGrantInfo(isGrant, providerKey); + multiplePermissionValueProviderGrantInfo.Result[permissionName] = new ResourcePermissionValueProviderGrantInfo(isGrant, providerKey); } return multiplePermissionValueProviderGrantInfo; diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index f22ebb40e3..9081f40084 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -63,7 +63,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD public virtual async Task GetAsync(string permissionName, string resourceName, string resourceKey, string providerName, string providerKey) { var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); - if (permission == null) + if (permission == null || permission.ResourceName != resourceName) { return new PermissionWithGrantedProviders(permissionName, false); } @@ -85,7 +85,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD foreach (var permissionName in permissionNames) { var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); - if (permission != null) + if (permission != null && permission.ResourceName == resourceName) { permissions.Add(permission); } @@ -116,19 +116,60 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD return result; } - public virtual async Task> GetAllAsync(string resourceName, string resourceKey, string providerName, string providerKey) + public virtual async Task> GetAllAsync(string resourceName, string resourceKey) { - var permissionDefinitions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()).ToArray(); + var resourcePermissionDefinitions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()).Where(x => x.ResourceName == resourceName).ToArray(); + var resourcePermissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); + var result = new List(); + foreach (var resourcePermissionDefinition in resourcePermissionDefinitions) + { + var permissionWithGrantedProviders = new PermissionWithGrantedProviders(resourcePermissionDefinition.Name, false); - var multiplePermissionWithGrantedProviders = await GetInternalAsync(permissionDefinitions, resourceName, resourceKey, providerName, providerKey); + var grantedPermissions = resourcePermissionGrants + .Where(x => x.Name == resourcePermissionDefinition.Name) + .ToList(); + + if (grantedPermissions.Any()) + { + permissionWithGrantedProviders.IsGranted = true; + foreach (var grantedPermission in grantedPermissions) + { + permissionWithGrantedProviders.Providers.Add(new PermissionValueProviderInfo(grantedPermission.ProviderName, grantedPermission.ProviderKey)); + } + } + + result.Add(permissionWithGrantedProviders); + } + return result; + } + + public virtual async Task> GetAllAsync(string resourceName, string resourceKey, string providerName, string providerKey) + { + var permissionDefinitions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()).Where(x => x.ResourceName == resourceName).ToArray(); + var multiplePermissionWithGrantedProviders = await GetInternalAsync(permissionDefinitions, resourceName, resourceKey, providerName, providerKey); return multiplePermissionWithGrantedProviders.Result; } + public virtual async Task> GetAllGroupAsync(string resourceName, string resourceKey) + { + var resourcePermissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); + var resourcePermissionGrantsGroup = resourcePermissionGrants.GroupBy(x => new { x.ProviderName, x.ProviderKey }); + var result = new List(); + foreach (var resourcePermissionGrant in resourcePermissionGrantsGroup) + { + result.Add(new PermissionProviderWithPermissions(resourcePermissionGrant.Key.ProviderName, resourcePermissionGrant.Key.ProviderKey) + { + Permissions = resourcePermissionGrant.Select(x => x.Name).ToList() + }); + } + return result; + } + public virtual async Task SetAsync(string permissionName, string resourceName, string resourceKey, string providerName, string providerKey, bool isGranted) { var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); - if (permission == null) + if (permission == null || permission.ResourceName != resourceName) { /* Silently ignore undefined permissions, maybe they were removed from dynamic permission definition store */ 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 index 7942549b73..914a05203a 100644 --- 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 @@ -206,7 +206,7 @@ public class ResourcePermissionStore : IResourcePermissionStore, ITransientDepen var permissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); foreach (var resourcePermission in resourcePermissions) { - var isGranted = permissionGrants.Any(x => x == resourcePermission.Name); + var isGranted = permissionGrants.Any(x => x.Name == resourcePermission.Name); result.Result.Add(resourcePermission.Name, isGranted ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined); } @@ -222,9 +222,9 @@ public class ResourcePermissionStore : IResourcePermissionStore, ITransientDepen var result = new List(); foreach (var grantedPermission in grantedPermissions) { - if (resourcePermissions.Any(x => x.Name == grantedPermission)) + if (resourcePermissions.Any(x => x.Name == grantedPermission.Name)) { - result.Add(grantedPermission); + result.Add(grantedPermission.Name); } } @@ -233,7 +233,7 @@ public class ResourcePermissionStore : IResourcePermissionStore, ITransientDepen public virtual async Task GetGrantedResourceKeysAsync(string resourceName, string name) { - return (await ResourcePermissionGrantRepository.GetResourceKeys(resourceName, name)).ToArray(); + return (await ResourcePermissionGrantRepository.GetResourceKeys(resourceName, name)).Select(x => x.Name).ToArray(); } protected virtual string GetPermissionNameFormCacheKeyOrNull(string key) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionValueProviderGrantInfo.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionValueProviderGrantInfo.cs new file mode 100644 index 0000000000..5cb0d5013a --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionValueProviderGrantInfo.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionValueProviderGrantInfo //TODO: Rename to ResourcePermissionGrantInfo +{ + public static ResourcePermissionValueProviderGrantInfo NonGranted { get; } = new ResourcePermissionValueProviderGrantInfo(false); + + public virtual bool IsGranted { get; set; } + + public virtual string ProviderKey { get; set; } + + public ResourcePermissionValueProviderGrantInfo(bool isGranted, [CanBeNull] string providerKey = null) + { + IsGranted = isGranted; + ProviderKey = providerKey; + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs index 0e096389ff..0e4bb75945 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs @@ -54,21 +54,21 @@ public class EfCoreResourcePermissionGrantRepository : EfCoreRepository> GetPermissionsAsync(string resourceName, string resourceKey, CancellationToken cancellationToken = default) + public virtual async Task> GetPermissionsAsync(string resourceName, string resourceKey, CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .Where(s => s.ResourceName == resourceName && s.ResourceKey == resourceKey - ).Select(x => x.Name).ToListAsync(GetCancellationToken(cancellationToken)); + ).ToListAsync(GetCancellationToken(cancellationToken)); } - public virtual async Task> GetResourceKeys(string resourceName, string name, CancellationToken cancellationToken = default) + public virtual async Task> GetResourceKeys(string resourceName, string name, CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .Where(s => s.ResourceName == resourceName && s.Name == name - ).Select(x => x.ResourceKey).ToListAsync(GetCancellationToken(cancellationToken)); + ).ToListAsync(GetCancellationToken(cancellationToken)); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs index 2fe22472f0..d22ed1d398 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs @@ -45,4 +45,23 @@ public partial class PermissionsClientProxy : ClientProxyBase GetResourceAsync(string resourceName, string resourceKey) + { + return await RequestAsync(nameof(GetResourceAsync), new ClientProxyRequestTypeValue + { + { typeof(string), resourceName }, + { typeof(string), resourceKey } + }); + } + + public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) + { + await RequestAsync(nameof(UpdateResourceAsync), new ClientProxyRequestTypeValue + { + { typeof(string), resourceName }, + { typeof(string), resourceKey }, + { typeof(UpdateResourcePermissionsDto), input } + }); + } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json index 208271e67d..a8f3d02a25 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json @@ -21,7 +21,7 @@ "parametersOnMethod": [ { "name": "input", - "typeAsString": "System.Collections.Generic.List`1[[Volo.Abp.PermissionManagement.IsGrantedRequest, Volo.Abp.PermissionManagement.Domain.Shared, Version=9.3.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib", + "typeAsString": "System.Collections.Generic.List`1[[Volo.Abp.PermissionManagement.IsGrantedRequest, Volo.Abp.PermissionManagement.Domain.Shared, Version=10.1.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib", "type": "System.Collections.Generic.List", "typeSimple": "[Volo.Abp.PermissionManagement.IsGrantedRequest]", "isOptional": false, @@ -46,7 +46,7 @@ "parametersOnMethod": [ { "name": "input", - "typeAsString": "System.Collections.Generic.List`1[[Volo.Abp.PermissionManagement.IsGrantedRequest, Volo.Abp.PermissionManagement.Domain.Shared, Version=9.3.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib", + "typeAsString": "System.Collections.Generic.List`1[[Volo.Abp.PermissionManagement.IsGrantedRequest, Volo.Abp.PermissionManagement.Domain.Shared, Version=10.1.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib", "type": "System.Collections.Generic.List", "typeSimple": "[Volo.Abp.PermissionManagement.IsGrantedRequest]", "isOptional": false, @@ -178,6 +178,64 @@ "type": "System.Void", "typeSimple": "System.Void" } + }, + { + "name": "GetResourceAsync", + "parametersOnMethod": [ + { + "name": "resourceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, + { + "name": "resourceKey", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "returnValue": { + "type": "Volo.Abp.PermissionManagement.GetResourcePermissionListResultDto", + "typeSimple": "Volo.Abp.PermissionManagement.GetResourcePermissionListResultDto" + } + }, + { + "name": "UpdateResourceAsync", + "parametersOnMethod": [ + { + "name": "resourceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, + { + "name": "resourceKey", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, + { + "name": "input", + "typeAsString": "Volo.Abp.PermissionManagement.UpdateResourcePermissionsDto, Volo.Abp.PermissionManagement.Application.Contracts", + "type": "Volo.Abp.PermissionManagement.UpdateResourcePermissionsDto", + "typeSimple": "Volo.Abp.PermissionManagement.UpdateResourcePermissionsDto", + "isOptional": false, + "defaultValue": null + } + ], + "returnValue": { + "type": "System.Void", + "typeSimple": "System.Void" + } } ] } @@ -393,6 +451,140 @@ }, "allowAnonymous": null, "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" + }, + "GetResourceAsyncByResourceNameAndResourceKey": { + "uniqueName": "GetResourceAsyncByResourceNameAndResourceKey", + "name": "GetResourceAsync", + "httpMethod": "GET", + "url": "api/permission-management/permissions/resource", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "resourceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, + { + "name": "resourceKey", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "resourceName", + "name": "resourceName", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + }, + { + "nameOnMethod": "resourceKey", + "name": "resourceKey", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + } + ], + "returnValue": { + "type": "Volo.Abp.PermissionManagement.GetResourcePermissionListResultDto", + "typeSimple": "Volo.Abp.PermissionManagement.GetResourcePermissionListResultDto" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" + }, + "UpdateResourceAsyncByResourceNameAndResourceKeyAndInput": { + "uniqueName": "UpdateResourceAsyncByResourceNameAndResourceKeyAndInput", + "name": "UpdateResourceAsync", + "httpMethod": "PUT", + "url": "api/permission-management/permissions/resource", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "resourceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, + { + "name": "resourceKey", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, + { + "name": "input", + "typeAsString": "Volo.Abp.PermissionManagement.UpdateResourcePermissionsDto, Volo.Abp.PermissionManagement.Application.Contracts", + "type": "Volo.Abp.PermissionManagement.UpdateResourcePermissionsDto", + "typeSimple": "Volo.Abp.PermissionManagement.UpdateResourcePermissionsDto", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "resourceName", + "name": "resourceName", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + }, + { + "nameOnMethod": "resourceKey", + "name": "resourceKey", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + }, + { + "nameOnMethod": "input", + "name": "input", + "jsonName": null, + "type": "Volo.Abp.PermissionManagement.UpdateResourcePermissionsDto", + "typeSimple": "Volo.Abp.PermissionManagement.UpdateResourcePermissionsDto", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "Body", + "descriptorName": "" + } + ], + "returnValue": { + "type": "System.Void", + "typeSimple": "System.Void" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" } } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs index 5a27663ffa..0c283faec4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs @@ -36,14 +36,14 @@ public class PermissionsController : AbpControllerBase, IPermissionAppService } [HttpGet("resource")] - public virtual Task GetAsync(string resourceName, string resourceKey, string providerName, string providerKey) + public virtual Task GetResourceAsync(string resourceName, string resourceKey) { - return PermissionAppService.GetAsync(resourceName, resourceKey, providerName, providerKey); + return PermissionAppService.GetResourceAsync(resourceName, resourceKey); } [HttpPut("resource")] - public virtual Task UpdateAsync(string resourceName, string resourceKey, string providerName, string providerKey, UpdatePermissionsDto input) + public virtual Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { - return PermissionAppService.UpdateAsync(resourceName, resourceKey, providerName, providerKey, input); + return PermissionAppService.UpdateResourceAsync(resourceName, resourceKey, input); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs index 5b96ac536f..0a8312718a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs @@ -57,23 +57,23 @@ public class MongoResourcePermissionGrantRepository : MongoDbRepository> GetPermissionsAsync(string resourceName, string resourceKey, CancellationToken cancellationToken = default) + public virtual async Task> GetPermissionsAsync(string resourceName, string resourceKey, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); return await (await GetQueryableAsync(cancellationToken)) .Where(s => s.ResourceName == resourceName && s.ResourceKey == resourceKey - ).Select(x => x.Name).ToListAsync(cancellationToken); + ).ToListAsync(cancellationToken); } - public virtual async Task> GetResourceKeys(string resourceName, string name, CancellationToken cancellationToken = default) + public virtual async Task> GetResourceKeys(string resourceName, string name, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); return await (await GetQueryableAsync(cancellationToken)) .Where(s => s.ResourceName == resourceName && s.Name == name - ).Select(x => x.ResourceKey).ToListAsync(cancellationToken); + ).ToListAsync(cancellationToken); } } From 39b60db9f52c6ea639c4263aaf1184dfc455b75c Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 15 Nov 2025 14:34:44 +0800 Subject: [PATCH 021/130] Refactor permission grant info types in role provider --- .../AbpPermissionManagementDomainIdentityModule.cs | 4 ---- .../Identity/RoleResourcePermissionManagementProvider.cs | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs index 483fae7f20..bd2f0d8324 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs @@ -26,10 +26,6 @@ public class AbpPermissionManagementDomainIdentityModule : AbpModule options.ResourceManagementProviders.Add(); options.ResourceManagementProviders.Add(); - - //TODO: Can we prevent duplication of permission names without breaking the design and making the system complicated - options.ResourceProviderPolicies[UserResourcePermissionValueProvider.ProviderName] = "AbpIdentity.Users.ManagePermissions"; - options.ResourceProviderPolicies[RoleResourcePermissionValueProvider.ProviderName] = "AbpIdentity.Roles.ManagePermissions"; }); } } diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs index 8b77face53..21bc1a1b86 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs @@ -29,18 +29,18 @@ public class RoleResourcePermissionManagementProvider : ResourcePermissionManage UserRoleFinder = userRoleFinder; } - public override async Task CheckAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) + public override async Task CheckAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) { var multipleGrantInfo = await CheckAsync(new[] { name }, resourceName, resourceKey, providerName, providerKey); return multipleGrantInfo.Result.Values.First(); } - public override async Task CheckAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) + public override async Task CheckAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) { using (ResourcePermissionGrantRepository.DisableTracking()) { - var multiplePermissionValueProviderGrantInfo = new MultiplePermissionValueProviderGrantInfo(names); + var multiplePermissionValueProviderGrantInfo = new MultipleResourcePermissionValueProviderGrantInfo(names); var resourcePermissionGrants = new List(); if (providerName == Name) @@ -70,7 +70,7 @@ public class RoleResourcePermissionManagementProvider : ResourcePermissionManage var resourcePermissionGrant = resourcePermissionGrants.FirstOrDefault(x => x.Name == permissionName); if (resourcePermissionGrant != null) { - multiplePermissionValueProviderGrantInfo.Result[permissionName] = new PermissionValueProviderGrantInfo(true, resourcePermissionGrant.ProviderKey); + multiplePermissionValueProviderGrantInfo.Result[permissionName] = new ResourcePermissionValueProviderGrantInfo(true, resourcePermissionGrant.ProviderKey); } } From 25f68775e7269a88c613781a641b7d60476e368c Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 16 Nov 2025 18:28:21 +0800 Subject: [PATCH 022/130] feat(permission-management): add resource permission management features --- .../Pages/Identity/Roles/Index.cshtml | 1 + .../Pages/Identity/Roles/index.js | 14 + .../Pages/Identity/Users/Index.cshtml | 1 + .../Pages/Identity/Users/index.js | 15 +- ...sourcePermissionDefinitionListResultDto.cs | 8 + ...urcePermissionWithProviderListResultDto.cs | 8 + .../IPermissionAppService.cs | 6 + .../ResourcePermissionDefinitionDto.cs | 8 + ...ourcePermissionWithProdiverGrantInfoDto.cs | 10 + .../Volo/Abp/PermissionManagement/Test.cs | 18 - .../TestPermissionDefinitionProvider.cs | 28 ++ .../UpdateResourcePermissionDto.cs | 12 - .../UpdateResourcePermissionsDto.cs | 8 +- .../PermissionAppService.cs | 71 +++- .../IResourcePermissionManager.cs | 3 + .../ResourcePermissionGrant.cs | 8 +- .../ResourcePermissionManager.cs | 44 ++- .../PermissionsClientProxy.Generated.cs | 30 ++ .../permissionManagement-generate-proxy.json | 330 ++++++++++++++++++ .../PermissionsController.cs | 29 +- .../AbpPermissionManagementWebModule.cs | 2 +- ...ddResourcePermissionManagementModal.cshtml | 62 ++++ ...esourcePermissionManagementModal.cshtml.cs | 76 ++++ .../ResourcePermissionManagementModal.cshtml | 31 ++ ...esourcePermissionManagementModal.cshtml.cs | 58 +++ ...teResourcePermissionManagementModal.cshtml | 49 +++ ...esourcePermissionManagementModal.cshtml.cs | 79 +++++ ...dd-resource-permission-management-modal.js | 11 + .../resource-permission-management-modal.js | 126 +++++++ ...te-resource-permission-management-modal.js | 11 + .../permissionManagement-proxy.js | 38 ++ 31 files changed, 1133 insertions(+), 62 deletions(-) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionDefinitionListResultDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionWithProviderListResultDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionDefinitionDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionWithProdiverGrantInfoDto.cs delete mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/Test.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs delete mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/update-resource-permission-management-modal.js diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml index 5b2c767228..6f4a56ba24 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml @@ -28,6 +28,7 @@ + } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js index b83db95758..3430a3b0a0 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js @@ -5,6 +5,10 @@ var _permissionsModal = new abp.ModalManager( abp.appPath + 'AbpPermissionManagement/PermissionManagementModal' ); + var _resourcePermissionsModal = new abp.ModalManager({ + viewUrl: abp.appPath + "AbpPermissionManagement/ResourcePermissionManagementModal", + modalClass: 'ResourcePermissionManagement' + }); var _editModal = new abp.ModalManager( abp.appPath + 'Identity/Roles/EditModal' ); @@ -42,6 +46,16 @@ }); }, }, + { + text: l('ResourcePermissions'), + action: function (data) { + _resourcePermissionsModal.open({ + resourceName: 'Volo.Abp.Identity.IdentityRole', + resourceKey: data.record.name, + resourceDisplayName: data.record.name + '(Volo.Abp.Identity.IdentityRole)' + }); + }, + }, { text: l('Delete'), visible: function (data) { diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml index 31b3751cbd..baff68d720 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml @@ -29,6 +29,7 @@ + } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js index fa63b96af4..56541b5828 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js @@ -50,7 +50,10 @@ var _permissionsModal = new abp.ModalManager( abp.appPath + 'AbpPermissionManagement/PermissionManagementModal' ); - + var _resourcePermissionsModal = new abp.ModalManager({ + viewUrl: abp.appPath + "AbpPermissionManagement/ResourcePermissionManagementModal", + modalClass: 'ResourcePermissionManagement' + }); var _dataTable = null; abp.ui.extensions.entityActions.get('identity.user').addContributor( @@ -81,6 +84,16 @@ }); }, }, + { + text: l('ResourcePermissions'), + action: function (data) { + _resourcePermissionsModal.open({ + resourceName: 'Volo.Abp.Identity.IdentityUser', + resourceKey: data.record.name, + resourceDisplayName: data.record.name + '(Volo.Abp.Identity.IdentityUser)' + }); + }, + }, { text: l('Delete'), visible: function(data) { diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionDefinitionListResultDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionDefinitionListResultDto.cs new file mode 100644 index 0000000000..ea1ce882df --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionDefinitionListResultDto.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.PermissionManagement; + +public class GetResourcePermissionDefinitionListResultDto +{ + public List Permissions { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionWithProviderListResultDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionWithProviderListResultDto.cs new file mode 100644 index 0000000000..dcf4556d04 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourcePermissionWithProviderListResultDto.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.PermissionManagement; + +public class GetResourcePermissionWithProviderListResultDto +{ + public List Permissions { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs index 4344f0d662..99285a17fa 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs @@ -12,7 +12,13 @@ public interface IPermissionAppService : IApplicationService Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdatePermissionsDto input); + Task GetResourceDefinitionsAsync([NotNull] string resourceName); + Task GetResourceAsync([NotNull] string resourceName, [NotNull] string resourceKey); + Task GetResourceByProviderAsync([NotNull] string resourceName, [NotNull] string resourceKey, [NotNull] string providerName, [NotNull] string providerKey); + Task UpdateResourceAsync([NotNull] string resourceName, [NotNull] string resourceKey, UpdateResourcePermissionsDto input); + + Task DeleteResourceAsync([NotNull] string resourceName, [NotNull] string resourceKey, [NotNull] string providerName, [NotNull] string providerKey); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionDefinitionDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionDefinitionDto.cs new file mode 100644 index 0000000000..b8218b562c --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionDefinitionDto.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionDefinitionDto +{ + public string Name { get; set; } + + public string DisplayName { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionWithProdiverGrantInfoDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionWithProdiverGrantInfoDto.cs new file mode 100644 index 0000000000..1f32d6a3c3 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionWithProdiverGrantInfoDto.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionWithProdiverGrantInfoDto +{ + public string Name { get; set; } + + public string DisplayName { get; set; } + + public bool IsGranted { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/Test.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/Test.cs deleted file mode 100644 index deec5ed657..0000000000 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/Test.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Localization; -using Volo.Abp.PermissionManagement.Localization; - -namespace Volo.Abp.PermissionManagement; - -public class TestPermissionDefinitionProvider : PermissionDefinitionProvider -{ - public override void Define(IPermissionDefinitionContext context) - { - context.AddResourcePermission("AbpIdentity.Resource.ChangeName", "Volo.Abp.IdentityUser", L("OnlyProviderPermissons")); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); - } -} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs new file mode 100644 index 0000000000..bc7fdb87f2 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs @@ -0,0 +1,28 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; +using Volo.Abp.PermissionManagement.Localization; + +namespace Volo.Abp.PermissionManagement; + +public class TestPermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + context.AddResourcePermission("PR1", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission1")); + context.AddResourcePermission("PR2", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission2")); + context.AddResourcePermission("PR3", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission3")); + context.AddResourcePermission("PR4", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission4")); + context.AddResourcePermission("PR5", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission5")); + + context.AddResourcePermission("PU1", "Volo.Abp.Identity.IdentityUser", L("User Resource Permission1")); + context.AddResourcePermission("PU2", "Volo.Abp.Identity.IdentityUser", L("User Resource Permission2")); + context.AddResourcePermission("PU3", "Volo.Abp.Identity.IdentityUser", L("User Resource Permission3")); + context.AddResourcePermission("PU4", "Volo.Abp.Identity.IdentityUser", L("User Resource Permission4")); + context.AddResourcePermission("PU5", "VVolo.Abp.Identity.IdentityUser", L("User Resource Permission5")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionDto.cs deleted file mode 100644 index e6ca8fff20..0000000000 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Volo.Abp.PermissionManagement; - -public class UpdateResourcePermissionDto -{ - public string ProviderName { get; set; } - - public string ProviderKey { get; set; } - - public string Name { get; set; } - - public bool IsGranted { get; set; } -} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionsDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionsDto.cs index 3ca137d367..f82c27ff02 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionsDto.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/UpdateResourcePermissionsDto.cs @@ -1,6 +1,12 @@ +using System.Collections.Generic; + namespace Volo.Abp.PermissionManagement; public class UpdateResourcePermissionsDto { - public UpdateResourcePermissionDto[] Permissions { get; set; } + public string ProviderName { get; set; } + + public string ProviderKey { get; set; } + + public List Permissions { get; set; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index a85b9f5e1a..ccfe0a9c2a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -166,24 +166,36 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } - public virtual async Task GetResourceAsync(string resourceName, string resourceKey) + [Authorize] //TODO: Check permission + public virtual async Task GetResourceDefinitionsAsync(string resourceName) { - var result = new GetResourcePermissionListResultDto + var result = new GetResourcePermissionDefinitionListResultDto { - Permissions = new List() + Permissions = new List() }; - var multiTenancySide = CurrentTenant.GetMultiTenancySide(); - var resourcePermissions = new List(); - foreach (var resourcePermission in (await PermissionDefinitionManager.GetResourcePermissionsAsync()) - .Where(x => x.IsEnabled && x.MultiTenancySide.HasFlag(multiTenancySide) && x.ResourceName == resourceName)) + var resourcePermissions = await ResourcePermissionManager.GetAvailableResourcePermissionsAsync(resourceName); + foreach (var resourcePermission in resourcePermissions) { - if (await SimpleStateCheckerManager.IsEnabledAsync(resourcePermission)) + result.Permissions.Add(new ResourcePermissionDefinitionDto() { - resourcePermissions.Add(resourcePermission); - } + Name = resourcePermission.Name, + DisplayName = resourcePermission.DisplayName?.Localize(StringLocalizerFactory), + }); } + return result; + } + + [Authorize] //TODO: Check permission + public virtual async Task GetResourceAsync(string resourceName, string resourceKey) + { + var result = new GetResourcePermissionListResultDto + { + Permissions = new List() + }; + + var resourcePermissions = await ResourcePermissionManager.GetAvailableResourcePermissionsAsync(resourceName); var resourcePermissionGrantsGroup = await ResourcePermissionManager.GetAllGroupAsync(resourceName, resourceKey); foreach (var resourcePermissionGrant in resourcePermissionGrantsGroup) { @@ -209,11 +221,46 @@ public class PermissionAppService : ApplicationService, IPermissionAppService return result; } + [Authorize] //TODO: Check permission + public virtual async Task GetResourceByProviderAsync(string resourceName, string resourceKey, string providerName, string providerKey) + { + var result = new GetResourcePermissionWithProviderListResultDto + { + Permissions = new List() + }; + + var resourcePermissionGrants = await ResourcePermissionManager.GetAllAsync(resourceName, resourceKey, providerName, providerKey); + foreach (var resourcePermission in resourcePermissionGrants) + { + result.Permissions.Add(new ResourcePermissionWithProdiverGrantInfoDto + { + Name = resourcePermission.Name, + DisplayName = (await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName))?.DisplayName?.Localize(StringLocalizerFactory), + IsGranted = resourcePermission.IsGranted + }); + } + + return result; + } + + [Authorize] //TODO: Check permission public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { - foreach (var permissionDto in input.Permissions) + var resourcePermissions = await ResourcePermissionManager.GetAvailableResourcePermissionsAsync(resourceName); + foreach (var resourcePermission in resourcePermissions) + { + var isGranted = !input.Permissions.IsNullOrEmpty() && input.Permissions.Any(p => p == resourcePermission.Name); + await ResourcePermissionManager.SetAsync(resourcePermission.Name, resourceName, resourceKey, input.ProviderName, input.ProviderKey, isGranted); + } + } + + [Authorize] //TODO: Check permission + public virtual async Task DeleteResourceAsync(string resourceName, string resourceKey, string providerName, string providerKey) + { + var resourcePermissions = await ResourcePermissionManager.GetAvailableResourcePermissionsAsync(resourceName); + foreach (var resourcePermission in resourcePermissions) { - await ResourcePermissionManager.SetAsync(permissionDto.Name, resourceName, resourceKey, permissionDto.ProviderName, permissionDto.ProviderKey, permissionDto.IsGranted); + await ResourcePermissionManager.SetAsync(resourcePermission.Name, resourceName, resourceKey, providerName, providerKey, false); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs index b8d8ae91a7..c756289ecb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs @@ -1,10 +1,13 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; namespace Volo.Abp.PermissionManagement; public interface IResourcePermissionManager { + Task> GetAvailableResourcePermissionsAsync(string resourceName); + Task GetAsync( string permissionName, string resourceName, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs index 3d67c2bc53..b74da527ae 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrant.cs @@ -33,20 +33,20 @@ public class ResourcePermissionGrant : Entity, IMultiTenant public ResourcePermissionGrant( Guid id, [NotNull] string name, - [NotNull] string providerName, - [CanBeNull] string providerKey, [NotNull] string resourceName, [NotNull] string resourceKey, + [NotNull] string providerName, + [CanBeNull] string providerKey, Guid? tenantId = null) { Check.NotNull(name, nameof(name)); Id = id; Name = Check.NotNullOrWhiteSpace(name, nameof(name)); - ProviderName = Check.NotNullOrWhiteSpace(providerName, nameof(providerName)); - ProviderKey = Check.NotNullOrWhiteSpace(providerKey, nameof(providerKey)); ResourceName = Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName)); ResourceKey = Check.NotNullOrWhiteSpace(resourceKey, nameof(resourceKey)); + ProviderName = Check.NotNullOrWhiteSpace(providerName, nameof(providerName)); + ProviderKey = Check.NotNullOrWhiteSpace(providerKey, nameof(providerKey)); TenantId = tenantId; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index 9081f40084..b8256cd7d2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -60,6 +60,22 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD ); } + public virtual async Task> GetAvailableResourcePermissionsAsync(string resourceName) + { + var multiTenancySide = CurrentTenant.GetMultiTenancySide(); + var resourcePermissions = new List(); + foreach (var resourcePermission in (await PermissionDefinitionManager.GetResourcePermissionsAsync()) + .Where(x => x.IsEnabled && x.MultiTenancySide.HasFlag(multiTenancySide) && x.ResourceName == resourceName)) + { + if (await SimpleStateCheckerManager.IsEnabledAsync(resourcePermission)) + { + resourcePermissions.Add(resourcePermission); + } + } + + return resourcePermissions; + } + public virtual async Task GetAsync(string permissionName, string resourceName, string resourceKey, string providerName, string providerKey) { var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); @@ -118,7 +134,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD public virtual async Task> GetAllAsync(string resourceName, string resourceKey) { - var resourcePermissionDefinitions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()).Where(x => x.ResourceName == resourceName).ToArray(); + var resourcePermissionDefinitions = await GetAvailableResourcePermissionsAsync(resourceName); var resourcePermissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); var result = new List(); foreach (var resourcePermissionDefinition in resourcePermissionDefinitions) @@ -126,7 +142,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD var permissionWithGrantedProviders = new PermissionWithGrantedProviders(resourcePermissionDefinition.Name, false); var grantedPermissions = resourcePermissionGrants - .Where(x => x.Name == resourcePermissionDefinition.Name) + .Where(x => x.Name == resourcePermissionDefinition.Name && x.ResourceName == resourceName && x.ResourceKey == resourceKey) .ToList(); if (grantedPermissions.Any()) @@ -146,14 +162,18 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD public virtual async Task> GetAllAsync(string resourceName, string resourceKey, string providerName, string providerKey) { - var permissionDefinitions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()).Where(x => x.ResourceName == resourceName).ToArray(); - var multiplePermissionWithGrantedProviders = await GetInternalAsync(permissionDefinitions, resourceName, resourceKey, providerName, providerKey); + var permissionDefinitions = await GetAvailableResourcePermissionsAsync(resourceName); + var multiplePermissionWithGrantedProviders = await GetInternalAsync(permissionDefinitions.ToArray(), resourceName, resourceKey, providerName, providerKey); return multiplePermissionWithGrantedProviders.Result; } public virtual async Task> GetAllGroupAsync(string resourceName, string resourceKey) { + var resourcePermissions = await GetAvailableResourcePermissionsAsync(resourceName); var resourcePermissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); + resourcePermissionGrants = resourcePermissionGrants + .Where(x => resourcePermissions.Any(rp => rp.Name == x.Name)) + .ToList(); var resourcePermissionGrantsGroup = resourcePermissionGrants.GroupBy(x => new { x.ProviderName, x.ProviderKey }); var result = new List(); foreach (var resourcePermissionGrant in resourcePermissionGrantsGroup) @@ -259,10 +279,8 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD var neededCheckPermissions = new List(); - foreach (var permission in permissions - .Where(x => x.IsEnabled) - .Where(x => x.MultiTenancySide.HasFlag(CurrentTenant.GetMultiTenancySide())) - .Where(x => !x.Providers.Any() || x.Providers.Contains(providerName))) + var resourcePermissions = await GetAvailableResourcePermissionsAsync(resourceName); + foreach (var permission in resourcePermissions) { if (await SimpleStateCheckerManager.IsEnabledAsync(permission)) { @@ -285,10 +303,16 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD if (providerResultDict.Value.IsGranted) { var permissionWithGrantedProvider = multiplePermissionWithGrantedProviders.Result - .First(x => x.Name == providerResultDict.Key); + .FirstOrDefault(x => x.Name == providerResultDict.Key); + + if (permissionWithGrantedProvider == null) + { + continue; + } permissionWithGrantedProvider.IsGranted = true; - permissionWithGrantedProvider.Providers.Add(new PermissionValueProviderInfo(provider.Name, providerResultDict.Value.ProviderKey)); + permissionWithGrantedProvider.Providers.Add( + new PermissionValueProviderInfo(provider.Name, providerResultDict.Value.ProviderKey)); } } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs index d22ed1d398..dc9c9ee6ec 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs @@ -46,6 +46,14 @@ public partial class PermissionsClientProxy : ClientProxyBase GetResourceDefinitionsAsync(string resourceName) + { + return await RequestAsync(nameof(GetResourceDefinitionsAsync), new ClientProxyRequestTypeValue + { + { typeof(string), resourceName } + }); + } + public virtual async Task GetResourceAsync(string resourceName, string resourceKey) { return await RequestAsync(nameof(GetResourceAsync), new ClientProxyRequestTypeValue @@ -55,6 +63,17 @@ public partial class PermissionsClientProxy : ClientProxyBase GetResourceByProviderAsync(string resourceName, string resourceKey, string providerName, string providerKey) + { + return await RequestAsync(nameof(GetResourceByProviderAsync), new ClientProxyRequestTypeValue + { + { typeof(string), resourceName }, + { typeof(string), resourceKey }, + { typeof(string), providerName }, + { typeof(string), providerKey } + }); + } + public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { await RequestAsync(nameof(UpdateResourceAsync), new ClientProxyRequestTypeValue @@ -64,4 +83,15 @@ public partial class PermissionsClientProxy : ClientProxyBase GetResourceDefinitionsAsync(string resourceName) + { + return PermissionAppService.GetResourceDefinitionsAsync(resourceName); + } + + [HttpGet] + [Route("resource")] public virtual Task GetResourceAsync(string resourceName, string resourceKey) { return PermissionAppService.GetResourceAsync(resourceName, resourceKey); } - [HttpPut("resource")] + [HttpGet] + [Route("resource/by-provider")] + public virtual Task GetResourceByProviderAsync(string resourceName, string resourceKey, string providerName, string providerKey) + { + return PermissionAppService.GetResourceByProviderAsync(resourceName, resourceKey, providerName, providerKey); + } + + [HttpPut] + [Route("resource")] public virtual Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { return PermissionAppService.UpdateResourceAsync(resourceName, resourceKey, input); } + + [HttpDelete] + [Route("resource")] + public virtual Task DeleteResourceAsync(string resourceName, string resourceKey, string providerName, string providerKey) + { + return PermissionAppService.DeleteResourceAsync(resourceName, resourceKey, providerName, providerKey); + } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebModule.cs index baa27b44ff..e20e38c53a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebModule.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebModule.cs @@ -20,7 +20,7 @@ public class AbpPermissionManagementWebModule : AbpModule { options.AddAssemblyResource( typeof(AbpPermissionManagementResource), - typeof(AbpPermissionManagementWebModule).Assembly, + typeof(AbpPermissionManagementWebModule).Assembly, typeof(AbpPermissionManagementApplicationContractsModule).Assembly ); }); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml new file mode 100644 index 0000000000..060fa5e2d5 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -0,0 +1,62 @@ +@page +@using System.Web; +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Localization +@using Volo.Abp.PermissionManagement.Localization +@using Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement +@model AddResourcePermissionManagementModal +@inject IHtmlLocalizer L + +@{ + Layout = null; +} + + + + +
+ + + + + + +
+ +

@(HttpUtility.HtmlEncode(Model.ResourceDisplayName))

+
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+
+ +
+ + +
+
+ @foreach (var permission in Model.ResourcePermissionDefinitions.Permissions) + { +
+ + +
+ } +
+
+ +
+
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs new file mode 100644 index 0000000000..4f4fe632a1 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement; + +public class AddResourcePermissionManagementModal : AbpPageModel +{ + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ResourceName { get; set; } + + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ResourceKey { get; set; } + + [BindProperty(SupportsGet = true)] + [HiddenInput] + public string ResourceDisplayName { get; set; } + + [BindProperty] + public ResourcePermissionViewModel AddModel { get; set; } + + public GetResourcePermissionDefinitionListResultDto ResourcePermissionDefinitions { get; set; } + + protected IPermissionAppService PermissionAppService { get; } + + public AddResourcePermissionManagementModal(IPermissionAppService permissionAppService) + { + ObjectMapperContext = typeof(AbpPermissionManagementWebModule); + + PermissionAppService = permissionAppService; + } + + public virtual async Task OnGetAsync() + { + ValidateModel(); + + ResourcePermissionDefinitions = await PermissionAppService.GetResourceDefinitionsAsync(ResourceName); + + return Page(); + } + + public virtual async Task OnPostAsync() + { + ValidateModel(); + + await PermissionAppService.UpdateResourceAsync( + ResourceName, + ResourceKey, + new UpdateResourcePermissionsDto() + { + ProviderName = AddModel.ProviderName, + ProviderKey = AddModel.ProviderKey, + Permissions = AddModel.Permissions ?? new List() + } + ); + + return NoContent(); + } + + public class ResourcePermissionViewModel + { + [Required] + public string ProviderName { get; set; } + + [Required] + public string ProviderKey { get; set; } + + public List Permissions { get; set; } + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml new file mode 100644 index 0000000000..d1aeae69fe --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml @@ -0,0 +1,31 @@ +@page +@using System.Web; +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Localization +@using Volo.Abp.PermissionManagement.Localization +@using Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement +@model ResourcePermissionManagementModal +@inject IHtmlLocalizer L + +@{ + Layout = null; +} + + + +
+ + + + + + +
+ +
+ +
+ +
+
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs new file mode 100644 index 0000000000..fe9425069b --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement; + +public class ResourcePermissionManagementModal : AbpPageModel +{ + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ResourceName { get; set; } + + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ResourceKey { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ResourceDisplayName { get; set; } + + public GetResourcePermissionListResultDto ResourcePermissions { get; set; } + + protected IPermissionAppService PermissionAppService { get; } + + public ResourcePermissionManagementModal(IPermissionAppService permissionAppService) + { + ObjectMapperContext = typeof(AbpPermissionManagementWebModule); + + PermissionAppService = permissionAppService; + } + + public virtual Task OnGetAsync() + { + ValidateModel(); + return Task.FromResult(Page()); + } + + public virtual async Task OnPostAsync() + { + ValidateModel(); + + + return NoContent(); + } + + public class ResourcePermissionViewModel + { + public string ProviderName { get; set; } + + public string ProviderKey { get; set; } + + public List Permissions { get; set; } + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml new file mode 100644 index 0000000000..ea8ac1164b --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml @@ -0,0 +1,49 @@ +@page +@using System.Web; +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Localization +@using Volo.Abp.PermissionManagement.Localization +@using Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement +@model UpdateResourcePermissionManagementModal +@inject IHtmlLocalizer L + +@{ + Layout = null; +} + + + + +
+ + + + + + + +
+ +

@(HttpUtility.HtmlEncode(Model.ResourceDisplayName))

+
+
+
+ +
+ + +
+
+ @foreach (var permission in Model.ResourcePermissions.Permissions) + { +
+ + +
+ } +
+
+ +
+
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml.cs new file mode 100644 index 0000000000..db2e661993 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement; + +public class UpdateResourcePermissionManagementModal : AbpPageModel +{ + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ResourceName { get; set; } + + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ResourceKey { get; set; } + + [BindProperty(SupportsGet = true)] + public string ResourceDisplayName { get; set; } + + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ProviderName { get; set; } + + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ProviderKey { get; set; } + + [BindProperty(SupportsGet = true)] + public ResourcePermissionViewModel UpdateModel { get; set; } + + public GetResourcePermissionWithProviderListResultDto ResourcePermissions { get; set; } + + protected IPermissionAppService PermissionAppService { get; } + + public UpdateResourcePermissionManagementModal(IPermissionAppService permissionAppService) + { + ObjectMapperContext = typeof(AbpPermissionManagementWebModule); + + PermissionAppService = permissionAppService; + } + + public virtual async Task OnGetAsync() + { + ValidateModel(); + + ResourcePermissions = await PermissionAppService.GetResourceByProviderAsync(ResourceName, ResourceKey, ProviderName, ProviderKey); + + return Page(); + } + + public virtual async Task OnPostAsync() + { + ValidateModel(); + + await PermissionAppService.UpdateResourceAsync( + ResourceName, + ResourceKey, + new UpdateResourcePermissionsDto() + { + ProviderName = ProviderName, + ProviderKey = ProviderKey, + Permissions = UpdateModel.Permissions ?? new List() + } + ); + + return NoContent(); + } + + public class ResourcePermissionViewModel + { + public List Permissions { get; set; } + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js new file mode 100644 index 0000000000..d1e1f02fa4 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -0,0 +1,11 @@ +var abp = abp || {}; +(function ($) { + var $all = $("#grantAllresourcePermissions"); + var $items = $("#permissionList input[type='checkbox']").not("#grantAllresourcePermissions"); + $all.on("change", function () { + $items.prop("checked", this.checked); + }); + $items.on("change", function () { + $all.prop("checked", $items.length === $items.filter(":checked").length); + }); +})(jQuery); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js new file mode 100644 index 0000000000..4ed6d99f12 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js @@ -0,0 +1,126 @@ +var abp = abp || {}; +(function ($) { + var l = abp.localization.getResource('AbpPermissionManagement'); + var _dataTable = null; + + abp.ui.extensions.entityActions.get('permissionManagement.resource').addContributor( + function (actionList) { + return actionList.addManyTail( + [ + { + text: l('Edit'), + action: function (data) { + var _updateResourcePermissionsModal = new abp.ModalManager(abp.appPath + "AbpPermissionManagement/UpdateResourcePermissionManagementModal"); + _updateResourcePermissionsModal.open({ + resourceName: $("#ResourceName").val(), + resourceKey: $("#ResourceKey").val(), + resourceDisplayName: $("#ResourceDisplayName").val(), + providerName: data.record.providerName, + providerKey: data.record.providerKey + }); + _updateResourcePermissionsModal.onResult(function () { + _dataTable.ajax.reloadEx(); + }); + }, + }, + { + text: l('Delete'), + confirmMessage: function (data) { + return l( + 'ResourcePermissionDeletionConfirmationMessage', + data.record.name + ); + }, + action: function (data) { + volo.abp.permissionManagement.permissions.deleteResource($("#ResourceName").val(), $("#ResourceKey").val(), data.record.providerName, data.record.providerKey).then(function () { + abp.notify.info(l('SuccessfullyDeleted')); + _dataTable.ajax.reloadEx(); + }); + }, + } + ] + ); + } + ); + + abp.ui.extensions.tableColumns.get('permissionManagement.resource').addContributor( + function (columnList) { + columnList.addManyTail( + [ + { + title: l("Actions"), + rowAction: { + items: abp.ui.extensions.entityActions.get('permissionManagement.resource').actions.toArray() + } + }, + { + title: l("Target"), + data: 'providerName', + render: function (data, type, row) { + return row.providerName + ' (' + row.providerKey + ')'; + }, + }, + { + title: l("Permissions"), + data: 'permissions', + render: function (data, type, row) { + var spans = ''; + for (var i = 0; i < row.permissions.length; i++) { + spans += '' + row.permissions[i].name + ''; + } + return spans; + }, + } + ] + ); + }, + 0 //adds as the first contributor + ); + + abp.modals = abp.modals || {}; + + abp.modals.ResourcePermissionManagement = function () { + var initModal = function (publicApi, args) { + _dataTable = $('#resourcePermissionTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + order: [], + searching: false, + processing: true, + scrollX: true, + serverSide: true, + paging: true, + ajax: function () { + return function (requestData, callback, settings) { + if (callback) { + volo.abp.permissionManagement.permissions.getResource(args.resourceName, args.resourceKey).then(function (result) { + callback({ + recordsTotal: result.permissions.length, + recordsFiltered: result.permissions.length, + data: result.permissions + }); + }); + } + } + }(), + columnDefs: abp.ui.extensions.tableColumns.get('permissionManagement.resource').columns.toArray(), + }) + ); + + $("#addPermission").click(function () { + var _addResourcePermissionsModal = new abp.ModalManager(abp.appPath + "AbpPermissionManagement/AddResourcePermissionManagementModal"); + _addResourcePermissionsModal.open({ + resourceName: $("#ResourceName").val(), + resourceKey: $("#ResourceKey").val(), + resourceDisplayName: $("#ResourceDisplayName").val() + }); + _addResourcePermissionsModal.onResult(function () { + _dataTable.ajax.reloadEx(); + }); + }); + }; + + return { + initModal: initModal, + }; + }; +})(jQuery); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/update-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/update-resource-permission-management-modal.js new file mode 100644 index 0000000000..d1e1f02fa4 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/update-resource-permission-management-modal.js @@ -0,0 +1,11 @@ +var abp = abp || {}; +(function ($) { + var $all = $("#grantAllresourcePermissions"); + var $items = $("#permissionList input[type='checkbox']").not("#grantAllresourcePermissions"); + $all.on("change", function () { + $items.prop("checked", this.checked); + }); + $items.on("change", function () { + $all.prop("checked", $items.length === $items.filter(":checked").length); + }); +})(jQuery); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js index a296877917..9a0529c67e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js @@ -34,6 +34,44 @@ }, ajaxParams)); }; + volo.abp.permissionManagement.permissions.getResourceDefinitions = function(resourceName, ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/permission-management/permissions/resource-definitions' + abp.utils.buildQueryString([{ name: 'resourceName', value: resourceName }]) + '', + type: 'GET' + }, ajaxParams)); + }; + + volo.abp.permissionManagement.permissions.getResource = function(resourceName, resourceKey, ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/permission-management/permissions/resource' + abp.utils.buildQueryString([{ name: 'resourceName', value: resourceName }, { name: 'resourceKey', value: resourceKey }]) + '', + type: 'GET' + }, ajaxParams)); + }; + + volo.abp.permissionManagement.permissions.getResourceByProvider = function(resourceName, resourceKey, providerName, providerKey, ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/permission-management/permissions/resource/by-provider' + abp.utils.buildQueryString([{ name: 'resourceName', value: resourceName }, { name: 'resourceKey', value: resourceKey }, { name: 'providerName', value: providerName }, { name: 'providerKey', value: providerKey }]) + '', + type: 'GET' + }, ajaxParams)); + }; + + volo.abp.permissionManagement.permissions.updateResource = function(resourceName, resourceKey, input, ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/permission-management/permissions/resource' + abp.utils.buildQueryString([{ name: 'resourceName', value: resourceName }, { name: 'resourceKey', value: resourceKey }]) + '', + type: 'PUT', + dataType: null, + data: JSON.stringify(input) + }, ajaxParams)); + }; + + volo.abp.permissionManagement.permissions.deleteResource = function(resourceName, resourceKey, providerName, providerKey, ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/permission-management/permissions/resource' + abp.utils.buildQueryString([{ name: 'resourceName', value: resourceName }, { name: 'resourceKey', value: resourceKey }, { name: 'providerName', value: providerName }, { name: 'providerKey', value: providerKey }]) + '', + type: 'DELETE', + dataType: null + }, ajaxParams)); + }; + })(); })(); From 1398c8d67b15fb01e14fc483b2568019483df48c Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 17 Nov 2025 11:52:27 +0800 Subject: [PATCH 023/130] fix(permission-management): validate user ID parsing in role permission providers --- .../Identity/RolePermissionManagementProvider.cs | 3 +-- .../Identity/RoleResourcePermissionManagementProvider.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RolePermissionManagementProvider.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RolePermissionManagementProvider.cs index e97d524cd7..5cd2dda193 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RolePermissionManagementProvider.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RolePermissionManagementProvider.cs @@ -49,9 +49,8 @@ public class RolePermissionManagementProvider : PermissionManagementProvider } - if (providerName == UserPermissionValueProvider.ProviderName) + if (providerName == UserPermissionValueProvider.ProviderName && Guid.TryParse(providerKey, out var userId)) { - var userId = Guid.Parse(providerKey); var roleNames = await UserRoleFinder.GetRoleNamesAsync(userId); foreach (var roleName in roleNames) diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs index 21bc1a1b86..bce070130c 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionManagementProvider.cs @@ -48,9 +48,8 @@ public class RoleResourcePermissionManagementProvider : ResourcePermissionManage resourcePermissionGrants.AddRange(await ResourcePermissionGrantRepository.GetListAsync(names, resourceName, resourceKey, providerName, providerKey)); } - if (providerName == UserResourcePermissionValueProvider.ProviderName) + if (providerName == UserResourcePermissionValueProvider.ProviderName && Guid.TryParse(providerKey, out var userId)) { - var userId = Guid.Parse(providerKey); var roleNames = await UserRoleFinder.GetRoleNamesAsync(userId); foreach (var roleName in roleNames) From 4b96a90b2d89e5f49288805bdb5190a1e0850f85 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 17 Nov 2025 20:40:29 +0800 Subject: [PATCH 024/130] feat: Enhance user and role management with search capabilities and resource provider integration --- .../IIdentityUserIntegrationService.cs | 8 +- .../Abp/Identity/RoleLookupCountInputDto.cs | 6 + .../Abp/Identity/RoleLookupSearchInputDto.cs | 8 + .../IdentityUserIntegrationService.cs | 20 +- .../Volo/Abp/Identity/IUserRoleFinder.cs | 7 +- .../Volo/Abp/Identity/Localization/en.json | 4 +- .../Volo/Abp/Identity/RoleFinderResult.cs | 10 + .../Volo/Abp/Identity/UserFinderResult.cs | 10 + .../Volo/Abp/Identity/UserRoleFinder.cs | 35 ++- ...ityUserIntegrationClientProxy.Generated.cs | 16 ++ .../identity-generate-proxy.json | 204 ++++++++++++++++++ .../Abp/Identity/HttpClientUserRoleFinder.cs | 30 ++- .../IdentityUserIntegrationController.cs | 18 +- ...ermissionManagementDomainIdentityModule.cs | 3 + ...ourcePermissionProviderKeyLookupService.cs | 31 +++ ...ourcePermissionProviderKeyLookupService.cs | 31 +++ .../GetResourceProviderListResultDto.cs | 8 + .../IPermissionAppService.cs | 4 + .../ResourceProviderDto.cs | 8 + .../SearchProviderKeyInfo.cs | 8 + .../SearchProviderKeyListResultDto.cs | 8 + .../PermissionAppService.cs | 39 +++- .../IResourcePermissionManager.cs | 6 +- ...ourcePermissionProviderKeyLookupService.cs | 15 ++ .../PermissionManagementOptions.cs | 5 +- .../ResourcePermissionManager.cs | 33 ++- .../ResourcePermissionProviderKeyInfo.cs | 16 ++ .../PermissionsClientProxy.Generated.cs | 14 ++ .../permissionManagement-generate-proxy.json | 105 +++++++++ .../PermissionsController.cs | 17 +- ...ddResourcePermissionManagementModal.cshtml | 15 +- ...esourcePermissionManagementModal.cshtml.cs | 2 + .../permissionManagement-proxy.js | 14 ++ .../Volo/Abp/Users/IRoleData.cs | 19 ++ .../Volo/Abp/Users/RoleData.cs | 57 +++++ 35 files changed, 801 insertions(+), 33 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/RoleLookupCountInputDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/RoleLookupSearchInputDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/RoleFinderResult.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/UserFinderResult.cs create mode 100644 modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs create mode 100644 modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourceProviderListResultDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourceProviderDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/SearchProviderKeyInfo.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/SearchProviderKeyListResultDto.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionProviderKeyInfo.cs create mode 100644 modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IRoleData.cs create mode 100644 modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs index 815c1c48c9..951a7bf401 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.Identity.Integration; public interface IIdentityUserIntegrationService : IApplicationService { Task GetRoleNamesAsync(Guid id); - + Task FindByIdAsync(Guid id); Task FindByUserNameAsync(string userName); @@ -18,4 +18,8 @@ public interface IIdentityUserIntegrationService : IApplicationService Task> SearchAsync(UserLookupSearchInputDto input); Task GetCountAsync(UserLookupCountInputDto input); -} \ No newline at end of file + + Task> SearchRoleAsync(RoleLookupSearchInputDto input); + + Task GetRoleCountAsync(RoleLookupCountInputDto input); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/RoleLookupCountInputDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/RoleLookupCountInputDto.cs new file mode 100644 index 0000000000..875d350f1c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/RoleLookupCountInputDto.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Identity; + +public class RoleLookupCountInputDto +{ + public string Filter { get; set; } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/RoleLookupSearchInputDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/RoleLookupSearchInputDto.cs new file mode 100644 index 0000000000..ebc1707055 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/RoleLookupSearchInputDto.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.Identity; + +public class RoleLookupSearchInputDto : ExtensiblePagedAndSortedResultRequestDto +{ + public string Filter { get; set; } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs index 37446ce1f3..491d495a97 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; +using Volo.Abp.Domain.Repositories; using Volo.Abp.Users; namespace Volo.Abp.Identity.Integration; @@ -10,13 +11,16 @@ public class IdentityUserIntegrationService : IdentityAppServiceBase, IIdentityU { protected IUserRoleFinder UserRoleFinder { get; } protected IdentityUserRepositoryExternalUserLookupServiceProvider UserLookupServiceProvider { get; } + protected IIdentityRoleRepository RoleRepository { get; } public IdentityUserIntegrationService( IUserRoleFinder userRoleFinder, - IdentityUserRepositoryExternalUserLookupServiceProvider userLookupServiceProvider) + IdentityUserRepositoryExternalUserLookupServiceProvider userLookupServiceProvider, + IIdentityRoleRepository roleRepository) { UserRoleFinder = userRoleFinder; UserLookupServiceProvider = userLookupServiceProvider; + RoleRepository = roleRepository; } public virtual async Task GetRoleNamesAsync(Guid id) @@ -66,4 +70,18 @@ public class IdentityUserIntegrationService : IdentityAppServiceBase, IIdentityU { return await UserLookupServiceProvider.GetCountAsync(input.Filter); } + + public virtual async Task> SearchRoleAsync(RoleLookupSearchInputDto input) + { + using (RoleRepository.DisableTracking()) + { + var roles = await RoleRepository.GetListAsync(input.Filter); + return new ListResultDto(roles.Select(r => new RoleData(r.Id, r.Name, r.IsDefault, r.IsStatic, r.IsPublic, r.TenantId, r.ExtraProperties)).ToList()); + } + } + + public virtual async Task GetRoleCountAsync(RoleLookupCountInputDto input) + { + return await RoleRepository.GetCountAsync(input.Filter); + } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs index 1835f38952..ef7ec9bedd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Volo.Abp.Identity; @@ -7,6 +8,10 @@ public interface IUserRoleFinder { [Obsolete("Use GetRoleNamesAsync instead.")] Task GetRolesAsync(Guid userId); - + Task GetRoleNamesAsync(Guid userId); + + Task> SearchUserAsync(string filter); + + Task> SearchRoleAsync(string filter); } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index f2cc74cef0..0deb6a4b56 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -135,6 +135,8 @@ "ModificationTime": "Modification time", "PasswordUpdateTime": "Password update time", "LockoutEndTime": "Lockout end time", - "FailedAccessCount": "Failed access count" + "FailedAccessCount": "Failed access count", + "UserResourcePermissionProviderKeyLookupService": "User", + "RoleResourcePermissionProviderKeyLookupService": "Role" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/RoleFinderResult.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/RoleFinderResult.cs new file mode 100644 index 0000000000..8fe118f13d --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/RoleFinderResult.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Identity; + +public class RoleFinderResult +{ + public Guid Id { get; set; } + + public string RoleName { get; set; } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/UserFinderResult.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/UserFinderResult.cs new file mode 100644 index 0000000000..48ba5ace7a --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/UserFinderResult.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Identity; + +public class UserFinderResult +{ + public Guid Id { get; set; } + + public string UserName { get; set; } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs index 3ec1b2fbb1..06a1d2f308 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs @@ -1,16 +1,21 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; namespace Volo.Abp.Identity; public class UserRoleFinder : IUserRoleFinder, ITransientDependency { protected IIdentityUserRepository IdentityUserRepository { get; } + protected IIdentityRoleRepository IdentityRoleRepository { get; } - public UserRoleFinder(IIdentityUserRepository identityUserRepository) + public UserRoleFinder(IIdentityUserRepository identityUserRepository, IIdentityRoleRepository identityRoleRepository) { IdentityUserRepository = identityUserRepository; + IdentityRoleRepository = identityRoleRepository; } [Obsolete("Use GetRoleNamesAsync instead.")] @@ -19,8 +24,34 @@ public class UserRoleFinder : IUserRoleFinder, ITransientDependency return (await IdentityUserRepository.GetRoleNamesAsync(userId)).ToArray(); } - public async Task GetRoleNamesAsync(Guid userId) + public virtual async Task GetRoleNamesAsync(Guid userId) { return (await IdentityUserRepository.GetRoleNamesAsync(userId)).ToArray(); } + + public virtual async Task> SearchUserAsync(string filter) + { + using (IdentityUserRepository.DisableTracking()) + { + var users = await IdentityUserRepository.GetListAsync(filter: filter); + return users.Select(user => new UserFinderResult + { + Id = user.Id, + UserName = user.UserName + }).ToList(); + } + } + + public virtual async Task> SearchRoleAsync(string filter) + { + using (IdentityUserRepository.DisableTracking()) + { + var roles = await IdentityRoleRepository.GetListAsync(filter: filter); + return roles.Select(user => new RoleFinderResult + { + Id = user.Id, + RoleName = user.Name + }).ToList(); + } + } } diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs index 1c2f1b61cb..bebd7ee37a 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs @@ -59,4 +59,20 @@ public partial class IdentityUserIntegrationClientProxy : ClientProxyBase> SearchRoleAsync(RoleLookupSearchInputDto input) + { + return await RequestAsync>(nameof(SearchRoleAsync), new ClientProxyRequestTypeValue + { + { typeof(RoleLookupSearchInputDto), input } + }); + } + + public virtual async Task GetRoleCountAsync(RoleLookupCountInputDto input) + { + return await RequestAsync(nameof(GetRoleCountAsync), new ClientProxyRequestTypeValue + { + { typeof(RoleLookupCountInputDto), input } + }); + } } diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json index 33efd0a6e7..e1337bd3f0 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json @@ -200,6 +200,18 @@ "constraintTypes": null, "bindingSourceId": "ModelBinding", "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "ExtraProperties", + "jsonName": null, + "type": "Volo.Abp.Data.ExtraPropertyDictionary", + "typeSimple": "{string:object}", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" } ], "returnValue": { @@ -673,6 +685,18 @@ "constraintTypes": null, "bindingSourceId": "ModelBinding", "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "ExtraProperties", + "jsonName": null, + "type": "Volo.Abp.Data.ExtraPropertyDictionary", + "typeSimple": "{string:object}", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" } ], "returnValue": { @@ -1220,6 +1244,18 @@ "constraintTypes": null, "bindingSourceId": "ModelBinding", "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "ExtraProperties", + "jsonName": null, + "type": "Volo.Abp.Data.ExtraPropertyDictionary", + "typeSimple": "{string:object}", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" } ], "returnValue": { @@ -1364,6 +1400,40 @@ "type": "System.Int64", "typeSimple": "number" } + }, + { + "name": "SearchRoleAsync", + "parametersOnMethod": [ + { + "name": "input", + "typeAsString": "Volo.Abp.Identity.RoleLookupSearchInputDto, Volo.Abp.Identity.Application.Contracts", + "type": "Volo.Abp.Identity.RoleLookupSearchInputDto", + "typeSimple": "Volo.Abp.Identity.RoleLookupSearchInputDto", + "isOptional": false, + "defaultValue": null + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + } + }, + { + "name": "GetRoleCountAsync", + "parametersOnMethod": [ + { + "name": "input", + "typeAsString": "Volo.Abp.Identity.RoleLookupCountInputDto, Volo.Abp.Identity.Application.Contracts", + "type": "Volo.Abp.Identity.RoleLookupCountInputDto", + "typeSimple": "Volo.Abp.Identity.RoleLookupCountInputDto", + "isOptional": false, + "defaultValue": null + } + ], + "returnValue": { + "type": "System.Int64", + "typeSimple": "number" + } } ] } @@ -1544,6 +1614,18 @@ "constraintTypes": null, "bindingSourceId": "ModelBinding", "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "ExtraProperties", + "jsonName": null, + "type": "Volo.Abp.Data.ExtraPropertyDictionary", + "typeSimple": "{string:object}", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" } ], "returnValue": { @@ -1589,6 +1671,128 @@ }, "allowAnonymous": null, "implementFrom": "Volo.Abp.Identity.Integration.IIdentityUserIntegrationService" + }, + "SearchRoleAsyncByInput": { + "uniqueName": "SearchRoleAsyncByInput", + "name": "SearchRoleAsync", + "httpMethod": "GET", + "url": "integration-api/identity/users/search/roles", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "input", + "typeAsString": "Volo.Abp.Identity.RoleLookupSearchInputDto, Volo.Abp.Identity.Application.Contracts", + "type": "Volo.Abp.Identity.RoleLookupSearchInputDto", + "typeSimple": "Volo.Abp.Identity.RoleLookupSearchInputDto", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "input", + "name": "Filter", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "Sorting", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "SkipCount", + "jsonName": null, + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "MaxResultCount", + "jsonName": null, + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "ExtraProperties", + "jsonName": null, + "type": "Volo.Abp.Data.ExtraPropertyDictionary", + "typeSimple": "{string:object}", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.Identity.Integration.IIdentityUserIntegrationService" + }, + "GetRoleCountAsyncByInput": { + "uniqueName": "GetRoleCountAsyncByInput", + "name": "GetRoleCountAsync", + "httpMethod": "GET", + "url": "integration-api/identity/users/count/roles", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "input", + "typeAsString": "Volo.Abp.Identity.RoleLookupCountInputDto, Volo.Abp.Identity.Application.Contracts", + "type": "Volo.Abp.Identity.RoleLookupCountInputDto", + "typeSimple": "Volo.Abp.Identity.RoleLookupCountInputDto", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "input", + "name": "Filter", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + } + ], + "returnValue": { + "type": "System.Int64", + "typeSimple": "number" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.Identity.Integration.IIdentityUserIntegrationService" } } } diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs index 201c9f3eb0..f9c00d6053 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -25,8 +26,35 @@ public class HttpClientUserRoleFinder : IUserRoleFinder, ITransientDependency return output.Items.Select(r => r.Name).ToArray(); } - public async Task GetRoleNamesAsync(Guid userId) + public virtual async Task GetRoleNamesAsync(Guid userId) { return await _userIntegrationService.GetRoleNamesAsync(userId); } + + public virtual async Task> SearchUserAsync(string filter) + { + var users = await _userIntegrationService.SearchAsync(new UserLookupSearchInputDto() + { + Filter = filter + }); + return users.Items.Select(u => new UserFinderResult + { + Id = u.Id, + UserName = u.UserName + }).ToList(); + } + + public virtual async Task> SearchRoleAsync(string filter) + { + var roles = await _userIntegrationService.SearchRoleAsync(new RoleLookupSearchInputDto() + { + Filter = filter + }); + + return roles.Items.Select(r => new RoleFinderResult + { + Id = r.Id, + RoleName = r.Name + }).ToList(); + } } diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs index 2a84385e48..8fea364374 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs @@ -20,7 +20,7 @@ public class IdentityUserIntegrationController : AbpControllerBase, IIdentityUse { UserIntegrationService = userIntegrationService; } - + [HttpGet] [Route("{id}/role-names")] public virtual Task GetRoleNamesAsync(Guid id) @@ -55,4 +55,18 @@ public class IdentityUserIntegrationController : AbpControllerBase, IIdentityUse { return UserIntegrationService.GetCountAsync(input); } -} \ No newline at end of file + + [HttpGet] + [Route("search/roles")] + public virtual Task> SearchRoleAsync(RoleLookupSearchInputDto input) + { + return UserIntegrationService.SearchRoleAsync(input); + } + + [HttpGet] + [Route("count/roles")] + public virtual Task GetRoleCountAsync(RoleLookupCountInputDto input) + { + return UserIntegrationService.GetRoleCountAsync(input); + } +} diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs index bd2f0d8324..43399a134d 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/AbpPermissionManagementDomainIdentityModule.cs @@ -26,6 +26,9 @@ public class AbpPermissionManagementDomainIdentityModule : AbpModule options.ResourceManagementProviders.Add(); options.ResourceManagementProviders.Add(); + + options.ResourcePermissionProviderKeyLookupServices.Add(); + options.ResourcePermissionProviderKeyLookupServices.Add(); }); } } diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs new file mode 100644 index 0000000000..93501cb83f --- /dev/null +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; + +namespace Volo.Abp.PermissionManagement.Identity; + +public class RoleResourcePermissionProviderKeyLookupService : IResourcePermissionProviderKeyLookupService, ITransientDependency +{ + public string Name => "Role"; + + public ILocalizableString DisplayName { get; } + + protected IUserRoleFinder UserRoleFinder { get; } + + public RoleResourcePermissionProviderKeyLookupService(IUserRoleFinder userRoleFinder) + { + UserRoleFinder = userRoleFinder; + DisplayName = LocalizableString.Create(nameof(RoleResourcePermissionProviderKeyLookupService)); + } + + public virtual async Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default) + { + var roles = await UserRoleFinder.SearchRoleAsync(filter); + return roles.Select(r => new ResourcePermissionProviderKeyInfo(r.RoleName, r.RoleName)).ToList(); + } +} diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs new file mode 100644 index 0000000000..fd2b9cffc0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; + +namespace Volo.Abp.PermissionManagement.Identity; + +public class UserResourcePermissionProviderKeyLookupService : IResourcePermissionProviderKeyLookupService, ITransientDependency +{ + public string Name => "User"; + + public ILocalizableString DisplayName { get; } + + protected IUserRoleFinder UserRoleFinder { get; } + + public UserResourcePermissionProviderKeyLookupService(IUserRoleFinder userRoleFinder) + { + UserRoleFinder = userRoleFinder; + DisplayName = LocalizableString.Create(nameof(UserResourcePermissionProviderKeyLookupService)); + } + + public virtual async Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default) + { + var users = await UserRoleFinder.SearchUserAsync(filter); + return users.Select(u => new ResourcePermissionProviderKeyInfo(u.Id.ToString(), u.UserName)).ToList(); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourceProviderListResultDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourceProviderListResultDto.cs new file mode 100644 index 0000000000..ede31a6ca5 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/GetResourceProviderListResultDto.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.PermissionManagement; + +public class GetResourceProviderListResultDto +{ + public List Providers { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs index 99285a17fa..1e4f5d54ac 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs @@ -12,6 +12,10 @@ public interface IPermissionAppService : IApplicationService Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdatePermissionsDto input); + Task GetResourceProviderKeyLookupServicesAsync(); + + Task SearchResourceProviderKeyAsync(string serviceName, string filter); + Task GetResourceDefinitionsAsync([NotNull] string resourceName); Task GetResourceAsync([NotNull] string resourceName, [NotNull] string resourceKey); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourceProviderDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourceProviderDto.cs new file mode 100644 index 0000000000..98ee22c6b7 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourceProviderDto.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.PermissionManagement; + +public class ResourceProviderDto +{ + public string Name { get; set; } + + public string DisplayName { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/SearchProviderKeyInfo.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/SearchProviderKeyInfo.cs new file mode 100644 index 0000000000..efc6ae2b18 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/SearchProviderKeyInfo.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.PermissionManagement; + +public class SearchProviderKeyInfo +{ + public string ProviderKey { get; set; } + + public string ProviderDisplayName { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/SearchProviderKeyListResultDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/SearchProviderKeyListResultDto.cs new file mode 100644 index 0000000000..7fe4edd6e7 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/SearchProviderKeyListResultDto.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.PermissionManagement; + +public class SearchProviderKeyListResultDto +{ + public List Keys { get; set; } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index ccfe0a9c2a..adcd7ea0f9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -166,6 +166,35 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } + [Authorize] //TODO: Check permission + public virtual async Task GetResourceProviderKeyLookupServicesAsync() + { + var lookupServices = await ResourcePermissionManager.GetProviderKeyLookupServicesAsync(); + return new GetResourceProviderListResultDto + { + Providers = lookupServices.Select(s => new ResourceProviderDto + { + Name = s.Name, + DisplayName = s.DisplayName.Localize(StringLocalizerFactory), + }).ToList() + }; + } + + [Authorize] //TODO: Check permission + public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter) + { + var lookupService = await ResourcePermissionManager.GetProviderKeyLookupServiceAsync(serviceName); + var keys = await lookupService.SearchAsync(filter); + return new SearchProviderKeyListResultDto + { + Keys = keys.Select(x => new SearchProviderKeyInfo + { + ProviderKey = x.ProviderKey, + ProviderDisplayName = x.ProviderDisplayName, + }).ToList() + }; + } + [Authorize] //TODO: Check permission public virtual async Task GetResourceDefinitionsAsync(string resourceName) { @@ -174,7 +203,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService Permissions = new List() }; - var resourcePermissions = await ResourcePermissionManager.GetAvailableResourcePermissionsAsync(resourceName); + var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { result.Permissions.Add(new ResourcePermissionDefinitionDto() @@ -195,7 +224,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService Permissions = new List() }; - var resourcePermissions = await ResourcePermissionManager.GetAvailableResourcePermissionsAsync(resourceName); + var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); var resourcePermissionGrantsGroup = await ResourcePermissionManager.GetAllGroupAsync(resourceName, resourceKey); foreach (var resourcePermissionGrant in resourcePermissionGrantsGroup) { @@ -235,7 +264,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService result.Permissions.Add(new ResourcePermissionWithProdiverGrantInfoDto { Name = resourcePermission.Name, - DisplayName = (await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName))?.DisplayName?.Localize(StringLocalizerFactory), + DisplayName = (await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourcePermission.Name))?.DisplayName.Localize(StringLocalizerFactory), IsGranted = resourcePermission.IsGranted }); } @@ -246,7 +275,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService [Authorize] //TODO: Check permission public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { - var resourcePermissions = await ResourcePermissionManager.GetAvailableResourcePermissionsAsync(resourceName); + var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { var isGranted = !input.Permissions.IsNullOrEmpty() && input.Permissions.Any(p => p == resourcePermission.Name); @@ -257,7 +286,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService [Authorize] //TODO: Check permission public virtual async Task DeleteResourceAsync(string resourceName, string resourceKey, string providerName, string providerKey) { - var resourcePermissions = await ResourcePermissionManager.GetAvailableResourcePermissionsAsync(resourceName); + var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { await ResourcePermissionManager.SetAsync(resourcePermission.Name, resourceName, resourceKey, providerName, providerKey, false); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs index c756289ecb..7bb09a4f00 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs @@ -6,7 +6,11 @@ namespace Volo.Abp.PermissionManagement; public interface IResourcePermissionManager { - Task> GetAvailableResourcePermissionsAsync(string resourceName); + Task> GetProviderKeyLookupServicesAsync(); + + Task GetProviderKeyLookupServiceAsync(string providerName); + + Task> GetAvailablePermissionsAsync(string resourceName); Task GetAsync( string permissionName, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs new file mode 100644 index 0000000000..de154d8ab8 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Localization; + +namespace Volo.Abp.PermissionManagement; + +public interface IResourcePermissionProviderKeyLookupService +{ + public string Name { get; } + + public ILocalizableString DisplayName { get; } + + Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default); +} 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 cf5ff73115..2971cc18ae 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,9 +8,11 @@ public class PermissionManagementOptions public ITypeList ManagementProviders { get; } public Dictionary ProviderPolicies { get; } - + public ITypeList ResourceManagementProviders { get; } + public ITypeList ResourcePermissionProviderKeyLookupServices { get; } + /// /// Default: true. /// @@ -27,5 +29,6 @@ public class PermissionManagementOptions ProviderPolicies = new Dictionary(); ResourceManagementProviders = new TypeList(); + ResourcePermissionProviderKeyLookupServices = new TypeList(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index b8256cd7d2..33bbd2efb7 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -33,6 +33,8 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD private readonly Lazy> _lazyProviders; + private readonly Lazy> _lazyProviderKeyLookupServices; + public ResourcePermissionManager( IPermissionDefinitionManager permissionDefinitionManager, ISimpleStateCheckerManager simpleStateCheckerManager, @@ -58,9 +60,30 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD .ToList(), true ); + + _lazyProviderKeyLookupServices = new Lazy>( + () => Options + .ResourcePermissionProviderKeyLookupServices + .Select(c => serviceProvider.GetRequiredService(c) as IResourcePermissionProviderKeyLookupService) + .ToList(), + true + ); + } + + public virtual Task> GetProviderKeyLookupServicesAsync() + { + return Task.FromResult(_lazyProviderKeyLookupServices.Value); + } + + public virtual Task GetProviderKeyLookupServiceAsync(string serviceName) + { + var service = _lazyProviderKeyLookupServices.Value.FirstOrDefault(s => s.Name == serviceName); + return service == null + ? throw new AbpException("Unknown resource permission provider key lookup service: " + serviceName) + : Task.FromResult(service); } - public virtual async Task> GetAvailableResourcePermissionsAsync(string resourceName) + public virtual async Task> GetAvailablePermissionsAsync(string resourceName) { var multiTenancySide = CurrentTenant.GetMultiTenancySide(); var resourcePermissions = new List(); @@ -134,7 +157,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD public virtual async Task> GetAllAsync(string resourceName, string resourceKey) { - var resourcePermissionDefinitions = await GetAvailableResourcePermissionsAsync(resourceName); + var resourcePermissionDefinitions = await GetAvailablePermissionsAsync(resourceName); var resourcePermissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); var result = new List(); foreach (var resourcePermissionDefinition in resourcePermissionDefinitions) @@ -162,14 +185,14 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD public virtual async Task> GetAllAsync(string resourceName, string resourceKey, string providerName, string providerKey) { - var permissionDefinitions = await GetAvailableResourcePermissionsAsync(resourceName); + var permissionDefinitions = await GetAvailablePermissionsAsync(resourceName); var multiplePermissionWithGrantedProviders = await GetInternalAsync(permissionDefinitions.ToArray(), resourceName, resourceKey, providerName, providerKey); return multiplePermissionWithGrantedProviders.Result; } public virtual async Task> GetAllGroupAsync(string resourceName, string resourceKey) { - var resourcePermissions = await GetAvailableResourcePermissionsAsync(resourceName); + var resourcePermissions = await GetAvailablePermissionsAsync(resourceName); var resourcePermissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); resourcePermissionGrants = resourcePermissionGrants .Where(x => resourcePermissions.Any(rp => rp.Name == x.Name)) @@ -279,7 +302,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD var neededCheckPermissions = new List(); - var resourcePermissions = await GetAvailableResourcePermissionsAsync(resourceName); + var resourcePermissions = await GetAvailablePermissionsAsync(resourceName); foreach (var permission in resourcePermissions) { if (await SimpleStateCheckerManager.IsEnabledAsync(permission)) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionProviderKeyInfo.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionProviderKeyInfo.cs new file mode 100644 index 0000000000..841277514a --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionProviderKeyInfo.cs @@ -0,0 +1,16 @@ +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionProviderKeyInfo +{ + public string ProviderKey { get; set; } + + public string ProviderDisplayName { get; set; } + + public ResourcePermissionProviderKeyInfo(string providerKey, string providerDisplayName) + { + ProviderKey = providerKey; + ProviderDisplayName = providerDisplayName; + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs index dc9c9ee6ec..cc9dfdf4f7 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs @@ -46,6 +46,20 @@ public partial class PermissionsClientProxy : ClientProxyBase GetResourceProviderKeyLookupServicesAsync() + { + return await RequestAsync(nameof(GetResourceProviderKeyLookupServicesAsync)); + } + + public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter) + { + return await RequestAsync(nameof(SearchResourceProviderKeyAsync), new ClientProxyRequestTypeValue + { + { typeof(string), serviceName }, + { typeof(string), filter } + }); + } + public virtual async Task GetResourceDefinitionsAsync(string resourceName) { return await RequestAsync(nameof(GetResourceDefinitionsAsync), new ClientProxyRequestTypeValue diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json index 54db403ec5..62dedb4c1b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json @@ -179,6 +179,39 @@ "typeSimple": "System.Void" } }, + { + "name": "GetResourceProviderKeyLookupServicesAsync", + "parametersOnMethod": [], + "returnValue": { + "type": "Volo.Abp.PermissionManagement.GetResourceProviderListResultDto", + "typeSimple": "Volo.Abp.PermissionManagement.GetResourceProviderListResultDto" + } + }, + { + "name": "SearchResourceProviderKeyAsync", + "parametersOnMethod": [ + { + "name": "serviceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, + { + "name": "filter", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "returnValue": { + "type": "Volo.Abp.PermissionManagement.SearchProviderKeyListResultDto", + "typeSimple": "Volo.Abp.PermissionManagement.SearchProviderKeyListResultDto" + } + }, { "name": "GetResourceDefinitionsAsync", "parametersOnMethod": [ @@ -551,6 +584,78 @@ "allowAnonymous": null, "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" }, + "GetResourceProviderKeyLookupServicesAsync": { + "uniqueName": "GetResourceProviderKeyLookupServicesAsync", + "name": "GetResourceProviderKeyLookupServicesAsync", + "httpMethod": "GET", + "url": "api/permission-management/permissions/resource-provider-key-lookup-services", + "supportedVersions": [], + "parametersOnMethod": [], + "parameters": [], + "returnValue": { + "type": "Volo.Abp.PermissionManagement.GetResourceProviderListResultDto", + "typeSimple": "Volo.Abp.PermissionManagement.GetResourceProviderListResultDto" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" + }, + "SearchResourceProviderKeyAsyncByServiceNameAndFilter": { + "uniqueName": "SearchResourceProviderKeyAsyncByServiceNameAndFilter", + "name": "SearchResourceProviderKeyAsync", + "httpMethod": "GET", + "url": "api/permission-management/permissions/search-resource-provider-keys", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "serviceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, + { + "name": "filter", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "serviceName", + "name": "serviceName", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + }, + { + "nameOnMethod": "filter", + "name": "filter", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + } + ], + "returnValue": { + "type": "Volo.Abp.PermissionManagement.SearchProviderKeyListResultDto", + "typeSimple": "Volo.Abp.PermissionManagement.SearchProviderKeyListResultDto" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" + }, "GetResourceDefinitionsAsyncByResourceName": { "uniqueName": "GetResourceDefinitionsAsyncByResourceName", "name": "GetResourceDefinitionsAsync", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs index 91db09ac05..1b5aa19b5e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc; @@ -36,8 +35,20 @@ public class PermissionsController : AbpControllerBase, IPermissionAppService return PermissionAppService.UpdateAsync(providerName, providerKey, input); } + [HttpGet("resource-provider-key-lookup-services")] + public virtual Task GetResourceProviderKeyLookupServicesAsync() + { + return PermissionAppService.GetResourceProviderKeyLookupServicesAsync(); + } + + [HttpGet("search-resource-provider-keys")] + public virtual Task SearchResourceProviderKeyAsync(string serviceName, string filter) + { + return PermissionAppService.SearchResourceProviderKeyAsync(serviceName, filter); + } + [HttpGet("resource-definitions")] - public Task GetResourceDefinitionsAsync(string resourceName) + public virtual Task GetResourceDefinitionsAsync(string resourceName) { return PermissionAppService.GetResourceDefinitionsAsync(resourceName); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index 060fa5e2d5..b8f1a237a8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -29,14 +29,13 @@
-
- - -
-
- - -
+ @foreach (var provider in Model.ResourceProviders.Providers) + { +
+ + +
+ }
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs index 4f4fe632a1..2c6c1a5474 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs @@ -26,6 +26,7 @@ public class AddResourcePermissionManagementModal : AbpPageModel public ResourcePermissionViewModel AddModel { get; set; } public GetResourcePermissionDefinitionListResultDto ResourcePermissionDefinitions { get; set; } + public GetResourceProviderListResultDto ResourceProviders { get; set; } protected IPermissionAppService PermissionAppService { get; } @@ -41,6 +42,7 @@ public class AddResourcePermissionManagementModal : AbpPageModel ValidateModel(); ResourcePermissionDefinitions = await PermissionAppService.GetResourceDefinitionsAsync(ResourceName); + ResourceProviders = await PermissionAppService.GetResourceProviderKeyLookupServicesAsync(); return Page(); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js index 9a0529c67e..df85c0be12 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js @@ -34,6 +34,20 @@ }, ajaxParams)); }; + volo.abp.permissionManagement.permissions.getResourceProviderKeyLookupServices = function(ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/permission-management/permissions/resource-provider-key-lookup-services', + type: 'GET' + }, ajaxParams)); + }; + + volo.abp.permissionManagement.permissions.searchResourceProviderKey = function(serviceName, filter, ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/permission-management/permissions/search-resource-provider-keys' + abp.utils.buildQueryString([{ name: 'serviceName', value: serviceName }, { name: 'filter', value: filter }]) + '', + type: 'GET' + }, ajaxParams)); + }; + volo.abp.permissionManagement.permissions.getResourceDefinitions = function(resourceName, ajaxParams) { return abp.ajax($.extend(true, { url: abp.appPath + 'api/permission-management/permissions/resource-definitions' + abp.utils.buildQueryString([{ name: 'resourceName', value: resourceName }]) + '', diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IRoleData.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IRoleData.cs new file mode 100644 index 0000000000..37656f6588 --- /dev/null +++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IRoleData.cs @@ -0,0 +1,19 @@ +using System; +using Volo.Abp.Data; + +namespace Volo.Abp.Users; + +public interface IRoleData : IHasExtraProperties +{ + Guid Id { get; } + + Guid? TenantId { get; } + + string Name { get; } + + bool IsDefault { get; } + + bool IsStatic { get; } + + bool IsPublic { get; } +} diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs new file mode 100644 index 0000000000..5ff861cd9b --- /dev/null +++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs @@ -0,0 +1,57 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Data; + +namespace Volo.Abp.Users; + +public class RoleData : IRoleData +{ + public Guid Id { get; set; } + + public Guid? TenantId { get; set; } + + public string Name { get; set; } + + public bool IsDefault { get; set; } + + public bool IsStatic { get; set; } + + public bool IsPublic { get; set; } + + public ExtraPropertyDictionary ExtraProperties { get; } + + public RoleData() + { + + } + + public RoleData(IRoleData roleData) + { + Id = roleData.Id; + Name = roleData.Name; + IsDefault = roleData.IsDefault; + IsStatic = roleData.IsStatic; + IsPublic = roleData.IsPublic; + TenantId = roleData.TenantId; + ExtraProperties = roleData.ExtraProperties; + } + + public RoleData( + Guid id, + [NotNull] string name, + bool isDefault = false, + bool isStatic = false, + bool isPublic = false, + Guid? tenantId = null, + ExtraPropertyDictionary extraProperties = null) + { + Id = id; + Name = name; + Name = name; + IsDefault = isDefault; + IsStatic = isStatic; + IsPublic = isPublic; + TenantId = tenantId; + ExtraProperties = extraProperties; + } +} From 0e5cce38b194c819a0a6efbe92b0e81fb9d5f296 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 12:16:17 +0800 Subject: [PATCH 025/130] feat(permission-management): update resource permission management modal with dynamic provider key selection --- ...ourcePermissionProviderKeyLookupService.cs | 3 +- ...ourcePermissionProviderKeyLookupService.cs | 3 +- ...ddResourcePermissionManagementModal.cshtml | 9 +++-- ...dd-resource-permission-management-modal.js | 36 +++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs index 93501cb83f..ed68382aa7 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions.Resources; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Identity.Localization; @@ -11,7 +12,7 @@ namespace Volo.Abp.PermissionManagement.Identity; public class RoleResourcePermissionProviderKeyLookupService : IResourcePermissionProviderKeyLookupService, ITransientDependency { - public string Name => "Role"; + public string Name => RoleResourcePermissionValueProvider.ProviderName; public ILocalizableString DisplayName { get; } diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs index fd2b9cffc0..85d6259ede 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions.Resources; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Identity.Localization; @@ -11,7 +12,7 @@ namespace Volo.Abp.PermissionManagement.Identity; public class UserResourcePermissionProviderKeyLookupService : IResourcePermissionProviderKeyLookupService, ITransientDependency { - public string Name => "User"; + public string Name => UserResourcePermissionValueProvider.ProviderName; public ILocalizableString DisplayName { get; } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index b8f1a237a8..e233418ead 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -16,7 +16,7 @@
- + @@ -38,7 +38,12 @@ } - + +
+ + +
+
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js index d1e1f02fa4..21ee2b048e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -8,4 +8,40 @@ var abp = abp || {}; $items.on("change", function () { $all.prop("checked", $items.length === $items.filter(":checked").length); }); + + var $providerKey = $("#AddModel\\.ProviderKey"); + $providerKey.select2({ + ajax: { + url: '/api/permission-management/permissions/search-resource-provider-keys', + delay: 250, + dataType: "json", + data: function (params) { + var query = {}; + query["serviceName"] = $('input[name="AddModel.ProviderName"]:checked').val(); + query["filter"] = params.term; + return query; + }, + processResults: function (data) { + var keyValues = []; + data.keys.forEach(function (item, index) { + keyValues.push({ + id: item["providerKey"], + text: item["providerDisplayName"], + displayName: item["providerDisplayName"] + }) + }); + return { + results: keyValues + }; + } + }, + width: '100%', + dropdownParent: $('#addResourcePermissionManagementModal'), + language: abp.localization.currentCulture.cultureName + }); + + $('input[name="AddModel.ProviderName"]').change(function () { + $providerKey.val(null).trigger('change'); + }); + })(jQuery); From a14a53da56292020e850ad066d9e1bc07130a9a6 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 15:02:49 +0800 Subject: [PATCH 026/130] feat: Add SearchByIdsAsync methods for users and roles in Identity module --- .../IIdentityUserIntegrationService.cs | 4 + .../IdentityUserIntegrationService.cs | 34 ++++++ .../Volo/Abp/Identity/IUserRoleFinder.cs | 4 + .../Volo/Abp/Identity/UserRoleFinder.cs | 26 +++++ ...ityUserIntegrationClientProxy.Generated.cs | 16 +++ .../identity-generate-proxy.json | 108 ++++++++++++++++++ .../Abp/Identity/HttpClientUserRoleFinder.cs | 20 ++++ .../IdentityUserIntegrationController.cs | 14 +++ .../Pages/Identity/Roles/index.js | 3 +- .../Pages/Identity/Users/index.js | 3 +- ...ourcePermissionProviderKeyLookupService.cs | 12 ++ ...ourcePermissionProviderKeyLookupService.cs | 12 ++ .../ResourcePermissionGrantInfoDto.cs | 2 + .../PermissionAppService.cs | 1 + .../Localization/Domain/en.json | 16 ++- ...ourcePermissionProviderKeyLookupService.cs | 2 + .../PermissionProviderWithPermissions.cs | 10 +- .../ResourcePermissionManager.cs | 38 +++++- ...ddResourcePermissionManagementModal.cshtml | 29 ++--- .../ResourcePermissionManagementModal.cshtml | 2 +- ...teResourcePermissionManagementModal.cshtml | 14 ++- ...dd-resource-permission-management-modal.js | 6 +- .../resource-permission-management-modal.js | 25 ++-- 23 files changed, 358 insertions(+), 43 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs index 951a7bf401..3f15309077 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs @@ -17,9 +17,13 @@ public interface IIdentityUserIntegrationService : IApplicationService Task> SearchAsync(UserLookupSearchInputDto input); + Task> SearchByIdsAsync(Guid[] ids); + Task GetCountAsync(UserLookupCountInputDto input); Task> SearchRoleAsync(RoleLookupSearchInputDto input); + Task> SearchRoleByIdsAsync(Guid[] ids); + Task GetRoleCountAsync(RoleLookupCountInputDto input); } diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs index 491d495a97..6745bc8837 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs @@ -11,15 +11,18 @@ public class IdentityUserIntegrationService : IdentityAppServiceBase, IIdentityU { protected IUserRoleFinder UserRoleFinder { get; } protected IdentityUserRepositoryExternalUserLookupServiceProvider UserLookupServiceProvider { get; } + protected IIdentityUserRepository UserRepository { get; } protected IIdentityRoleRepository RoleRepository { get; } public IdentityUserIntegrationService( IUserRoleFinder userRoleFinder, IdentityUserRepositoryExternalUserLookupServiceProvider userLookupServiceProvider, + IIdentityUserRepository userRepository, IIdentityRoleRepository roleRepository) { UserRoleFinder = userRoleFinder; UserLookupServiceProvider = userLookupServiceProvider; + UserRepository = userRepository; RoleRepository = roleRepository; } @@ -66,6 +69,28 @@ public class IdentityUserIntegrationService : IdentityAppServiceBase, IIdentityU ); } + public virtual async Task> SearchByIdsAsync(Guid[] ids) + { + var users = await UserRepository.GetListByIdsAsync(ids); + + return new ListResultDto( + users + .Select(u => new UserData( + u.Id, + u.UserName, + u.Email, + u.Name, + u.Surname, + u.EmailConfirmed, + u.PhoneNumber, + u.PhoneNumberConfirmed, + u.TenantId, + u.IsActive, + u.ExtraProperties)) + .ToList() + ); + } + public virtual async Task GetCountAsync(UserLookupCountInputDto input) { return await UserLookupServiceProvider.GetCountAsync(input.Filter); @@ -80,6 +105,15 @@ public class IdentityUserIntegrationService : IdentityAppServiceBase, IIdentityU } } + public virtual async Task> SearchRoleByIdsAsync(Guid[] ids) + { + using (RoleRepository.DisableTracking()) + { + var roles = await RoleRepository.GetListAsync(ids); + return new ListResultDto(roles.Select(r => new RoleData(r.Id, r.Name, r.IsDefault, r.IsStatic, r.IsPublic, r.TenantId, r.ExtraProperties)).ToList()); + } + } + public virtual async Task GetRoleCountAsync(RoleLookupCountInputDto input) { return await RoleRepository.GetCountAsync(input.Filter); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs index ef7ec9bedd..751160236d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs @@ -14,4 +14,8 @@ public interface IUserRoleFinder Task> SearchUserAsync(string filter); Task> SearchRoleAsync(string filter); + + Task> SearchUserByIdsAsync(Guid[] ids); + + Task> SearchRoleByIdsAsync(Guid[] ids); } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs index 06a1d2f308..dfd658292f 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs @@ -54,4 +54,30 @@ public class UserRoleFinder : IUserRoleFinder, ITransientDependency }).ToList(); } } + + public virtual async Task> SearchUserByIdsAsync(Guid[] ids) + { + using (IdentityUserRepository.DisableTracking()) + { + var users = await IdentityUserRepository.GetListByIdsAsync(ids); + return users.Select(user => new UserFinderResult + { + Id = user.Id, + UserName = user.UserName + }).ToList(); + } + } + + public virtual async Task> SearchRoleByIdsAsync(Guid[] ids) + { + using (IdentityUserRepository.DisableTracking()) + { + var roles = await IdentityRoleRepository.GetListAsync(ids); + return roles.Select(user => new RoleFinderResult + { + Id = user.Id, + RoleName = user.Name + }).ToList(); + } + } } diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs index bebd7ee37a..aa91f7d6cd 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs @@ -52,6 +52,14 @@ public partial class IdentityUserIntegrationClientProxy : ClientProxyBase> SearchByIdsAsync(Guid[] ids) + { + return await RequestAsync>(nameof(SearchByIdsAsync), new ClientProxyRequestTypeValue + { + { typeof(Guid[]), ids } + }); + } + public virtual async Task GetCountAsync(UserLookupCountInputDto input) { return await RequestAsync(nameof(GetCountAsync), new ClientProxyRequestTypeValue @@ -68,6 +76,14 @@ public partial class IdentityUserIntegrationClientProxy : ClientProxyBase> SearchRoleByIdsAsync(Guid[] ids) + { + return await RequestAsync>(nameof(SearchRoleByIdsAsync), new ClientProxyRequestTypeValue + { + { typeof(Guid[]), ids } + }); + } + public virtual async Task GetRoleCountAsync(RoleLookupCountInputDto input) { return await RequestAsync(nameof(GetRoleCountAsync), new ClientProxyRequestTypeValue diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json index e1337bd3f0..3977808e84 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json @@ -1384,6 +1384,23 @@ "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" } }, + { + "name": "SearchByIdsAsync", + "parametersOnMethod": [ + { + "name": "ids", + "typeAsString": "System.Guid[], System.Private.CoreLib", + "type": "System.Guid[]", + "typeSimple": "[string]", + "isOptional": false, + "defaultValue": null + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + } + }, { "name": "GetCountAsync", "parametersOnMethod": [ @@ -1418,6 +1435,23 @@ "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" } }, + { + "name": "SearchRoleByIdsAsync", + "parametersOnMethod": [ + { + "name": "ids", + "typeAsString": "System.Guid[], System.Private.CoreLib", + "type": "System.Guid[]", + "typeSimple": "[string]", + "isOptional": false, + "defaultValue": null + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + } + }, { "name": "GetRoleCountAsync", "parametersOnMethod": [ @@ -1635,6 +1669,43 @@ "allowAnonymous": null, "implementFrom": "Volo.Abp.Identity.Integration.IIdentityUserIntegrationService" }, + "SearchByIdsAsyncByIds": { + "uniqueName": "SearchByIdsAsyncByIds", + "name": "SearchByIdsAsync", + "httpMethod": "GET", + "url": "integration-api/identity/users/search/by-ids", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "ids", + "typeAsString": "System.Guid[], System.Private.CoreLib", + "type": "System.Guid[]", + "typeSimple": "[string]", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "ids", + "name": "ids", + "jsonName": null, + "type": "System.Guid[]", + "typeSimple": "[string]", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.Identity.Integration.IIdentityUserIntegrationService" + }, "GetCountAsyncByInput": { "uniqueName": "GetCountAsyncByInput", "name": "GetCountAsync", @@ -1757,6 +1828,43 @@ "allowAnonymous": null, "implementFrom": "Volo.Abp.Identity.Integration.IIdentityUserIntegrationService" }, + "SearchRoleByIdsAsyncByIds": { + "uniqueName": "SearchRoleByIdsAsyncByIds", + "name": "SearchRoleByIdsAsync", + "httpMethod": "GET", + "url": "integration-api/identity/users/search/roles/by-ids", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "ids", + "typeAsString": "System.Guid[], System.Private.CoreLib", + "type": "System.Guid[]", + "typeSimple": "[string]", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "ids", + "name": "ids", + "jsonName": null, + "type": "System.Guid[]", + "typeSimple": "[string]", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.Identity.Integration.IIdentityUserIntegrationService" + }, "GetRoleCountAsyncByInput": { "uniqueName": "GetRoleCountAsyncByInput", "name": "GetRoleCountAsync", diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs index f9c00d6053..6299112311 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs @@ -57,4 +57,24 @@ public class HttpClientUserRoleFinder : IUserRoleFinder, ITransientDependency RoleName = r.Name }).ToList(); } + + public virtual async Task> SearchUserByIdsAsync(Guid[] ids) + { + var users = await _userIntegrationService.SearchByIdsAsync(ids); + return users.Items.Select(u => new UserFinderResult + { + Id = u.Id, + UserName = u.UserName + }).ToList(); + } + + public virtual async Task> SearchRoleByIdsAsync(Guid[] ids) + { + var roles = await _userIntegrationService.SearchRoleByIdsAsync(ids); + return roles.Items.Select(r => new RoleFinderResult + { + Id = r.Id, + RoleName = r.Name + }).ToList(); + } } diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs index 8fea364374..dba6c4f253 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs @@ -49,6 +49,13 @@ public class IdentityUserIntegrationController : AbpControllerBase, IIdentityUse return UserIntegrationService.SearchAsync(input); } + [HttpGet] + [Route("search/by-ids")] + public virtual Task> SearchByIdsAsync(Guid[] ids) + { + return UserIntegrationService.SearchByIdsAsync(ids); + } + [HttpGet] [Route("count")] public Task GetCountAsync(UserLookupCountInputDto input) @@ -63,6 +70,13 @@ public class IdentityUserIntegrationController : AbpControllerBase, IIdentityUse return UserIntegrationService.SearchRoleAsync(input); } + [HttpGet] + [Route("search/roles/by-ids")] + public virtual Task> SearchRoleByIdsAsync(Guid[] ids) + { + return UserIntegrationService.SearchRoleByIdsAsync(ids); + } + [HttpGet] [Route("count/roles")] public virtual Task GetRoleCountAsync(RoleLookupCountInputDto input) diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js index 3430a3b0a0..9197a4eb1d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js @@ -1,5 +1,6 @@ (function ($) { var l = abp.localization.getResource('AbpIdentity'); + var rl = abp.localization.getResource('AbpPermissionManagement'); var _identityRoleAppService = volo.abp.identity.identityRole; var _permissionsModal = new abp.ModalManager( @@ -47,7 +48,7 @@ }, }, { - text: l('ResourcePermissions'), + text: rl('ResourcePermissions'), action: function (data) { _resourcePermissionsModal.open({ resourceName: 'Volo.Abp.Identity.IdentityRole', diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js index 56541b5828..053b4b337e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js @@ -1,5 +1,6 @@ (function ($) { var l = abp.localization.getResource('AbpIdentity'); + var rl = abp.localization.getResource('AbpPermissionManagement'); var _identityUserAppService = volo.abp.identity.identityUser; @@ -85,7 +86,7 @@ }, }, { - text: l('ResourcePermissions'), + text: rl('ResourcePermissions'), action: function (data) { _resourcePermissionsModal.open({ resourceName: 'Volo.Abp.Identity.IdentityUser', diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs index ed68382aa7..18096e5167 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -29,4 +30,15 @@ public class RoleResourcePermissionProviderKeyLookupService : IResourcePermissio var roles = await UserRoleFinder.SearchRoleAsync(filter); return roles.Select(r => new ResourcePermissionProviderKeyInfo(r.RoleName, r.RoleName)).ToList(); } + + public virtual async Task> SearchAsync(string[] keys, CancellationToken cancellationToken = new CancellationToken()) + { + var ids = keys + .Select(key => Guid.TryParse(key, out var id) ? (Guid?)id : null) + .Where(id => id.HasValue) + .Select(id => id.Value) + .ToArray(); + var roles = await UserRoleFinder.SearchRoleByIdsAsync(ids.ToArray()); + return roles.Select(r => new ResourcePermissionProviderKeyInfo(r.RoleName, r.RoleName)).ToList(); + } } diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs index 85d6259ede..a0cf06d40d 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -29,4 +30,15 @@ public class UserResourcePermissionProviderKeyLookupService : IResourcePermissio var users = await UserRoleFinder.SearchUserAsync(filter); return users.Select(u => new ResourcePermissionProviderKeyInfo(u.Id.ToString(), u.UserName)).ToList(); } + + public virtual async Task> SearchAsync(string[] keys, CancellationToken cancellationToken = default) + { + var ids = keys + .Select(key => Guid.TryParse(key, out var id) ? (Guid?)id : null) + .Where(id => id.HasValue) + .Select(id => id.Value) + .ToArray(); + var users = await UserRoleFinder.SearchUserByIdsAsync(ids.ToArray()); + return users.Select(u => new ResourcePermissionProviderKeyInfo(u.Id.ToString(), u.UserName)).ToList(); + } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs index 9e38d1e257..fd7fb3a8b1 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs @@ -8,5 +8,7 @@ public class ResourcePermissionGrantInfoDto public string ProviderKey { get; set; } + public string ProviderDisplayName { get; set; } + public List Permissions { get; set; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index adcd7ea0f9..d91053c945 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -232,6 +232,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService { ProviderName = resourcePermissionGrant.ProviderName, ProviderKey = resourcePermissionGrant.ProviderKey, + ProviderDisplayName = resourcePermissionGrant.ProviderDisplayName, Permissions = new List() }; diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index b8299d4e5b..a08b5e09e8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -8,6 +8,18 @@ "SelectAllInThisTab": "Select all", "SaveWithoutAnyPermissionsWarningMessage": "Are you sure you want to save without any permissions?", "PermissionGroup": "Permission Group", - "Filter": "Filter" + "Filter": "Filter", + "ResourcePermissions": "Resource permissions", + "ResourcePermissionTarget": "Target", + "ResourcePermissionPermissions": "Permissions", + "AddResourcePermission": "Add resource permissions", + "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete this resource permissions?", + "AddResourcePermissions": "Add resource permissions", + "UpdateResourcePermissions": "Update resource permissions", + "ResourceName": "Resource Name", + "ResourceKey": "Resource Key", + "ProviderName": "Provider Name", + "ProviderKey": "Provider Key", + "GrantAllResourcePermissions": "Grant all" } -} \ No newline at end of file +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs index de154d8ab8..bdd5eb8f84 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs @@ -12,4 +12,6 @@ public interface IResourcePermissionProviderKeyLookupService public ILocalizableString DisplayName { get; } Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default); + + Task> SearchAsync(string[] keys, CancellationToken cancellationToken = default); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs index 2e53a23956..0443d54891 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs @@ -1,20 +1,22 @@ using System.Collections.Generic; -using JetBrains.Annotations; namespace Volo.Abp.PermissionManagement; public class PermissionProviderWithPermissions { - public string ProviderName { get; } + public string ProviderName { get; set; } - public string ProviderKey { get; } + public string ProviderKey { get; set; } + + public string ProviderDisplayName { get; set; } public List Permissions { get; set; } - public PermissionProviderWithPermissions(string providerName, string providerKey) + public PermissionProviderWithPermissions(string providerName, string providerKey, string providerDisplayName) { ProviderName = providerName; ProviderKey = providerKey; + ProviderDisplayName = providerDisplayName; Permissions = new List(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index 33bbd2efb7..b9b80eb881 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -194,18 +194,48 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD { var resourcePermissions = await GetAvailablePermissionsAsync(resourceName); var resourcePermissionGrants = await ResourcePermissionGrantRepository.GetPermissionsAsync(resourceName, resourceKey); - resourcePermissionGrants = resourcePermissionGrants - .Where(x => resourcePermissions.Any(rp => rp.Name == x.Name)) - .ToList(); + resourcePermissionGrants = resourcePermissionGrants.Where(x => resourcePermissions.Any(rp => rp.Name == x.Name)).ToList(); var resourcePermissionGrantsGroup = resourcePermissionGrants.GroupBy(x => new { x.ProviderName, x.ProviderKey }); var result = new List(); foreach (var resourcePermissionGrant in resourcePermissionGrantsGroup) { - result.Add(new PermissionProviderWithPermissions(resourcePermissionGrant.Key.ProviderName, resourcePermissionGrant.Key.ProviderKey) + result.Add(new PermissionProviderWithPermissions(resourcePermissionGrant.Key.ProviderName, resourcePermissionGrant.Key.ProviderKey, resourcePermissionGrant.Key.ProviderKey) { Permissions = resourcePermissionGrant.Select(x => x.Name).ToList() }); } + + if (result.Any()) + { + var providerKeyInfos = new Dictionary>(); + var resourcePermissionProviderGroup = resourcePermissionGrants.GroupBy(x => x.ProviderName); + var providerKeyLookupServices = await GetProviderKeyLookupServicesAsync(); + foreach (var resourcePermissionProvider in resourcePermissionProviderGroup) + { + var providerKeyLookupService = providerKeyLookupServices.FirstOrDefault(s => s.Name == resourcePermissionProvider.Key); + if (providerKeyLookupService == null) + { + continue; + } + var keys = resourcePermissionProvider.Select(rp => rp.ProviderKey).ToList(); + providerKeyInfos.Add(resourcePermissionProvider.Key, await providerKeyLookupService.SearchAsync(keys.ToArray())); + } + + foreach (var item in result) + { + if (!providerKeyInfos.TryGetValue(item.ProviderName, out var providerKeyInfoList)) + { + continue; + } + + var providerKeyInfo = providerKeyInfoList.FirstOrDefault(p => p.ProviderKey == item.ProviderKey); + if (providerKeyInfo != null) + { + item.ProviderDisplayName = providerKeyInfo.ProviderDisplayName; + } + } + } + return result; } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index e233418ead..bb6905389a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -15,7 +15,7 @@ - + @@ -23,40 +23,43 @@
- -

@(HttpUtility.HtmlEncode(Model.ResourceDisplayName))

+ +

@(HttpUtility.HtmlEncode(Model.ResourceName))

+ +

@(HttpUtility.HtmlEncode(Model.ResourceKey))

- +
- @foreach (var provider in Model.ResourceProviders.Providers) + @foreach (var provider in Model.ResourceProviders.Providers.Select((value, i) => new { i, value })) {
- - + +
}
- - + + +
- +
- +
@foreach (var permission in Model.ResourcePermissionDefinitions.Permissions) {
- - + +
}
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml index d1aeae69fe..dec4c52fd8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml @@ -22,7 +22,7 @@
- +
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml index ea8ac1164b..9986f920da 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml @@ -24,22 +24,24 @@
- -

@(HttpUtility.HtmlEncode(Model.ResourceDisplayName))

+ +

@(HttpUtility.HtmlEncode(Model.ResourceName))

+ +

@(HttpUtility.HtmlEncode(Model.ResourceKey))

- +
- +
@foreach (var permission in Model.ResourcePermissions.Permissions) {
- - + +
}
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js index 21ee2b048e..a37877be00 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -9,7 +9,7 @@ var abp = abp || {}; $all.prop("checked", $items.length === $items.filter(":checked").length); }); - var $providerKey = $("#AddModel\\.ProviderKey"); + var $providerKey = $("#AddModel_ProviderKey"); $providerKey.select2({ ajax: { url: '/api/permission-management/permissions/search-resource-provider-keys', @@ -44,4 +44,8 @@ var abp = abp || {}; $providerKey.val(null).trigger('change'); }); + $('#addResourcePermissionManagementForm').submit(function () { + $(this).valid(); + }); + })(jQuery); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js index 4ed6d99f12..713bc0ab96 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js @@ -2,7 +2,6 @@ var abp = abp || {}; (function ($) { var l = abp.localization.getResource('AbpPermissionManagement'); var _dataTable = null; - abp.ui.extensions.entityActions.get('permissionManagement.resource').addContributor( function (actionList) { return actionList.addManyTail( @@ -19,7 +18,9 @@ var abp = abp || {}; providerKey: data.record.providerKey }); _updateResourcePermissionsModal.onResult(function () { - _dataTable.ajax.reloadEx(); + _dataTable.ajax.reloadEx(function (json) { + _dataTable.columns.adjust(); + }); }); }, }, @@ -34,7 +35,9 @@ var abp = abp || {}; action: function (data) { volo.abp.permissionManagement.permissions.deleteResource($("#ResourceName").val(), $("#ResourceKey").val(), data.record.providerName, data.record.providerKey).then(function () { abp.notify.info(l('SuccessfullyDeleted')); - _dataTable.ajax.reloadEx(); + _dataTable.ajax.reloadEx(function (json) { + _dataTable.columns.adjust(); + }); }); }, } @@ -54,19 +57,19 @@ var abp = abp || {}; } }, { - title: l("Target"), + title: l("ResourcePermissionTarget"), data: 'providerName', render: function (data, type, row) { - return row.providerName + ' (' + row.providerKey + ')'; + return '' + row.providerName + '' + row.providerDisplayName; }, }, { - title: l("Permissions"), + title: l("ResourcePermissionPermissions"), data: 'permissions', render: function (data, type, row) { var spans = ''; for (var i = 0; i < row.permissions.length; i++) { - spans += '' + row.permissions[i].name + ''; + spans += '' + row.permissions[i].displayName + ''; } return spans; }, @@ -86,8 +89,8 @@ var abp = abp || {}; order: [], searching: false, processing: true, - scrollX: true, - serverSide: true, + scrollX: false, + serverSide: false, paging: true, ajax: function () { return function (requestData, callback, settings) { @@ -114,7 +117,9 @@ var abp = abp || {}; resourceDisplayName: $("#ResourceDisplayName").val() }); _addResourcePermissionsModal.onResult(function () { - _dataTable.ajax.reloadEx(); + _dataTable.ajax.reloadEx(function (json) { + _dataTable.columns.adjust(); + }); }); }); }; From 2844e595900222a687f9902c1df422cf33be9842 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 15:55:12 +0800 Subject: [PATCH 027/130] feat(permission-management): update resource permission handling and improve resource key usage in identity role and user management --- .../Abp/Authorization/AbpAuthorizationPolicyProvider.cs | 9 +++++++++ .../Permissions/Resources/ResourcePermissionChecker.cs | 4 ++-- .../Resources/ResourcePermissionValueProviderManager.cs | 4 ++-- .../Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js | 2 +- .../Volo.Abp.Identity.Web/Pages/Identity/Users/index.js | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationPolicyProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationPolicyProvider.cs index 7958f2a979..0792044ba3 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationPolicyProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationPolicyProvider.cs @@ -40,6 +40,15 @@ public class AbpAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider return policyBuilder.Build(); } + var resourcePermission = await _permissionDefinitionManager.GetResourcePermissionOrNullAsync(policyName); + if (resourcePermission != null) + { + //TODO: Optimize & Cache! + var policyBuilder = new AuthorizationPolicyBuilder(Array.Empty()); + policyBuilder.Requirements.Add(new ResourcePermissionRequirement(policyName)); + return policyBuilder.Build(); + } + return null; } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs index 09b2e7c3bc..67cf436184 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs @@ -45,7 +45,7 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD { Check.NotNull(name, nameof(name)); - var permission = await PermissionDefinitionManager.GetOrNullAsync(name); + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(name); if (permission == null) { return false; @@ -115,7 +115,7 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD var permissionDefinitions = new List(); foreach (var name in names) { - var permission = await PermissionDefinitionManager.GetOrNullAsync(name); + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(name); if (permission == null) { result.Result.Add(name, PermissionGrantResult.Prohibited); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProviderManager.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProviderManager.cs index 628893397e..bab3180fea 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProviderManager.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueProviderManager.cs @@ -28,14 +28,14 @@ public class ResourcePermissionValueProviderManager : IResourcePermissionValuePr protected virtual List GetProviders() { var providers = Options - .ValueProviders + .ResourceValueProviders .Select(type => (ServiceProvider.GetRequiredService(type) as IResourcePermissionValueProvider)!) .ToList(); var multipleProviders = providers.GroupBy(p => p.Name).FirstOrDefault(x => x.Count() > 1); if(multipleProviders != null) { - throw new AbpException($"Duplicate permission value provider name detected: {multipleProviders.Key}. Providers:{Environment.NewLine}{multipleProviders.Select(p => p.GetType().FullName!).JoinAsString(Environment.NewLine)}"); + throw new AbpException($"Duplicate resource permission value provider name detected: {multipleProviders.Key}. Providers:{Environment.NewLine}{multipleProviders.Select(p => p.GetType().FullName!).JoinAsString(Environment.NewLine)}"); } return providers; diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js index 9197a4eb1d..5c9696520c 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js @@ -52,7 +52,7 @@ action: function (data) { _resourcePermissionsModal.open({ resourceName: 'Volo.Abp.Identity.IdentityRole', - resourceKey: data.record.name, + resourceKey: data.record.id, resourceDisplayName: data.record.name + '(Volo.Abp.Identity.IdentityRole)' }); }, diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js index 053b4b337e..907749c8e4 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js @@ -90,7 +90,7 @@ action: function (data) { _resourcePermissionsModal.open({ resourceName: 'Volo.Abp.Identity.IdentityUser', - resourceKey: data.record.name, + resourceKey: data.record.id, resourceDisplayName: data.record.name + '(Volo.Abp.Identity.IdentityUser)' }); }, From 8e7218fc0d5c1ef67c370f6d3f1f6b2bba8b157c Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 15:56:43 +0800 Subject: [PATCH 028/130] Remove resource permissions modal and related actions from roles and users --- .../Pages/Identity/Roles/index.js | 15 --------------- .../Pages/Identity/Users/index.js | 16 +--------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js index 5c9696520c..b83db95758 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js @@ -1,15 +1,10 @@ (function ($) { var l = abp.localization.getResource('AbpIdentity'); - var rl = abp.localization.getResource('AbpPermissionManagement'); var _identityRoleAppService = volo.abp.identity.identityRole; var _permissionsModal = new abp.ModalManager( abp.appPath + 'AbpPermissionManagement/PermissionManagementModal' ); - var _resourcePermissionsModal = new abp.ModalManager({ - viewUrl: abp.appPath + "AbpPermissionManagement/ResourcePermissionManagementModal", - modalClass: 'ResourcePermissionManagement' - }); var _editModal = new abp.ModalManager( abp.appPath + 'Identity/Roles/EditModal' ); @@ -47,16 +42,6 @@ }); }, }, - { - text: rl('ResourcePermissions'), - action: function (data) { - _resourcePermissionsModal.open({ - resourceName: 'Volo.Abp.Identity.IdentityRole', - resourceKey: data.record.id, - resourceDisplayName: data.record.name + '(Volo.Abp.Identity.IdentityRole)' - }); - }, - }, { text: l('Delete'), visible: function (data) { diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js index 907749c8e4..fa63b96af4 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js @@ -1,6 +1,5 @@ (function ($) { var l = abp.localization.getResource('AbpIdentity'); - var rl = abp.localization.getResource('AbpPermissionManagement'); var _identityUserAppService = volo.abp.identity.identityUser; @@ -51,10 +50,7 @@ var _permissionsModal = new abp.ModalManager( abp.appPath + 'AbpPermissionManagement/PermissionManagementModal' ); - var _resourcePermissionsModal = new abp.ModalManager({ - viewUrl: abp.appPath + "AbpPermissionManagement/ResourcePermissionManagementModal", - modalClass: 'ResourcePermissionManagement' - }); + var _dataTable = null; abp.ui.extensions.entityActions.get('identity.user').addContributor( @@ -85,16 +81,6 @@ }); }, }, - { - text: rl('ResourcePermissions'), - action: function (data) { - _resourcePermissionsModal.open({ - resourceName: 'Volo.Abp.Identity.IdentityUser', - resourceKey: data.record.id, - resourceDisplayName: data.record.name + '(Volo.Abp.Identity.IdentityUser)' - }); - }, - }, { text: l('Delete'), visible: function(data) { From 7eb50663da46a83a89822c0ee81960c4c4211fea Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 15:57:34 +0800 Subject: [PATCH 029/130] Remove resource permission management script from roles and users pages --- .../src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml | 1 - .../src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml | 1 - 2 files changed, 2 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml index 6f4a56ba24..5b2c767228 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml @@ -28,7 +28,6 @@ - } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml index baff68d720..31b3751cbd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml @@ -29,7 +29,6 @@ - } From 706ae23260f4d9da552f11a8329039139a2e50a9 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 16:02:08 +0800 Subject: [PATCH 030/130] Add 'ResourcePermissions' to English UI resources --- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json index 7a5a806124..260b846537 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profile picture", "Theme": "Theme", "NotAssigned": "Not Assigned", - "EntityActionsDisabledTooltip": "You do not have permission to perform any action." + "EntityActionsDisabledTooltip": "You do not have permission to perform any action.", + "ResourcePermissions": "Resource permissions" } } From 4698cf380b794c1a932c78204e25952e3e0c128e Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 16:38:42 +0800 Subject: [PATCH 031/130] Remove TestPermissionDefinitionProvider and update localization keys for resource management modals --- .../TestPermissionDefinitionProvider.cs | 28 ------------------- .../Localization/Domain/en.json | 9 +++--- ...ddResourcePermissionManagementModal.cshtml | 8 ++---- ...teResourcePermissionManagementModal.cshtml | 6 ++-- 4 files changed, 9 insertions(+), 42 deletions(-) delete mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs deleted file mode 100644 index bc7fdb87f2..0000000000 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/TestPermissionDefinitionProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Localization; -using Volo.Abp.PermissionManagement.Localization; - -namespace Volo.Abp.PermissionManagement; - -public class TestPermissionDefinitionProvider : PermissionDefinitionProvider -{ - public override void Define(IPermissionDefinitionContext context) - { - context.AddResourcePermission("PR1", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission1")); - context.AddResourcePermission("PR2", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission2")); - context.AddResourcePermission("PR3", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission3")); - context.AddResourcePermission("PR4", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission4")); - context.AddResourcePermission("PR5", "Volo.Abp.Identity.IdentityRole", L("Role Resource Permission5")); - - context.AddResourcePermission("PU1", "Volo.Abp.Identity.IdentityUser", L("User Resource Permission1")); - context.AddResourcePermission("PU2", "Volo.Abp.Identity.IdentityUser", L("User Resource Permission2")); - context.AddResourcePermission("PU3", "Volo.Abp.Identity.IdentityUser", L("User Resource Permission3")); - context.AddResourcePermission("PU4", "Volo.Abp.Identity.IdentityUser", L("User Resource Permission4")); - context.AddResourcePermission("PU5", "VVolo.Abp.Identity.IdentityUser", L("User Resource Permission5")); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); - } -} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index a08b5e09e8..72b8c6a55a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -16,10 +16,11 @@ "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete this resource permissions?", "AddResourcePermissions": "Add resource permissions", "UpdateResourcePermissions": "Update resource permissions", - "ResourceName": "Resource Name", - "ResourceKey": "Resource Key", - "ProviderName": "Provider Name", - "ProviderKey": "Provider Key", + "Resource": "Resource", + "ResourceName": "Resource name", + "ResourceKey": "Resource key", + "ProviderName": "Provider name", + "ProviderKey": "Provider key", "GrantAllResourcePermissions": "Grant all" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index bb6905389a..243bbd15bf 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -23,10 +23,8 @@
- -

@(HttpUtility.HtmlEncode(Model.ResourceName))

- -

@(HttpUtility.HtmlEncode(Model.ResourceKey))

+ +

@(HttpUtility.HtmlEncode(Model.ResourceDisplayName))

@@ -40,13 +38,11 @@ }
-
-
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml index 9986f920da..6c3ac520fb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml @@ -24,10 +24,8 @@
- -

@(HttpUtility.HtmlEncode(Model.ResourceName))

- -

@(HttpUtility.HtmlEncode(Model.ResourceKey))

+ +

@(HttpUtility.HtmlEncode(Model.ResourceDisplayName))

From bf8bfb9c00c4f930ff5bab28259c7ba60e29c8cc Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 17:20:13 +0800 Subject: [PATCH 032/130] Enhance permission management and localization for resource permissions --- .../Authorization/Permissions/PermissionDefinitionManager.cs | 2 +- .../Resources/EntityResourcePermissionCheckerExtensions.cs | 2 +- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json | 4 ++-- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json | 4 ++-- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json | 5 +++-- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json | 3 ++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json | 3 ++- .../Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json | 3 ++- .../Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json | 3 ++- 28 files changed, 55 insertions(+), 31 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs index 41c261de49..336e8f42fd 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs @@ -43,7 +43,7 @@ public class PermissionDefinitionManager : IPermissionDefinitionManager, ITransi var permission = await GetResourcePermissionOrNullAsync(name); if (permission == null) { - throw new AbpException("Undefined permission: " + name); + throw new AbpException("Undefined resource permission: " + name); } return permission; diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs index 28f72f2abe..963eaf20e0 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Authorization.Permissions.Resources; public static class EntityResourcePermissionCheckerExtensions { - /// + /// /// Checks if the specified permission is granted for the given entity. /// /// The type of the entity. diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json index 93cf62ab84..50c3e7fcda 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json @@ -61,6 +61,7 @@ "ProfilePicture": "الصوره الشخصيه", "Theme": "سمة", "NotAssigned": "غيرمعتمد", - "EntityActionsDisabledTooltip": "ليس لديك إذن لتنفيذ أي إجراء." + "EntityActionsDisabledTooltip": "ليس لديك إذن لتنفيذ أي إجراء.", + "ResourcePermissions": "أذونات المورد" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json index ff2d09fdf8..892e1ed5f2 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json @@ -61,6 +61,6 @@ "ProfilePicture": "Profilový obrázek", "Theme": "Téma", "NotAssigned": "Nepřiřazena", - "EntityActionsDisabledTooltip": "Nemáte oprávnění provést žádnou akci." - } + "EntityActionsDisabledTooltip": "Nemáte oprávnění provést žádnou akci.", + "ResourcePermissions": "Oprávnění ke zdrojům" } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json index 37c361c1e3..9fd38d88ed 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profilbild", "Theme": "Thema", "NotAssigned": "Nicht zugeordnet", - "EntityActionsDisabledTooltip": "Sie haben keine Berechtigung, Aktionen auszuführen." + "EntityActionsDisabledTooltip": "Sie haben keine Berechtigung, Aktionen auszuführen.", + "ResourcePermissions": "Ressourcenberechtigungen" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json index f7d837a361..16b21e4149 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json @@ -55,6 +55,7 @@ "OthersGroup": "άλλος", "Today": "Σήμερα", "Apply": "Ισχύουν", - "EntityActionsDisabledTooltip": "Δεν έχετε δικαίωμα να εκτελέσετε καμία ενέργεια." + "EntityActionsDisabledTooltip": "Δεν έχετε δικαίωμα να εκτελέσετε καμία ενέργεια.", + "ResourcePermissions": "Δικαιώματα Πόρων", } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json index c5aaa5957c..b17bf084b3 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json @@ -56,6 +56,6 @@ "NotAssigned": "Not Assigned", "Today": "Today", "Apply": "Apply", - "EntityActionsDisabledTooltip": "You do not have permission to perform any action." - } + "EntityActionsDisabledTooltip": "You do not have permission to perform any action.", + "ResourcePermissions": "Resource permissions" } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json index 132243a740..29efdb558d 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json @@ -61,6 +61,7 @@ "ProfilePicture": "Foto de perfil", "Theme": "Tema", "NotAssigned": "No asignado", - "EntityActionsDisabledTooltip": "No tienes permisos para realizar ninguna acción." + "EntityActionsDisabledTooltip": "No tienes permisos para realizar ninguna acción.", + "ResourcePermissions": "Permisos de recurso" } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json index f1738957c9..dcd7968a11 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json @@ -55,6 +55,7 @@ "OthersGroup": "دیگر", "Today": "امروز", "Apply": "درخواست دادن", - "EntityActionsDisabledTooltip": "شما دسترسی به انجام هر گونه عملیات ندارید." + "EntityActionsDisabledTooltip": "شما دسترسی به انجام هر گونه عملیات ندارید.", + "ResourcePermissions": "مجوزهای منبع" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json index 1af59eb720..1721dc369a 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profiilikuva", "Theme": "Teema", "NotAssigned": "Ei määritetty", - "EntityActionsDisabledTooltip": "Sinulla ei ole oikeutta suorittaa mitään toimintoa." + "EntityActionsDisabledTooltip": "Sinulla ei ole oikeutta suorittaa mitään toimintoa.", + "ResourcePermissions": "Resurssin käyttöoikeudet" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json index 8af5661d1a..3559421694 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json @@ -61,6 +61,7 @@ "ProfilePicture": "Image de profil", "Theme": "Thème", "NotAssigned": "Non attribué", - "EntityActionsDisabledTooltip": "Vous n'avez pas les permissions pour effectuer une action." + "EntityActionsDisabledTooltip": "Vous n'avez pas les permissions pour effectuer une action.", + "ResourcePermissions": "Autorisations de ressources" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json index ba873b7685..e66b51428f 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json @@ -61,6 +61,7 @@ "ProfilePicture": "प्रोफ़ाइल फोटो", "Theme": "विषय", "NotAssigned": "सौंपा नहीं गया है", - "EntityActionsDisabledTooltip": "आपके पास कोई कार्रवाई नहीं है जो करने के लिए है।" + "EntityActionsDisabledTooltip": "आपके पास कोई कार्रवाई नहीं है जो करने के लिए है।", + "ResourcePermissions": "संसाधन अनुमतियाँ" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json index 5b16c84963..a4b6b4b14d 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profilna slika", "Theme": "Tema", "NotAssigned": "Nije dodijeljeno", - "EntityActionsDisabledTooltip": "Nemate dozvolu za izvođenje bilo kakve akcije." + "EntityActionsDisabledTooltip": "Nemate dozvolu za izvođenje bilo kakve akcije.", + "ResourcePermissions": "Dozvole resursa" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json index 0539276498..c23730dde8 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profil kép", "Theme": "Téma", "NotAssigned": "Nem kijelölt", - "EntityActionsDisabledTooltip": "Nincs jogosultsága bármely művelethez." + "EntityActionsDisabledTooltip": "Nincs jogosultsága bármely művelethez.", + "ResourcePermissions": "Erőforrás engedélyek" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json index 8a1eda9e4e..4793285732 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json @@ -61,6 +61,7 @@ "ProfilePicture": "Forsíðumynd", "Theme": "Þema", "NotAssigned": "Ekki skráður", - "EntityActionsDisabledTooltip": "Þú hefur ekki aðgang að þessum aðgerðum." + "EntityActionsDisabledTooltip": "Þú hefur ekki aðgang að þessum aðgerðum.", + "ResourcePermissions": "Aðgangsheimildir auðlinda" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json index 77d4742680..b287522c7a 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json @@ -61,6 +61,7 @@ "ProfilePicture": "Immagine del profilo", "Theme": "Tema", "NotAssigned": "Non assegnato", - "EntityActionsDisabledTooltip": "Non hai i permessi per eseguire alcuna azione." + "EntityActionsDisabledTooltip": "Non hai i permessi per eseguire alcuna azione.", + "ResourcePermissions": "Autorizzazioni risorse" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json index 38182ec9e6..7f3480dd4f 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profielfoto", "Theme": "Thema", "NotAssigned": "Niet toegekend", - "EntityActionsDisabledTooltip": "U hebt geen toegang tot deze acties." + "EntityActionsDisabledTooltip": "U hebt geen toegang tot deze acties.", + "ResourcePermissions": "Bronmachtigingen" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json index d7f321f3d6..1d90a86ffe 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json @@ -61,6 +61,7 @@ "ProfilePicture": "Zdjęcie profilowe", "Theme": "Temat", "NotAssigned": "Nie przypisano", - "EntityActionsDisabledTooltip": "Nie masz uprawnień do wykonania żadnej akcji." + "EntityActionsDisabledTooltip": "Nie masz uprawnień do wykonania żadnej akcji.", + "ResourcePermissions": "Uprawnienia zasobów" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json index 13c8380fa3..c0f11bcd63 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json @@ -61,6 +61,7 @@ "ProfilePicture": "Foto do perfil", "Theme": "Tema", "NotAssigned": "Não atribuído", - "EntityActionsDisabledTooltip": "Você não tem permissão para executar qualquer ação." + "EntityActionsDisabledTooltip": "Você não tem permissão para executar qualquer ação.", + "ResourcePermissions": "Permissões de recurso" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json index e03fd91f50..71616243c8 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json @@ -61,6 +61,7 @@ "ProfilePicture": "Poză de profil", "Theme": "Temă", "NotAssigned": "Nealocat", - "EntityActionsDisabledTooltip": "Nu aveți permisiune să efectuați nicio acțiune." + "EntityActionsDisabledTooltip": "Nu aveți permisiune să efectuați nicio acțiune.", + "ResourcePermissions": "Permisiuni resurse" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json index 57f3079cb4..45636606e3 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json @@ -61,6 +61,7 @@ "ProfilePicture": "Изображение профиля", "Theme": "Тема", "NotAssigned": "Не назначен", - "EntityActionsDisabledTooltip": "У вас нет прав на выполнение каких-либо действий." + "EntityActionsDisabledTooltip": "У вас нет прав на выполнение каких-либо действий.", + "ResourcePermissions": "Разрешения ресурса" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json index eb0ba2958f..c04c550d9c 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profilový obrázok", "Theme": "Téma", "NotAssigned": "Nepridelené", - "EntityActionsDisabledTooltip": "Nemáte oprávnenie vykonávať žiadnu akciu." + "EntityActionsDisabledTooltip": "Nemáte oprávnenie vykonávať žiadnu akciu.", + "ResourcePermissions": "Oprávnenia zdrojov" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json index bc3f795b34..218e51c725 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profilna slika", "Theme": "Tema", "NotAssigned": "Ni dodeljena", - "EntityActionsDisabledTooltip": "Nimate pravic za izvajanje kakršne koli dejanje." + "EntityActionsDisabledTooltip": "Nimate pravic za izvajanje kakršne koli dejanje.", + "ResourcePermissions": "Dovoljenja virov" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json index f0257637c1..bafb6252de 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json @@ -60,6 +60,7 @@ "ProfilePicture": "Profilbild", "Theme": "Tema", "NotAssigned": "Ej tilldelad", - "EntityActionsDisabledTooltip": "Du har inte tillgång till dessa åtgärder." + "EntityActionsDisabledTooltip": "Du har inte tillgång till dessa åtgärder.", + "ResourcePermissions": "Resurstillstånd" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json index b21adf632a..fb27d0edda 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json @@ -61,6 +61,7 @@ "ProfilePicture": "Profil resmi", "Theme": "Tema", "NotAssigned": "Atanmadı", - "EntityActionsDisabledTooltip": "Bu işlemi gerçekleştirmek için yeterli yetkiniz yok." + "EntityActionsDisabledTooltip": "Bu işlemi gerçekleştirmek için yeterli yetkiniz yok.", + "ResourcePermissions": "Kaynak izinleri" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json index 700798895f..e06a2d2b51 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json @@ -61,6 +61,7 @@ "ProfilePicture": "Ảnh đại diện", "Theme": "chủ đề", "NotAssigned": "Không được chỉ định", - "EntityActionsDisabledTooltip": "Bạn không có quyền thực hiện bất kỳ hành động nào." + "EntityActionsDisabledTooltip": "Bạn không có quyền thực hiện bất kỳ hành động nào.", + "ResourcePermissions": "Quyền tài nguyên" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json index 60f70e1545..831d51fa97 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json @@ -61,6 +61,7 @@ "ProfilePicture": "个人资料图片", "Theme": "主题", "NotAssigned": "未分配", - "EntityActionsDisabledTooltip": "您没有权限执行任何操作。" + "EntityActionsDisabledTooltip": "您没有权限执行任何操作。", + "ResourcePermissions": "资源权限" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json index 0f8fdeb254..ee47b4db20 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json @@ -61,6 +61,7 @@ "ProfilePicture": "個人資料圖片", "Theme": "主題", "NotAssigned": "未分配", - "EntityActionsDisabledTooltip": "您沒有權限執行任何操作。" + "EntityActionsDisabledTooltip": "您沒有權限執行任何操作。", + "ResourcePermissions": "資源權限" } } From 185485d61d8d24c20bbf8abc8a7dd98d0a54f7a7 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 17:58:25 +0800 Subject: [PATCH 033/130] Add localization for User and Role resource permission provider keys across multiple languages --- .../Volo/Abp/Identity/Localization/ar.json | 4 +++- .../Volo/Abp/Identity/Localization/cs.json | 4 +++- .../Volo/Abp/Identity/Localization/de.json | 4 +++- .../Volo/Abp/Identity/Localization/el.json | 4 +++- .../Volo/Abp/Identity/Localization/en-GB.json | 4 +++- .../Volo/Abp/Identity/Localization/es.json | 4 +++- .../Volo/Abp/Identity/Localization/fa.json | 4 +++- .../Volo/Abp/Identity/Localization/fi.json | 4 +++- .../Volo/Abp/Identity/Localization/fr.json | 4 +++- .../Volo/Abp/Identity/Localization/hi.json | 4 +++- .../Volo/Abp/Identity/Localization/hr.json | 4 +++- .../Volo/Abp/Identity/Localization/hu.json | 4 +++- .../Volo/Abp/Identity/Localization/is.json | 4 +++- .../Volo/Abp/Identity/Localization/it.json | 4 +++- .../Volo/Abp/Identity/Localization/nl.json | 4 +++- .../Volo/Abp/Identity/Localization/pl-PL.json | 4 +++- .../Volo/Abp/Identity/Localization/pt-BR.json | 4 +++- .../Volo/Abp/Identity/Localization/ro-RO.json | 4 +++- .../Volo/Abp/Identity/Localization/ru.json | 4 +++- .../Volo/Abp/Identity/Localization/sk.json | 4 +++- .../Volo/Abp/Identity/Localization/sl.json | 4 +++- .../Volo/Abp/Identity/Localization/sv.json | 4 +++- .../Volo/Abp/Identity/Localization/tr.json | 4 +++- .../Volo/Abp/Identity/Localization/vi.json | 4 +++- .../Volo/Abp/Identity/Localization/zh-Hans.json | 4 +++- .../Volo/Abp/Identity/Localization/zh-Hant.json | 4 +++- .../Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs | 1 - 27 files changed, 78 insertions(+), 27 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json index 49b22071cd..71b858f834 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json @@ -135,6 +135,8 @@ "ModificationTime": "وقت التعديل", "PasswordUpdateTime": "وقت تحديث كلمة المرور", "LockoutEndTime": "وقت انتهاء القفل", - "FailedAccessCount": "فشل عدد الوصول" + "FailedAccessCount": "فشل عدد الوصول", + "UserResourcePermissionProviderKeyLookupService": "المستخدم", + "RoleResourcePermissionProviderKeyLookupService": "الدور" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json index 8f0456c601..e84320059e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json @@ -135,6 +135,8 @@ "ModificationTime": "Doba úpravy", "PasswordUpdateTime": "Čas aktualizace hesla", "LockoutEndTime": "Čas konce uzamčení", - "FailedAccessCount": "Počet neúspěšných přístupů" + "FailedAccessCount": "Počet neúspěšných přístupů", + "UserResourcePermissionProviderKeyLookupService": "Uživatel", + "RoleResourcePermissionProviderKeyLookupService": "Role" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json index 19daf4d440..4064866f00 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json @@ -135,6 +135,8 @@ "LockoutEndTime": "Endzeit der Sperrung", "FailedAccessCount": "Anzahl der fehlgeschlagenen Zugriffe", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "E-Mail-Verifizierung für die Registrierung erzwingen", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Benutzerkonten werden nicht erstellt, es sei denn, sie verifizieren ihre E-Mail-Adressen." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Benutzerkonten werden nicht erstellt, es sei denn, sie verifizieren ihre E-Mail-Adressen.", + "UserResourcePermissionProviderKeyLookupService": "Benutzer", + "RoleResourcePermissionProviderKeyLookupService": "Rolle" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json index 1e2c8a23ce..ba610a993a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json @@ -128,6 +128,8 @@ "Description:Abp.Identity.User.IsEmailUpdateEnabled": "Εάν το email μπορεί να ενημερωθεί από τον χρήστη.", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Επιβολή επαλήθευσης email για εγγραφή", "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Οι λογαριασμοί χρηστών δεν θα δημιουργηθούν εκτός αν επαληθεύσουν τις διευθύνσεις email τους.", - "Details": "Λεπτομέρειες" + "Details": "Λεπτομέρειες", + "UserResourcePermissionProviderKeyLookupService": "Χρήστης", + "RoleResourcePermissionProviderKeyLookupService": "Ρόλος" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json index 45f25bc864..9f10e1f188 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json @@ -137,6 +137,8 @@ "Description:Abp.Identity.UsersCanChange": "Allow users to change their Two Factor.", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Enforce email verification to register", "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "User accounts will not be created unless they verify their email addresses.", - "Details": "Details" + "Details": "Details", + "UserResourcePermissionProviderKeyLookupService": "User", + "RoleResourcePermissionProviderKeyLookupService": "Role" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json index ff4d4302f8..a2a9414214 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json @@ -135,6 +135,8 @@ "ModificationTime": "tiempo de modificación", "PasswordUpdateTime": "Hora de actualización de contraseña", "LockoutEndTime": "Hora de finalización del bloqueo", - "FailedAccessCount": "Recuento de acceso fallido" + "FailedAccessCount": "Recuento de acceso fallido", + "UserResourcePermissionProviderKeyLookupService": "Usuario", + "RoleResourcePermissionProviderKeyLookupService": "Rol" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json index 024b443fec..e9a1911b52 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json @@ -139,6 +139,8 @@ "Description:Abp.Identity.SignIn.RequireConfirmedEmail": "کاربران می‌توانند حساب ایجاد کنند اما تا زمانی که آدرس ایمیل خود را تایید نکنند نمی‌توانند وارد شوند.", "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "کاربران می‌توانند حساب ایجاد کنند اما تا زمانی که شماره تلفن خود را تایید نکنند نمی‌توانند وارد شوند.", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "اجباری کردن تایید ایمیل برای ثبت‌نام", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "حساب‌های کاربری ایجاد نخواهند شد مگر اینکه آدرس ایمیل خود را تایید کنند." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "حساب‌های کاربری ایجاد نخواهند شد مگر اینکه آدرس ایمیل خود را تایید کنند.", + "UserResourcePermissionProviderKeyLookupService": "کاربر", + "RoleResourcePermissionProviderKeyLookupService": "نقش" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json index 71cfe15712..2a37974cbb 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json @@ -135,6 +135,8 @@ "ModificationTime": "Muutosaika", "PasswordUpdateTime": "Salasanan päivityksen aika", "LockoutEndTime": "Lukituksen päättymisaika", - "FailedAccessCount": "Epäonnistuneet käyttöoikeudet" + "FailedAccessCount": "Epäonnistuneet käyttöoikeudet", + "UserResourcePermissionProviderKeyLookupService": "Käyttäjä", + "RoleResourcePermissionProviderKeyLookupService": "Rooli" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fr.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fr.json index 61bc1dea98..323bb5927a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fr.json @@ -135,6 +135,8 @@ "LockoutEndTime": "Heure de fin du verrouillage", "FailedAccessCount": "Nombre d'accès ayant échoué", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Exiger la vérification de l'e-mail pour s'inscrire", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Les comptes d’utilisateurs ne seront pas créés à moins qu’ils ne vérifient leurs adresses e-mail." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Les comptes d’utilisateurs ne seront pas créés à moins qu’ils ne vérifient leurs adresses e-mail.", + "UserResourcePermissionProviderKeyLookupService": "Utilisateur", + "RoleResourcePermissionProviderKeyLookupService": "Rôle" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hi.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hi.json index 464426cf34..0d5fca3dde 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hi.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hi.json @@ -135,6 +135,8 @@ "ModificationTime": "संशोधन का समय", "PasswordUpdateTime": "पासवर्ड अद्यतन समय", "LockoutEndTime": "तालाबंदी समाप्ति समय", - "FailedAccessCount": "विफल पहुंच गणना" + "FailedAccessCount": "विफल पहुंच गणना", + "UserResourcePermissionProviderKeyLookupService": "उपयोगकर्ता", + "RoleResourcePermissionProviderKeyLookupService": "भूमिका" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hr.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hr.json index e8af421629..0e73aa5f1e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hr.json @@ -135,6 +135,8 @@ "ModificationTime": "Vrijeme izmjene", "PasswordUpdateTime": "Vrijeme ažuriranja lozinke", "LockoutEndTime": "Vrijeme završetka zaključavanja", - "FailedAccessCount": "Broj neuspjelih pristupa" + "FailedAccessCount": "Broj neuspjelih pristupa", + "UserResourcePermissionProviderKeyLookupService": "Korisnik", + "RoleResourcePermissionProviderKeyLookupService": "Uloga" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hu.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hu.json index 935a59e2f5..730a831d23 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hu.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hu.json @@ -135,6 +135,8 @@ "ModificationTime": "Módosítási idő", "PasswordUpdateTime": "A jelszó frissítési ideje", "LockoutEndTime": "A zárolás befejezési ideje", - "FailedAccessCount": "Sikertelen hozzáférések száma" + "FailedAccessCount": "Sikertelen hozzáférések száma", + "UserResourcePermissionProviderKeyLookupService": "Felhasználó", + "RoleResourcePermissionProviderKeyLookupService": "Szerep" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/is.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/is.json index ebf43ceab3..da53a10657 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/is.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/is.json @@ -135,6 +135,8 @@ "ModificationTime": "Breytingartími", "PasswordUpdateTime": "Uppfærslutími lykilorðs", "LockoutEndTime": "Lokatími lokunar", - "FailedAccessCount": "Misheppnuð fjöldi aðgangs" + "FailedAccessCount": "Misheppnuð fjöldi aðgangs", + "UserResourcePermissionProviderKeyLookupService": "Notandi", + "RoleResourcePermissionProviderKeyLookupService": "Hlutverk" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/it.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/it.json index 1d1d9771fd..163ff98754 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/it.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/it.json @@ -135,6 +135,8 @@ "LockoutEndTime": "Ora di fine del blocco", "FailedAccessCount": "Conteggio accessi non riusciti", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Richiedi verifica email per la registrazione", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Gli account utente non verranno creati a meno che non verifichino i loro indirizzi email." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Gli account utente non verranno creati a meno che non verifichino i loro indirizzi email.", + "UserResourcePermissionProviderKeyLookupService": "Utente", + "RoleResourcePermissionProviderKeyLookupService": "Ruolo" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json index 2b405697ae..9836137c80 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json @@ -135,6 +135,8 @@ "LockoutEndTime": "Eindtijd uitsluiting", "FailedAccessCount": "Aantal mislukte toegangen", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "E-mailverificatie vereisen voor registratie", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Gebruikersaccounts worden niet aangemaakt tenzij ze hun e-mailadressen verifiëren." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Gebruikersaccounts worden niet aangemaakt tenzij ze hun e-mailadressen verifiëren.", + "UserResourcePermissionProviderKeyLookupService": "Gebruiker", + "RoleResourcePermissionProviderKeyLookupService": "Rol" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json index c3c8053556..18f33d89c1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json @@ -135,6 +135,8 @@ "LockoutEndTime": "Czas zakończenia blokady", "FailedAccessCount": "Liczba nieudanych dostępów", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Wymagaj weryfikacji e-mail do rejestracji", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Konta użytkowników nie zostaną utworzone, dopóki nie zweryfikują swoich adresów e-mail." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Konta użytkowników nie zostaną utworzone, dopóki nie zweryfikują swoich adresów e-mail.", + "UserResourcePermissionProviderKeyLookupService": "Użytkownik", + "RoleResourcePermissionProviderKeyLookupService": "Rola" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json index 40ef80556f..cd796edbde 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json @@ -135,6 +135,8 @@ "ModificationTime": "Hora da modificação", "PasswordUpdateTime": "Hora de atualização da senha", "LockoutEndTime": "Hora de término do bloqueio", - "FailedAccessCount": "Contagem de acessos com falha" + "FailedAccessCount": "Contagem de acessos com falha", + "UserResourcePermissionProviderKeyLookupService": "Usuário", + "RoleResourcePermissionProviderKeyLookupService": "Perfil" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ro-RO.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ro-RO.json index efe0c53541..3ebc98d179 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ro-RO.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ro-RO.json @@ -135,6 +135,8 @@ "ModificationTime": "Timp de modificare", "PasswordUpdateTime": "Ora actualizării parolei", "LockoutEndTime": "Ora de încheiere a blocării", - "FailedAccessCount": "Număr de acces eșuat" + "FailedAccessCount": "Număr de acces eșuat", + "UserResourcePermissionProviderKeyLookupService": "Utilizator", + "RoleResourcePermissionProviderKeyLookupService": "Rol" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json index 6afaf14b1a..f9ddb814d4 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json @@ -135,6 +135,8 @@ "LockoutEndTime": "Время окончания блокировки", "FailedAccessCount": "Количество неудачных попыток доступа", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Требовать подтверждение электронной почты для регистрации", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Учетные записи пользователей не будут созданы, пока они не подтвердят свои адреса электронной почты." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Учетные записи пользователей не будут созданы, пока они не подтвердят свои адреса электронной почты.", + "UserResourcePermissionProviderKeyLookupService": "Пользователь", + "RoleResourcePermissionProviderKeyLookupService": "Роль" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sk.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sk.json index e529910a5e..26486aa6ab 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sk.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sk.json @@ -135,6 +135,8 @@ "ModificationTime": "Čas úpravy", "PasswordUpdateTime": "Čas aktualizácie hesla", "LockoutEndTime": "Čas ukončenia uzamknutia", - "FailedAccessCount": "Počet neúspešných prístupov" + "FailedAccessCount": "Počet neúspešných prístupov", + "UserResourcePermissionProviderKeyLookupService": "Používateľ", + "RoleResourcePermissionProviderKeyLookupService": "Rola" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json index c02577c385..8ae2a2f81b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json @@ -135,6 +135,8 @@ "ModificationTime": "Čas spreminjanja", "PasswordUpdateTime": "Čas posodobitve gesla", "LockoutEndTime": "Končni čas zaklepanja", - "FailedAccessCount": "Število neuspešnih dostopov" + "FailedAccessCount": "Število neuspešnih dostopov", + "UserResourcePermissionProviderKeyLookupService": "Uporabnik", + "RoleResourcePermissionProviderKeyLookupService": "Vloga" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sv.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sv.json index 166f32a10a..aef65e2b11 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sv.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sv.json @@ -135,6 +135,8 @@ "ModificationTime": "Tid för modifiering", "PasswordUpdateTime": "Uppdateringstid för lösenord", "LockoutEndTime": "Sluttid för låsning", - "FailedAccessCount": "Antal misslyckade åtkomster" + "FailedAccessCount": "Antal misslyckade åtkomster", + "UserResourcePermissionProviderKeyLookupService": "Användare", + "RoleResourcePermissionProviderKeyLookupService": "Roll" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json index 2da62b0355..0a6e4d393c 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json @@ -135,6 +135,8 @@ "LockoutEndTime": "Kilitlenme bitiş zamanı", "FailedAccessCount": "Başarısız giriş denemesi sayısı", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Kayıt için e-posta doğrulaması zorunlu kıl", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Kullanıcı hesapları, e-posta adreslerini doğrulamadıkça oluşturulmayacaktır." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Kullanıcı hesapları, e-posta adreslerini doğrulamadıkça oluşturulmayacaktır.", + "UserResourcePermissionProviderKeyLookupService": "Kullanıcı", + "RoleResourcePermissionProviderKeyLookupService": "Rol" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json index a98baf6566..32c1dfe008 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json @@ -135,6 +135,8 @@ "LockoutEndTime": "Thời gian kết thúc khóa", "FailedAccessCount": "Số lượt truy cập không thành công", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Yêu cầu xác minh email để đăng ký", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Tài khoản người dùng sẽ không được tạo ra trừ khi họ xác minh địa chỉ email của mình." + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "Tài khoản người dùng sẽ không được tạo ra trừ khi họ xác minh địa chỉ email của mình.", + "UserResourcePermissionProviderKeyLookupService": "Người dùng", + "RoleResourcePermissionProviderKeyLookupService": "Vai trò" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json index 0cfc117cf6..3a1239ddc7 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json @@ -135,6 +135,8 @@ "ModificationTime": "修改时间", "PasswordUpdateTime": "密码更新时间", "LockoutEndTime": "锁定结束时间", - "FailedAccessCount": "访问失败次数" + "FailedAccessCount": "访问失败次数", + "UserResourcePermissionProviderKeyLookupService": "用户", + "RoleResourcePermissionProviderKeyLookupService": "角色" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json index 39f6fe5267..a28cc6c7d4 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json @@ -135,6 +135,8 @@ "LockoutEndTime": "鎖定結束時間", "FailedAccessCount": "訪問失敗次數", "DisplayName:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "強制要求驗證電子郵件才能註冊", - "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "除非驗證他們的電子郵件地址,否則不會創建用戶帳戶。" + "Description:Abp.Identity.SignIn.RequireEmailVerificationToRegister": "除非驗證他們的電子郵件地址,否則不會創建用戶帳戶。", + "UserResourcePermissionProviderKeyLookupService": "使用者", + "RoleResourcePermissionProviderKeyLookupService": "角色" } } diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs index 5ff861cd9b..960a3dfcc7 100644 --- a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs +++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/RoleData.cs @@ -47,7 +47,6 @@ public class RoleData : IRoleData { Id = id; Name = name; - Name = name; IsDefault = isDefault; IsStatic = isStatic; IsPublic = isPublic; From 286e312e5ecdd78ec754a86f55df3fb2c2525cfd Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Nov 2025 18:13:39 +0800 Subject: [PATCH 034/130] Refactor permission retrieval methods and clean up unused model validation in ResourcePermissionManagementModal --- .../PermissionManagement/DynamicPermissionDefinitionStore.cs | 4 ++-- .../ResourcePermissionManagementModal.cshtml.cs | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) 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 5245db41ae..3e70ce25b1 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 @@ -82,7 +82,7 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor using (await StoreCache.SyncSemaphore.LockAsync()) { await EnsureCacheIsUptoDateAsync(); - return StoreCache.GetPermissionOrNull(name); + return StoreCache.GetResourcePermissionOrNull(name); } } @@ -96,7 +96,7 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor using (await StoreCache.SyncSemaphore.LockAsync()) { await EnsureCacheIsUptoDateAsync(); - return StoreCache.GetPermissions().ToImmutableList(); + return StoreCache.GetResourcePermissions().ToImmutableList(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs index fe9425069b..1f2000735a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs @@ -35,15 +35,11 @@ public class ResourcePermissionManagementModal : AbpPageModel public virtual Task OnGetAsync() { - ValidateModel(); return Task.FromResult(Page()); } public virtual async Task OnPostAsync() { - ValidateModel(); - - return NoContent(); } From 908e51ecc7573bf3d0b96c7653ebc0975455b412 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 12:12:08 +0800 Subject: [PATCH 035/130] Add missing closing brackets in localization files for Czech and English --- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json | 1 + .../src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json | 1 + 2 files changed, 2 insertions(+) diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json index 892e1ed5f2..550d2a1892 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json @@ -63,4 +63,5 @@ "NotAssigned": "Nepřiřazena", "EntityActionsDisabledTooltip": "Nemáte oprávnění provést žádnou akci.", "ResourcePermissions": "Oprávnění ke zdrojům" + } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json index b17bf084b3..fdb360ceb8 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json @@ -58,4 +58,5 @@ "Apply": "Apply", "EntityActionsDisabledTooltip": "You do not have permission to perform any action.", "ResourcePermissions": "Resource permissions" + } } From 7d5a988a52ea6e09d6b3ef659b7a5cf22d8c70d6 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 13:45:27 +0800 Subject: [PATCH 036/130] Add resource management permissions and update authorization attributes in PermissionAppService --- ...nManagementPermissionDefinitionProvider.cs | 19 +++++++++++++++++++ .../PermissionManagementPermissions.cs | 15 +++++++++++++++ .../PermissionAppService.cs | 14 +++++++------- .../Localization/Domain/ar.json | 17 ++++++++++++++++- .../Localization/Domain/cs.json | 17 ++++++++++++++++- .../Localization/Domain/de.json | 17 ++++++++++++++++- .../Localization/Domain/el.json | 17 ++++++++++++++++- .../Localization/Domain/en-GB.json | 17 ++++++++++++++++- .../Localization/Domain/en.json | 2 ++ .../Localization/Domain/es.json | 17 ++++++++++++++++- .../Localization/Domain/fa.json | 17 ++++++++++++++++- .../Localization/Domain/fi.json | 17 ++++++++++++++++- .../Localization/Domain/fr.json | 17 ++++++++++++++++- .../Localization/Domain/hi.json | 17 ++++++++++++++++- .../Localization/Domain/hr.json | 17 ++++++++++++++++- .../Localization/Domain/hu.json | 17 ++++++++++++++++- .../Localization/Domain/is.json | 17 ++++++++++++++++- .../Localization/Domain/it.json | 17 ++++++++++++++++- .../Localization/Domain/nl.json | 17 ++++++++++++++++- .../Localization/Domain/pl-PL.json | 17 ++++++++++++++++- .../Localization/Domain/pt-BR.json | 17 ++++++++++++++++- .../Localization/Domain/ro-RO.json | 17 ++++++++++++++++- .../Localization/Domain/ru.json | 17 ++++++++++++++++- .../Localization/Domain/sk.json | 17 ++++++++++++++++- .../Localization/Domain/sl.json | 17 ++++++++++++++++- .../Localization/Domain/sv.json | 17 ++++++++++++++++- .../Localization/Domain/tr.json | 17 ++++++++++++++++- .../Localization/Domain/vi.json | 17 ++++++++++++++++- .../Localization/Domain/zh-Hans.json | 17 ++++++++++++++++- .../Localization/Domain/zh-Hant.json | 17 ++++++++++++++++- 30 files changed, 459 insertions(+), 33 deletions(-) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissionDefinitionProvider.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissions.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissionDefinitionProvider.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissionDefinitionProvider.cs new file mode 100644 index 0000000000..74a24d1013 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissionDefinitionProvider.cs @@ -0,0 +1,19 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; +using Volo.Abp.PermissionManagement.Localization; + +namespace Volo.Abp.PermissionManagement; + +public class PermissionManagementPermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + var group = context.AddGroup(PermissionManagementPermissions.GroupName, L("Permission:PermissionManagement")); + var manageResourcePermissions = group.AddPermission(PermissionManagementPermissions.ManageResourcePermissions, L("Permission:ManageResourcePermissions")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissions.cs new file mode 100644 index 0000000000..c4d34b23ad --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissions.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Reflection; + +namespace Volo.Abp.PermissionManagement; + +public static class PermissionManagementPermissions +{ + public const string GroupName = "PermissionManagement"; + + public const string ManageResourcePermissions = GroupName + ".ManageResourcePermissions"; + + public static string[] GetAll() + { + return ReflectionHelper.GetPublicConstantsRecursively(typeof(PermissionManagementPermissions)); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index d91053c945..a31a296684 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -166,7 +166,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } - [Authorize] //TODO: Check permission + [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task GetResourceProviderKeyLookupServicesAsync() { var lookupServices = await ResourcePermissionManager.GetProviderKeyLookupServicesAsync(); @@ -180,7 +180,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService }; } - [Authorize] //TODO: Check permission + [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter) { var lookupService = await ResourcePermissionManager.GetProviderKeyLookupServiceAsync(serviceName); @@ -195,7 +195,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService }; } - [Authorize] //TODO: Check permission + [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task GetResourceDefinitionsAsync(string resourceName) { var result = new GetResourcePermissionDefinitionListResultDto @@ -216,7 +216,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService return result; } - [Authorize] //TODO: Check permission + [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task GetResourceAsync(string resourceName, string resourceKey) { var result = new GetResourcePermissionListResultDto @@ -251,7 +251,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService return result; } - [Authorize] //TODO: Check permission + [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task GetResourceByProviderAsync(string resourceName, string resourceKey, string providerName, string providerKey) { var result = new GetResourcePermissionWithProviderListResultDto @@ -273,7 +273,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService return result; } - [Authorize] //TODO: Check permission + [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); @@ -284,7 +284,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } - [Authorize] //TODO: Check permission + [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task DeleteResourceAsync(string resourceName, string resourceKey, string providerName, string providerKey) { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index 30f4b647e8..607066f6f6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "تحديد الكل", "SaveWithoutAnyPermissionsWarningMessage": "هل أنت متأكد أنك تريد الحفظ بدون أي أذونات؟", "PermissionGroup": "مجموعة الأذونات", - "Filter": "تصفية" + "Filter": "تصفية", + "Permission:PermissionManagement": "إدارة الأذونات", + "Permission:ManageResourcePermissions": "إدارة أذونات الموارد", + "ResourcePermissions": "أذونات الموارد", + "ResourcePermissionTarget": "الهدف", + "ResourcePermissionPermissions": "الأذونات", + "AddResourcePermission": "إضافة أذونات الموارد", + "ResourcePermissionDeletionConfirmationMessage": "هل أنت متأكد أنك تريد حذف أذونات الموارد هذه؟", + "AddResourcePermissions": "إضافة أذونات الموارد", + "UpdateResourcePermissions": "تحديث أذونات الموارد", + "Resource": "المورد", + "ResourceName": "اسم المورد", + "ResourceKey": "مفتاح المورد", + "ProviderName": "اسم الموفر", + "ProviderKey": "مفتاح الموفر", + "GrantAllResourcePermissions": "منح الكل" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json index 814b8e423c..c04b29aed3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Vybrat vše", "SaveWithoutAnyPermissionsWarningMessage": "Opravdu chcete ukládat bez jakýchkoli oprávnění?", "PermissionGroup": "Skupina oprávnění", - "Filter": "Filtr" + "Filter": "Filtr", + "Permission:PermissionManagement": "Správa oprávnění", + "Permission:ManageResourcePermissions": "Správa oprávnění zdrojů", + "ResourcePermissions": "Oprávnění zdrojů", + "ResourcePermissionTarget": "Cíl", + "ResourcePermissionPermissions": "Oprávnění", + "AddResourcePermission": "Přidat oprávnění zdrojů", + "ResourcePermissionDeletionConfirmationMessage": "Opravdu chcete smazat tato oprávnění zdrojů?", + "AddResourcePermissions": "Přidat oprávnění zdrojů", + "UpdateResourcePermissions": "Aktualizovat oprávnění zdrojů", + "Resource": "Zdroj", + "ResourceName": "Název zdroje", + "ResourceKey": "Klíč zdroje", + "ProviderName": "Název poskytovatele", + "ProviderKey": "Klíč poskytovatele", + "GrantAllResourcePermissions": "Udělit vše" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index 7a40c5d4d4..0d9d57e490 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Alle auswählen", "SaveWithoutAnyPermissionsWarningMessage": "Sind Sie sicher, dass Sie ohne Berechtigungen speichern möchten?", "PermissionGroup": "Berechtigungsgruppe", - "Filter": "Filtern" + "Filter": "Filtern", + "Permission:PermissionManagement": "Berechtigungsverwaltung", + "Permission:ManageResourcePermissions": "Ressourcenberechtigungen verwalten", + "ResourcePermissions": "Ressourcenberechtigungen", + "ResourcePermissionTarget": "Ziel", + "ResourcePermissionPermissions": "Berechtigungen", + "AddResourcePermission": "Ressourcenberechtigungen hinzufügen", + "ResourcePermissionDeletionConfirmationMessage": "Sind Sie sicher, dass Sie diese Ressourcenberechtigungen löschen möchten?", + "AddResourcePermissions": "Ressourcenberechtigungen hinzufügen", + "UpdateResourcePermissions": "Ressourcenberechtigungen aktualisieren", + "Resource": "Ressource", + "ResourceName": "Ressourcenname", + "ResourceKey": "Ressourcenschlüssel", + "ProviderName": "Anbietername", + "ProviderKey": "Anbieterschlüssel", + "GrantAllResourcePermissions": "Alle gewähren" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index 3449b7fab6..3368a05023 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Επιλογή όλων", "SaveWithoutAnyPermissionsWarningMessage": "Είστε βέβαιοι ότι θέλετε να αποθηκεύσετε χωρίς δικαιώματα;", "PermissionGroup": "Ομάδα δικαιωμάτων", - "Filter": "Φίλτρο" + "Filter": "Φίλτρο", + "Permission:PermissionManagement": "Διαχείριση δικαιωμάτων", + "Permission:ManageResourcePermissions": "Διαχείριση δικαιωμάτων πόρων", + "ResourcePermissions": "Δικαιώματα πόρων", + "ResourcePermissionTarget": "Στόχος", + "ResourcePermissionPermissions": "Δικαιώματα", + "AddResourcePermission": "Προσθήκη δικαιωμάτων πόρων", + "ResourcePermissionDeletionConfirmationMessage": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτά τα δικαιώματα πόρων;", + "AddResourcePermissions": "Προσθήκη δικαιωμάτων πόρων", + "UpdateResourcePermissions": "Ενημέρωση δικαιωμάτων πόρων", + "Resource": "Πόρος", + "ResourceName": "Όνομα πόρου", + "ResourceKey": "Κλειδί πόρου", + "ProviderName": "Όνομα παρόχου", + "ProviderKey": "Κλειδί παρόχου", + "GrantAllResourcePermissions": "Παραχώρηση όλων" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index be6cbcd1f5..b765a1a9dd 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Select all", "SaveWithoutAnyPermissionsWarningMessage": "Are you sure you want to save without any permissions?", "PermissionGroup": "Permission Group", - "Filter": "Filter" + "Filter": "Filter", + "Permission:PermissionManagement": "Permission Management", + "Permission:ManageResourcePermissions": "Manage resource permissions", + "ResourcePermissions": "Resource permissions", + "ResourcePermissionTarget": "Target", + "ResourcePermissionPermissions": "Permissions", + "AddResourcePermission": "Add resource permissions", + "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete this resource permissions?", + "AddResourcePermissions": "Add resource permissions", + "UpdateResourcePermissions": "Update resource permissions", + "Resource": "Resource", + "ResourceName": "Resource name", + "ResourceKey": "Resource key", + "ProviderName": "Provider name", + "ProviderKey": "Provider key", + "GrantAllResourcePermissions": "Grant all" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index 72b8c6a55a..6d00a0595d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -9,6 +9,8 @@ "SaveWithoutAnyPermissionsWarningMessage": "Are you sure you want to save without any permissions?", "PermissionGroup": "Permission Group", "Filter": "Filter", + "Permission:PermissionManagement": "Permission Management", + "Permission:ManageResourcePermissions": "Manage resource permissions", "ResourcePermissions": "Resource permissions", "ResourcePermissionTarget": "Target", "ResourcePermissionPermissions": "Permissions", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index 622883b259..6e37c9b75b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Seleccionar todo", "SaveWithoutAnyPermissionsWarningMessage": "¿Estás seguro de que quieres guardar sin ningún permiso?", "PermissionGroup": "Grupo de permisos", - "Filter": "Filtrar" + "Filter": "Filtrar", + "Permission:PermissionManagement": "Gestión de permisos", + "Permission:ManageResourcePermissions": "Gestionar permisos de recursos", + "ResourcePermissions": "Permisos de recursos", + "ResourcePermissionTarget": "Objetivo", + "ResourcePermissionPermissions": "Permisos", + "AddResourcePermission": "Agregar permisos de recursos", + "ResourcePermissionDeletionConfirmationMessage": "¿Está seguro de que desea eliminar estos permisos de recursos?", + "AddResourcePermissions": "Agregar permisos de recursos", + "UpdateResourcePermissions": "Actualizar permisos de recursos", + "Resource": "Recurso", + "ResourceName": "Nombre del recurso", + "ResourceKey": "Clave del recurso", + "ProviderName": "Nombre del proveedor", + "ProviderKey": "Clave del proveedor", + "GrantAllResourcePermissions": "Conceder todos" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index 7ea9a6c4f3..d46d69eef3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "انتخاب همه", "SaveWithoutAnyPermissionsWarningMessage": "آیا مطمئن هستید که می خواهید بدون هیچ دسترسی ذخیره کنید؟", "PermissionGroup": "گروه دسترسی", - "Filter": "فیلتر" + "Filter": "فیلتر", + "Permission:PermissionManagement": "مدیریت دسترسی‌ها", + "Permission:ManageResourcePermissions": "مدیریت دسترسی‌های منابع", + "ResourcePermissions": "دسترسی‌های منابع", + "ResourcePermissionTarget": "هدف", + "ResourcePermissionPermissions": "دسترسی‌ها", + "AddResourcePermission": "افزودن دسترسی‌های منابع", + "ResourcePermissionDeletionConfirmationMessage": "آیا مطمئن هستید که می‌خواهید این دسترسی‌های منابع را حذف کنید؟", + "AddResourcePermissions": "افزودن دسترسی‌های منابع", + "UpdateResourcePermissions": "به‌روزرسانی دسترسی‌های منابع", + "Resource": "منبع", + "ResourceName": "نام منبع", + "ResourceKey": "کلید منبع", + "ProviderName": "نام ارائه‌دهنده", + "ProviderKey": "کلید ارائه‌دهنده", + "GrantAllResourcePermissions": "اعطای همه" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index f9b828ade4..bbd0ceab1c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Valitse kaikki", "SaveWithoutAnyPermissionsWarningMessage": "Haluatko varmasti tallentaa ilman käyttöoikeuksia?", "PermissionGroup": "Käyttöoikeus", - "Filter": "Suodatus" + "Filter": "Suodatus", + "Permission:PermissionManagement": "Käyttöoikeuksien hallinta", + "Permission:ManageResourcePermissions": "Hallitse resurssien käyttöoikeuksia", + "ResourcePermissions": "Resurssien käyttöoikeudet", + "ResourcePermissionTarget": "Kohde", + "ResourcePermissionPermissions": "Käyttöoikeudet", + "AddResourcePermission": "Lisää resurssien käyttöoikeudet", + "ResourcePermissionDeletionConfirmationMessage": "Oletko varma, että haluat poistaa nämä resurssien käyttöoikeudet?", + "AddResourcePermissions": "Lisää resurssien käyttöoikeudet", + "UpdateResourcePermissions": "Päivitä resurssien käyttöoikeudet", + "Resource": "Resurssi", + "ResourceName": "Resurssin nimi", + "ResourceKey": "Resurssiavain", + "ProviderName": "Palveluntarjoajan nimi", + "ProviderKey": "Palveluntarjoajan avain", + "GrantAllResourcePermissions": "Myönnä kaikki" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index 79f4e68377..688db98758 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Sélectionner tous les", "SaveWithoutAnyPermissionsWarningMessage": "Êtes-vous sûr de vouloir enregistrer sans aucune autorisation ?", "PermissionGroup": "Groupe d'autorisations", - "Filter": "Filtrer" + "Filter": "Filtrer", + "Permission:PermissionManagement": "Gestion des autorisations", + "Permission:ManageResourcePermissions": "Gérer les autorisations des ressources", + "ResourcePermissions": "Autorisations des ressources", + "ResourcePermissionTarget": "Cible", + "ResourcePermissionPermissions": "Autorisations", + "AddResourcePermission": "Ajouter des autorisations de ressources", + "ResourcePermissionDeletionConfirmationMessage": "Êtes-vous sûr de vouloir supprimer ces autorisations de ressources ?", + "AddResourcePermissions": "Ajouter des autorisations de ressources", + "UpdateResourcePermissions": "Mettre à jour les autorisations de ressources", + "Resource": "Ressource", + "ResourceName": "Nom de la ressource", + "ResourceKey": "Clé de la ressource", + "ProviderName": "Nom du fournisseur", + "ProviderKey": "Clé du fournisseur", + "GrantAllResourcePermissions": "Accorder tout" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index cdc030e8ef..b0d2586a74 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "सभी का चयन करे", "SaveWithoutAnyPermissionsWarningMessage": "क्या आप वाकई बिना किसी अनुमति के सहेजना चाहते हैं?", "PermissionGroup": "अनुमति समूह", - "Filter": "फ़िल्टर" + "Filter": "फ़िल्टर", + "Permission:PermissionManagement": "अनुमति प्रबंधन", + "Permission:ManageResourcePermissions": "संसाधन अनुमतियों का प्रबंधन करें", + "ResourcePermissions": "संसाधन अनुमतियाँ", + "ResourcePermissionTarget": "लक्ष्य", + "ResourcePermissionPermissions": "अनुमतियाँ", + "AddResourcePermission": "संसाधन अनुमतियाँ जोड़ें", + "ResourcePermissionDeletionConfirmationMessage": "क्या आप वाकई इन संसाधन अनुमतियों को हटाना चाहते हैं?", + "AddResourcePermissions": "संसाधन अनुमतियाँ जोड़ें", + "UpdateResourcePermissions": "संसाधन अनुमतियों को अपडेट करें", + "Resource": "संसाधन", + "ResourceName": "संसाधन का नाम", + "ResourceKey": "संसाधन कुंजी", + "ProviderName": "प्रदाता का नाम", + "ProviderKey": "प्रदाता कुंजी", + "GrantAllResourcePermissions": "सभी प्रदान करें" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index 531d0a35d2..7f35537bea 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Odaberi sve", "SaveWithoutAnyPermissionsWarningMessage": "Jeste li sigurni da želite spremiti bez ikakvih dopuštenja?", "PermissionGroup": "Grupa dozvola", - "Filter": "Filtriraj" + "Filter": "Filtriraj", + "Permission:PermissionManagement": "Upravljanje dozvolama", + "Permission:ManageResourcePermissions": "Upravljanje dozvolama resursa", + "ResourcePermissions": "Dozvole resursa", + "ResourcePermissionTarget": "Cilj", + "ResourcePermissionPermissions": "Dozvole", + "AddResourcePermission": "Dodaj dozvole resursa", + "ResourcePermissionDeletionConfirmationMessage": "Jeste li sigurni da želite izbrisati ove dozvole resursa?", + "AddResourcePermissions": "Dodaj dozvole resursa", + "UpdateResourcePermissions": "Ažuriraj dozvole resursa", + "Resource": "Resurs", + "ResourceName": "Naziv resursa", + "ResourceKey": "Ključ resursa", + "ProviderName": "Naziv pružatelja usluga", + "ProviderKey": "Ključ pružatelja usluga", + "GrantAllResourcePermissions": "Dodijeli sve" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index 3177ef8fa7..506793ddc8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Mindet kiválaszt", "SaveWithoutAnyPermissionsWarningMessage": "Biztos, hogy engedélyek nélkül akar menteni?", "PermissionGroup": "Engedélycsoport", - "Filter": "Szűrő" + "Filter": "Szűrő", + "Permission:PermissionManagement": "Engedélyek kezelése", + "Permission:ManageResourcePermissions": "Erőforrás-engedélyek kezelése", + "ResourcePermissions": "Erőforrás-engedélyek", + "ResourcePermissionTarget": "Cél", + "ResourcePermissionPermissions": "Engedélyek", + "AddResourcePermission": "Erőforrás-engedélyek hozzáadása", + "ResourcePermissionDeletionConfirmationMessage": "Biztosan törölni szeretné ezeket az erőforrás-engedélyeket?", + "AddResourcePermissions": "Erőforrás-engedélyek hozzáadása", + "UpdateResourcePermissions": "Erőforrás-engedélyek frissítése", + "Resource": "Erőforrás", + "ResourceName": "Erőforrás neve", + "ResourceKey": "Erőforrás kulcs", + "ProviderName": "Szolgáltató neve", + "ProviderKey": "Szolgáltató kulcs", + "GrantAllResourcePermissions": "Összes engedély megadása" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index 7db32eb99e..0fc52514c0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Velja allt", "SaveWithoutAnyPermissionsWarningMessage": "Ertu viss um að þú viljir vista án nokkurra heimilda?", "PermissionGroup": "Heimildahópur", - "Filter": "Sía" + "Filter": "Sía", + "Permission:PermissionManagement": "Heimildastjórnun", + "Permission:ManageResourcePermissions": "Stjórna heimildum auðlinda", + "ResourcePermissions": "Heimildir auðlinda", + "ResourcePermissionTarget": "Markmið", + "ResourcePermissionPermissions": "Heimildir", + "AddResourcePermission": "Bæta við heimildum auðlinda", + "ResourcePermissionDeletionConfirmationMessage": "Ertu viss um að þú viljir eyða þessum heimildum auðlinda?", + "AddResourcePermissions": "Bæta við heimildum auðlinda", + "UpdateResourcePermissions": "Uppfæra heimildir auðlinda", + "Resource": "Auðlind", + "ResourceName": "Nafn auðlindar", + "ResourceKey": "Lykill auðlindar", + "ProviderName": "Nafn veitanda", + "ProviderKey": "Lykill veitanda", + "GrantAllResourcePermissions": "Veita allt" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index e473c7b310..adad2dd5b0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Seleziona tutto", "SaveWithoutAnyPermissionsWarningMessage": "Sei sicuro di voler salvare senza alcuna autorizzazione?", "PermissionGroup": "Gruppo di autorizzazioni", - "Filter": "Filtro" + "Filter": "Filtro", + "Permission:PermissionManagement": "Gestione delle autorizzazioni", + "Permission:ManageResourcePermissions": "Gestisci le autorizzazioni delle risorse", + "ResourcePermissions": "Autorizzazioni delle risorse", + "ResourcePermissionTarget": "Obiettivo", + "ResourcePermissionPermissions": "Autorizzazioni", + "AddResourcePermission": "Aggiungi autorizzazioni delle risorse", + "ResourcePermissionDeletionConfirmationMessage": "Sei sicuro di voler eliminare queste autorizzazioni delle risorse?", + "AddResourcePermissions": "Aggiungi autorizzazioni delle risorse", + "UpdateResourcePermissions": "Aggiorna autorizzazioni delle risorse", + "Resource": "Risorsa", + "ResourceName": "Nome della risorsa", + "ResourceKey": "Chiave della risorsa", + "ProviderName": "Nome del provider", + "ProviderKey": "Chiave del provider", + "GrantAllResourcePermissions": "Concedi tutto" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index 4b928c4d03..8ae7a7f26a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Selecteer alles", "SaveWithoutAnyPermissionsWarningMessage": "Weet u zeker dat u zonder rechten wilt opslaan?", "PermissionGroup": "Rechtengroep", - "Filter": "Filter" + "Filter": "Filter", + "Permission:PermissionManagement": "Rechtenbeheer", + "Permission:ManageResourcePermissions": "Beheer van rechten voor bronnen", + "ResourcePermissions": "Bronrechten", + "ResourcePermissionTarget": "Doel", + "ResourcePermissionPermissions": "Rechten", + "AddResourcePermission": "Bronrechten toevoegen", + "ResourcePermissionDeletionConfirmationMessage": "Weet u zeker dat u deze bronrechten wilt verwijderen?", + "AddResourcePermissions": "Bronrechten toevoegen", + "UpdateResourcePermissions": "Bronrechten bijwerken", + "Resource": "Bron", + "ResourceName": "Bronnaam", + "ResourceKey": "Bronsleutel", + "ProviderName": "Naam van provider", + "ProviderKey": "Providersleutel", + "GrantAllResourcePermissions": "Alles toekennen" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index ee483651eb..b26df5ad9b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Zaznacz wszystkie", "SaveWithoutAnyPermissionsWarningMessage": "Czy na pewno chcesz zapisać bez żadnych uprawnień?", "PermissionGroup": "Grupa uprawnień", - "Filter": "Filtr" + "Filter": "Filtr", + "Permission:PermissionManagement": "Zarządzanie uprawnieniami", + "Permission:ManageResourcePermissions": "Zarządzaj uprawnieniami zasobów", + "ResourcePermissions": "Uprawnienia zasobów", + "ResourcePermissionTarget": "Cel", + "ResourcePermissionPermissions": "Uprawnienia", + "AddResourcePermission": "Dodaj uprawnienia zasobów", + "ResourcePermissionDeletionConfirmationMessage": "Czy na pewno chcesz usunąć te uprawnienia zasobów?", + "AddResourcePermissions": "Dodaj uprawnienia zasobów", + "UpdateResourcePermissions": "Zaktualizuj uprawnienia zasobów", + "Resource": "Zasób", + "ResourceName": "Nazwa zasobu", + "ResourceKey": "Klucz zasobu", + "ProviderName": "Nazwa dostawcy", + "ProviderKey": "Klucz dostawcy", + "GrantAllResourcePermissions": "Przyznaj wszystko" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index 28231b227f..26974dd4f6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Selecionar todos", "SaveWithoutAnyPermissionsWarningMessage": "Tem certeza que deseja salvar sem nenhuma permissão?", "PermissionGroup": "Grupo de permissão", - "Filter": "Filtrar" + "Filter": "Filtrar", + "Permission:PermissionManagement": "Gerenciamento de Permissões", + "Permission:ManageResourcePermissions": "Gerenciar permissões de recursos", + "ResourcePermissions": "Permissões de recursos", + "ResourcePermissionTarget": "Alvo", + "ResourcePermissionPermissions": "Permissões", + "AddResourcePermission": "Adicionar permissões de recursos", + "ResourcePermissionDeletionConfirmationMessage": "Tem certeza de que deseja excluir essas permissões de recursos?", + "AddResourcePermissions": "Adicionar permissões de recursos", + "UpdateResourcePermissions": "Atualizar permissões de recursos", + "Resource": "Recurso", + "ResourceName": "Nome do recurso", + "ResourceKey": "Chave do recurso", + "ProviderName": "Nome do provedor", + "ProviderKey": "Chave do provedor", + "GrantAllResourcePermissions": "Conceder tudo" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index af7db26acb..0cc196ba53 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Selectează toate", "SaveWithoutAnyPermissionsWarningMessage": "Sigur doriți să salvați fără nicio permisiune?", "PermissionGroup": "Grup de permisiuni", - "Filter": "Filtru" + "Filter": "Filtru", + "Permission:PermissionManagement": "Gestionarea permisiunilor", + "Permission:ManageResourcePermissions": "Gestionați permisiunile resurselor", + "ResourcePermissions": "Permisiuni pentru resurse", + "ResourcePermissionTarget": "Țintă", + "ResourcePermissionPermissions": "Permisiuni", + "AddResourcePermission": "Adăugați permisiuni pentru resurse", + "ResourcePermissionDeletionConfirmationMessage": "Sigur doriți să ștergeți aceste permisiuni pentru resurse?", + "AddResourcePermissions": "Adăugați permisiuni pentru resurse", + "UpdateResourcePermissions": "Actualizați permisiunile pentru resurse", + "Resource": "Resursă", + "ResourceName": "Numele resursei", + "ResourceKey": "Cheia resursei", + "ProviderName": "Numele furnizorului", + "ProviderKey": "Cheia furnizorului", + "GrantAllResourcePermissions": "Acordați toate" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index 6041357b0e..7807d337ea 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Выбрать все", "SaveWithoutAnyPermissionsWarningMessage": "Вы уверены, что хотите сохранить без каких-либо разрешений?", "PermissionGroup": "Группа разрешений", - "Filter": "Фильтр" + "Filter": "Фильтр", + "Permission:PermissionManagement": "Управление разрешениями", + "Permission:ManageResourcePermissions": "Управление разрешениями ресурсов", + "ResourcePermissions": "Разрешения ресурсов", + "ResourcePermissionTarget": "Цель", + "ResourcePermissionPermissions": "Разрешения", + "AddResourcePermission": "Добавить разрешения ресурсов", + "ResourcePermissionDeletionConfirmationMessage": "Вы уверены, что хотите удалить эти разрешения ресурсов?", + "AddResourcePermissions": "Добавить разрешения ресурсов", + "UpdateResourcePermissions": "Обновить разрешения ресурсов", + "Resource": "Ресурс", + "ResourceName": "Имя ресурса", + "ResourceKey": "Ключ ресурса", + "ProviderName": "Имя поставщика", + "ProviderKey": "Ключ поставщика", + "GrantAllResourcePermissions": "Предоставить все" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index c079b8eba1..80c26956d6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Vybrať všetky", "SaveWithoutAnyPermissionsWarningMessage": "Naozaj chcete ukladať bez akýchkoľvek povolení?", "PermissionGroup": "Skupina oprávnení", - "Filter": "Filtrovať" + "Filter": "Filtrovať", + "Permission:PermissionManagement": "Správa oprávnení", + "Permission:ManageResourcePermissions": "Správa oprávnení zdrojov", + "ResourcePermissions": "Oprávnenia zdrojov", + "ResourcePermissionTarget": "Cieľ", + "ResourcePermissionPermissions": "Oprávnenia", + "AddResourcePermission": "Pridať oprávnenia zdrojov", + "ResourcePermissionDeletionConfirmationMessage": "Naozaj chcete odstrániť tieto oprávnenia zdrojov?", + "AddResourcePermissions": "Pridať oprávnenia zdrojov", + "UpdateResourcePermissions": "Aktualizovať oprávnenia zdrojov", + "Resource": "Zdroj", + "ResourceName": "Názov zdroja", + "ResourceKey": "Kľúč zdroja", + "ProviderName": "Názov poskytovateľa", + "ProviderKey": "Kľúč poskytovateľa", + "GrantAllResourcePermissions": "Udeľ všetko" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index 9c906f7387..753f47f9af 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Izberi vse", "SaveWithoutAnyPermissionsWarningMessage": "Ali ste prepričani, da želite shraniti brez kakršnih koli dovoljenj?", "PermissionGroup": "Skupina dovoljenj", - "Filter": "Filtriraj" + "Filter": "Filtriraj", + "Permission:PermissionManagement": "Upravljanje dovoljenj", + "Permission:ManageResourcePermissions": "Upravljanje dovoljenj virov", + "ResourcePermissions": "Dovoljenja virov", + "ResourcePermissionTarget": "Cilj", + "ResourcePermissionPermissions": "Dovoljenja", + "AddResourcePermission": "Dodaj dovoljenja virov", + "ResourcePermissionDeletionConfirmationMessage": "Ali ste prepričani, da želite izbrisati ta dovoljenja virov?", + "AddResourcePermissions": "Dodaj dovoljenja virov", + "UpdateResourcePermissions": "Posodobi dovoljenja virov", + "Resource": "Vir", + "ResourceName": "Ime vira", + "ResourceKey": "Ključ vira", + "ProviderName": "Ime ponudnika", + "ProviderKey": "Ključ ponudnika", + "GrantAllResourcePermissions": "Dodeli vse" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index d46fca4dc4..bf415fce24 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Välj alla", "SaveWithoutAnyPermissionsWarningMessage": "Är du säker på att du vill spara utan några behörigheter?", "PermissionGroup": "Behörighetsgrupp", - "Filter": "Filtrera" + "Filter": "Filtrera", + "Permission:PermissionManagement": "Behörighetshantering", + "Permission:ManageResourcePermissions": "Hantera resursbehörigheter", + "ResourcePermissions": "Resursbehörigheter", + "ResourcePermissionTarget": "Mål", + "ResourcePermissionPermissions": "Behörigheter", + "AddResourcePermission": "Lägg till resursbehörigheter", + "ResourcePermissionDeletionConfirmationMessage": "Är du säker på att du vill ta bort dessa resursbehörigheter?", + "AddResourcePermissions": "Lägg till resursbehörigheter", + "UpdateResourcePermissions": "Uppdatera resursbehörigheter", + "Resource": "Resurs", + "ResourceName": "Resursnamn", + "ResourceKey": "Resursnyckel", + "ProviderName": "Leverantörsnamn", + "ProviderKey": "Leverantörsnyckel", + "GrantAllResourcePermissions": "Bevilja alla" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index 960cd8cf02..cafd9d15ef 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Hepsini seç", "SaveWithoutAnyPermissionsWarningMessage": "Hiçbir izin olmadan kaydetmek istediğinize emin misiniz?", "PermissionGroup": "İzin Grubu", - "Filter": "Filtre" + "Filter": "Filtre", + "Permission:PermissionManagement": "İzin Yönetimi", + "Permission:ManageResourcePermissions": "Kaynak izinlerini yönet", + "ResourcePermissions": "Kaynak izinleri", + "ResourcePermissionTarget": "Hedef", + "ResourcePermissionPermissions": "İzinler", + "AddResourcePermission": "Kaynak izinleri ekle", + "ResourcePermissionDeletionConfirmationMessage": "Bu kaynak izinlerini silmek istediğinizden emin misiniz?", + "AddResourcePermissions": "Kaynak izinleri ekle", + "UpdateResourcePermissions": "Kaynak izinlerini güncelle", + "Resource": "Kaynak", + "ResourceName": "Kaynak adı", + "ResourceKey": "Kaynak anahtarı", + "ProviderName": "Sağlayıcı adı", + "ProviderKey": "Sağlayıcı anahtarı", + "GrantAllResourcePermissions": "Tümünü ver" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index 5e9a9d8565..824372dd0f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "Chọn tất cả", "SaveWithoutAnyPermissionsWarningMessage": "Bạn có chắc chắn muốn lưu mà không có bất kỳ quyền nào không?", "PermissionGroup": "Nhóm quyền", - "Filter": "Lọc" + "Filter": "Lọc", + "Permission:PermissionManagement": "Quản lý quyền", + "Permission:ManageResourcePermissions": "Quản lý quyền tài nguyên", + "ResourcePermissions": "Quyền tài nguyên", + "ResourcePermissionTarget": "Mục tiêu", + "ResourcePermissionPermissions": "Quyền", + "AddResourcePermission": "Thêm quyền tài nguyên", + "ResourcePermissionDeletionConfirmationMessage": "Bạn có chắc chắn muốn xóa các quyền tài nguyên này không?", + "AddResourcePermissions": "Thêm quyền tài nguyên", + "UpdateResourcePermissions": "Cập nhật quyền tài nguyên", + "Resource": "Tài nguyên", + "ResourceName": "Tên tài nguyên", + "ResourceKey": "Khóa tài nguyên", + "ProviderName": "Tên nhà cung cấp", + "ProviderKey": "Khóa nhà cung cấp", + "GrantAllResourcePermissions": "Cấp tất cả" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index df844ca2bf..885c74b678 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "全选", "SaveWithoutAnyPermissionsWarningMessage": "您确定要在没有任何权限的情况下保存吗?", "PermissionGroup": "权限组", - "Filter": "过滤" + "Filter": "过滤", + "Permission:PermissionManagement": "权限管理", + "Permission:ManageResourcePermissions": "管理资源权限", + "ResourcePermissions": "资源权限", + "ResourcePermissionTarget": "目标", + "ResourcePermissionPermissions": "权限", + "AddResourcePermission": "添加资源权限", + "ResourcePermissionDeletionConfirmationMessage": "您确定要删除这些资源权限吗?", + "AddResourcePermissions": "添加资源权限", + "UpdateResourcePermissions": "更新资源权限", + "Resource": "资源", + "ResourceName": "资源名称", + "ResourceKey": "资源键", + "ProviderName": "提供者名称", + "ProviderKey": "提供者键", + "GrantAllResourcePermissions": "授予所有" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index 72af56c960..d93e12ee2e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -8,6 +8,21 @@ "SelectAllInThisTab": "全選", "SaveWithoutAnyPermissionsWarningMessage": "您確定要在沒有任何權限的情況下保存嗎?", "PermissionGroup": "權限組", - "Filter": "過濾" + "Filter": "過濾", + "Permission:PermissionManagement": "權限管理", + "Permission:ManageResourcePermissions": "管理資源權限", + "ResourcePermissions": "資源權限", + "ResourcePermissionTarget": "目標", + "ResourcePermissionPermissions": "權限", + "AddResourcePermission": "添加資源權限", + "ResourcePermissionDeletionConfirmationMessage": "您確定要刪除這些資源權限嗎?", + "AddResourcePermissions": "添加資源權限", + "UpdateResourcePermissions": "更新資源權限", + "Resource": "資源", + "ResourceName": "資源名稱", + "ResourceKey": "資源鍵", + "ProviderName": "提供者名稱", + "ProviderKey": "提供者鍵", + "GrantAllResourcePermissions": "授予所有" } } \ No newline at end of file From ba036ad8c2310e2b40d30f33887f60fe8dbbb092 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 13:58:56 +0800 Subject: [PATCH 037/130] Fix form validation trigger on provider key change in resource permission management modal --- .../add-resource-permission-management-modal.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js index a37877be00..7f59156f9d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -9,6 +9,7 @@ var abp = abp || {}; $all.prop("checked", $items.length === $items.filter(":checked").length); }); + var $permissionManagementForm = $("#addResourcePermissionManagementForm"); var $providerKey = $("#AddModel_ProviderKey"); $providerKey.select2({ ajax: { @@ -44,7 +45,13 @@ var abp = abp || {}; $providerKey.val(null).trigger('change'); }); - $('#addResourcePermissionManagementForm').submit(function () { + $providerKey.change(function () { + if ($providerKey.val()) { + $permissionManagementForm.valid(); + } + }); + + $permissionManagementForm.submit(function () { $(this).valid(); }); From 9f33eb0db8e1b78d2232807c68d62d9a08827d89 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 14:01:20 +0800 Subject: [PATCH 038/130] Update modal footer buttons in permission management modals for consistency --- .../AbpPermissionManagement/PermissionManagementModal.cshtml | 2 -- .../ResourcePermissionManagementModal.cshtml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml index a9caf5c348..c1241af454 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml @@ -79,5 +79,3 @@ - - \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml index dec4c52fd8..ef0337e38f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml @@ -26,6 +26,6 @@
- + From 147d88c75bac73ae641df09eca5831d695e57540 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 17:08:23 +0800 Subject: [PATCH 039/130] Add localization for "NoResourceProviderKeyLookupServiceFound" message and update ResourcePermissionManagementModal to conditionally display modal content based on resource provider availability --- .../Localization/Domain/ar.json | 3 +- .../Localization/Domain/cs.json | 3 +- .../Localization/Domain/de.json | 3 +- .../Localization/Domain/el.json | 3 +- .../Localization/Domain/en-GB.json | 3 +- .../Localization/Domain/en.json | 3 +- .../Localization/Domain/es.json | 3 +- .../Localization/Domain/fa.json | 3 +- .../Localization/Domain/fi.json | 3 +- .../Localization/Domain/fr.json | 3 +- .../Localization/Domain/hi.json | 3 +- .../Localization/Domain/hr.json | 1 + .../Localization/Domain/hu.json | 3 +- .../Localization/Domain/is.json | 3 +- .../Localization/Domain/it.json | 3 +- .../Localization/Domain/nl.json | 3 +- .../Localization/Domain/pl-PL.json | 3 +- .../Localization/Domain/pt-BR.json | 3 +- .../Localization/Domain/ro-RO.json | 3 +- .../Localization/Domain/ru.json | 3 +- .../Localization/Domain/sk.json | 3 +- .../Localization/Domain/sl.json | 3 +- .../Localization/Domain/sv.json | 3 +- .../Localization/Domain/tr.json | 3 +- .../Localization/Domain/vi.json | 3 +- .../Localization/Domain/zh-Hans.json | 3 +- .../Localization/Domain/zh-Hant.json | 3 +- .../ResourcePermissionManagementModal.cshtml | 35 +++++++++++++------ ...esourcePermissionManagementModal.cshtml.cs | 8 +++-- 29 files changed, 84 insertions(+), 38 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index 607066f6f6..c3493a78b7 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -23,6 +23,7 @@ "ResourceKey": "مفتاح المورد", "ProviderName": "اسم الموفر", "ProviderKey": "مفتاح الموفر", - "GrantAllResourcePermissions": "منح الكل" + "GrantAllResourcePermissions": "منح الكل", + "NoResourceProviderKeyLookupServiceFound": "لم يتم العثور على خدمة البحث عن مفتاح مزود المورد" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json index c04b29aed3..dc1a67e6fa 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json @@ -23,6 +23,7 @@ "ResourceKey": "Klíč zdroje", "ProviderName": "Název poskytovatele", "ProviderKey": "Klíč poskytovatele", - "GrantAllResourcePermissions": "Udělit vše" + "GrantAllResourcePermissions": "Udělit vše", + "NoResourceProviderKeyLookupServiceFound": "Nebyla nalezena služba pro vyhledávání klíče poskytovatele zdrojů" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index 0d9d57e490..5f82577980 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -23,6 +23,7 @@ "ResourceKey": "Ressourcenschlüssel", "ProviderName": "Anbietername", "ProviderKey": "Anbieterschlüssel", - "GrantAllResourcePermissions": "Alle gewähren" + "GrantAllResourcePermissions": "Alle gewähren", + "NoResourceProviderKeyLookupServiceFound": "Es wurde kein Dienst zum Nachschlagen des Ressourcenschlüsselanbieters gefunden" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index 3368a05023..cba8f92961 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -23,6 +23,7 @@ "ResourceKey": "Κλειδί πόρου", "ProviderName": "Όνομα παρόχου", "ProviderKey": "Κλειδί παρόχου", - "GrantAllResourcePermissions": "Παραχώρηση όλων" + "GrantAllResourcePermissions": "Παραχώρηση όλων", + "NoResourceProviderKeyLookupServiceFound": "Δεν βρέθηκε υπηρεσία αναζήτησης κλειδιού παρόχου πόρων" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index b765a1a9dd..78f804b685 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -23,6 +23,7 @@ "ResourceKey": "Resource key", "ProviderName": "Provider name", "ProviderKey": "Provider key", - "GrantAllResourcePermissions": "Grant all" + "GrantAllResourcePermissions": "Grant all", + "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index 6d00a0595d..290a7cc035 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -23,6 +23,7 @@ "ResourceKey": "Resource key", "ProviderName": "Provider name", "ProviderKey": "Provider key", - "GrantAllResourcePermissions": "Grant all" + "GrantAllResourcePermissions": "Grant all", + "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index 6e37c9b75b..61d7f9492e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -23,6 +23,7 @@ "ResourceKey": "Clave del recurso", "ProviderName": "Nombre del proveedor", "ProviderKey": "Clave del proveedor", - "GrantAllResourcePermissions": "Conceder todos" + "GrantAllResourcePermissions": "Conceder todos", + "NoResourceProviderKeyLookupServiceFound": "No se encontró ningún servicio de búsqueda de clave de proveedor de recursos" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index d46d69eef3..c3bdc8bdf1 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -23,6 +23,7 @@ "ResourceKey": "کلید منبع", "ProviderName": "نام ارائه‌دهنده", "ProviderKey": "کلید ارائه‌دهنده", - "GrantAllResourcePermissions": "اعطای همه" + "GrantAllResourcePermissions": "اعطای همه", + "NoResourceProviderKeyLookupServiceFound": "هیچ سرویس جستجوی کلید ارائه‌دهنده منبع یافت نشد" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index bbd0ceab1c..fd5ca81872 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -23,6 +23,7 @@ "ResourceKey": "Resurssiavain", "ProviderName": "Palveluntarjoajan nimi", "ProviderKey": "Palveluntarjoajan avain", - "GrantAllResourcePermissions": "Myönnä kaikki" + "GrantAllResourcePermissions": "Myönnä kaikki", + "NoResourceProviderKeyLookupServiceFound": "Resurssin tarjoajan avaimen hakupalvelua ei löytynyt" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index 688db98758..4f40ddbb1d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -23,6 +23,7 @@ "ResourceKey": "Clé de la ressource", "ProviderName": "Nom du fournisseur", "ProviderKey": "Clé du fournisseur", - "GrantAllResourcePermissions": "Accorder tout" + "GrantAllResourcePermissions": "Accorder tout", + "NoResourceProviderKeyLookupServiceFound": "Aucun service de recherche de clé de fournisseur de ressources n'a été trouvé" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index b0d2586a74..b1c58d763a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -23,6 +23,7 @@ "ResourceKey": "संसाधन कुंजी", "ProviderName": "प्रदाता का नाम", "ProviderKey": "प्रदाता कुंजी", - "GrantAllResourcePermissions": "सभी प्रदान करें" + "GrantAllResourcePermissions": "सभी प्रदान करें", + "NoResourceProviderKeyLookupServiceFound": "कोई संसाधन प्रदाता कुंजी खोज सेवा नहीं मिली" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index 7f35537bea..438fb70ece 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -24,5 +24,6 @@ "ProviderName": "Naziv pružatelja usluga", "ProviderKey": "Ključ pružatelja usluga", "GrantAllResourcePermissions": "Dodijeli sve" + ,"NoResourceProviderKeyLookupServiceFound": "Nije pronađena usluga za pronalaženje ključa davatelja resursa" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index 506793ddc8..8b48bfe052 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -23,6 +23,7 @@ "ResourceKey": "Erőforrás kulcs", "ProviderName": "Szolgáltató neve", "ProviderKey": "Szolgáltató kulcs", - "GrantAllResourcePermissions": "Összes engedély megadása" + "GrantAllResourcePermissions": "Összes engedély megadása", + "NoResourceProviderKeyLookupServiceFound": "Nem található erőforrás-szolgáltató kulcs kereső szolgáltatás" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index 0fc52514c0..21e8964f40 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -23,6 +23,7 @@ "ResourceKey": "Lykill auðlindar", "ProviderName": "Nafn veitanda", "ProviderKey": "Lykill veitanda", - "GrantAllResourcePermissions": "Veita allt" + "GrantAllResourcePermissions": "Veita allt", + "NoResourceProviderKeyLookupServiceFound": "Engin þjónusta fannst til að leita að lykli auðlindaveitanda" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index adad2dd5b0..2fced689c8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -23,6 +23,7 @@ "ResourceKey": "Chiave della risorsa", "ProviderName": "Nome del provider", "ProviderKey": "Chiave del provider", - "GrantAllResourcePermissions": "Concedi tutto" + "GrantAllResourcePermissions": "Concedi tutto", + "NoResourceProviderKeyLookupServiceFound": "Non è stato trovato alcun servizio di ricerca chiave provider risorse" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index 8ae7a7f26a..7e8930a5fc 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -23,6 +23,7 @@ "ResourceKey": "Bronsleutel", "ProviderName": "Naam van provider", "ProviderKey": "Providersleutel", - "GrantAllResourcePermissions": "Alles toekennen" + "GrantAllResourcePermissions": "Alles toekennen", + "NoResourceProviderKeyLookupServiceFound": "Er is geen service gevonden voor het opzoeken van de sleutel van de bronprovider" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index b26df5ad9b..dc5f0cdecf 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -23,6 +23,7 @@ "ResourceKey": "Klucz zasobu", "ProviderName": "Nazwa dostawcy", "ProviderKey": "Klucz dostawcy", - "GrantAllResourcePermissions": "Przyznaj wszystko" + "GrantAllResourcePermissions": "Przyznaj wszystko", + "NoResourceProviderKeyLookupServiceFound": "Nie znaleziono usługi wyszukiwania klucza dostawcy zasobów" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index 26974dd4f6..47360c9e64 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -23,6 +23,7 @@ "ResourceKey": "Chave do recurso", "ProviderName": "Nome do provedor", "ProviderKey": "Chave do provedor", - "GrantAllResourcePermissions": "Conceder tudo" + "GrantAllResourcePermissions": "Conceder tudo", + "NoResourceProviderKeyLookupServiceFound": "Nenhum serviço de pesquisa de chave de provedor de recursos foi encontrado" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index 0cc196ba53..f6b222e7cb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -23,6 +23,7 @@ "ResourceKey": "Cheia resursei", "ProviderName": "Numele furnizorului", "ProviderKey": "Cheia furnizorului", - "GrantAllResourcePermissions": "Acordați toate" + "GrantAllResourcePermissions": "Acordați toate", + "NoResourceProviderKeyLookupServiceFound": "Nu a fost găsit niciun serviciu de căutare a cheii furnizorului de resurse" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index 7807d337ea..ca89412b3e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -23,6 +23,7 @@ "ResourceKey": "Ключ ресурса", "ProviderName": "Имя поставщика", "ProviderKey": "Ключ поставщика", - "GrantAllResourcePermissions": "Предоставить все" + "GrantAllResourcePermissions": "Предоставить все", + "NoResourceProviderKeyLookupServiceFound": "Служба поиска ключа поставщика ресурсов не найдена" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index 80c26956d6..b411bd7385 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -23,6 +23,7 @@ "ResourceKey": "Kľúč zdroja", "ProviderName": "Názov poskytovateľa", "ProviderKey": "Kľúč poskytovateľa", - "GrantAllResourcePermissions": "Udeľ všetko" + "GrantAllResourcePermissions": "Udeľ všetko", + "NoResourceProviderKeyLookupServiceFound": "Nebola nájdená služba na vyhľadávanie kľúča poskytovateľa zdrojov" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index 753f47f9af..3ccf267d9c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -23,6 +23,7 @@ "ResourceKey": "Ključ vira", "ProviderName": "Ime ponudnika", "ProviderKey": "Ključ ponudnika", - "GrantAllResourcePermissions": "Dodeli vse" + "GrantAllResourcePermissions": "Dodeli vse", + "NoResourceProviderKeyLookupServiceFound": "Ni bilo mogoče najti storitve za iskanje ključa ponudnika virov" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index bf415fce24..b9be85d2e4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -23,6 +23,7 @@ "ResourceKey": "Resursnyckel", "ProviderName": "Leverantörsnamn", "ProviderKey": "Leverantörsnyckel", - "GrantAllResourcePermissions": "Bevilja alla" + "GrantAllResourcePermissions": "Bevilja alla", + "NoResourceProviderKeyLookupServiceFound": "Ingen tjänst för att söka efter nyckel till resursleverantör hittades" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index cafd9d15ef..445624c35f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -23,6 +23,7 @@ "ResourceKey": "Kaynak anahtarı", "ProviderName": "Sağlayıcı adı", "ProviderKey": "Sağlayıcı anahtarı", - "GrantAllResourcePermissions": "Tümünü ver" + "GrantAllResourcePermissions": "Tümünü ver", + "NoResourceProviderKeyLookupServiceFound": "Herhangi bir kaynak sağlayıcı anahtar arama hizmeti bulunamadı" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index 824372dd0f..fd720ff068 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -23,6 +23,7 @@ "ResourceKey": "Khóa tài nguyên", "ProviderName": "Tên nhà cung cấp", "ProviderKey": "Khóa nhà cung cấp", - "GrantAllResourcePermissions": "Cấp tất cả" + "GrantAllResourcePermissions": "Cấp tất cả", + "NoResourceProviderKeyLookupServiceFound": "Không tìm thấy dịch vụ tra cứu khóa nhà cung cấp tài nguyên" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index 885c74b678..d6887f5ba3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -23,6 +23,7 @@ "ResourceKey": "资源键", "ProviderName": "提供者名称", "ProviderKey": "提供者键", - "GrantAllResourcePermissions": "授予所有" + "GrantAllResourcePermissions": "授予所有", + "NoResourceProviderKeyLookupServiceFound": "未找到资源提供者键查找服务" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index d93e12ee2e..67059c2436 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -23,6 +23,7 @@ "ResourceKey": "資源鍵", "ProviderName": "提供者名稱", "ProviderKey": "提供者鍵", - "GrantAllResourcePermissions": "授予所有" + "GrantAllResourcePermissions": "授予所有", + "NoResourceProviderKeyLookupServiceFound": "未找到資源提供者鍵查找服務" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml index ef0337e38f..9753407a8a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml @@ -12,20 +12,35 @@ Layout = null; } - +@if(Model.HasAnyResourceProviderKeyLookupService) +{ + -
- + + + + + + + +
+ +
+ +
+ +
+ +} +else +{ + - - - -
- + - - +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs index 1f2000735a..b068da3cd6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; @@ -22,6 +23,8 @@ public class ResourcePermissionManagementModal : AbpPageModel [BindProperty(SupportsGet = true)] public string ResourceDisplayName { get; set; } + public bool HasAnyResourceProviderKeyLookupService { get; set; } + public GetResourcePermissionListResultDto ResourcePermissions { get; set; } protected IPermissionAppService PermissionAppService { get; } @@ -33,9 +36,10 @@ public class ResourcePermissionManagementModal : AbpPageModel PermissionAppService = permissionAppService; } - public virtual Task OnGetAsync() + public virtual async Task OnGetAsync() { - return Task.FromResult(Page()); + HasAnyResourceProviderKeyLookupService = (await PermissionAppService.GetResourceProviderKeyLookupServicesAsync()).Providers.Count > 0; + return Page(); } public virtual async Task OnPostAsync() From 7cbc6c370308ba2fb5d065818047fe68eefb20dd Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 18:23:28 +0800 Subject: [PATCH 040/130] Implement resource permissions management with new interfaces and extension methods --- .../Resources/IHasResourcePermissions.cs | 8 ++++ .../ResourcePermissionCheckerExtensions.cs | 27 +------------ .../ResourcePermissionStoreExtensions.cs | 24 +++++++++++- .../Resources/ResourcePermissionPopulator.cs | 39 +++++++++++++++++++ .../Permissions/Resources/EntityExtensions.cs | 19 +++++++++ ...tityResourcePermissionCheckerExtensions.cs | 34 ++-------------- ...ityResourcePermissionRequirementHandler.cs | 5 +-- ...EntityResourcePermissionStoreExtensions.cs | 36 +++++++---------- .../ResourcePermissionManager.cs | 14 +------ 9 files changed, 109 insertions(+), 97 deletions(-) create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs create mode 100644 framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityExtensions.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs new file mode 100644 index 0000000000..971b326d77 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public interface IHasResourcePermissions +{ + public Dictionary ResourcePermissions { get; } +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs index 3a5fad9d7d..c1d153b246 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionCheckerExtensions.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Volo.Abp.Authorization.Permissions.Resources; @@ -32,28 +31,4 @@ public static class ResourcePermissionCheckerExtensions resourceKey.ToString()! ); } - - /// - /// Retrieves a dictionary of permissions and their granted statuses for a specific resource. - /// - /// The type of the resource. - /// The resource permission checker instance. - /// The resource instance for which permissions are being checked. - /// The unique key identifying the resource instance. - /// A task that represents the asynchronous operation. The task result contains a dictionary where keys are permission names and values indicate whether the corresponding permission is granted. - public static Task> GetPermissionsAsync( - this IResourcePermissionChecker resourcePermissionChecker, - TResource resource, - object resourceKey - ) - { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); - Check.NotNull(resource, nameof(resource)); - Check.NotNull(resourceKey, nameof(resourceKey)); - - return resourcePermissionChecker.GetPermissionsAsync( - typeof(TResource).FullName!, - resourceKey.ToString()! - ); - } } diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs index 6ed817c232..f5604a7683 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionStoreExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Volo.Abp.Authorization.Permissions.Resources; @@ -13,7 +14,24 @@ public static class ResourcePermissionStoreExtensions /// The resource instance to retrieve permissions for. /// The unique key identifying the resource instance. /// A task that represents the asynchronous operation. The task result contains an array of strings representing the granted permissions. - public static Task GetGrantedPermissionsAsync( + public static async Task GetGrantedPermissionsAsync( + this IResourcePermissionStore resourcePermissionStore, + TResource resource, + object resourceKey + ) + { + return (await GetPermissionsAsync(resourcePermissionStore, resource, resourceKey)).Where(x => x.Value).Select(x => x.Key).ToArray(); + } + + /// + /// Retrieves a dictionary of permissions and their granted status for the specified entity. + /// + /// The type of the resource. + /// The resource permission store instance. + /// The resource for which the permissions are being retrieved. + /// The unique key identifying the resource instance. + /// A dictionary where the keys are permission names and the values are booleans indicating whether the permission is granted. + public static async Task> GetPermissionsAsync( this IResourcePermissionStore resourcePermissionStore, TResource resource, object resourceKey @@ -23,10 +41,12 @@ public static class ResourcePermissionStoreExtensions Check.NotNull(resource, nameof(resource)); Check.NotNull(resourceKey, nameof(resourceKey)); - return resourcePermissionStore.GetGrantedPermissionsAsync( + var result = await resourcePermissionStore.GetPermissionsAsync( typeof(TResource).FullName!, resourceKey.ToString()! ); + + return result.Result.ToDictionary(x => x.Key, x => x.Value == PermissionGrantResult.Granted); } /// diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs new file mode 100644 index 0000000000..bc30fd20ab --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs @@ -0,0 +1,39 @@ +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class ResourcePermissionPopulator : ITransientDependency +{ + protected IPermissionDefinitionManager PermissionDefinitionManager { get; } + + protected IResourcePermissionChecker ResourcePermissionChecker { get; } + + public ResourcePermissionPopulator(IPermissionDefinitionManager permissionDefinitionManager, IResourcePermissionChecker resourcePermissionChecker) + { + PermissionDefinitionManager = permissionDefinitionManager; + ResourcePermissionChecker = resourcePermissionChecker; + } + + public virtual async Task PopulateAsync(IHasResourcePermissions resource, string resourceName, string resourceKey) + { + Check.NotNull(resource, nameof(resource)); + Check.NotNull(resource.ResourcePermissions, nameof(resource.ResourcePermissions)); + Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName)); + Check.NotNullOrWhiteSpace(resourceKey, nameof(resourceKey)); + + var resopurcePermissionNames = (await PermissionDefinitionManager.GetResourcePermissionsAsync()) + .Where(x => x.ResourceName == resourceName) + .Select(x => x.Name) + .ToArray(); + + var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissionNames, resourceName, resourceKey); + foreach (var resopurcePermission in resopurcePermissionNames) + { + var hasPermission = results.Result.TryGetValue(resopurcePermission, out var granted) && + granted == PermissionGrantResult.Granted; + resource.ResourcePermissions[resopurcePermission] = hasPermission; + } + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityExtensions.cs new file mode 100644 index 0000000000..737cb95077 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public static class EntityExtensions +{ + public static string GetResourceName(this IEntity entity) + { + Check.NotNull(entity, nameof(entity)); + return entity.GetType().FullName!; + } + + public static string GetResourceKey(this IEntity entity) + { + Check.NotNull(entity, nameof(entity)); + return entity.GetKeys().JoinAsString(","); + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs index 963eaf20e0..8c61f3cb39 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Domain.Entities; @@ -14,11 +13,7 @@ public static class EntityResourcePermissionCheckerExtensions /// The name of the permission to check. /// The entity for which the permission is being checked. /// A task that represents the asynchronous operation. The task result is a boolean indicating whether the permission is granted. - public static Task IsGrantedAsync( - this IResourcePermissionChecker resourcePermissionChecker, - string permissionName, - TEntity entity - ) + public static Task IsGrantedAsync(this IResourcePermissionChecker resourcePermissionChecker, string permissionName, TEntity entity) where TEntity : class, IEntity { Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); @@ -27,31 +22,8 @@ public static class EntityResourcePermissionCheckerExtensions return resourcePermissionChecker.IsGrantedAsync( permissionName, - typeof(TEntity).FullName!, - entity.GetKeys().JoinAsString(",") + entity, + entity.GetResourceKey() ); } - - /// - /// Retrieves a dictionary of permissions and their granted status for the specified entity. - /// - /// The type of the entity. - /// The resource permission checker instance. - /// The entity for which the permissions are being retrieved. - /// A dictionary where the keys are permission names and the values are booleans indicating whether the permission is granted. - public static Task> GetPermissionsAsync( - this IResourcePermissionChecker resourcePermissionChecker, - TEntity entity - ) - where TEntity : class, IEntity - { - Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); - Check.NotNull(entity, nameof(entity)); - - return resourcePermissionChecker.GetPermissionsAsync( - typeof(TEntity).FullName!, - entity.GetKeys().JoinAsString(",") - ); - } - } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs index f97ddbafef..9fe07efde9 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Domain.Entities; @@ -21,8 +20,8 @@ public class EntityResourcePermissionRequirementHandler : AuthorizationHandlerThe resource permission store instance. /// The entity for which the permissions are being checked. /// An array of granted permission names as strings. - public static Task GetGrantedPermissionsAsync( + public static async Task GetGrantedPermissionsAsync( this IResourcePermissionStore resourcePermissionStore, TEntity entity ) @@ -24,37 +23,28 @@ public static class EntityResourcePermissionStoreExtensions Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNull(entity, nameof(entity)); - return resourcePermissionStore.GetGrantedPermissionsAsync( - typeof(TEntity).FullName!, - entity.GetKeys().JoinAsString(",") - ); + return (await GetPermissionsAsync(resourcePermissionStore, entity)).Where(x => x.Value).Select(x => x.Key).ToArray(); } /// - /// Retrieves an array of granted entity IDs for a specific permission. + /// Retrieves a dictionary of permissions and their granted status for the specified entity. /// /// The type of the entity. - /// The type of the entity's primary key. /// The resource permission store instance. - /// The name of the permission to check. - /// An array of entity IDs (of type ) for which the permission is granted. - public static async Task GetGrantedEntityIdsAsync( + /// The entity for which the permissions are being retrieved. + /// A dictionary where the keys are permission names and the values are booleans indicating whether the permission is granted. + public static async Task> GetPermissionsAsync( this IResourcePermissionStore resourcePermissionStore, - string permissionName + TEntity entity ) - where TEntity : class, IEntity + where TEntity : class, IEntity { Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); - Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); + Check.NotNull(entity, nameof(entity)); - var keys = await resourcePermissionStore.GetGrantedResourceKeysAsync( - typeof(TEntity).FullName!, - permissionName + return await resourcePermissionStore.GetPermissionsAsync( + entity, + entity.GetResourceKey() ); - - return keys - .Select(x => Convert.ChangeType(x, typeof(TKey))) - .Cast() - .ToArray(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index b9b80eb881..6228ddb97d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -330,25 +330,15 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD var permissionNames = permissions.Select(x => x.Name).ToArray(); var multiplePermissionWithGrantedProviders = new MultiplePermissionWithGrantedProviders(permissionNames); - var neededCheckPermissions = new List(); - var resourcePermissions = await GetAvailablePermissionsAsync(resourceName); - foreach (var permission in resourcePermissions) - { - if (await SimpleStateCheckerManager.IsEnabledAsync(permission)) - { - neededCheckPermissions.Add(permission); - } - } - - if (!neededCheckPermissions.Any()) + if (!resourcePermissions.Any()) { return multiplePermissionWithGrantedProviders; } foreach (var provider in ManagementProviders) { - permissionNames = neededCheckPermissions.Select(x => x.Name).ToArray(); + permissionNames = resourcePermissions.Select(x => x.Name).ToArray(); var multiplePermissionValueProviderGrantInfo = await provider.CheckAsync(permissionNames, resourceName, resourceKey, providerName, providerKey); foreach (var providerResultDict in multiplePermissionValueProviderGrantInfo.Result) From c8c46c8521094ff4540eda6467de75860329360b Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 19:54:46 +0800 Subject: [PATCH 041/130] Add resource permission management to RoleUpdateEventHandler --- .../Identity/RoleUpdateEventHandler.cs | 15 +++++++++++++- .../PermissionAppService.cs | 6 +----- .../IResourcePermissionGrantRepository.cs | 6 ++++++ .../IResourcePermissionManager.cs | 12 +++++++++++ .../ResourcePermissionManager.cs | 20 +++++++++++++++++++ ...EfCoreResourcePermissionGrantRepository.cs | 9 +++++++++ .../MongoResourcePermissionGrantRepository.cs | 10 ++++++++++ 7 files changed, 72 insertions(+), 6 deletions(-) diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleUpdateEventHandler.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleUpdateEventHandler.cs index ca865063e0..bf9a86c06d 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleUpdateEventHandler.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleUpdateEventHandler.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; using Volo.Abp.Identity; @@ -12,13 +13,19 @@ public class RoleUpdateEventHandler : { protected IPermissionManager PermissionManager { get; } protected IPermissionGrantRepository PermissionGrantRepository { get; } + protected IResourcePermissionManager ResourcePermissionManager { get; } + protected IResourcePermissionGrantRepository ResourcePermissionGrantRepository { get; } public RoleUpdateEventHandler( IPermissionManager permissionManager, - IPermissionGrantRepository permissionGrantRepository) + IPermissionGrantRepository permissionGrantRepository, + IResourcePermissionManager resourcePermissionManager, + IResourcePermissionGrantRepository resourcePermissionGrantRepository) { PermissionManager = permissionManager; PermissionGrantRepository = permissionGrantRepository; + ResourcePermissionManager = resourcePermissionManager; + ResourcePermissionGrantRepository = resourcePermissionGrantRepository; } public async Task HandleEventAsync(IdentityRoleNameChangedEto eventData) @@ -28,5 +35,11 @@ public class RoleUpdateEventHandler : { await PermissionManager.UpdateProviderKeyAsync(permissionGrant, eventData.Name); } + + var resourcePermissionGrantsInRole = await ResourcePermissionGrantRepository.GetListAsync(RoleResourcePermissionValueProvider.ProviderName, eventData.OldName); + foreach (var resourcePermissionGrant in resourcePermissionGrantsInRole) + { + await ResourcePermissionManager.UpdateProviderKeyAsync(resourcePermissionGrant, eventData.Name); + } } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index a31a296684..8e5c5b0b79 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -287,11 +287,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task DeleteResourceAsync(string resourceName, string resourceKey, string providerName, string providerKey) { - var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); - foreach (var resourcePermission in resourcePermissions) - { - await ResourcePermissionManager.SetAsync(resourcePermission.Name, resourceName, resourceKey, providerName, providerKey, false); - } + await ResourcePermissionManager.DeleteAsync(resourceName, resourceKey, providerName, providerKey); } protected virtual async Task CheckProviderPolicy(string providerName) 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 index 62fb51fbb8..82060c7ea7 100644 --- 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 @@ -34,6 +34,12 @@ public interface IResourcePermissionGrantRepository : IBasicRepository> GetListAsync( + string providerName, + string providerKey, + CancellationToken cancellationToken = default + ); + Task> GetPermissionsAsync( string resourceName, string resourceKey, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs index 7bb09a4f00..97abeef0b4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs @@ -53,4 +53,16 @@ public interface IResourcePermissionManager string providerKey, bool isGranted ); + + Task UpdateProviderKeyAsync( + ResourcePermissionGrant resourcePermissionGrant, + string providerKey + ); + + Task DeleteAsync( + string resourceName, + string resourceKey, + string providerName, + string providerKey + ); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index 6228ddb97d..0b2d728f62 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -283,6 +283,26 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD await provider.SetAsync(permissionName, resourceName, resourceKey, providerKey, isGranted); } + public virtual async Task UpdateProviderKeyAsync(ResourcePermissionGrant resourcePermissionGrant, string providerKey) + { + using (CurrentTenant.Change(resourcePermissionGrant.TenantId)) + { + //Invalidating the cache for the old key + await Cache.RemoveAsync( + ResourcePermissionGrantCacheItem.CalculateCacheKey( + resourcePermissionGrant.Name, + resourcePermissionGrant.ResourceName, + resourcePermissionGrant.ResourceKey, + resourcePermissionGrant.ProviderName, + resourcePermissionGrant.ProviderKey + ) + ); + } + + resourcePermissionGrant.ProviderKey = providerKey; + return await ResourcePermissionGrantRepository.UpdateAsync(resourcePermissionGrant, true); + } + public virtual async Task UpdateProviderKeyAsync(ResourcePermissionGrant resourcePermissionGrant, string resourceName, string resourceKey, string providerKey) { using (CurrentTenant.Change(resourcePermissionGrant.TenantId)) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs index 0e4bb75945..385a45f989 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/EfCoreResourcePermissionGrantRepository.cs @@ -54,6 +54,15 @@ public class EfCoreResourcePermissionGrantRepository : EfCoreRepository> GetListAsync(string providerName, string providerKey, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(s => + s.ProviderName == providerName && + s.ProviderKey == providerKey + ).ToListAsync(GetCancellationToken(cancellationToken)); + } + public virtual async Task> GetPermissionsAsync(string resourceName, string resourceKey, CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs index 0a8312718a..30db095854 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoResourcePermissionGrantRepository.cs @@ -57,6 +57,16 @@ public class MongoResourcePermissionGrantRepository : MongoDbRepository> GetListAsync(string providerName, string providerKey, CancellationToken cancellationToken = default) + { + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)) + .Where(s => + s.ProviderName == providerName && + s.ProviderKey == providerKey + ).ToListAsync(cancellationToken); + } + public virtual async Task> GetPermissionsAsync(string resourceName, string resourceKey, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); From 288578bb5faa7941ca44c0921098978a36723384 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 20:40:13 +0800 Subject: [PATCH 042/130] Refactor ResourcePermissionGrant properties and index for improved clarity and structure --- ...bpPermissionManagementDbContextModelBuilderExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs index 19d848b9db..e1e569cd06 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs @@ -34,12 +34,12 @@ public static class AbpPermissionManagementDbContextModelBuilderExtensions b.ConfigureByConvention(); b.Property(x => x.Name).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength).IsRequired(); - b.Property(x => x.ProviderName).HasMaxLength(PermissionGrantConsts.MaxProviderNameLength).IsRequired(); - b.Property(x => x.ProviderKey).HasMaxLength(PermissionGrantConsts.MaxProviderKeyLength).IsRequired(); b.Property(x => x.ResourceName).HasMaxLength(PermissionGrantConsts.MaxResourceNameLength).IsRequired(); b.Property(x => x.ResourceKey).HasMaxLength(PermissionGrantConsts.MaxResourceKeyLength).IsRequired(); + b.Property(x => x.ProviderName).HasMaxLength(PermissionGrantConsts.MaxProviderNameLength).IsRequired(); + b.Property(x => x.ProviderKey).HasMaxLength(PermissionGrantConsts.MaxProviderKeyLength).IsRequired(); - b.HasIndex(x => new { x.TenantId, x.Name, x.ProviderName, x.ProviderKey, x.ResourceName, x.ResourceKey }).IsUnique(); + b.HasIndex(x => new { x.TenantId, x.Name, x.ResourceName, x.ResourceKey, x.ProviderName, x.ProviderKey }).IsUnique(); b.ApplyObjectExtensionMappings(); }); From c7413e687758d996ca0e20fe3afec3f3fbedba96 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Nov 2025 21:14:59 +0800 Subject: [PATCH 043/130] Refactor permission group assertions in tests --- .../PermissionAppService_Tests.cs | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs index 11e9e8ca04..61f984a27b 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs @@ -32,34 +32,39 @@ public class PermissionAppService_Tests : AbpPermissionManagementApplicationTest permissionListResultDto.ShouldNotBeNull(); permissionListResultDto.EntityDisplayName.ShouldBe(PermissionTestDataBuilder.User1Id.ToString()); - permissionListResultDto.Groups.Count.ShouldBe(2); + + permissionListResultDto.Groups.Count.ShouldBe(3); permissionListResultDto.Groups.ShouldContain(x => x.Name == "TestGroup"); - permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission1"); - permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission2"); - permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission2.ChildPermission1"); - permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission3"); - permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission4"); + var testGroup = permissionListResultDto.Groups.FirstOrDefault(g => g.Name == "TestGroup"); + testGroup.ShouldNotBeNull(); + testGroup.Permissions.ShouldContain(x => x.Name == "MyPermission1"); + testGroup.Permissions.ShouldContain(x => x.Name == "MyPermission2"); + testGroup.Permissions.ShouldContain(x => x.Name == "MyPermission2.ChildPermission1"); + testGroup.Permissions.ShouldContain(x => x.Name == "MyPermission3"); + testGroup.Permissions.ShouldContain(x => x.Name == "MyPermission4"); - permissionListResultDto.Groups.First().Permissions.ShouldNotContain(x => x.Name == "MyPermission5"); - permissionListResultDto.Groups.First().Permissions.ShouldNotContain(x => x.Name == "MyPermission5.ChildPermission1"); + testGroup.Permissions.ShouldNotContain(x => x.Name == "MyPermission5"); + testGroup.Permissions.ShouldNotContain(x => x.Name == "MyPermission5.ChildPermission1"); using (_currentPrincipalAccessor.Change(new Claim(AbpClaimTypes.Role, "super-admin"))) { var result = await _permissionAppService.GetAsync(UserPermissionValueProvider.ProviderName, PermissionTestDataBuilder.User1Id.ToString()); - result.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission5"); - result.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission5.ChildPermission1"); + var testGroupWithRole = result.Groups.FirstOrDefault(g => g.Name == "TestGroup"); + testGroupWithRole.ShouldNotBeNull(); + testGroupWithRole.Permissions.ShouldContain(x => x.Name == "MyPermission5"); + testGroupWithRole.Permissions.ShouldContain(x => x.Name == "MyPermission5.ChildPermission1"); } - permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission6"); - permissionListResultDto.Groups.First().Permissions.ShouldNotContain(x => x.Name == "MyPermission6.ChildDisabledPermission1"); - permissionListResultDto.Groups.First().Permissions.ShouldContain(x => x.Name == "MyPermission6.ChildPermission2"); + testGroup.Permissions.ShouldContain(x => x.Name == "MyPermission6"); + testGroup.Permissions.ShouldNotContain(x => x.Name == "MyPermission6.ChildDisabledPermission1"); + testGroup.Permissions.ShouldContain(x => x.Name == "MyPermission6.ChildPermission2"); - permissionListResultDto.Groups.First().Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission1"); - permissionListResultDto.Groups.First().Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission2"); - permissionListResultDto.Groups.First().Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission2.ChildPermission1"); - permissionListResultDto.Groups.First().Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission2.ChildPermission2"); - permissionListResultDto.Groups.First().Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission2.ChildPermission2.ChildPermission1"); + testGroup.Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission1"); + testGroup.Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission2"); + testGroup.Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission2.ChildPermission1"); + testGroup.Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission2.ChildPermission2"); + testGroup.Permissions.ShouldNotContain(x => x.Name == "MyDisabledPermission2.ChildPermission2.ChildPermission1"); } [Fact] From 3925ba39c85f2a6b4f4b60e37272396aceb6f006 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Nov 2025 11:22:32 +0800 Subject: [PATCH 044/130] Refactor resource permission population logic --- .../Resources/IHasResourcePermissions.cs | 2 ++ .../Resources/ResourcePermissionPopulator.cs | 34 +++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs index 971b326d77..128c3065fd 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs @@ -5,4 +5,6 @@ namespace Volo.Abp.Authorization.Permissions.Resources; public interface IHasResourcePermissions { public Dictionary ResourcePermissions { get; } + + string GetResourceKey(); } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs index bc30fd20ab..efc0eddd42 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -10,30 +11,43 @@ public class ResourcePermissionPopulator : ITransientDependency protected IResourcePermissionChecker ResourcePermissionChecker { get; } - public ResourcePermissionPopulator(IPermissionDefinitionManager permissionDefinitionManager, IResourcePermissionChecker resourcePermissionChecker) + protected IResourcePermissionStore ResourcePermissionStore { get; } + + public ResourcePermissionPopulator( + IPermissionDefinitionManager permissionDefinitionManager, + IResourcePermissionChecker resourcePermissionChecker, + IResourcePermissionStore resourcePermissionStore) { PermissionDefinitionManager = permissionDefinitionManager; ResourcePermissionChecker = resourcePermissionChecker; + ResourcePermissionStore = resourcePermissionStore; + } + + public virtual async Task PopulateAsync(TResource resource, string resourceName) + where TResource : IHasResourcePermissions + { + await PopulateAsync([resource], resourceName); } - public virtual async Task PopulateAsync(IHasResourcePermissions resource, string resourceName, string resourceKey) + public virtual async Task PopulateAsync(List resources, string resourceName) + where TResource : IHasResourcePermissions { - Check.NotNull(resource, nameof(resource)); - Check.NotNull(resource.ResourcePermissions, nameof(resource.ResourcePermissions)); + Check.NotNull(resources, nameof(resources)); Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName)); - Check.NotNullOrWhiteSpace(resourceKey, nameof(resourceKey)); var resopurcePermissionNames = (await PermissionDefinitionManager.GetResourcePermissionsAsync()) .Where(x => x.ResourceName == resourceName) .Select(x => x.Name) .ToArray(); - var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissionNames, resourceName, resourceKey); - foreach (var resopurcePermission in resopurcePermissionNames) + foreach (var resource in resources) { - var hasPermission = results.Result.TryGetValue(resopurcePermission, out var granted) && - granted == PermissionGrantResult.Granted; - resource.ResourcePermissions[resopurcePermission] = hasPermission; + var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissionNames, resourceName, resource.GetResourceKey()); + foreach (var resopurcePermission in resopurcePermissionNames) + { + var hasPermission = results.Result.TryGetValue(resopurcePermission, out var granted) && granted == PermissionGrantResult.Granted; + resource.ResourcePermissions[resopurcePermission] = hasPermission; + } } } } From 0e6fa6bb2926b1d5f3ea11ab7ae85f2d30f7db02 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Nov 2025 12:37:14 +0800 Subject: [PATCH 045/130] Add "NoResourcePermissionFound" localization key and update related modals --- .../Localization/Domain/ar.json | 3 ++- .../Localization/Domain/cs.json | 3 ++- .../Localization/Domain/de.json | 3 ++- .../Localization/Domain/el.json | 3 ++- .../Localization/Domain/en-GB.json | 3 ++- .../Localization/Domain/en.json | 3 ++- .../Localization/Domain/es.json | 3 ++- .../Localization/Domain/fa.json | 3 ++- .../Localization/Domain/fi.json | 3 ++- .../Localization/Domain/fr.json | 3 ++- .../Localization/Domain/hi.json | 3 ++- .../Localization/Domain/hr.json | 5 ++-- .../Localization/Domain/hu.json | 3 ++- .../Localization/Domain/is.json | 3 ++- .../Localization/Domain/it.json | 3 ++- .../Localization/Domain/nl.json | 3 ++- .../Localization/Domain/pl-PL.json | 3 ++- .../Localization/Domain/pt-BR.json | 3 ++- .../Localization/Domain/ro-RO.json | 3 ++- .../Localization/Domain/ru.json | 3 ++- .../Localization/Domain/sk.json | 3 ++- .../Localization/Domain/sl.json | 3 ++- .../Localization/Domain/sv.json | 3 ++- .../Localization/Domain/tr.json | 3 ++- .../Localization/Domain/vi.json | 3 ++- .../Localization/Domain/zh-Hans.json | 3 ++- .../Localization/Domain/zh-Hant.json | 3 ++- ...ddResourcePermissionManagementModal.cshtml | 3 +-- .../ResourcePermissionManagementModal.cshtml | 15 ++++++++---- ...esourcePermissionManagementModal.cshtml.cs | 23 +++++++------------ ...teResourcePermissionManagementModal.cshtml | 3 +-- 31 files changed, 76 insertions(+), 51 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index c3493a78b7..75f4ecc141 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -24,6 +24,7 @@ "ProviderName": "اسم الموفر", "ProviderKey": "مفتاح الموفر", "GrantAllResourcePermissions": "منح الكل", - "NoResourceProviderKeyLookupServiceFound": "لم يتم العثور على خدمة البحث عن مفتاح مزود المورد" + "NoResourceProviderKeyLookupServiceFound": "لم يتم العثور على خدمة البحث عن مفتاح مزود المورد", + "NoResourcePermissionFound": "لا يوجد إذن مورد معرف للموارد الحالية." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json index dc1a67e6fa..ff0ef28d13 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json @@ -24,6 +24,7 @@ "ProviderName": "Název poskytovatele", "ProviderKey": "Klíč poskytovatele", "GrantAllResourcePermissions": "Udělit vše", - "NoResourceProviderKeyLookupServiceFound": "Nebyla nalezena služba pro vyhledávání klíče poskytovatele zdrojů" + "NoResourceProviderKeyLookupServiceFound": "Nebyla nalezena služba pro vyhledávání klíče poskytovatele zdrojů", + "NoResourcePermissionFound": "Pro aktuální prostředek není definováno žádné oprávnění." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index 5f82577980..b0db8d9404 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -24,6 +24,7 @@ "ProviderName": "Anbietername", "ProviderKey": "Anbieterschlüssel", "GrantAllResourcePermissions": "Alle gewähren", - "NoResourceProviderKeyLookupServiceFound": "Es wurde kein Dienst zum Nachschlagen des Ressourcenschlüsselanbieters gefunden" + "NoResourceProviderKeyLookupServiceFound": "Es wurde kein Dienst zum Nachschlagen des Ressourcenschlüsselanbieters gefunden", + "NoResourcePermissionFound": "Es ist keine Ressourcenberechtigung für die aktuelle Ressource definiert." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index cba8f92961..140fd52d97 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -24,6 +24,7 @@ "ProviderName": "Όνομα παρόχου", "ProviderKey": "Κλειδί παρόχου", "GrantAllResourcePermissions": "Παραχώρηση όλων", - "NoResourceProviderKeyLookupServiceFound": "Δεν βρέθηκε υπηρεσία αναζήτησης κλειδιού παρόχου πόρων" + "NoResourceProviderKeyLookupServiceFound": "Δεν βρέθηκε υπηρεσία αναζήτησης κλειδιού παρόχου πόρων", + "NoResourcePermissionFound": "Δεν έχει οριστεί καμία άδεια πόρου για τον τρέχοντα πόρο." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index 78f804b685..5d8520e5bf 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -24,6 +24,7 @@ "ProviderName": "Provider name", "ProviderKey": "Provider key", "GrantAllResourcePermissions": "Grant all", - "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found" + "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found", + "NoResourcePermissionFound": "There is no resource permission defined for the current resource." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index 290a7cc035..faa2940f7e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -24,6 +24,7 @@ "ProviderName": "Provider name", "ProviderKey": "Provider key", "GrantAllResourcePermissions": "Grant all", - "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found" + "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found", + "NoResourcePermissionFound": "There is no resource permission defined for the current resource." } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index 61d7f9492e..b14aee2747 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -24,6 +24,7 @@ "ProviderName": "Nombre del proveedor", "ProviderKey": "Clave del proveedor", "GrantAllResourcePermissions": "Conceder todos", - "NoResourceProviderKeyLookupServiceFound": "No se encontró ningún servicio de búsqueda de clave de proveedor de recursos" + "NoResourceProviderKeyLookupServiceFound": "No se encontró ningún servicio de búsqueda de clave de proveedor de recursos", + "NoResourcePermissionFound": "No hay ningún permiso de recurso definido para el recurso actual." } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index c3bdc8bdf1..3d2608f091 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -24,6 +24,7 @@ "ProviderName": "نام ارائه‌دهنده", "ProviderKey": "کلید ارائه‌دهنده", "GrantAllResourcePermissions": "اعطای همه", - "NoResourceProviderKeyLookupServiceFound": "هیچ سرویس جستجوی کلید ارائه‌دهنده منبع یافت نشد" + "NoResourceProviderKeyLookupServiceFound": "هیچ سرویس جستجوی کلید ارائه‌دهنده منبع یافت نشد", + "NoResourcePermissionFound": "هیچ مجوز منبعی برای منبع فعلی تعریف نشده است." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index fd5ca81872..0043d3fcb0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -24,6 +24,7 @@ "ProviderName": "Palveluntarjoajan nimi", "ProviderKey": "Palveluntarjoajan avain", "GrantAllResourcePermissions": "Myönnä kaikki", - "NoResourceProviderKeyLookupServiceFound": "Resurssin tarjoajan avaimen hakupalvelua ei löytynyt" + "NoResourceProviderKeyLookupServiceFound": "Resurssin tarjoajan avaimen hakupalvelua ei löytynyt", + "NoResourcePermissionFound": "Nykyiselle resurssille ei ole määritetty käyttöoikeutta." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index 4f40ddbb1d..974c08eb40 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -24,6 +24,7 @@ "ProviderName": "Nom du fournisseur", "ProviderKey": "Clé du fournisseur", "GrantAllResourcePermissions": "Accorder tout", - "NoResourceProviderKeyLookupServiceFound": "Aucun service de recherche de clé de fournisseur de ressources n'a été trouvé" + "NoResourceProviderKeyLookupServiceFound": "Aucun service de recherche de clé de fournisseur de ressources n'a été trouvé", + "NoResourcePermissionFound": "Aucune autorisation de ressource n'est définie pour la ressource actuelle." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index b1c58d763a..3a31aae436 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -24,6 +24,7 @@ "ProviderName": "प्रदाता का नाम", "ProviderKey": "प्रदाता कुंजी", "GrantAllResourcePermissions": "सभी प्रदान करें", - "NoResourceProviderKeyLookupServiceFound": "कोई संसाधन प्रदाता कुंजी खोज सेवा नहीं मिली" + "NoResourceProviderKeyLookupServiceFound": "कोई संसाधन प्रदाता कुंजी खोज सेवा नहीं मिली", + "NoResourcePermissionFound": "वर्तमान संसाधन के लिए कोई संसाधन अनुमति परिभाषित नहीं है।" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index 438fb70ece..c4ce2be7da 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -23,7 +23,8 @@ "ResourceKey": "Ključ resursa", "ProviderName": "Naziv pružatelja usluga", "ProviderKey": "Ključ pružatelja usluga", - "GrantAllResourcePermissions": "Dodijeli sve" - ,"NoResourceProviderKeyLookupServiceFound": "Nije pronađena usluga za pronalaženje ključa davatelja resursa" + "GrantAllResourcePermissions": "Dodijeli sve", + "NoResourceProviderKeyLookupServiceFound": "Nije pronađena usluga za pronalaženje ključa davatelja resursa", + "NoResourcePermissionFound": "Za trenutni resurs nije definirano nijedno dopuštenje resursa." } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index 8b48bfe052..91c13fba04 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -24,6 +24,7 @@ "ProviderName": "Szolgáltató neve", "ProviderKey": "Szolgáltató kulcs", "GrantAllResourcePermissions": "Összes engedély megadása", - "NoResourceProviderKeyLookupServiceFound": "Nem található erőforrás-szolgáltató kulcs kereső szolgáltatás" + "NoResourceProviderKeyLookupServiceFound": "Nem található erőforrás-szolgáltató kulcs kereső szolgáltatás", + "NoResourcePermissionFound": "Az aktuális erőforráshoz nincs meghatározva erőforrás-engedély." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index 21e8964f40..502d489e41 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -24,6 +24,7 @@ "ProviderName": "Nafn veitanda", "ProviderKey": "Lykill veitanda", "GrantAllResourcePermissions": "Veita allt", - "NoResourceProviderKeyLookupServiceFound": "Engin þjónusta fannst til að leita að lykli auðlindaveitanda" + "NoResourceProviderKeyLookupServiceFound": "Engin þjónusta fannst til að leita að lykli auðlindaveitanda", + "NoResourcePermissionFound": "Engin heimild er skilgreind fyrir núverandi auðlind." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index 2fced689c8..77215c6026 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -24,6 +24,7 @@ "ProviderName": "Nome del provider", "ProviderKey": "Chiave del provider", "GrantAllResourcePermissions": "Concedi tutto", - "NoResourceProviderKeyLookupServiceFound": "Non è stato trovato alcun servizio di ricerca chiave provider risorse" + "NoResourceProviderKeyLookupServiceFound": "Non è stato trovato alcun servizio di ricerca chiave provider risorse", + "NoResourcePermissionFound": "Non è definita alcuna autorizzazione per la risorsa corrente." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index 7e8930a5fc..bb01e3b60a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -24,6 +24,7 @@ "ProviderName": "Naam van provider", "ProviderKey": "Providersleutel", "GrantAllResourcePermissions": "Alles toekennen", - "NoResourceProviderKeyLookupServiceFound": "Er is geen service gevonden voor het opzoeken van de sleutel van de bronprovider" + "NoResourceProviderKeyLookupServiceFound": "Er is geen service gevonden voor het opzoeken van de sleutel van de bronprovider", + "NoResourcePermissionFound": "Er is geen bronmachtiging gedefinieerd voor de huidige bron." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index dc5f0cdecf..11f496a7ca 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -24,6 +24,7 @@ "ProviderName": "Nazwa dostawcy", "ProviderKey": "Klucz dostawcy", "GrantAllResourcePermissions": "Przyznaj wszystko", - "NoResourceProviderKeyLookupServiceFound": "Nie znaleziono usługi wyszukiwania klucza dostawcy zasobów" + "NoResourceProviderKeyLookupServiceFound": "Nie znaleziono usługi wyszukiwania klucza dostawcy zasobów", + "NoResourcePermissionFound": "Nie zdefiniowano żadnych uprawnień do zasobów dla bieżącego zasobu." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index 47360c9e64..e9ffcff835 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -24,6 +24,7 @@ "ProviderName": "Nome do provedor", "ProviderKey": "Chave do provedor", "GrantAllResourcePermissions": "Conceder tudo", - "NoResourceProviderKeyLookupServiceFound": "Nenhum serviço de pesquisa de chave de provedor de recursos foi encontrado" + "NoResourceProviderKeyLookupServiceFound": "Nenhum serviço de pesquisa de chave de provedor de recursos foi encontrado", + "NoResourcePermissionFound": "Não há permissão de recurso definida para o recurso atual." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index f6b222e7cb..5b0e48e287 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -24,6 +24,7 @@ "ProviderName": "Numele furnizorului", "ProviderKey": "Cheia furnizorului", "GrantAllResourcePermissions": "Acordați toate", - "NoResourceProviderKeyLookupServiceFound": "Nu a fost găsit niciun serviciu de căutare a cheii furnizorului de resurse" + "NoResourceProviderKeyLookupServiceFound": "Nu a fost găsit niciun serviciu de căutare a cheii furnizorului de resurse", + "NoResourcePermissionFound": "Nu există nicio permisiune de resursă definită pentru resursa curentă." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index ca89412b3e..886de35245 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -24,6 +24,7 @@ "ProviderName": "Имя поставщика", "ProviderKey": "Ключ поставщика", "GrantAllResourcePermissions": "Предоставить все", - "NoResourceProviderKeyLookupServiceFound": "Служба поиска ключа поставщика ресурсов не найдена" + "NoResourceProviderKeyLookupServiceFound": "Служба поиска ключа поставщика ресурсов не найдена", + "NoResourcePermissionFound": "Для текущего ресурса не определено ни одного разрешения." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index b411bd7385..ee914e37c3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -24,6 +24,7 @@ "ProviderName": "Názov poskytovateľa", "ProviderKey": "Kľúč poskytovateľa", "GrantAllResourcePermissions": "Udeľ všetko", - "NoResourceProviderKeyLookupServiceFound": "Nebola nájdená služba na vyhľadávanie kľúča poskytovateľa zdrojov" + "NoResourceProviderKeyLookupServiceFound": "Nebola nájdená služba na vyhľadávanie kľúča poskytovateľa zdrojov", + "NoResourcePermissionFound": "Pre aktuálny zdroj nie je definované žiadne povolenie." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index 3ccf267d9c..97cbf94540 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -24,6 +24,7 @@ "ProviderName": "Ime ponudnika", "ProviderKey": "Ključ ponudnika", "GrantAllResourcePermissions": "Dodeli vse", - "NoResourceProviderKeyLookupServiceFound": "Ni bilo mogoče najti storitve za iskanje ključa ponudnika virov" + "NoResourceProviderKeyLookupServiceFound": "Ni bilo mogoče najti storitve za iskanje ključa ponudnika virov", + "NoResourcePermissionFound": "Za trenutni vir ni določeno nobeno dovoljenje." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index b9be85d2e4..c633fb47ad 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -24,6 +24,7 @@ "ProviderName": "Leverantörsnamn", "ProviderKey": "Leverantörsnyckel", "GrantAllResourcePermissions": "Bevilja alla", - "NoResourceProviderKeyLookupServiceFound": "Ingen tjänst för att söka efter nyckel till resursleverantör hittades" + "NoResourceProviderKeyLookupServiceFound": "Ingen tjänst för att söka efter nyckel till resursleverantör hittades", + "NoResourcePermissionFound": "Det finns ingen resursbehörighet definierad för den aktuella resursen." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index 445624c35f..6135b6dcba 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -24,6 +24,7 @@ "ProviderName": "Sağlayıcı adı", "ProviderKey": "Sağlayıcı anahtarı", "GrantAllResourcePermissions": "Tümünü ver", - "NoResourceProviderKeyLookupServiceFound": "Herhangi bir kaynak sağlayıcı anahtar arama hizmeti bulunamadı" + "NoResourceProviderKeyLookupServiceFound": "Herhangi bir kaynak sağlayıcı anahtar arama hizmeti bulunamadı", + "NoResourcePermissionFound": "Geçerli kaynak için tanımlı bir kaynak izni yok." } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index fd720ff068..835232084f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -24,6 +24,7 @@ "ProviderName": "Tên nhà cung cấp", "ProviderKey": "Khóa nhà cung cấp", "GrantAllResourcePermissions": "Cấp tất cả", - "NoResourceProviderKeyLookupServiceFound": "Không tìm thấy dịch vụ tra cứu khóa nhà cung cấp tài nguyên" + "NoResourceProviderKeyLookupServiceFound": "Không tìm thấy dịch vụ tra cứu khóa nhà cung cấp tài nguyên", + "NoResourcePermissionFound": "Không có quyền tài nguyên nào được định nghĩa cho tài nguyên hiện tại." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index d6887f5ba3..7afaade31f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -24,6 +24,7 @@ "ProviderName": "提供者名称", "ProviderKey": "提供者键", "GrantAllResourcePermissions": "授予所有", - "NoResourceProviderKeyLookupServiceFound": "未找到资源提供者键查找服务" + "NoResourceProviderKeyLookupServiceFound": "未找到资源提供者键查找服务", + "NoResourcePermissionFound": "当前资源未定义任何资源权限。" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index 67059c2436..c93e778a22 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -24,6 +24,7 @@ "ProviderName": "提供者名稱", "ProviderKey": "提供者鍵", "GrantAllResourcePermissions": "授予所有", - "NoResourceProviderKeyLookupServiceFound": "未找到資源提供者鍵查找服務" + "NoResourceProviderKeyLookupServiceFound": "未找到資源提供者鍵查找服務", + "NoResourcePermissionFound": "當前資源未定義任何資源權限。" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index 243bbd15bf..b56371d70d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -1,5 +1,4 @@ @page -@using System.Web; @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.Localization @@ -24,7 +23,7 @@
-

@(HttpUtility.HtmlEncode(Model.ResourceDisplayName))

+

@Model.ResourceDisplayName

diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml index 9753407a8a..54280753b3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml @@ -12,13 +12,13 @@ Layout = null; } -@if(Model.HasAnyResourceProviderKeyLookupService) +@if(Model.HasAnyResourcePermission && Model.HasAnyResourceProviderKeyLookupService) {
- + @@ -35,10 +35,17 @@ else { - + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs index b068da3cd6..c4ac219428 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -23,10 +22,9 @@ public class ResourcePermissionManagementModal : AbpPageModel [BindProperty(SupportsGet = true)] public string ResourceDisplayName { get; set; } + public bool HasAnyResourcePermission { get; set; } public bool HasAnyResourceProviderKeyLookupService { get; set; } - public GetResourcePermissionListResultDto ResourcePermissions { get; set; } - protected IPermissionAppService PermissionAppService { get; } public ResourcePermissionManagementModal(IPermissionAppService permissionAppService) @@ -38,21 +36,16 @@ public class ResourcePermissionManagementModal : AbpPageModel public virtual async Task OnGetAsync() { - HasAnyResourceProviderKeyLookupService = (await PermissionAppService.GetResourceProviderKeyLookupServicesAsync()).Providers.Count > 0; + HasAnyResourcePermission = (await PermissionAppService.GetResourceDefinitionsAsync(ResourceName)).Permissions.Any(); + if (HasAnyResourcePermission) + { + HasAnyResourceProviderKeyLookupService = (await PermissionAppService.GetResourceProviderKeyLookupServicesAsync()).Providers.Count > 0; + } return Page(); } - public virtual async Task OnPostAsync() - { - return NoContent(); - } - - public class ResourcePermissionViewModel + public virtual Task OnPostAsync() { - public string ProviderName { get; set; } - - public string ProviderKey { get; set; } - - public List Permissions { get; set; } + return Task.FromResult(NoContent()); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml index 6c3ac520fb..87f26f1a2f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml @@ -1,5 +1,4 @@ @page -@using System.Web; @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.Localization @@ -25,7 +24,7 @@
-

@(HttpUtility.HtmlEncode(Model.ResourceDisplayName))

+

@Model.ResourceDisplayName

From f30c53ba2e091c808c1b28068dea1b7b23b122b0 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Nov 2025 13:36:34 +0800 Subject: [PATCH 046/130] Add ResourcePermissionGrant cache invalidator --- .../Resources/ResourcePermissionPopulator.cs | 6 ++- ...urcePermissionGrantCacheItemInvalidator.cs | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator.cs diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs index efc0eddd42..700f9104cf 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs @@ -45,8 +45,12 @@ public class ResourcePermissionPopulator : ITransientDependency var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissionNames, resourceName, resource.GetResourceKey()); foreach (var resopurcePermission in resopurcePermissionNames) { + if(resource.ResourcePermissions == null) + { + ObjectHelper.TrySetProperty(resource, x => x.ResourcePermissions, () => new Dictionary()); + } var hasPermission = results.Result.TryGetValue(resopurcePermission, out var granted) && granted == PermissionGrantResult.Granted; - resource.ResourcePermissions[resopurcePermission] = hasPermission; + resource.ResourcePermissions![resopurcePermission] = hasPermission; } } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator.cs new file mode 100644 index 0000000000..97c116de56 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionGrantCacheItemInvalidator : + ILocalEventHandler>, + ITransientDependency +{ + protected ICurrentTenant CurrentTenant { get; } + + protected IDistributedCache Cache { get; } + + public ResourcePermissionGrantCacheItemInvalidator(IDistributedCache cache, ICurrentTenant currentTenant) + { + Cache = cache; + CurrentTenant = currentTenant; + } + + public virtual async Task HandleEventAsync(EntityChangedEventData eventData) + { + var cacheKey = CalculateCacheKey( + eventData.Entity.Name, + eventData.Entity.ResourceName, + eventData.Entity.ResourceKey, + eventData.Entity.ProviderName, + eventData.Entity.ProviderKey + ); + + using (CurrentTenant.Change(eventData.Entity.TenantId)) + { + await Cache.RemoveAsync(cacheKey, considerUow: true); + } + } + + protected virtual string CalculateCacheKey(string name, string resourceName, string resourceKey, string providerName, string providerKey) + { + return ResourcePermissionGrantCacheItem.CalculateCacheKey(name, resourceName, resourceKey, providerName, providerKey); + } +} From d278998a4a4b0d98b0e7156b2b0e65418bc67c22 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Nov 2025 15:55:33 +0800 Subject: [PATCH 047/130] Add resource permission value provider tests --- .../AbpAuthorizationTestModule.cs | 4 ++ .../PermissionValueProviderManager_Tests.cs | 11 ++-- ...rcePermissionValueProviderManager_Tests.cs | 58 +++++++++++++++++++ ...estResourcePermissionDefinitionProvider.cs | 23 ++++++++ .../Resources/FakeResourcePermissionStore.cs | 34 +++++++++++ .../Resources/TestEntityResource.cs | 6 ++ .../TestResourcePermissionValueProvider1.cs | 39 +++++++++++++ .../TestResourcePermissionValueProvider2.cs | 39 +++++++++++++ 8 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionValueProviderManager_Tests.cs create mode 100644 framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs create mode 100644 framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs create mode 100644 framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs create mode 100644 framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider1.cs create mode 100644 framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider2.cs diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs index a4980a58ec..99eff844e4 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.TestServices; +using Volo.Abp.Authorization.TestServices.Resources; using Volo.Abp.Autofac; using Volo.Abp.DynamicProxy; using Volo.Abp.ExceptionHandling; @@ -31,6 +32,9 @@ public class AbpAuthorizationTestModule : AbpModule { options.ValueProviders.Add(); options.ValueProviders.Add(); + + options.ResourceValueProviders.Add(); + options.ResourceValueProviders.Add(); }); } } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/PermissionValueProviderManager_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/PermissionValueProviderManager_Tests.cs index 4144df2e1e..43736f0dce 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/PermissionValueProviderManager_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/PermissionValueProviderManager_Tests.cs @@ -2,12 +2,11 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; -using Volo.Abp.Authorization; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.TestServices; using Xunit; -namespace Volo.Abp; +namespace Volo.Abp.Authorization; public class PermissionValueProviderManager_Tests: AuthorizationTestBase { @@ -17,7 +16,7 @@ public class PermissionValueProviderManager_Tests: AuthorizationTestBase { _permissionValueProviderManager = GetRequiredService(); } - + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) { options.Services.Configure(permissionOptions => @@ -25,7 +24,7 @@ public class PermissionValueProviderManager_Tests: AuthorizationTestBase permissionOptions.ValueProviders.Add(); }); } - + [Fact] public void Should_Throw_Exception_If_Duplicate_Provider_Name_Detected() { @@ -33,7 +32,7 @@ public class PermissionValueProviderManager_Tests: AuthorizationTestBase { var providers = _permissionValueProviderManager.ValueProviders; }); - + exception.Message.ShouldBe($"Duplicate permission value provider name detected: TestPermissionValueProvider1. Providers:{Environment.NewLine}{typeof(TestDuplicatePermissionValueProvider).FullName}{Environment.NewLine}{typeof(TestPermissionValueProvider1).FullName}"); } } @@ -55,4 +54,4 @@ public class TestDuplicatePermissionValueProvider : PermissionValueProvider { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionValueProviderManager_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionValueProviderManager_Tests.cs new file mode 100644 index 0000000000..ceebc1ebac --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionValueProviderManager_Tests.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.Authorization.TestServices.Resources; +using Xunit; + +namespace Volo.Abp.Authorization; + +public class ResourcePermissionValueProviderManager_Tests: AuthorizationTestBase +{ + private readonly IResourcePermissionValueProviderManager _resourcePermissionValueProviderManager; + + public ResourcePermissionValueProviderManager_Tests() + { + _resourcePermissionValueProviderManager = GetRequiredService(); + } + + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.Services.Configure(permissionOptions => + { + permissionOptions.ResourceValueProviders.Add(); + }); + } + + [Fact] + public void Should_Throw_Exception_If_Duplicate_Provider_Name_Detected() + { + var exception = Assert.Throws(() => + { + var providers = _resourcePermissionValueProviderManager.ValueProviders; + }); + + exception.Message.ShouldBe($"Duplicate resource permission value provider name detected: TestResourcePermissionValueProvider1. Providers:{Environment.NewLine}{typeof(TestDuplicateResourcePermissionValueProvider).FullName}{Environment.NewLine}{typeof(TestResourcePermissionValueProvider1).FullName}"); + } +} + +public class TestDuplicateResourcePermissionValueProvider : ResourcePermissionValueProvider +{ + public TestDuplicateResourcePermissionValueProvider(IResourcePermissionStore permissionStore) : base(permissionStore) + { + } + + public override string Name => "TestResourcePermissionValueProvider1"; + + public override Task CheckAsync(ResourcePermissionValueCheckContext context) + { + throw new NotImplementedException(); + } + + public override Task CheckAsync(ResourcePermissionValuesCheckContext context) + { + throw new NotImplementedException(); + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs new file mode 100644 index 0000000000..3100ff6952 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -0,0 +1,23 @@ +using Shouldly; +using Volo.Abp.Authorization.Permissions; + +namespace Volo.Abp.Authorization.TestServices.Resources; + +public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + var permission1 = context.AddResourcePermission("MyResourcePermissions1", resourceName: typeof(TestEntityResource).FullName!); + permission1[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName].ShouldBe(typeof(AuthorizationTestResourcePermissionDefinitionProvider).FullName); + + context.AddResourcePermission("MyResourcePermissions1", resourceName: typeof(TestEntityResource).FullName!).StateCheckers.Add(new TestRequireEditionPermissionSimpleStateChecker()); + context.AddResourcePermission("MyResourcePermissions2", resourceName: typeof(TestEntityResource).FullName!); + context.AddResourcePermission("MyResourcePermissions3", resourceName: typeof(TestEntityResource).FullName!); + context.AddResourcePermission("MyResourcePermissions4", resourceName: typeof(TestEntityResource).FullName!); + context.AddResourcePermission("MyResourcePermissions5", resourceName: typeof(TestEntityResource).FullName!); + context.AddResourcePermission("MyResourcePermissions6", resourceName: typeof(TestEntityResource).FullName!).WithProviders(nameof(TestPermissionValueProvider1)); + context.AddResourcePermission("MyResourcePermissions7", resourceName: typeof(TestEntityResource).FullName!).WithProviders(nameof(TestPermissionValueProvider2)); + + context.GetResourcePermissionOrNull("MyResourcePermissions1").ShouldNotBeNull(); + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs new file mode 100644 index 0000000000..64884db17a --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Authorization.TestServices.Resources; + +public class FakeResourcePermissionStore : IResourcePermissionStore, ITransientDependency +{ + public Task IsGrantedAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) + { + throw new System.NotImplementedException(); + } + + public Task IsGrantedAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) + { + throw new System.NotImplementedException(); + } + + public Task GetPermissionsAsync(string resourceName, string resourceKey) + { + throw new System.NotImplementedException(); + } + + public Task GetGrantedPermissionsAsync(string resourceName, string resourceKey) + { + throw new System.NotImplementedException(); + } + + public Task GetGrantedResourceKeysAsync(string resourceName, string name) + { + throw new System.NotImplementedException(); + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs new file mode 100644 index 0000000000..ca702503d8 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Authorization.TestServices.Resources; + +public class TestEntityResource +{ + +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider1.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider1.cs new file mode 100644 index 0000000000..287e1e6370 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider1.cs @@ -0,0 +1,39 @@ +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; + +namespace Volo.Abp.Authorization.TestServices.Resources; + +public class TestResourcePermissionValueProvider1 : ResourcePermissionValueProvider +{ + public TestResourcePermissionValueProvider1(IResourcePermissionStore permissionStore) : base(permissionStore) + { + } + + public override string Name => "TestResourcePermissionValueProvider1"; + + public override Task CheckAsync(ResourcePermissionValueCheckContext context) + { + var result = PermissionGrantResult.Undefined; + if (context.Permission.Name == "MyPermission6") + { + result = PermissionGrantResult.Granted; + } + + return Task.FromResult(result); + } + + public override Task CheckAsync(ResourcePermissionValuesCheckContext context) + { + var result = new MultiplePermissionGrantResult(); + foreach (var name in context.Permissions.Select(x => x.Name)) + { + result.Result.Add(name, name == "MyPermission6" + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined); + } + + return Task.FromResult(result); + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider2.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider2.cs new file mode 100644 index 0000000000..c606c8c646 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider2.cs @@ -0,0 +1,39 @@ +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; + +namespace Volo.Abp.Authorization.TestServices.Resources; + +public class TestResourcePermissionValueProvider2 : ResourcePermissionValueProvider +{ + public TestResourcePermissionValueProvider2(IResourcePermissionStore permissionStore) : base(permissionStore) + { + } + + public override string Name => "TestResourcePermissionValueProvider2"; + + public override Task CheckAsync(ResourcePermissionValueCheckContext context) + { + var result = PermissionGrantResult.Undefined; + if (context.Permission.Name == "MyPermission7") + { + result = PermissionGrantResult.Granted; + } + + return Task.FromResult(result); + } + + public override Task CheckAsync(ResourcePermissionValuesCheckContext context) + { + var result = new MultiplePermissionGrantResult(); + foreach (var name in context.Permissions.Select(x => x.Name)) + { + result.Result.Add(name, name == "MyPermission7" + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined); + } + + return Task.FromResult(result); + } +} From da995d427c22052210b0022cb38f7fb70011ad16 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Nov 2025 17:06:04 +0800 Subject: [PATCH 048/130] Add resource permission checker tests and update permission definitions --- .../ResourcePermissionChecker_Tests.cs | 58 +++++++++++++++++++ ...estResourcePermissionDefinitionProvider.cs | 18 +++--- .../Resources/FakeResourcePermissionStore.cs | 16 ++++- .../Resources/TestEntityResource.cs | 10 ++++ .../TestResourcePermissionValueProvider1.cs | 8 ++- .../TestResourcePermissionValueProvider2.cs | 8 ++- 6 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionChecker_Tests.cs diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionChecker_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionChecker_Tests.cs new file mode 100644 index 0000000000..37a624cd04 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionChecker_Tests.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.Authorization.TestServices.Resources; +using Xunit; + +namespace Volo.Abp.Authorization; + +public class ResourcePermissionChecker_Tests: AuthorizationTestBase +{ + private readonly IResourcePermissionChecker _resourcePermissionChecker; + + public ResourcePermissionChecker_Tests() + { + _resourcePermissionChecker = GetRequiredService(); + } + + [Fact] + public async Task IsGrantedAsync() + { + (await _resourcePermissionChecker.IsGrantedAsync("MyResourcePermission5", TestEntityResource.ResourceName, TestEntityResource.ResourceKey5)).ShouldBe(true); + (await _resourcePermissionChecker.IsGrantedAsync("UndefinedResourcePermission", TestEntityResource.ResourceName, TestEntityResource.ResourceKey5)).ShouldBe(false); + } + + [Fact] + public async Task IsGranted_Multiple_Result_Async() + { + var result = await _resourcePermissionChecker.IsGrantedAsync(new [] + { + "MyResourcePermission1", + "MyResourcePermission2", + "UndefinedPermission", + "MyResourcePermission3", + "MyResourcePermission4", + "MyResourcePermission5" + }, TestEntityResource.ResourceName, TestEntityResource.ResourceKey5); + + result.Result["MyResourcePermission1"].ShouldBe(PermissionGrantResult.Undefined); + result.Result["MyResourcePermission2"].ShouldBe(PermissionGrantResult.Prohibited); + result.Result["UndefinedPermission"].ShouldBe(PermissionGrantResult.Prohibited); + result.Result["MyResourcePermission3"].ShouldBe(PermissionGrantResult.Granted); + result.Result["MyResourcePermission4"].ShouldBe(PermissionGrantResult.Prohibited); + result.Result["MyResourcePermission5"].ShouldBe(PermissionGrantResult.Granted); + + result = await _resourcePermissionChecker.IsGrantedAsync(new [] + { + "MyResourcePermission6", + }, TestEntityResource.ResourceName, TestEntityResource.ResourceKey6); + result.Result["MyResourcePermission6"].ShouldBe(PermissionGrantResult.Granted); + + result = await _resourcePermissionChecker.IsGrantedAsync(new [] + { + "MyResourcePermission7", + }, TestEntityResource.ResourceName, TestEntityResource.ResourceKey7); + result.Result["MyResourcePermission7"].ShouldBe(PermissionGrantResult.Granted); + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs index 3100ff6952..567963f4da 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -7,17 +7,17 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD { public override void Define(IPermissionDefinitionContext context) { - var permission1 = context.AddResourcePermission("MyResourcePermissions1", resourceName: typeof(TestEntityResource).FullName!); + var permission1 = context.AddResourcePermission("MyResourcePermission1", resourceName: TestEntityResource.ResourceName); + permission1.StateCheckers.Add(new TestRequireEditionPermissionSimpleStateChecker());; permission1[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName].ShouldBe(typeof(AuthorizationTestResourcePermissionDefinitionProvider).FullName); - context.AddResourcePermission("MyResourcePermissions1", resourceName: typeof(TestEntityResource).FullName!).StateCheckers.Add(new TestRequireEditionPermissionSimpleStateChecker()); - context.AddResourcePermission("MyResourcePermissions2", resourceName: typeof(TestEntityResource).FullName!); - context.AddResourcePermission("MyResourcePermissions3", resourceName: typeof(TestEntityResource).FullName!); - context.AddResourcePermission("MyResourcePermissions4", resourceName: typeof(TestEntityResource).FullName!); - context.AddResourcePermission("MyResourcePermissions5", resourceName: typeof(TestEntityResource).FullName!); - context.AddResourcePermission("MyResourcePermissions6", resourceName: typeof(TestEntityResource).FullName!).WithProviders(nameof(TestPermissionValueProvider1)); - context.AddResourcePermission("MyResourcePermissions7", resourceName: typeof(TestEntityResource).FullName!).WithProviders(nameof(TestPermissionValueProvider2)); + context.AddResourcePermission("MyResourcePermission2", resourceName: typeof(TestEntityResource).FullName!); + context.AddResourcePermission("MyResourcePermission3", resourceName: typeof(TestEntityResource).FullName!); + context.AddResourcePermission("MyResourcePermission4", resourceName: typeof(TestEntityResource).FullName!); + context.AddResourcePermission("MyResourcePermission5", resourceName: typeof(TestEntityResource).FullName!); + context.AddResourcePermission("MyResourcePermission6", resourceName: typeof(TestEntityResource).FullName!).WithProviders(nameof(TestResourcePermissionValueProvider1)); + context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!).WithProviders(nameof(TestResourcePermissionValueProvider2)); - context.GetResourcePermissionOrNull("MyResourcePermissions1").ShouldNotBeNull(); + context.GetResourcePermissionOrNull("MyResourcePermission1").ShouldNotBeNull(); } } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs index 64884db17a..54ef8cd9a0 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs @@ -9,12 +9,24 @@ public class FakeResourcePermissionStore : IResourcePermissionStore, ITransientD { public Task IsGrantedAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) { - throw new System.NotImplementedException(); + return Task.FromResult((name == "MyResourcePermission3" || name == "MyResourcePermission5") && + resourceName == TestEntityResource.ResourceName && + (resourceKey == TestEntityResource.ResourceKey3 || resourceKey == TestEntityResource.ResourceKey5)); } public Task IsGrantedAsync(string[] names, string resourceName, string resourceKey, string providerName, string providerKey) { - throw new System.NotImplementedException(); + var result = new MultiplePermissionGrantResult(); + foreach (var name in names) + { + result.Result.Add(name, (name == "MyResourcePermission3" || name == "MyResourcePermission5" && + resourceName == TestEntityResource.ResourceName && + (resourceKey == TestEntityResource.ResourceKey3 || resourceKey == TestEntityResource.ResourceKey5) + ? PermissionGrantResult.Granted + : PermissionGrantResult.Prohibited)); + } + + return Task.FromResult(result); } public Task GetPermissionsAsync(string resourceName, string resourceKey) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs index ca702503d8..81db47bf9a 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs @@ -1,6 +1,16 @@ +using System; + namespace Volo.Abp.Authorization.TestServices.Resources; public class TestEntityResource { + public static readonly string ResourceName = typeof(TestEntityResource).FullName; + public static readonly string ResourceKey1 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey2 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey3 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey4 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey5 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey6 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey7 = Guid.NewGuid().ToString(); } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider1.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider1.cs index 287e1e6370..3c7dc49a83 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider1.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider1.cs @@ -16,7 +16,9 @@ public class TestResourcePermissionValueProvider1 : ResourcePermissionValueProvi public override Task CheckAsync(ResourcePermissionValueCheckContext context) { var result = PermissionGrantResult.Undefined; - if (context.Permission.Name == "MyPermission6") + if (context.Permission.Name == "MyResourcePermission6" && + context.ResourceName == TestEntityResource.ResourceName && + context.ResourceKey == TestEntityResource.ResourceKey6) { result = PermissionGrantResult.Granted; } @@ -29,7 +31,9 @@ public class TestResourcePermissionValueProvider1 : ResourcePermissionValueProvi var result = new MultiplePermissionGrantResult(); foreach (var name in context.Permissions.Select(x => x.Name)) { - result.Result.Add(name, name == "MyPermission6" + result.Result.Add(name, name == "MyResourcePermission6" && + context.ResourceName == TestEntityResource.ResourceName && + context.ResourceKey == TestEntityResource.ResourceKey6 ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined); } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider2.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider2.cs index c606c8c646..c4ac61cb2c 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider2.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestResourcePermissionValueProvider2.cs @@ -16,7 +16,9 @@ public class TestResourcePermissionValueProvider2 : ResourcePermissionValueProvi public override Task CheckAsync(ResourcePermissionValueCheckContext context) { var result = PermissionGrantResult.Undefined; - if (context.Permission.Name == "MyPermission7") + if (context.Permission.Name == "MyResourcePermission7" && + context.ResourceName == TestEntityResource.ResourceName && + context.ResourceKey == TestEntityResource.ResourceKey7) { result = PermissionGrantResult.Granted; } @@ -29,7 +31,9 @@ public class TestResourcePermissionValueProvider2 : ResourcePermissionValueProvi var result = new MultiplePermissionGrantResult(); foreach (var name in context.Permissions.Select(x => x.Name)) { - result.Result.Add(name, name == "MyPermission7" + result.Result.Add(name, name == "MyResourcePermission7" && + context.ResourceName == TestEntityResource.ResourceName && + context.ResourceKey == TestEntityResource.ResourceKey7 ? PermissionGrantResult.Granted : PermissionGrantResult.Undefined); } From d83b9b38b0bcdaf93df14e0d63c96803477579fe Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Nov 2025 17:12:10 +0800 Subject: [PATCH 049/130] Add resource permission tests to StaticPermissionDefinitionStore --- .../StaticPermissionDefinitionStore_Tests.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs index d3a484c32f..ddec7ee451 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs @@ -44,4 +44,29 @@ public class StaticPermissionDefinitionStore_Tests : AuthorizationTestBase var groups = await _store.GetGroupsAsync(); groups.ShouldNotContain(x => x.Name == "TestGetGroup"); } + + [Fact] + public async Task GetResourcePermissionOrNullAsync() + { + var permission = await _store.GetResourcePermissionOrNullAsync("MyResourcePermission1"); + permission.ShouldNotBeNull(); + permission.Name.ShouldBe("MyResourcePermission1"); + permission.StateCheckers.ShouldContain(x => x.GetType() == typeof(TestRequireEditionPermissionSimpleStateChecker)); + + permission = await _store.GetResourcePermissionOrNullAsync("NotExists"); + permission.ShouldBeNull(); + } + + [Fact] + public async Task GetResourcePermissionsAsync() + { + var permissions = await _store.GetResourcePermissionsAsync(); + permissions.ShouldContain(x => x.Name == "MyResourcePermission1"); + permissions.ShouldContain(x => x.Name == "MyResourcePermission2"); + permissions.ShouldContain(x => x.Name == "MyResourcePermission3"); + permissions.ShouldContain(x => x.Name == "MyResourcePermission4"); + permissions.ShouldContain(x => x.Name == "MyResourcePermission5"); + permissions.ShouldContain(x => x.Name == "MyResourcePermission6"); + permissions.ShouldContain(x => x.Name == "MyResourcePermission7"); + } } From 9edc43998c81042266a1b3f51be96455ada09abc Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Nov 2025 17:28:03 +0800 Subject: [PATCH 050/130] Prevent child permissions for resource permissions --- .../Abp/Authorization/Permissions/PermissionDefinition.cs | 5 +++++ .../AuthorizationTestResourcePermissionDefinitionProvider.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index 5e4cdac85a..371d880ee5 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -115,6 +115,11 @@ public class PermissionDefinition : MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { + if (ResourceName != null) + { + throw new AbpException($"Resource permission cannot have child permissions. Resource: {ResourceName}"); + } + var child = new PermissionDefinition( name, displayName, diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs index 567963f4da..5ed46f261e 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -1,5 +1,6 @@ using Shouldly; using Volo.Abp.Authorization.Permissions; +using Xunit; namespace Volo.Abp.Authorization.TestServices.Resources; @@ -8,6 +9,10 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD public override void Define(IPermissionDefinitionContext context) { var permission1 = context.AddResourcePermission("MyResourcePermission1", resourceName: TestEntityResource.ResourceName); + Assert.Throws(() => + { + permission1.AddChild("MyResourcePermission1.ChildPermission1"); + }).Message.ShouldBe($"Resource permission cannot have child permissions. Resource: {TestEntityResource.ResourceName}"); permission1.StateCheckers.Add(new TestRequireEditionPermissionSimpleStateChecker());; permission1[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName].ShouldBe(typeof(AuthorizationTestResourcePermissionDefinitionProvider).FullName); From e42917d509482396b688213967e222b5153c842c Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Nov 2025 20:44:25 +0800 Subject: [PATCH 051/130] Add resource permission grant repository tests --- ...ResourcePermissionGrantRepository_Tests.cs | 6 + ...ResourcePermissionGrantRepository_Tests.cs | 9 ++ .../PermissionTestDataBuilder.cs | 62 ++++++++- ...ResourcePermissionGrantRepository_Tests.cs | 121 ++++++++++++++++++ .../TestEntityResource.cs | 16 +++ ...estResourcePermissionDefinitionProvider.cs | 26 ++++ 6 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/ResourcePermissionGrantRepository_Tests.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/ResourcePermissionGrantRepository_Tests.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/ResourcePermissionGrantRepository_Tests.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestEntityResource.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/ResourcePermissionGrantRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/ResourcePermissionGrantRepository_Tests.cs new file mode 100644 index 0000000000..89fdb9a9d4 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/ResourcePermissionGrantRepository_Tests.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.PermissionManagement.EntityFrameworkCore; + +public class ResourcePermissionGrantRepository_Tests : ResourcePermissionGrantRepository_Tests +{ + +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/ResourcePermissionGrantRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/ResourcePermissionGrantRepository_Tests.cs new file mode 100644 index 0000000000..32240f998c --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/ResourcePermissionGrantRepository_Tests.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace Volo.Abp.PermissionManagement.MongoDB; + +[Collection(MongoTestCollection.Name)] +public class ResourcePermissionGrantRepository_Tests : ResourcePermissionGrantRepository_Tests +{ + +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs index c60dff997a..c69d315766 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs @@ -12,12 +12,17 @@ public class PermissionTestDataBuilder : ITransientDependency public static Guid User2Id { get; } = Guid.NewGuid(); private readonly IPermissionGrantRepository _permissionGrantRepository; + private readonly IResourcePermissionGrantRepository _resourcePermissionGrantRepository; private readonly IGuidGenerator _guidGenerator; - public PermissionTestDataBuilder(IGuidGenerator guidGenerator, IPermissionGrantRepository permissionGrantRepository) + public PermissionTestDataBuilder( + IGuidGenerator guidGenerator, + IPermissionGrantRepository permissionGrantRepository, + IResourcePermissionGrantRepository resourcePermissionGrantRepository) { _guidGenerator = guidGenerator; _permissionGrantRepository = permissionGrantRepository; + _resourcePermissionGrantRepository = resourcePermissionGrantRepository; } public async Task BuildAsync() @@ -57,5 +62,60 @@ public class PermissionTestDataBuilder : ITransientDependency User1Id.ToString() ) ); + + await _resourcePermissionGrantRepository.InsertAsync( + new ResourcePermissionGrant( + _guidGenerator.Create(), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + User1Id.ToString() + ) + ); + + await _resourcePermissionGrantRepository.InsertAsync( + new ResourcePermissionGrant( + _guidGenerator.Create(), + "MyDisabledResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + User1Id.ToString() + ) + ); + + await _resourcePermissionGrantRepository.InsertAsync( + new ResourcePermissionGrant( + _guidGenerator.Create(), + "MyResourcePermission3", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey3, + UserPermissionValueProvider.ProviderName, + User1Id.ToString() + ) + ); + + await _resourcePermissionGrantRepository.InsertAsync( + new ResourcePermissionGrant( + _guidGenerator.Create(), + "MyResourcePermission5", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey3, + UserPermissionValueProvider.ProviderName, + User1Id.ToString() + ) + ); + + await _resourcePermissionGrantRepository.InsertAsync( + new ResourcePermissionGrant( + _guidGenerator.Create(), + "MyResourcePermission5", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey5, + UserPermissionValueProvider.ProviderName, + User1Id.ToString() + ) + ); } } diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/ResourcePermissionGrantRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/ResourcePermissionGrantRepository_Tests.cs new file mode 100644 index 0000000000..90652683b1 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/ResourcePermissionGrantRepository_Tests.cs @@ -0,0 +1,121 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public abstract class ResourcePermissionGrantRepository_Tests : PermissionManagementTestBase + where TStartupModule : IAbpModule +{ + protected IResourcePermissionGrantRepository ResourcePermissionGrantRepository { get; } + + protected ResourcePermissionGrantRepository_Tests() + { + ResourcePermissionGrantRepository = GetRequiredService(); + } + + [Fact] + public async Task FindAsync() + { + (await ResourcePermissionGrantRepository.FindAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString())).ShouldNotBeNull(); + + (await ResourcePermissionGrantRepository.FindAsync( + "Undefined-Permission", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString())).ShouldBeNull(); + + (await ResourcePermissionGrantRepository.FindAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Undefined-Provider", + "Unknown-Id")).ShouldBeNull(); + } + + [Fact] + public async Task GetList4Async() + { + var permissionGrants = + await ResourcePermissionGrantRepository.GetListAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey3, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission3"); + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission5"); + } + + [Fact] + public async Task GetList5Async() + { + var permissionGrants = + await ResourcePermissionGrantRepository.GetListAsync( + new[] { "MyResourcePermission1", "MyResourcePermission3", "MyResourcePermission5" }, + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey3, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + + permissionGrants.ShouldNotContain(p => p.Name == "MyResourcePermission1"); + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission3"); + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission5"); + } + + [Fact] + public async Task GetList2Async() + { + var permissionGrants = + await ResourcePermissionGrantRepository.GetListAsync( + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission1" && p.ResourceKey == TestEntityResource.ResourceKey1 && p.ResourceName == TestEntityResource.ResourceName); + permissionGrants.ShouldContain(p => p.Name == "MyDisabledResourcePermission1" && p.ResourceKey == TestEntityResource.ResourceKey1 && p.ResourceName == TestEntityResource.ResourceName); + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission3" && p.ResourceKey == TestEntityResource.ResourceKey3 && p.ResourceName == TestEntityResource.ResourceName); + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission5" && p.ResourceKey == TestEntityResource.ResourceKey3 && p.ResourceName == TestEntityResource.ResourceName); + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission5" && p.ResourceKey == TestEntityResource.ResourceKey5 && p.ResourceName == TestEntityResource.ResourceName); + + permissionGrants = + await ResourcePermissionGrantRepository.GetListAsync( + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User2Id.ToString()); + + permissionGrants.ShouldBeEmpty(); + } + + [Fact] + public async Task GetPermissionsAsync() + { + var permissionGrants = + await ResourcePermissionGrantRepository.GetPermissionsAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1); + + permissionGrants.Count.ShouldBe(2); + permissionGrants.ShouldContain(p => p.Name == "MyResourcePermission1"); + permissionGrants.ShouldContain(p => p.Name == "MyDisabledResourcePermission1"); + } + + [Fact] + public async Task GetResourceKeys() + { + var permissionGrants = + await ResourcePermissionGrantRepository.GetResourceKeys( + TestEntityResource.ResourceName, + "MyResourcePermission5"); + + permissionGrants.Count.ShouldBe(2); + permissionGrants.ShouldContain(p => p.ResourceKey == TestEntityResource.ResourceKey3); + permissionGrants.ShouldContain(p => p.ResourceKey == TestEntityResource.ResourceKey5); + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestEntityResource.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestEntityResource.cs new file mode 100644 index 0000000000..9f631b123d --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestEntityResource.cs @@ -0,0 +1,16 @@ +using System; + +namespace Volo.Abp.PermissionManagement; + +public class TestEntityResource +{ + public static readonly string ResourceName = typeof(TestEntityResource).FullName; + + public static readonly string ResourceKey1 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey2 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey3 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey4 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey5 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey6 = Guid.NewGuid().ToString(); + public static readonly string ResourceKey7 = Guid.NewGuid().ToString(); +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs new file mode 100644 index 0000000000..9e52d02eab --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs @@ -0,0 +1,26 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.PermissionManagement; + +public class TestResourcePermissionDefinitionProvider: PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + context.AddResourcePermission("MyResourcePermission1", TestEntityResource.ResourceName); + context.AddResourcePermission("MyResourceDisabledPermission1", TestEntityResource.ResourceName, isEnabled: false); + context.AddResourcePermission("MyResourcePermission2", TestEntityResource.ResourceName); + context.AddResourcePermission("MyResourcePermission3", TestEntityResource.ResourceName, multiTenancySide: MultiTenancySides.Host); + context.AddResourcePermission("MyResourcePermission4", TestEntityResource.ResourceName, multiTenancySide: MultiTenancySides.Host).WithProviders(UserPermissionValueProvider.ProviderName); + + var myPermission5 = context.AddResourcePermission("MyResourcePermission5", TestEntityResource.ResourceName); + myPermission5.StateCheckers.Add(new TestRequireRolePermissionStateProvider("super-admin")); + + context.AddResourcePermission("MyResourcePermission6", TestEntityResource.ResourceName); + + context.AddResourcePermission("MyResourceDisabledPermission2", TestEntityResource.ResourceName, isEnabled: false); + + context.AddResourcePermission("MyResourcePermission7", TestEntityResource.ResourceName); + context.AddResourcePermission("MyResourcePermission8", TestEntityResource.ResourceName); + } +} From efaeebf98abe2265a01d07051bd05cc8635b98d5 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 08:46:37 +0800 Subject: [PATCH 052/130] Implement resource permission deletion in Role and User event handlers --- .../Identity/RoleDeletedEventHandler.cs | 6 +++++- .../Identity/UserDeletedEventHandler.cs | 6 +++++- .../IResourcePermissionManager.cs | 5 +++++ .../ResourcePermissionManager.cs | 13 +++++++++++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleDeletedEventHandler.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleDeletedEventHandler.cs index cb63b2681a..82200f763f 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleDeletedEventHandler.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleDeletedEventHandler.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.EventBus; @@ -14,15 +15,18 @@ public class RoleDeletedEventHandler : ITransientDependency { protected IPermissionManager PermissionManager { get; } + protected IResourcePermissionManager ResourcePermissionManager { get; } - public RoleDeletedEventHandler(IPermissionManager permissionManager) + public RoleDeletedEventHandler(IPermissionManager permissionManager, IResourcePermissionManager resourcePermissionManager) { PermissionManager = permissionManager; + ResourcePermissionManager = resourcePermissionManager; } [UnitOfWork] public virtual async Task HandleEventAsync(EntityDeletedEto eventData) { await PermissionManager.DeleteAsync(RolePermissionValueProvider.ProviderName, eventData.Entity.Name); + await ResourcePermissionManager.DeleteAsync(RoleResourcePermissionValueProvider.ProviderName, eventData.Entity.Name); } } diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserDeletedEventHandler.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserDeletedEventHandler.cs index 35aaba29ae..b10cef8fe4 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserDeletedEventHandler.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserDeletedEventHandler.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.EventBus.Distributed; @@ -13,15 +14,18 @@ public class UserDeletedEventHandler : ITransientDependency { protected IPermissionManager PermissionManager { get; } + protected IResourcePermissionManager ResourcePermissionManager { get; } - public UserDeletedEventHandler(IPermissionManager permissionManager) + public UserDeletedEventHandler(IPermissionManager permissionManager, IResourcePermissionManager resourcePermissionManager) { PermissionManager = permissionManager; + ResourcePermissionManager = resourcePermissionManager; } [UnitOfWork] public virtual async Task HandleEventAsync(EntityDeletedEto eventData) { await PermissionManager.DeleteAsync(UserPermissionValueProvider.ProviderName, eventData.Entity.Id.ToString()); + await ResourcePermissionManager.DeleteAsync(UserResourcePermissionValueProvider.ProviderName, eventData.Entity.Id.ToString()); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs index 97abeef0b4..8af9cc211a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs @@ -65,4 +65,9 @@ public interface IResourcePermissionManager string providerName, string providerKey ); + + Task DeleteAsync( + string providerName, + string providerKey + ); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index 0b2d728f62..18615527af 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -323,9 +323,18 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD return await ResourcePermissionGrantRepository.UpdateAsync(resourcePermissionGrant, true); } - public virtual async Task DeleteAsync(string providerName, string resourceName, string resourceKey, string providerKey) + public virtual async Task DeleteAsync(string resourceName, string resourceKey, string providerName, string providerKey) { - var permissionGrants = await ResourcePermissionGrantRepository.GetListAsync(providerName, resourceName, resourceKey, providerKey); + var permissionGrants = await ResourcePermissionGrantRepository.GetListAsync(resourceName, resourceKey, providerName, providerKey); + foreach (var permissionGrant in permissionGrants) + { + await ResourcePermissionGrantRepository.DeleteAsync(permissionGrant, true); + } + } + + public virtual async Task DeleteAsync(string providerName, string providerKey) + { + var permissionGrants = await ResourcePermissionGrantRepository.GetListAsync(providerName, providerKey); foreach (var permissionGrant in permissionGrants) { await ResourcePermissionGrantRepository.DeleteAsync(permissionGrant, true); From 8c17421ce6e63a5bf9bf799640e0b6b9b4e865b0 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 08:56:09 +0800 Subject: [PATCH 053/130] Add resource permission manager extensions for roles and users --- ...RoleResourcePermissionManagerExtensions.cs | 30 +++++++++++++++++++ ...UserResourcePermissionManagerExtensions.cs | 24 +++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/RoleResourcePermissionManagerExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/UserResourcePermissionManagerExtensions.cs diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/RoleResourcePermissionManagerExtensions.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/RoleResourcePermissionManagerExtensions.cs new file mode 100644 index 0000000000..53ff327f7b --- /dev/null +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/RoleResourcePermissionManagerExtensions.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Authorization.Permissions; + +namespace Volo.Abp.PermissionManagement; + +public static class RoleResourceresourcePermissionManagerExtensions +{ + public static Task GetForRoleAsync([NotNull] this IResourcePermissionManager resourcePermissionManager, string roleName, string permissionName, [NotNull] string resourceName, [NotNull] string resourceKey) + { + Check.NotNull(resourcePermissionManager, nameof(resourcePermissionManager)); + + return resourcePermissionManager.GetAsync(permissionName, resourceName, resourceKey, RolePermissionValueProvider.ProviderName, roleName); + } + + public static Task> GetAllForRoleAsync([NotNull] this IResourcePermissionManager resourcePermissionManager, string roleName, [NotNull] string resourceName, [NotNull] string resourceKey) + { + Check.NotNull(resourcePermissionManager, nameof(resourcePermissionManager)); + + return resourcePermissionManager.GetAllAsync(resourceName, resourceKey, RolePermissionValueProvider.ProviderName, roleName); + } + + public static Task SetForRoleAsync([NotNull] this IResourcePermissionManager resourcePermissionManager, string roleName, [NotNull] string permissionName, [NotNull] string resourceName, [NotNull] string resourceKey, bool isGranted) + { + Check.NotNull(resourcePermissionManager, nameof(resourcePermissionManager)); + + return resourcePermissionManager.SetAsync(permissionName, resourceName, resourceKey, RolePermissionValueProvider.ProviderName, roleName, isGranted); + } +} diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/UserResourcePermissionManagerExtensions.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/UserResourcePermissionManagerExtensions.cs new file mode 100644 index 0000000000..c0fe2eb845 --- /dev/null +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/UserResourcePermissionManagerExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Authorization.Permissions.Resources; + +namespace Volo.Abp.PermissionManagement; + +public static class UserResourcePermissionManagerExtensions +{ + public static Task> GetAllForUserAsync([NotNull] this IResourcePermissionManager resourcePermissionManager, Guid userId, [NotNull] string resourceName, [NotNull] string resourceKey) + { + Check.NotNull(resourcePermissionManager, nameof(resourcePermissionManager)); + + return resourcePermissionManager.GetAllAsync(resourceName, resourceKey, UserResourcePermissionValueProvider.ProviderName, userId.ToString()); + } + + public static Task SetForUserAsync([NotNull] this IResourcePermissionManager resourcePermissionManager, Guid userId, [NotNull] string name, [NotNull] string resourceName, [NotNull] string resourceKey, bool isGranted) + { + Check.NotNull(resourcePermissionManager, nameof(resourcePermissionManager)); + + return resourcePermissionManager.SetAsync(name, resourceName, resourceKey, UserResourcePermissionValueProvider.ProviderName, userId.ToString(), isGranted); + } +} From 7d6e3d725980739d44c2926f0a71c7c0c58938ec Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 11:38:09 +0800 Subject: [PATCH 054/130] Add new tests for user and role search methods --- .../Volo/Abp/Identity/UserRoleFinder_Tests.cs | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/UserRoleFinder_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/UserRoleFinder_Tests.cs index 524c7c25f3..cd36fb3225 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/UserRoleFinder_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/UserRoleFinder_Tests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; using Shouldly; using Xunit; @@ -19,11 +16,47 @@ public class UserRoleFinder_Tests : AbpIdentityDomainTestBase } [Fact] - public async Task GetRolesAsync() + public async Task GetRoleNamesAsync() { - var roleNames = await _userRoleFinder.GetRolesAsync(_testData.UserJohnId); + var roleNames = await _userRoleFinder.GetRoleNamesAsync(_testData.UserJohnId); roleNames.ShouldNotBeEmpty(); roleNames.ShouldContain(x => x == "moderator"); roleNames.ShouldContain(x => x == "supporter"); } + + [Fact] + public async Task SearchUserAsync() + { + var userResults = await _userRoleFinder.SearchUserAsync("john"); + userResults.ShouldNotBeEmpty(); + userResults.ShouldContain(x => x.Id == _testData.UserJohnId); + } + + [Fact] + public async Task SearchRoleAsync() + { + var roleResults = await _userRoleFinder.SearchRoleAsync("moderator"); + roleResults.ShouldNotBeEmpty(); + roleResults.ShouldContain(x => x.RoleName == "moderator"); + } + + [Fact] + public async Task SearchUserByIdsAsync() + { + var userResults = await _userRoleFinder.SearchUserByIdsAsync(new[] { _testData.UserJohnId, _testData.UserBobId }); + userResults.ShouldNotBeEmpty(); + userResults.Count.ShouldBe(2); + userResults.ShouldContain(x => x.Id == _testData.UserJohnId && x.UserName == "john.nash"); + userResults.ShouldContain(x => x.Id == _testData.UserBobId && x.UserName == "bob"); + } + + [Fact] + public async Task SearchRoleByIdsAsync() + { + var roleResults = await _userRoleFinder.SearchRoleByIdsAsync(new[] { _testData.RoleModeratorId, _testData.RoleManagerId }); + roleResults.ShouldNotBeEmpty(); + roleResults.Count.ShouldBe(2); + roleResults.ShouldContain(x => x.RoleName == "moderator"); + roleResults.ShouldContain(x => x.RoleName == "manager"); + } } From 21a303c7716558ec28130af5c3c7b4f0ea70ddbe Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 11:39:37 +0800 Subject: [PATCH 055/130] Return empty list for empty filter in search methods --- .../Volo/Abp/Identity/UserRoleFinder.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs index dfd658292f..8baf079273 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs @@ -31,6 +31,11 @@ public class UserRoleFinder : IUserRoleFinder, ITransientDependency public virtual async Task> SearchUserAsync(string filter) { + if (filter.IsNullOrEmpty()) + { + return new List(); + } + using (IdentityUserRepository.DisableTracking()) { var users = await IdentityUserRepository.GetListAsync(filter: filter); @@ -44,6 +49,11 @@ public class UserRoleFinder : IUserRoleFinder, ITransientDependency public virtual async Task> SearchRoleAsync(string filter) { + if (filter.IsNullOrEmpty()) + { + return new List(); + } + using (IdentityUserRepository.DisableTracking()) { var roles = await IdentityRoleRepository.GetListAsync(filter: filter); From 3679b0b4b20bc3e5770e21abdc1024fffcf4003a Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 13:44:32 +0800 Subject: [PATCH 056/130] Add `ResourcePermissionStore_Tests` --- .../Resources/IResourcePermissionStore.cs | 2 +- .../ResourcePermissionStore.cs | 2 +- .../ResourcePermissionStore_Tests.cs | 117 ++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionStore_Tests.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs index 5da0462433..355f3330f9 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs @@ -49,7 +49,7 @@ public interface IResourcePermissionStore /// Resource name /// Resource key /// - /// A dictionary of permission names and their states. + /// A object containing the grant results for each permission. /// Task GetPermissionsAsync( string resourceName, 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 index 914a05203a..9e09ff2db4 100644 --- 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 @@ -233,7 +233,7 @@ public class ResourcePermissionStore : IResourcePermissionStore, ITransientDepen public virtual async Task GetGrantedResourceKeysAsync(string resourceName, string name) { - return (await ResourcePermissionGrantRepository.GetResourceKeys(resourceName, name)).Select(x => x.Name).ToArray(); + return (await ResourcePermissionGrantRepository.GetResourceKeys(resourceName, name)).Select(x => x.ResourceKey).ToArray(); } protected virtual string GetPermissionNameFormCacheKeyOrNull(string key) diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionStore_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionStore_Tests.cs new file mode 100644 index 0000000000..763ff7a0a1 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionStore_Tests.cs @@ -0,0 +1,117 @@ +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionStore_Tests : PermissionTestBase +{ + private readonly IResourcePermissionStore _resourcePermissionStore; + + public ResourcePermissionStore_Tests() + { + _resourcePermissionStore = GetRequiredService(); + } + + [Fact] + public async Task IsGrantedAsync() + { + (await _resourcePermissionStore.IsGrantedAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString())).ShouldBeTrue(); + + (await _resourcePermissionStore.IsGrantedAsync( + "MyPermission1NotExist", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString())).ShouldBeFalse(); + } + + [Fact] + public async Task IsGranted_Multiple() + { + var result = await _resourcePermissionStore.IsGrantedAsync( + new[] { "MyResourcePermission1", "MyResourcePermission1NotExist" }, + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + + result.Result.Count.ShouldBe(2); + + result.Result.FirstOrDefault(x => x.Key == "MyResourcePermission1").Value.ShouldBe(PermissionGrantResult.Granted); + result.Result.FirstOrDefault(x => x.Key == "MyResourcePermission1NotExist").Value.ShouldBe(PermissionGrantResult.Undefined); + } + + + [Fact] + public async Task GetPermissionsAsync() + { + var permissions = await _resourcePermissionStore.GetPermissionsAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1); + + permissions.Result.Count.ShouldBe(10); + permissions.Result.ShouldContain(p => p.Key == "MyResourcePermission1" && p.Value == PermissionGrantResult.Granted); + permissions.Result.ShouldContain(p => p.Key == "MyResourceDisabledPermission1" && p.Value == PermissionGrantResult.Undefined); + permissions.Result.ShouldContain(p => p.Key == "MyResourcePermission2" && p.Value == PermissionGrantResult.Undefined); + permissions.Result.ShouldContain(p => p.Key == "MyResourcePermission3" && p.Value == PermissionGrantResult.Undefined); + permissions.Result.ShouldContain(p => p.Key == "MyResourcePermission4" && p.Value == PermissionGrantResult.Undefined); + permissions.Result.ShouldContain(p => p.Key == "MyResourcePermission5" && p.Value == PermissionGrantResult.Undefined); + permissions.Result.ShouldContain(p => p.Key == "MyResourcePermission6" && p.Value == PermissionGrantResult.Undefined); + permissions.Result.ShouldContain(p => p.Key == "MyResourceDisabledPermission2" && p.Value == PermissionGrantResult.Undefined); + permissions.Result.ShouldContain(p => p.Key == "MyResourcePermission7" && p.Value == PermissionGrantResult.Undefined); + permissions.Result.ShouldContain(p => p.Key == "MyResourcePermission8" && p.Value == PermissionGrantResult.Undefined); + + permissions = await _resourcePermissionStore.GetPermissionsAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey2); + permissions.Result.ShouldAllBe(x => x.Value == PermissionGrantResult.Undefined); + } + + [Fact] + public async Task GetGrantedPermissionsAsync() + { + var grantedPermissions = await _resourcePermissionStore.GetGrantedPermissionsAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1); + + grantedPermissions.Length.ShouldBe(1); + grantedPermissions.ShouldContain("MyResourcePermission1"); + + grantedPermissions = await _resourcePermissionStore.GetGrantedPermissionsAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey3); + + grantedPermissions.Length.ShouldBe(2); + grantedPermissions.ShouldContain("MyResourcePermission3"); + grantedPermissions.ShouldContain("MyResourcePermission5"); + } + + + [Fact] + public async Task GetGrantedResourceKeysAsync() + { + var grantedResourceKeys = await _resourcePermissionStore.GetGrantedResourceKeysAsync( + TestEntityResource.ResourceName, + "MyResourcePermission1"); + + grantedResourceKeys.Length.ShouldBe(1); + grantedResourceKeys.ShouldContain(TestEntityResource.ResourceKey1); + + grantedResourceKeys = await _resourcePermissionStore.GetGrantedResourceKeysAsync( + TestEntityResource.ResourceName, + "MyResourcePermission5"); + + grantedResourceKeys.Length.ShouldBe(2); + grantedResourceKeys.ShouldContain(TestEntityResource.ResourceKey3); + grantedResourceKeys.ShouldContain(TestEntityResource.ResourceKey5); + } +} From 75230d905544fe7fca2c0f8bf7f4fc5f3cea7b6c Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 13:49:56 +0800 Subject: [PATCH 057/130] Add unit tests for ResourcePermissionChecker --- .../ResourcePermissionChecker_Basic_Tests.cs | 28 ++++ .../ResourcePermissionChecker_User_Tests.cs | 121 ++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionChecker_Basic_Tests.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionChecker_User_Tests.cs diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionChecker_Basic_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionChecker_Basic_Tests.cs new file mode 100644 index 0000000000..81ba38f7f9 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionChecker_Basic_Tests.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions.Resources; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionChecker_Basic_Tests : PermissionTestBase +{ + private readonly IResourcePermissionChecker _resourcePermissionChecker; + + public ResourcePermissionChecker_Basic_Tests() + { + _resourcePermissionChecker = GetRequiredService(); + } + + [Fact] + public async Task Should_Return_Prohibited_If_Permission_Is_Not_Defined() + { + (await _resourcePermissionChecker.IsGrantedAsync(TestEntityResource.ResourceName, TestEntityResource.ResourceKey1,"UndefinedResourcePermissionName")).ShouldBeFalse(); + } + + [Fact] + public async Task Should_Return_False_As_Default_For_Any_Permission() + { + (await _resourcePermissionChecker.IsGrantedAsync(TestEntityResource.ResourceName, TestEntityResource.ResourceKey1,"MyPermission1")).ShouldBeFalse(); + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionChecker_User_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionChecker_User_Tests.cs new file mode 100644 index 0000000000..261b5136d2 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionChecker_User_Tests.cs @@ -0,0 +1,121 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.Security.Claims; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionChecker_User_Tests : PermissionTestBase +{ + private readonly IResourcePermissionChecker _resourcePermissionChecker; + private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; + + public ResourcePermissionChecker_User_Tests() + { + _resourcePermissionChecker = GetRequiredService(); + _currentPrincipalAccessor = GetRequiredService(); + } + + [Fact] + public async Task Should_Return_True_For_Granted_Current_User() + { + (await _resourcePermissionChecker.IsGrantedAsync( + CreatePrincipal(PermissionTestDataBuilder.User1Id), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1 + )).ShouldBeTrue(); + } + + [Fact] + public async Task Should_Return_False_For_Non_Granted_Current_User() + { + (await _resourcePermissionChecker.IsGrantedAsync( + CreatePrincipal(PermissionTestDataBuilder.User2Id), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1 + )).ShouldBeFalse(); + } + + + [Fact] + public async Task Should_Return_False_For_Granted_Current_User_If_The_Permission_Is_Disabled() + { + //Disabled permissions always returns false! + (await _resourcePermissionChecker.IsGrantedAsync( + CreatePrincipal(PermissionTestDataBuilder.User1Id), + "MyDisabledPermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1 + )).ShouldBeFalse(); + } + + [Fact] + public async Task Should_Return_False_For_Current_User_If_Anonymous() + { + (await _resourcePermissionChecker.IsGrantedAsync( + CreatePrincipal(null), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1 + )).ShouldBeFalse(); + } + + [Fact] + public async Task Should_Not_Allow_Host_Permission_To_Tenant_User_Even_Granted_Before() + { + (await _resourcePermissionChecker.IsGrantedAsync( + CreatePrincipal(PermissionTestDataBuilder.User1Id, Guid.NewGuid()), + "MyResourcePermission3", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey3 + )).ShouldBeFalse(); + } + + [Fact] + public async Task Should_Return_False_For_Granted_Current_User_If_The_Permission_State_Is_Disabled() + { + (await _resourcePermissionChecker.IsGrantedAsync( + CreatePrincipal(PermissionTestDataBuilder.User1Id, Guid.NewGuid()), + "MyResourcePermission5", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey5 + )).ShouldBeFalse(); + } + + [Fact] + public async Task Should_Return_True_For_Granted_Current_User_If_The_Permission_State_Is_Enabled() + { + using (_currentPrincipalAccessor.Change(new Claim(AbpClaimTypes.Role, "super-admin"))) + { + (await _resourcePermissionChecker.IsGrantedAsync( + CreatePrincipal(PermissionTestDataBuilder.User1Id, Guid.NewGuid()), + "MyResourcePermission5", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey5 + )).ShouldBeTrue(); + } + } + + private static ClaimsPrincipal CreatePrincipal(Guid? userId, Guid? tenantId = null) + { + var claimsIdentity = new ClaimsIdentity(); + + if (userId != null) + { + claimsIdentity.AddClaim(new Claim(AbpClaimTypes.UserId, userId.ToString())); + } + + if (tenantId != null) + { + claimsIdentity.AddClaim(new Claim(AbpClaimTypes.TenantId, tenantId.ToString())); + } + + return new ClaimsPrincipal(claimsIdentity); + } +} From 5bb3ac057c1a1df4b0ee05e6005fc3b204082256 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 14:04:32 +0800 Subject: [PATCH 058/130] Add tests for ResourcePermissionGrant cache behavior --- ...rmissionGrantCacheItemInvalidator_Tests.cs | 69 +++++++++++++++++++ .../ResourcePermissionGrantCacheItem_Tests.cs | 15 ++++ 2 files changed, 84 insertions(+) create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator_Tests.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItem_Tests.cs diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator_Tests.cs new file mode 100644 index 0000000000..356290029e --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator_Tests.cs @@ -0,0 +1,69 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.Caching; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionGrantCacheItemInvalidator_Tests : PermissionTestBase +{ + private readonly IDistributedCache _cache; + private readonly IResourcePermissionStore _resourcePermissionStore; + private readonly IResourcePermissionGrantRepository _resourcePermissionGrantRepository; + + public ResourcePermissionGrantCacheItemInvalidator_Tests() + { + _cache = GetRequiredService>(); + _resourcePermissionStore = GetRequiredService(); + _resourcePermissionGrantRepository = GetRequiredService(); + } + + [Fact] + public async Task PermissionStore_IsGrantedAsync_Should_Cache_PermissionGrant() + { + (await _cache.GetAsync(ResourcePermissionGrantCacheItem.CalculateCacheKey("MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()))).ShouldBeNull(); + + await _resourcePermissionStore.IsGrantedAsync("MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + + (await _cache.GetAsync(ResourcePermissionGrantCacheItem.CalculateCacheKey("MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()))).ShouldNotBeNull(); + } + + [Fact] + public async Task Cache_Should_Invalidator_WhenPermissionGrantChanged() + { + // IsGrantedAsync will cache ResourcePermissionGrant + await _resourcePermissionStore.IsGrantedAsync("MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + + var resourcePermissionGrant = await _resourcePermissionGrantRepository.FindAsync("MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + resourcePermissionGrant.ShouldNotBeNull(); + await _resourcePermissionGrantRepository.DeleteAsync(permissionGrant); + + (await _cache.GetAsync(ResourcePermissionGrantCacheItem.CalculateCacheKey("MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()))).ShouldBeNull(); + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItem_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItem_Tests.cs new file mode 100644 index 0000000000..1d7b4ea12d --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItem_Tests.cs @@ -0,0 +1,15 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionGrantCacheItem_Tests +{ + [Fact] + public void GetPermissionNameFormCacheKeyOrNull() + { + var key = ResourcePermissionGrantCacheItem.CalculateCacheKey("aaa", TestEntityResource.ResourceName, TestEntityResource.ResourceKey1,"bbb", "ccc"); + ResourcePermissionGrantCacheItem.GetPermissionNameFormCacheKeyOrNull(key).ShouldBe("aaa"); + ResourcePermissionGrantCacheItem.GetPermissionNameFormCacheKeyOrNull("aaabbbccc").ShouldBeNull(); + } +} From 470cfdacdeab5b95e07259705035c9eeafce7898 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 14:50:46 +0800 Subject: [PATCH 059/130] Support resource permissions in permission serialization --- .../IPermissionDefinitionSerializer.cs | 5 +- .../PermissionDefinitionRecord.cs | 6 +- .../PermissionDefinitionSerializer.cs | 17 ++++- .../StaticPermissionSaver.cs | 6 ++ ...nagementDbContextModelBuilderExtensions.cs | 3 +- ...missionDefinitionRecordRepository_Tests.cs | 15 +++++ .../PermissionDefinitionSerializer_Tests.cs | 64 +++++++++++++++---- ...rmissionGrantCacheItemInvalidator_Tests.cs | 2 +- ...missionDefinitionRecordRepository_Tests.cs | 2 +- ...estResourcePermissionDefinitionProvider.cs | 2 +- 10 files changed, 100 insertions(+), 22 deletions(-) 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 8ed09a4380..3c5f4a8783 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 @@ -10,10 +10,13 @@ public interface IPermissionDefinitionSerializer Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> SerializeAsync(IEnumerable permissionGroups); + Task SerializeAsync( + IEnumerable permissions); + Task SerializeAsync( PermissionGroupDefinition permissionGroup); Task SerializeAsync( PermissionDefinition permission, [CanBeNull] PermissionGroupDefinition permissionGroup); -} \ No newline at end of file +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs index a513beafe9..0ef2032452 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs @@ -53,7 +53,11 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro string stateCheckers = null) : base(id) { - GroupName = Check.NotNullOrWhiteSpace(groupName, nameof(groupName), PermissionGroupDefinitionRecordConsts.MaxNameLength); + GroupName = groupName; + if (resourceName == null) + { + GroupName = Check.NotNullOrWhiteSpace(groupName, nameof(groupName), PermissionGroupDefinitionRecordConsts.MaxNameLength); + } Name = Check.NotNullOrWhiteSpace(name, nameof(name), PermissionDefinitionRecordConsts.MaxNameLength); ResourceName = resourceName; ParentName = Check.Length(parentName, nameof(parentName), PermissionDefinitionRecordConsts.MaxNameLength); 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 5d07e81a94..e5df5201cd 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 @@ -27,7 +27,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I GuidGenerator = guidGenerator; } - public async Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> + public virtual async Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> SerializeAsync(IEnumerable permissionGroups) { var permissionGroupRecords = new List(); @@ -46,7 +46,18 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I return (permissionGroupRecords.ToArray(), permissionRecords.ToArray()); } - public Task SerializeAsync(PermissionGroupDefinition permissionGroup) + public virtual async Task SerializeAsync(IEnumerable permissions) + { + var permissionRecords = new List(); + foreach (var permission in permissions) + { + permissionRecords.Add(await SerializeAsync(permission, null)); + } + + return permissionRecords.ToArray(); + } + + public virtual Task SerializeAsync(PermissionGroupDefinition permissionGroup) { using (CultureHelper.Use(CultureInfo.InvariantCulture)) { @@ -65,7 +76,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I } } - public Task SerializeAsync( + public virtual Task SerializeAsync( PermissionDefinition permission, PermissionGroupDefinition permissionGroup) { 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 d1dff35f35..c672207f4f 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 @@ -85,6 +85,12 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc await StaticStore.GetGroupsAsync() ); + var resourcePermissions = await PermissionSerializer.SerializeAsync( + await StaticStore.GetResourcePermissionsAsync() + ); + + permissionRecords = permissionRecords.Union(resourcePermissions).ToArray(); + var currentHash = CalculateHash( permissionGroupRecords, permissionRecords, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs index e1e569cd06..fa9fa6de12 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs @@ -69,8 +69,7 @@ public static class AbpPermissionManagementDbContextModelBuilderExtensions b.ConfigureByConvention(); - b.Property(x => x.GroupName).HasMaxLength(PermissionGroupDefinitionRecordConsts.MaxNameLength) - .IsRequired(); + b.Property(x => x.GroupName).HasMaxLength(PermissionGroupDefinitionRecordConsts.MaxNameLength); b.Property(x => x.Name).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength).IsRequired(); b.Property(x => x.ResourceName).HasMaxLength(PermissionDefinitionRecordConsts.MaxResourceNameLength); b.Property(x => x.ParentName).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionRecordRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionRecordRepository_Tests.cs index d7ca406624..62369869c4 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionRecordRepository_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionRecordRepository_Tests.cs @@ -26,4 +26,19 @@ public abstract class PermissionDefinitionRecordRepository_Tests permission.ShouldNotBeNull(); permission.Name.ShouldBe("MyPermission2"); } + + [Fact] + public async Task FindByResourceNameAsync() + { + var qq = await PermissionDefinitionRecordRepository.GetListAsync(); + var permission = await PermissionDefinitionRecordRepository.FindByNameAsync("MyResourcePermission1"); + permission.ShouldNotBeNull(); + permission.ResourceName.ShouldBe(TestEntityResource.ResourceName); + permission.Name.ShouldBe("MyResourcePermission1"); + + permission = await PermissionDefinitionRecordRepository.FindByNameAsync("MyResourcePermission2"); + permission.ShouldNotBeNull(); + permission.ResourceName.ShouldBe(TestEntityResource.ResourceName); + permission.Name.ShouldBe("MyResourcePermission2"); + } } 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 1e7c04580c..231626de72 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 @@ -16,7 +16,7 @@ namespace Volo.Abp.PermissionManagement; public class PermissionDefinitionSerializer_Tests : PermissionTestBase { private readonly IPermissionDefinitionSerializer _serializer; - + public PermissionDefinitionSerializer_Tests() { _serializer = GetRequiredService(); @@ -26,26 +26,26 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase public async Task Serialize_Permission_Group_Definition() { // Arrange - + var context = new PermissionDefinitionContext(null); var group1 = CreatePermissionGroup1(context); - + // Act var permissionGroupRecord = await _serializer.SerializeAsync(group1); - + //Assert permissionGroupRecord.Name.ShouldBe("Group1"); permissionGroupRecord.DisplayName.ShouldBe("F:Group one"); permissionGroupRecord.GetProperty("CustomProperty1").ShouldBe("CustomValue1"); } - + [Fact] public async Task Serialize_Complex_Permission_Definition() { // Arrange - + var context = new PermissionDefinitionContext(null); var group1 = CreatePermissionGroup1(context); var permission1 = group1.AddPermission( @@ -61,14 +61,14 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase .RequirePermissions(requiresAll: false, batchCheck: false,"Permission2", "Permission3"); // Act - + var permissionRecord = await _serializer.SerializeAsync( permission1, group1 ); - + //Assert - + permissionRecord.Name.ShouldBe("Permission1"); permissionRecord.GroupName.ShouldBe("Group1"); permissionRecord.DisplayName.ShouldBe("L:AbpPermissionManagement,Permission1"); @@ -78,6 +78,46 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase permissionRecord.StateCheckers.ShouldBe("[{\"T\":\"A\"},{\"T\":\"G\",\"A\":true,\"N\":[\"GlobalFeature1\",\"GlobalFeature2\"]},{\"T\":\"F\",\"A\":true,\"N\":[\"Feature1\",\"Feature2\"]},{\"T\":\"P\",\"A\":false,\"N\":[\"Permission2\",\"Permission3\"]}]"); } + + [Fact] + public async Task Serialize_Complex_Resource_Permission_Definition() + { + // Arrange + + var context = new PermissionDefinitionContext(null); + var resourcePermission1 = context.AddResourcePermission( + "ResourcePermission1", + TestEntityResource.ResourceName, + new LocalizableString(typeof(AbpPermissionManagementResource), "ResourcePermission1"), + MultiTenancySides.Tenant + ) + .WithProviders("ProviderA", "ProviderB") + .WithProperty("CustomProperty2", "CustomValue2") + .RequireAuthenticated() //For for testing, not so meaningful + .RequireGlobalFeatures("GlobalFeature1", "GlobalFeature2") + .RequireFeatures("Feature1", "Feature2") + .RequirePermissions(requiresAll: false, batchCheck: false,"Permission2", "Permission3"); + + // Act + + var permissionRecord = await _serializer.SerializeAsync( + resourcePermission1, + null + ); + + //Assert + + permissionRecord.Name.ShouldBe("ResourcePermission1"); + permissionRecord.GroupName.ShouldBe(null); + permissionRecord.ResourceName.ShouldBe(TestEntityResource.ResourceName); + permissionRecord.DisplayName.ShouldBe("L:AbpPermissionManagement,ResourcePermission1"); + permissionRecord.GetProperty("CustomProperty2").ShouldBe("CustomValue2"); + permissionRecord.Providers.ShouldBe("ProviderA,ProviderB"); + permissionRecord.MultiTenancySide.ShouldBe(MultiTenancySides.Tenant); + permissionRecord.StateCheckers.ShouldBe("[{\"T\":\"A\"},{\"T\":\"G\",\"A\":true,\"N\":[\"GlobalFeature1\",\"GlobalFeature2\"]},{\"T\":\"F\",\"A\":true,\"N\":[\"Feature1\",\"Feature2\"]},{\"T\":\"P\",\"A\":false,\"N\":[\"Permission2\",\"Permission3\"]}]"); + } + + private static PermissionGroupDefinition CreatePermissionGroup1( IPermissionDefinitionContext context) { @@ -85,9 +125,9 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase "Group1", displayName: new FixedLocalizableString("Group one") ); - + group["CustomProperty1"] = "CustomValue1"; - + return group; } -} \ No newline at end of file +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator_Tests.cs index 356290029e..3bdc4ee69e 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionGrantCacheItemInvalidator_Tests.cs @@ -58,7 +58,7 @@ public class ResourcePermissionGrantCacheItemInvalidator_Tests : PermissionTestB UserPermissionValueProvider.ProviderName, PermissionTestDataBuilder.User1Id.ToString()); resourcePermissionGrant.ShouldNotBeNull(); - await _resourcePermissionGrantRepository.DeleteAsync(permissionGrant); + await _resourcePermissionGrantRepository.DeleteAsync(resourcePermissionGrant); (await _cache.GetAsync(ResourcePermissionGrantCacheItem.CalculateCacheKey("MyResourcePermission1", TestEntityResource.ResourceName, diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbPermissionDefinitionRecordRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbPermissionDefinitionRecordRepository_Tests.cs index d43634cdb9..30f09c5720 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbPermissionDefinitionRecordRepository_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbPermissionDefinitionRecordRepository_Tests.cs @@ -3,7 +3,7 @@ namespace Volo.Abp.PermissionManagement.MongoDB; [Collection(MongoTestCollection.Name)] -public class MongoDbPermissionDefinitionRecordRepository_Tests : PermissionGrantRepository_Tests +public class MongoDbPermissionDefinitionRecordRepository_Tests : PermissionDefinitionRecordRepository_Tests { } diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs index 9e52d02eab..e255419313 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs @@ -3,7 +3,7 @@ using Volo.Abp.MultiTenancy; namespace Volo.Abp.PermissionManagement; -public class TestResourcePermissionDefinitionProvider: PermissionDefinitionProvider +public class TestResourcePermissionDefinitionProvider : PermissionDefinitionProvider { public override void Define(IPermissionDefinitionContext context) { From 843a3da0e24cee42f8c25f2ac0be0bebb7f379da Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 15:47:48 +0800 Subject: [PATCH 060/130] Add resource permission management provider and tests --- .../ResourcePermissionManager.cs | 2 +- ...rmissionManagementApplicationTestModule.cs | 1 + ...ourcePermissionManagementProvider_Tests.cs | 90 +++++ .../ResourcePermissionManager_Tests.cs | 337 ++++++++++++++++++ .../ResourcePermissionStore_Tests.cs | 4 +- .../AbpPermissionManagementTestBaseModule.cs | 2 + .../PermissionTestDataBuilder.cs | 11 + ...estResourcePermissionManagementProvider.cs | 24 ++ ...ourcePermissionProviderKeyLookupService.cs | 24 ++ 9 files changed, 493 insertions(+), 2 deletions(-) create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider_Tests.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionManager_Tests.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionManagementProvider.cs create mode 100644 modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionProviderKeyLookupService.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index 18615527af..c6e48db75a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -277,7 +277,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD if (provider == null) { //TODO: BusinessException - throw new AbpException("Unknown permission management provider: " + providerName); + throw new AbpException("Unknown resource permission management provider: " + providerName); } await provider.SetAsync(permissionName, resourceName, resourceKey, providerKey, isGranted); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/AbpPermissionManagementApplicationTestModule.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/AbpPermissionManagementApplicationTestModule.cs index a5e8461a28..e43cc0fa71 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/AbpPermissionManagementApplicationTestModule.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/AbpPermissionManagementApplicationTestModule.cs @@ -22,6 +22,7 @@ public class AbpPermissionManagementApplicationTestModule : AbpModule options.ProviderPolicies[UserPermissionValueProvider.ProviderName] = UserPermissionValueProvider.ProviderName; options.ProviderPolicies["Test"] = "Test"; options.ManagementProviders.Add(); + options.ResourceManagementProviders.Add(); }); } } diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider_Tests.cs new file mode 100644 index 0000000000..9651c3f313 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionManagementProvider_Tests.cs @@ -0,0 +1,90 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionManagementProvider_Tests : PermissionTestBase +{ + private readonly IResourcePermissionManagementProvider _resourcePermissionManagementProvider; + private readonly IResourcePermissionGrantRepository _resourcePermissionGrantRepository; + + public ResourcePermissionManagementProvider_Tests() + { + _resourcePermissionManagementProvider = GetRequiredService(); + _resourcePermissionGrantRepository = GetRequiredService(); + } + + [Fact] + public async Task CheckAsync() + { + await _resourcePermissionGrantRepository.InsertAsync( + new ResourcePermissionGrant( + Guid.NewGuid(), + "MyPermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test" + ) + ); + + var permissionValueProviderGrantInfo = await _resourcePermissionManagementProvider.CheckAsync( + "MyPermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test"); + + permissionValueProviderGrantInfo.IsGranted.ShouldBeTrue(); + permissionValueProviderGrantInfo.ProviderKey.ShouldBe("Test"); + } + + [Fact] + public async Task Check_Should_Return_NonGranted_When_ProviderName_NotEquals_Name() + { + var permissionValueProviderGrantInfo = await _resourcePermissionManagementProvider.CheckAsync( + "MyPermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "TestNotExist", + "Test"); + + permissionValueProviderGrantInfo.IsGranted.ShouldBeFalse(); + permissionValueProviderGrantInfo.ProviderKey.ShouldBeNull(); + } + + [Fact] + public async Task SetAsync() + { + await _resourcePermissionGrantRepository.InsertAsync( + new ResourcePermissionGrant( + Guid.NewGuid(), + "MyPermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test" + ) + ); + (await _resourcePermissionGrantRepository.FindAsync("MyPermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test")).ShouldNotBeNull(); + + await _resourcePermissionManagementProvider.SetAsync("MyPermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + false); + + (await _resourcePermissionGrantRepository.FindAsync("MyPermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test")).ShouldBeNull(); + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionManager_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionManager_Tests.cs new file mode 100644 index 0000000000..73d37a5ff3 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionManager_Tests.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.Permissions.Resources; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public class ResourcePermissionManager_Tests : PermissionTestBase +{ + private readonly IResourcePermissionManager _resourcePermissionManager; + private readonly IResourcePermissionGrantRepository _resourcePermissionGrantRepository; + + public ResourcePermissionManager_Tests() + { + _resourcePermissionManager = GetRequiredService(); + _resourcePermissionGrantRepository = GetRequiredService(); + } + + [Fact] + public async Task GetProviderKeyLookupServicesAsync() + { + var permissionProviderKeyLookupServices = await _resourcePermissionManager.GetProviderKeyLookupServicesAsync(); + + permissionProviderKeyLookupServices.ShouldNotBeNull(); + permissionProviderKeyLookupServices.First().Name.ShouldBe("Test"); + } + + [Fact] + public async Task GetProviderKeyLookupServiceAsync() + { + var testProviderKeyLookupService = await _resourcePermissionManager.GetProviderKeyLookupServiceAsync("Test"); + testProviderKeyLookupService.ShouldNotBeNull(); + testProviderKeyLookupService.Name.ShouldBe("Test"); + + var exception = await Assert.ThrowsAsync(async () => + { + await _resourcePermissionManager.GetProviderKeyLookupServiceAsync("UndefinedProvider"); + }); + exception.Message.ShouldBe("Unknown resource permission provider key lookup service: UndefinedProvider"); + } + + [Fact] + public async Task GetAvailablePermissionsAsync() + { + var availablePermissions = await _resourcePermissionManager.GetAvailablePermissionsAsync(TestEntityResource.ResourceName); + + availablePermissions.ShouldNotBeNull(); + availablePermissions.ShouldContain(p => p.Name == "MyResourcePermission1"); + availablePermissions.ShouldContain(p => p.Name == "MyResourcePermission2"); + availablePermissions.ShouldContain(p => p.Name == "MyResourcePermission3"); + availablePermissions.ShouldContain(p => p.Name == "MyResourcePermission4"); + availablePermissions.ShouldContain(p => p.Name == "MyResourcePermission6"); + availablePermissions.ShouldContain(p => p.Name == "MyResourcePermission7"); + + availablePermissions.ShouldNotContain(p => p.Name == "MyResourcePermission5"); + availablePermissions.ShouldNotContain(p => p.Name == "MyResourceDisabledPermission1"); + availablePermissions.ShouldNotContain(p => p.Name == "MyResourceDisabledPermission2"); + } + + [Fact] + public async Task GetAsync() + { + await _resourcePermissionGrantRepository.InsertAsync(new ResourcePermissionGrant( + Guid.NewGuid(), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test") + ); + + var grantedProviders = await _resourcePermissionManager.GetAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test"); + + grantedProviders.ShouldNotBeNull(); + grantedProviders.IsGranted.ShouldBeTrue(); + grantedProviders.Name.ShouldBe("MyResourcePermission1"); + grantedProviders.Providers.ShouldContain(x => x.Key == "Test"); + } + + [Fact] + public async Task Multiple_GetAsync() + { + await _resourcePermissionGrantRepository.InsertAsync(new ResourcePermissionGrant( + Guid.NewGuid(), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test") + ); + await _resourcePermissionGrantRepository.InsertAsync(new ResourcePermissionGrant( + Guid.NewGuid(), + "MyResourcePermission2", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test") + ); + + var grantedProviders = await _resourcePermissionManager.GetAsync( + new[] { "MyResourcePermission1", "MyResourcePermission2" }, + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test"); + + grantedProviders.Result.Count.ShouldBe(2); + grantedProviders.Result.First().IsGranted.ShouldBeTrue(); + grantedProviders.Result.First().Name.ShouldBe("MyResourcePermission1"); + grantedProviders.Result.First().Providers.ShouldContain(x => x.Key == "Test"); + + grantedProviders.Result.Last().IsGranted.ShouldBeTrue(); + grantedProviders.Result.Last().Name.ShouldBe("MyResourcePermission2"); + grantedProviders.Result.Last().Providers.ShouldContain(x => x.Key == "Test"); + } + + [Fact] + public async Task Get_Should_Return_Not_Granted_When_Permission_Undefined() + { + var result = await _resourcePermissionManager.GetAsync( + "MyResourcePermission1NotExist", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1,"Test", "Test"); + result.Name.ShouldBe("MyResourcePermission1NotExist"); + result.Providers.ShouldBeEmpty(); + result.IsGranted.ShouldBeFalse(); + } + + [Fact] + public async Task GetAllAsync() + { + await _resourcePermissionGrantRepository.InsertAsync(new ResourcePermissionGrant( + Guid.NewGuid(), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test") + ); + + await _resourcePermissionGrantRepository.InsertAsync(new ResourcePermissionGrant( + Guid.NewGuid(), + "MyResourcePermission2", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test") + ); + + var permissionWithGrantedProviders = await _resourcePermissionManager.GetAllAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test"); + + permissionWithGrantedProviders.ShouldNotBeNull(); + permissionWithGrantedProviders.ShouldContain(x => + x.IsGranted && x.Name == "MyResourcePermission1" && x.Providers.Any(p => p.Key == "Test")); + permissionWithGrantedProviders.ShouldContain(x => + x.IsGranted && x.Name == "MyResourcePermission2" && x.Providers.Any(p => p.Key == "Test")); + + + permissionWithGrantedProviders = await _resourcePermissionManager.GetAllAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1); + + permissionWithGrantedProviders.ShouldNotBeNull(); + permissionWithGrantedProviders.ShouldContain(x => x.IsGranted && x.Name == "MyResourcePermission1" && x.Providers.Any(p => p.Key == "Test")); + permissionWithGrantedProviders.ShouldContain(x => x.IsGranted && x.Name == "MyResourcePermission2" && x.Providers.Any(p => p.Key == "Test")); + + permissionWithGrantedProviders.ShouldNotContain(x => x.Name == "MyResourcePermission5"); // Not available permission + + permissionWithGrantedProviders.ShouldContain(x => !x.IsGranted && x.Name == "MyResourcePermission3" && x.Providers.Count == 0); + permissionWithGrantedProviders.ShouldContain(x => !x.IsGranted && x.Name == "MyResourcePermission4" && x.Providers.Count == 0); + permissionWithGrantedProviders.ShouldContain(x => !x.IsGranted && x.Name == "MyResourcePermission6" && x.Providers.Count == 0); + permissionWithGrantedProviders.ShouldContain(x => !x.IsGranted && x.Name == "MyResourcePermission7" && x.Providers.Count == 0); + permissionWithGrantedProviders.ShouldContain(x => !x.IsGranted && x.Name == "MyResourcePermission8" && x.Providers.Count == 0); + } + + [Fact] + public async Task GetAllGroupAsync() + { + var group = await _resourcePermissionManager.GetAllGroupAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1); + + group.ShouldNotBeNull(); + group.Count.ShouldBe(1); + group.First().ProviderName.ShouldBe(UserResourcePermissionValueProvider.ProviderName); + group.First().ProviderKey.ShouldBe(PermissionTestDataBuilder.User1Id.ToString()); + group.First().Permissions.Count.ShouldBe(1); + group.First().Permissions.ShouldContain(x => x == "MyResourcePermission1"); + + group = await _resourcePermissionManager.GetAllGroupAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey3); + + group.ShouldNotBeNull(); + group.Count.ShouldBe(1); + group.First().ProviderName.ShouldBe(UserResourcePermissionValueProvider.ProviderName); + group.First().ProviderKey.ShouldBe(PermissionTestDataBuilder.User1Id.ToString()); + group.First().Permissions.Count.ShouldBe(2); + group.First().Permissions.ShouldContain(x => x == "MyResourcePermission3"); + group.First().Permissions.ShouldContain(x => x == "MyResourcePermission6"); + } + + [Fact] + public async Task Set_Should_Silently_Ignore_When_Permission_Undefined() + { + await _resourcePermissionManager.SetAsync( + "MyResourcePermission1NotExist", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test", + true); + } + + [Fact] + public async Task Set_Should_Throw_Exception_If_Provider_Not_Found() + { + var exception = await Assert.ThrowsAsync(async () => + { + await _resourcePermissionManager.SetAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "UndefinedProvider", + "Test", + true); + }); + + exception.Message.ShouldBe("Unknown resource permission management provider: UndefinedProvider"); + } + + [Fact] + public async Task UpdateProviderKey() + { + await _resourcePermissionGrantRepository.InsertAsync(new ResourcePermissionGrant( + Guid.NewGuid(), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test") + ); + var permissionGrant = await _resourcePermissionGrantRepository.FindAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test"); + permissionGrant.ProviderKey.ShouldBe("Test"); + + await _resourcePermissionManager.UpdateProviderKeyAsync(permissionGrant, "NewProviderKey"); + (await _resourcePermissionGrantRepository.FindAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "NewProviderKey")).ShouldNotBeNull(); + } + + [Fact] + public async Task DeleteAsync() + { + await _resourcePermissionGrantRepository.InsertAsync(new ResourcePermissionGrant( + Guid.NewGuid(), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test") + ); + var permissionGrant = await _resourcePermissionGrantRepository.FindAsync("MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test"); + permissionGrant.ProviderKey.ShouldBe("Test"); + + await _resourcePermissionManager.DeleteAsync( + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test"); + + (await _resourcePermissionGrantRepository.FindAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test")).ShouldBeNull(); + } + + [Fact] + public async Task DeleteByProviderAsync() + { + await _resourcePermissionGrantRepository.InsertAsync(new ResourcePermissionGrant( + Guid.NewGuid(), + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test") + ); + + var permissionGrant = await _resourcePermissionGrantRepository.FindAsync("MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test"); + + permissionGrant.ProviderKey.ShouldBe("Test"); + + await _resourcePermissionManager.DeleteAsync( + "Test", + "Test"); + + (await _resourcePermissionGrantRepository.FindAsync( + "MyResourcePermission1", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey1, + "Test", + "Test")).ShouldBeNull(); + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionStore_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionStore_Tests.cs index 763ff7a0a1..34e6f89c0c 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionStore_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/ResourcePermissionStore_Tests.cs @@ -90,9 +90,11 @@ public class ResourcePermissionStore_Tests : PermissionTestBase TestEntityResource.ResourceName, TestEntityResource.ResourceKey3); - grantedPermissions.Length.ShouldBe(2); + grantedPermissions.Length.ShouldBe(3); grantedPermissions.ShouldContain("MyResourcePermission3"); grantedPermissions.ShouldContain("MyResourcePermission5"); + grantedPermissions.ShouldContain("MyResourcePermission6"); + } diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/AbpPermissionManagementTestBaseModule.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/AbpPermissionManagementTestBaseModule.cs index a6102a318e..88c6ea1d69 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/AbpPermissionManagementTestBaseModule.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/AbpPermissionManagementTestBaseModule.cs @@ -18,6 +18,8 @@ public class AbpPermissionManagementTestBaseModule : AbpModule context.Services.Configure(options => { options.ManagementProviders.Add(); + options.ResourceManagementProviders.Add(); + options.ResourcePermissionProviderKeyLookupServices.Add(); }); } diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs index c69d315766..eb345c0e0a 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs @@ -107,6 +107,17 @@ public class PermissionTestDataBuilder : ITransientDependency ) ); + await _resourcePermissionGrantRepository.InsertAsync( + new ResourcePermissionGrant( + _guidGenerator.Create(), + "MyResourcePermission6", + TestEntityResource.ResourceName, + TestEntityResource.ResourceKey3, + UserPermissionValueProvider.ProviderName, + User1Id.ToString() + ) + ); + await _resourcePermissionGrantRepository.InsertAsync( new ResourcePermissionGrant( _guidGenerator.Create(), diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionManagementProvider.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionManagementProvider.cs new file mode 100644 index 0000000000..e555c6133d --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionManagementProvider.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.PermissionManagement; + +public class TestResourcePermissionManagementProvider : ResourcePermissionManagementProvider +{ + public override string Name => "Test"; + + public TestResourcePermissionManagementProvider( + IResourcePermissionGrantRepository resourcePermissionGrantRepository, + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) + : base( + resourcePermissionGrantRepository, + guidGenerator, + currentTenant) + { + + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionProviderKeyLookupService.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionProviderKeyLookupService.cs new file mode 100644 index 0000000000..c86bbcc81a --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionProviderKeyLookupService.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; + +namespace Volo.Abp.PermissionManagement; + +public class TestResourcePermissionProviderKeyLookupService : IResourcePermissionProviderKeyLookupService, ITransientDependency +{ + public string Name => "Test"; + + public ILocalizableString DisplayName => new LocalizableString("Test", "TestResource"); + + public Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task> SearchAsync(string[] keys, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } +} From 46281964d9705fb719157879d00789531423e42e Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 18:18:54 +0800 Subject: [PATCH 061/130] Add pagination support to resource permission search methods --- .../Volo/Abp/Identity/IUserRoleFinder.cs | 5 +-- .../Volo/Abp/Identity/UserRoleFinder.cs | 20 ++++-------- .../Abp/Identity/HttpClientUserRoleFinder.cs | 12 ++++--- ...ourcePermissionProviderKeyLookupService.cs | 4 +-- ...ourcePermissionProviderKeyLookupService.cs | 4 +-- .../IPermissionAppService.cs | 2 +- .../PermissionAppService.cs | 4 +-- ...ourcePermissionProviderKeyLookupService.cs | 2 +- .../PermissionsClientProxy.Generated.cs | 5 +-- .../permissionManagement-generate-proxy.json | 32 +++++++++++++++++-- .../PermissionsController.cs | 4 +-- ...dd-resource-permission-management-modal.js | 6 +++- .../permissionManagement-proxy.js | 4 +-- ...ourcePermissionProviderKeyLookupService.cs | 2 +- 14 files changed, 68 insertions(+), 38 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs index 751160236d..166d066245 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Volo.Abp.Identity; @@ -11,9 +12,9 @@ public interface IUserRoleFinder Task GetRoleNamesAsync(Guid userId); - Task> SearchUserAsync(string filter); + Task> SearchUserAsync(string filter, int page = 1); - Task> SearchRoleAsync(string filter); + Task> SearchRoleAsync(string filter, int page = 1); Task> SearchUserByIdsAsync(Guid[] ids); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs index 8baf079273..a958ac1456 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs @@ -29,16 +29,12 @@ public class UserRoleFinder : IUserRoleFinder, ITransientDependency return (await IdentityUserRepository.GetRoleNamesAsync(userId)).ToArray(); } - public virtual async Task> SearchUserAsync(string filter) + public virtual async Task> SearchUserAsync(string filter, int page = 1) { - if (filter.IsNullOrEmpty()) - { - return new List(); - } - using (IdentityUserRepository.DisableTracking()) { - var users = await IdentityUserRepository.GetListAsync(filter: filter); + page = page < 1 ? 1 : page; + var users = await IdentityUserRepository.GetListAsync(filter: filter, skipCount: (page - 1) * 10, maxResultCount: 10); return users.Select(user => new UserFinderResult { Id = user.Id, @@ -47,16 +43,12 @@ public class UserRoleFinder : IUserRoleFinder, ITransientDependency } } - public virtual async Task> SearchRoleAsync(string filter) + public virtual async Task> SearchRoleAsync(string filter, int page = 1) { - if (filter.IsNullOrEmpty()) - { - return new List(); - } - using (IdentityUserRepository.DisableTracking()) { - var roles = await IdentityRoleRepository.GetListAsync(filter: filter); + page = page < 1 ? 1 : page; + var roles = await IdentityRoleRepository.GetListAsync(filter: filter, skipCount: (page - 1) * 10, maxResultCount: 10); return roles.Select(user => new RoleFinderResult { Id = user.Id, diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs index 6299112311..6c32d8b11d 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs @@ -31,11 +31,13 @@ public class HttpClientUserRoleFinder : IUserRoleFinder, ITransientDependency return await _userIntegrationService.GetRoleNamesAsync(userId); } - public virtual async Task> SearchUserAsync(string filter) + public virtual async Task> SearchUserAsync(string filter, int page = 1) { + page = page < 1 ? 1 : page; var users = await _userIntegrationService.SearchAsync(new UserLookupSearchInputDto() { - Filter = filter + Filter = filter, + SkipCount = (page - 1) * 10 }); return users.Items.Select(u => new UserFinderResult { @@ -44,11 +46,13 @@ public class HttpClientUserRoleFinder : IUserRoleFinder, ITransientDependency }).ToList(); } - public virtual async Task> SearchRoleAsync(string filter) + public virtual async Task> SearchRoleAsync(string filter, int page = 1) { + page = page < 1 ? 1 : page; var roles = await _userIntegrationService.SearchRoleAsync(new RoleLookupSearchInputDto() { - Filter = filter + Filter = filter, + SkipCount = (page - 1) * 10 }); return roles.Items.Select(r => new RoleFinderResult diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs index 18096e5167..c0e8aa548d 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs @@ -25,9 +25,9 @@ public class RoleResourcePermissionProviderKeyLookupService : IResourcePermissio DisplayName = LocalizableString.Create(nameof(RoleResourcePermissionProviderKeyLookupService)); } - public virtual async Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default) + public virtual async Task> SearchAsync(string filter = null, int page = 1, CancellationToken cancellationToken = default) { - var roles = await UserRoleFinder.SearchRoleAsync(filter); + var roles = await UserRoleFinder.SearchRoleAsync(filter, page); return roles.Select(r => new ResourcePermissionProviderKeyInfo(r.RoleName, r.RoleName)).ToList(); } diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs index a0cf06d40d..3e5470f432 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs @@ -25,9 +25,9 @@ public class UserResourcePermissionProviderKeyLookupService : IResourcePermissio DisplayName = LocalizableString.Create(nameof(UserResourcePermissionProviderKeyLookupService)); } - public virtual async Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default) + public virtual async Task> SearchAsync(string filter = null, int page = 1, CancellationToken cancellationToken = default) { - var users = await UserRoleFinder.SearchUserAsync(filter); + var users = await UserRoleFinder.SearchUserAsync(filter, page); return users.Select(u => new ResourcePermissionProviderKeyInfo(u.Id.ToString(), u.UserName)).ToList(); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs index 1e4f5d54ac..ea0017a544 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs @@ -14,7 +14,7 @@ public interface IPermissionAppService : IApplicationService Task GetResourceProviderKeyLookupServicesAsync(); - Task SearchResourceProviderKeyAsync(string serviceName, string filter); + Task SearchResourceProviderKeyAsync(string serviceName, string filter, int page); Task GetResourceDefinitionsAsync([NotNull] string resourceName); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 8e5c5b0b79..7421cf7fbd 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -181,10 +181,10 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] - public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter) + public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter, int page) { var lookupService = await ResourcePermissionManager.GetProviderKeyLookupServiceAsync(serviceName); - var keys = await lookupService.SearchAsync(filter); + var keys = await lookupService.SearchAsync(filter, page); return new SearchProviderKeyListResultDto { Keys = keys.Select(x => new SearchProviderKeyInfo diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs index bdd5eb8f84..aa6ad2a06e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionProviderKeyLookupService.cs @@ -11,7 +11,7 @@ public interface IResourcePermissionProviderKeyLookupService public ILocalizableString DisplayName { get; } - Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default); + Task> SearchAsync(string filter = null, int page = 1, CancellationToken cancellationToken = default); Task> SearchAsync(string[] keys, CancellationToken cancellationToken = default); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs index cc9dfdf4f7..fc1e851bae 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs @@ -51,12 +51,13 @@ public partial class PermissionsClientProxy : ClientProxyBase(nameof(GetResourceProviderKeyLookupServicesAsync)); } - public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter) + public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter, int page) { return await RequestAsync(nameof(SearchResourceProviderKeyAsync), new ClientProxyRequestTypeValue { { typeof(string), serviceName }, - { typeof(string), filter } + { typeof(string), filter }, + { typeof(int), page } }); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json index 62dedb4c1b..2907378292 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json @@ -205,6 +205,14 @@ "typeSimple": "string", "isOptional": false, "defaultValue": null + }, + { + "name": "page", + "typeAsString": "System.Int32, System.Private.CoreLib", + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null } ], "returnValue": { @@ -599,8 +607,8 @@ "allowAnonymous": null, "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" }, - "SearchResourceProviderKeyAsyncByServiceNameAndFilter": { - "uniqueName": "SearchResourceProviderKeyAsyncByServiceNameAndFilter", + "SearchResourceProviderKeyAsyncByServiceNameAndFilterAndPage": { + "uniqueName": "SearchResourceProviderKeyAsyncByServiceNameAndFilterAndPage", "name": "SearchResourceProviderKeyAsync", "httpMethod": "GET", "url": "api/permission-management/permissions/search-resource-provider-keys", @@ -621,6 +629,14 @@ "typeSimple": "string", "isOptional": false, "defaultValue": null + }, + { + "name": "page", + "typeAsString": "System.Int32, System.Private.CoreLib", + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null } ], "parameters": [ @@ -647,6 +663,18 @@ "constraintTypes": null, "bindingSourceId": "ModelBinding", "descriptorName": "" + }, + { + "nameOnMethod": "page", + "name": "page", + "jsonName": null, + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" } ], "returnValue": { diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs index 1b5aa19b5e..b7a9cc2ef5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs @@ -42,9 +42,9 @@ public class PermissionsController : AbpControllerBase, IPermissionAppService } [HttpGet("search-resource-provider-keys")] - public virtual Task SearchResourceProviderKeyAsync(string serviceName, string filter) + public virtual Task SearchResourceProviderKeyAsync(string serviceName, string filter, int page) { - return PermissionAppService.SearchResourceProviderKeyAsync(serviceName, filter); + return PermissionAppService.SearchResourceProviderKeyAsync(serviceName, filter, page); } [HttpGet("resource-definitions")] diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js index 7f59156f9d..5fd096fe29 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -19,6 +19,7 @@ var abp = abp || {}; data: function (params) { var query = {}; query["serviceName"] = $('input[name="AddModel.ProviderName"]:checked').val(); + query["page"] = params.page || 1; query["filter"] = params.term; return query; }, @@ -32,7 +33,10 @@ var abp = abp || {}; }) }); return { - results: keyValues + results: keyValues, + pagination: { + more: keyValues.length > 0 + } }; } }, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js index df85c0be12..a0de671e4e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js @@ -41,9 +41,9 @@ }, ajaxParams)); }; - volo.abp.permissionManagement.permissions.searchResourceProviderKey = function(serviceName, filter, ajaxParams) { + volo.abp.permissionManagement.permissions.searchResourceProviderKey = function(serviceName, filter, page, ajaxParams) { return abp.ajax($.extend(true, { - url: abp.appPath + 'api/permission-management/permissions/search-resource-provider-keys' + abp.utils.buildQueryString([{ name: 'serviceName', value: serviceName }, { name: 'filter', value: filter }]) + '', + url: abp.appPath + 'api/permission-management/permissions/search-resource-provider-keys' + abp.utils.buildQueryString([{ name: 'serviceName', value: serviceName }, { name: 'filter', value: filter }, { name: 'page', value: page }]) + '', type: 'GET' }, ajaxParams)); }; diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionProviderKeyLookupService.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionProviderKeyLookupService.cs index c86bbcc81a..f42a204f7a 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionProviderKeyLookupService.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionProviderKeyLookupService.cs @@ -12,7 +12,7 @@ public class TestResourcePermissionProviderKeyLookupService : IResourcePermissio public ILocalizableString DisplayName => new LocalizableString("Test", "TestResource"); - public Task> SearchAsync(string filter = null, CancellationToken cancellationToken = default) + public Task> SearchAsync(string filter = null, int page = 1, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } From 256250061866b9ff515f13d9a504a36109495d79 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Nov 2025 20:42:26 +0800 Subject: [PATCH 062/130] Update pagination logic in permission modal --- .../add-resource-permission-management-modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js index 5fd096fe29..289ac693cf 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -35,7 +35,7 @@ var abp = abp || {}; return { results: keyValues, pagination: { - more: keyValues.length > 0 + more: keyValues.length == 10 } }; } From f5404df9251c81a9e04bd5e269f578ce381640c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 27 Nov 2025 17:23:27 +0300 Subject: [PATCH 063/130] Introduce IKeyedObject and unify object key access Added the IKeyedObject interface to provide a standard way to retrieve object keys, including support for composite keys via KeyedObjectHelper. Updated IEntity, IEntityDto, and related DTOs to implement IKeyedObject, and refactored resource permission logic to use GetObjectKey for key retrieval. This change improves consistency and reliability in handling object keys across the framework. --- .../Resources/IHasResourcePermissions.cs | 6 +-- .../Resources/ResourcePermissionPopulator.cs | 12 +++++- .../Volo.Abp.Core/Volo/Abp/IKeyedObject.cs | 6 +++ .../Volo/Abp/KeyedObjectHelper.cs | 39 +++++++++++++++++++ .../Volo/Abp/Application/Dtos/EntityDto.cs | 5 +++ .../Application/Dtos/ExtensibleEntityDto.cs | 5 +++ .../Volo/Abp/Application/Dtos/IEntityDto.cs | 2 +- .../Volo/Abp/Domain/Entities/Entity.cs | 13 ++++++- .../Volo/Abp/Domain/Entities/IEntity.cs | 2 +- 9 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/IKeyedObject.cs create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/KeyedObjectHelper.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs index 128c3065fd..903f6bd2d6 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IHasResourcePermissions.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; namespace Volo.Abp.Authorization.Permissions.Resources; -public interface IHasResourcePermissions +public interface IHasResourcePermissions : IKeyedObject { - public Dictionary ResourcePermissions { get; } - - string GetResourceKey(); + Dictionary ResourcePermissions { get; } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs index 700f9104cf..e3684263a0 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -42,13 +43,20 @@ public class ResourcePermissionPopulator : ITransientDependency foreach (var resource in resources) { - var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissionNames, resourceName, resource.GetResourceKey()); + var resourceKey = resource.GetObjectKey(); + if (resourceKey.IsNullOrEmpty()) + { + throw new AbpException("Resource key can not be null or empty."); + } + + var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissionNames, resourceName, resourceKey); foreach (var resopurcePermission in resopurcePermissionNames) { - if(resource.ResourcePermissions == null) + if (resource.ResourcePermissions == null) { ObjectHelper.TrySetProperty(resource, x => x.ResourcePermissions, () => new Dictionary()); } + var hasPermission = results.Result.TryGetValue(resopurcePermission, out var granted) && granted == PermissionGrantResult.Granted; resource.ResourcePermissions![resopurcePermission] = hasPermission; } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/IKeyedObject.cs b/framework/src/Volo.Abp.Core/Volo/Abp/IKeyedObject.cs new file mode 100644 index 0000000000..d0cbb47790 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/IKeyedObject.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp; + +public interface IKeyedObject +{ + string? GetObjectKey(); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/KeyedObjectHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/KeyedObjectHelper.cs new file mode 100644 index 0000000000..9757e67ecc --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/KeyedObjectHelper.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Abp; + +public static class KeyedObjectHelper +{ + public static string EncodeCompositeKey(params object?[] keys) + { + var raw = keys.JoinAsString("||"); + var bytes = Encoding.UTF8.GetBytes(raw); + var base64 = Convert.ToBase64String(bytes); + var base64Url = base64 + .Replace("+", "-") + .Replace("/", "_") + .TrimEnd('='); + + return base64Url; + } + + public static string DecodeCompositeKey(string encoded) + { + var base64 = encoded + .Replace("-", "+") + .Replace("_", "/"); + + switch (encoded.Length % 4) + { + case 2: base64 += "=="; break; + case 3: base64 += "="; break; + } + + var bytes = Convert.FromBase64String(base64); + var raw = Encoding.UTF8.GetString(bytes); + + return raw; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/EntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/EntityDto.cs index 656e63b0d5..16c81c2f92 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/EntityDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/EntityDto.cs @@ -23,4 +23,9 @@ public abstract class EntityDto : EntityDto, IEntityDto { return $"[DTO: {GetType().Name}] Id = {Id}"; } + + public virtual string? GetObjectKey() + { + return Id?.ToString(); + } } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs index 9687af0cf7..16379f865c 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs @@ -27,6 +27,11 @@ public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto : IEntityDto +public interface IEntityDto : IEntityDto, IKeyedObject { TKey Id { get; set; } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs index 9e52d1abfd..93c5555a39 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs @@ -18,6 +18,17 @@ public abstract class Entity : IEntity return $"[ENTITY: {GetType().Name}] Keys = {GetKeys().JoinAsString(", ")}"; } + public virtual string? GetObjectKey() + { + var keys = GetKeys(); + return keys.Length switch + { + 0 => null, + 1 when keys[0] != null => keys[0]?.ToString(), + _ => KeyedObjectHelper.EncodeCompositeKey(keys) + }; + } + public abstract object?[] GetKeys(); public bool EntityEquals(IEntity other) @@ -45,7 +56,7 @@ public abstract class Entity : Entity, IEntity public override object?[] GetKeys() { - return new object?[] { Id }; + return [Id]; } /// diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/IEntity.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/IEntity.cs index 1df1b75696..db4a144539 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/IEntity.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/IEntity.cs @@ -4,7 +4,7 @@ /// Defines an entity. It's primary key may not be "Id" or it may have a composite primary key. /// Use where possible for better integration to repositories and other structures in the framework. ///
-public interface IEntity +public interface IEntity : IKeyedObject { /// /// Returns an array of ordered keys for this entity. From d425c0f86dec0bebb493263b68f2f3525d5d2d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 27 Nov 2025 17:23:44 +0300 Subject: [PATCH 064/130] Add GetObjectKey method to MyEntity class Introduced the GetObjectKey method to MyEntity, returning the string representation of the entity's Id. This may be used for scenarios requiring a unique key in string format. --- .../Volo/Abp/Domain/Entities/EntityHelper_Tests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs index 52ab34baee..adf82d74bd 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs @@ -55,6 +55,11 @@ public class EntityHelper_Tests { return new object[] { Id }; } + + public string GetObjectKey() + { + return Id.ToString(); + } } private class MyEntityDisablesIdGeneration : Entity From 5c93eb09b55a4eb2f9d788b7a4e8c8ed159264c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 27 Nov 2025 17:25:09 +0300 Subject: [PATCH 065/130] Add tests for KeyedObjectHelper composite key methods Introduces unit tests for encoding and decoding composite keys in the KeyedObjectHelper class, verifying correct behavior for various input scenarios. --- .../Volo/Abp/ObjectWithKeyHelper_Tests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 framework/test/Volo.Abp.Core.Tests/Volo/Abp/ObjectWithKeyHelper_Tests.cs diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/ObjectWithKeyHelper_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/ObjectWithKeyHelper_Tests.cs new file mode 100644 index 0000000000..d5f0186b3c --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/ObjectWithKeyHelper_Tests.cs @@ -0,0 +1,31 @@ +using System; +using Shouldly; +using Xunit; + +namespace Volo.Abp; + +public class KeyedObjectHelper_Tests +{ + [Fact] + public void EncodeCompositeKey() + { + var encoded = KeyedObjectHelper.EncodeCompositeKey("Book", "123"); + encoded.ShouldBe("Qm9va3x8MTIz"); + } + + [Fact] + public void DecodeCompositeKey() + { + var decoded = KeyedObjectHelper.DecodeCompositeKey("Qm9va3x8MTIz"); + decoded.ShouldBe("Book||123"); + } + + [Fact] + public void Encode_Decode_CompositeKey() + { + var encoded = KeyedObjectHelper.EncodeCompositeKey("User", 42, Guid.Empty); + var decoded = KeyedObjectHelper.DecodeCompositeKey(encoded); + + decoded.ShouldBe($"User||42||{Guid.Empty}"); + } +} \ No newline at end of file From 0658ad8e851c313c2e7f63fecf82145cdf337435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 27 Nov 2025 17:34:47 +0300 Subject: [PATCH 066/130] Remove JetBrains.Annotations and improve formatting Removed all usages of JetBrains.Annotations attributes and related imports from resource permission interfaces and context classes. Reformatted method signatures and constructors for better readability and consistency. --- .../Permissions/IPermissionValueProvider.cs | 1 - .../Resources/IResourcePermissionChecker.cs | 27 +++++++++++++++---- .../Resources/IResourcePermissionStore.cs | 1 - .../IResourcePermissionValueProvider.cs | 1 - .../Resources/ResourcePermissionGrantInfo.cs | 12 ++++++--- .../ResourcePermissionValueCheckContext.cs | 7 ++--- .../ResourcePermissionValuesCheckContext.cs | 11 +++----- 7 files changed, 36 insertions(+), 24 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs index ae9252632d..683f513cf4 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs @@ -6,7 +6,6 @@ public interface IPermissionValueProvider { string Name { get; } - //TODO: Rename to GetResult? (CheckAsync throws exception by naming convention) Task CheckAsync(PermissionValueCheckContext context); Task CheckAsync(PermissionValuesCheckContext context); diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs index 5b3bf1eee1..0270d2c96b 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionChecker.cs @@ -1,16 +1,33 @@ using System.Security.Claims; using System.Threading.Tasks; -using JetBrains.Annotations; namespace Volo.Abp.Authorization.Permissions.Resources; public interface IResourcePermissionChecker { - Task IsGrantedAsync([NotNull] string name, string resourceName, string resourceKey); + Task IsGrantedAsync( + string name, + string resourceName, + string resourceKey + ); - Task IsGrantedAsync(ClaimsPrincipal? claimsPrincipal, [NotNull] string name, string resourceName, string resourceKey); + Task IsGrantedAsync( + ClaimsPrincipal? claimsPrincipal, + string name, + string resourceName, + string resourceKey + ); - Task IsGrantedAsync([NotNull] string[] names, string resourceName, string resourceKey); + Task IsGrantedAsync( + string[] names, + string resourceName, + string resourceKey + ); - Task IsGrantedAsync(ClaimsPrincipal? claimsPrincipal, [NotNull] string[] names, string resourceName, string resourceKey); + Task IsGrantedAsync( + ClaimsPrincipal? claimsPrincipal, + string[] names, + string resourceName, + string resourceKey + ); } diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs index 355f3330f9..42f751b4c5 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionStore.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Threading.Tasks; namespace Volo.Abp.Authorization.Permissions.Resources; diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProvider.cs index 655b2cda6e..288b878e5d 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/IResourcePermissionValueProvider.cs @@ -6,7 +6,6 @@ public interface IResourcePermissionValueProvider { string Name { get; } - //TODO: Rename to GetResult? (CheckAsync throws exception by naming convention) Task CheckAsync(ResourcePermissionValueCheckContext context); Task CheckAsync(ResourcePermissionValuesCheckContext context); diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionGrantInfo.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionGrantInfo.cs index 9977c5eb2a..4eb9604454 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionGrantInfo.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionGrantInfo.cs @@ -1,6 +1,4 @@ -using JetBrains.Annotations; - -namespace Volo.Abp.Authorization.Permissions.Resources; +namespace Volo.Abp.Authorization.Permissions.Resources; public class ResourcePermissionGrantInfo : PermissionGrantInfo { @@ -8,7 +6,13 @@ public class ResourcePermissionGrantInfo : PermissionGrantInfo public string ResourceKey { get; } - public ResourcePermissionGrantInfo([NotNull] string name, bool isGranted, string resourceName, string resourceKey, string? providerName = null, string? providerKey = null) + public ResourcePermissionGrantInfo( + string name, + bool isGranted, + string resourceName, + string resourceKey, + string? providerName = null, + string? providerKey = null) : base(name, isGranted, providerName, providerKey) { ResourceName = resourceName; diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueCheckContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueCheckContext.cs index 54e7f80e35..dc8c78be64 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueCheckContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValueCheckContext.cs @@ -1,22 +1,19 @@ using System.Security.Claims; -using JetBrains.Annotations; namespace Volo.Abp.Authorization.Permissions.Resources; public class ResourcePermissionValueCheckContext : PermissionValueCheckContext { - [NotNull] public string ResourceName { get; } - [NotNull] public string ResourceKey { get; } - public ResourcePermissionValueCheckContext([NotNull] PermissionDefinition permission, string resourceName, string resourceKey) + public ResourcePermissionValueCheckContext(PermissionDefinition permission, string resourceName, string resourceKey) : this(permission, null, resourceName, resourceKey) { } - public ResourcePermissionValueCheckContext([NotNull] PermissionDefinition permission, ClaimsPrincipal? principal, string resourceName, string resourceKey) + public ResourcePermissionValueCheckContext(PermissionDefinition permission, ClaimsPrincipal? principal, string resourceName, string resourceKey) : base(permission, principal) { ResourceName = resourceName; diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValuesCheckContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValuesCheckContext.cs index 7de44b1a0d..239f74fc19 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValuesCheckContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionValuesCheckContext.cs @@ -1,38 +1,35 @@ using System.Collections.Generic; using System.Security.Claims; -using JetBrains.Annotations; namespace Volo.Abp.Authorization.Permissions.Resources; public class ResourcePermissionValuesCheckContext : PermissionValuesCheckContext { - [NotNull] public string ResourceName { get; } - [NotNull] public string ResourceKey { get; } - public ResourcePermissionValuesCheckContext([NotNull] PermissionDefinition permission,string resourceName, string resourceKey) + public ResourcePermissionValuesCheckContext(PermissionDefinition permission,string resourceName, string resourceKey) : this([permission], null, resourceName, resourceKey) { } - public ResourcePermissionValuesCheckContext([NotNull] PermissionDefinition permission, ClaimsPrincipal? principal, string resourceName, string resourceKey) + public ResourcePermissionValuesCheckContext(PermissionDefinition permission, ClaimsPrincipal? principal, string resourceName, string resourceKey) : this([permission], principal, resourceName, resourceKey) { } - public ResourcePermissionValuesCheckContext([NotNull] List permissions, string resourceName, string resourceKey) + public ResourcePermissionValuesCheckContext(List permissions, string resourceName, string resourceKey) : this(permissions, null, resourceName, resourceKey) { ResourceName = resourceName; ResourceKey = resourceKey; } - public ResourcePermissionValuesCheckContext([NotNull] List permissions, ClaimsPrincipal? principal, string resourceName, string resourceKey) + public ResourcePermissionValuesCheckContext(List permissions, ClaimsPrincipal? principal, string resourceName, string resourceKey) : base(permissions, principal) { ResourceName = resourceName; From 7500ec549072db546a00376d21f6eb8968173f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 27 Nov 2025 18:13:35 +0300 Subject: [PATCH 067/130] Make EntityResourcePermissionRequirementHandler --- ...sourcePermissionAuthorizationExtensions.cs | 4 +-- .../Permissions/Resources/EntityExtensions.cs | 19 ----------- ...ityResourcePermissionRequirementHandler.cs | 30 ---------------- ...ectResourcePermissionCheckerExtensions.cs} | 19 +++++------ ...ectResourcePermissionRequirementHandler.cs | 34 +++++++++++++++++++ ...bjectResourcePermissionStoreExtensions.cs} | 18 +++++----- .../Volo/Abp/Domain/AbpDddDomainModule.cs | 2 +- 7 files changed, 55 insertions(+), 71 deletions(-) delete mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityExtensions.cs delete mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs rename framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/{EntityResourcePermissionCheckerExtensions.cs => KeyedObjectResourcePermissionCheckerExtensions.cs} (56%) create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs rename framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/{EntityResourcePermissionStoreExtensions.cs => KeyedObjectResourcePermissionStoreExtensions.cs} (76%) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs index e392d6378d..3f6d2d1bfa 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs @@ -5,9 +5,9 @@ namespace Microsoft.Extensions.DependencyInjection; public static class ResourcePermissionExtenstions { - public static IServiceCollection AddEntityResourcePermissionAuthorization(this IServiceCollection services) + public static IServiceCollection AddKeyedObjectResourcePermissionAuthorization(this IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); return services; } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityExtensions.cs deleted file mode 100644 index 737cb95077..0000000000 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.Domain.Entities; - -namespace Volo.Abp.Authorization.Permissions.Resources; - -public static class EntityExtensions -{ - public static string GetResourceName(this IEntity entity) - { - Check.NotNull(entity, nameof(entity)); - return entity.GetType().FullName!; - } - - public static string GetResourceKey(this IEntity entity) - { - Check.NotNull(entity, nameof(entity)); - return entity.GetKeys().JoinAsString(","); - } -} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs deleted file mode 100644 index 9fe07efde9..0000000000 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionRequirementHandler.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Domain.Entities; - -namespace Volo.Abp.Authorization.Permissions.Resources; - -public class EntityResourcePermissionRequirementHandler : AuthorizationHandler -{ - protected readonly IResourcePermissionChecker PermissionChecker; - - public EntityResourcePermissionRequirementHandler(IResourcePermissionChecker permissionChecker) - { - PermissionChecker = permissionChecker; - } - - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ResourcePermissionRequirement requirement, IEntity? resource) - { - if (resource == null) - { - return; - } - - var resourceName = resource.GetResourceName(); - var resourceKey = resource.GetResourceKey(); - if (await PermissionChecker.IsGrantedAsync(context.User, requirement.PermissionName, resourceName, resourceKey)) - { - context.Succeed(requirement); - } - } -} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionCheckerExtensions.cs similarity index 56% rename from framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs rename to framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionCheckerExtensions.cs index 8c61f3cb39..e8b9e38218 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionCheckerExtensions.cs @@ -1,29 +1,28 @@ using System.Threading.Tasks; -using Volo.Abp.Domain.Entities; namespace Volo.Abp.Authorization.Permissions.Resources; -public static class EntityResourcePermissionCheckerExtensions +public static class KeyedObjectResourcePermissionCheckerExtensions { /// - /// Checks if the specified permission is granted for the given entity. + /// Checks if the specified permission is granted for the given resource. /// - /// The type of the entity. + /// The type of the object. /// The resource permission checker instance. /// The name of the permission to check. - /// The entity for which the permission is being checked. + /// The resource for which the permission is being checked. /// A task that represents the asynchronous operation. The task result is a boolean indicating whether the permission is granted. - public static Task IsGrantedAsync(this IResourcePermissionChecker resourcePermissionChecker, string permissionName, TEntity entity) - where TEntity : class, IEntity + public static Task IsGrantedAsync(this IResourcePermissionChecker resourcePermissionChecker, string permissionName, TResource resource) + where TResource : class, IKeyedObject { Check.NotNull(resourcePermissionChecker, nameof(resourcePermissionChecker)); Check.NotNullOrWhiteSpace(permissionName, nameof(permissionName)); - Check.NotNull(entity, nameof(entity)); + Check.NotNull(resource, nameof(resource)); return resourcePermissionChecker.IsGrantedAsync( permissionName, - entity, - entity.GetResourceKey() + resource, + resource.GetObjectKey() ?? throw new AbpException("The resource doesn't have a key.") ); } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs new file mode 100644 index 0000000000..df09e0457b --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; + +namespace Volo.Abp.Authorization.Permissions.Resources; + +public class KeyedObjectResourcePermissionRequirementHandler : AuthorizationHandler +{ + protected readonly IResourcePermissionChecker PermissionChecker; + + public KeyedObjectResourcePermissionRequirementHandler( + IResourcePermissionChecker permissionChecker) + { + PermissionChecker = permissionChecker; + } + + protected override async Task HandleRequirementAsync( + AuthorizationHandlerContext context, + ResourcePermissionRequirement requirement, + IKeyedObject? resource) + { + if (resource == null) + { + return; + } + + var resourceName = resource.GetType().FullName!; + var resourceKey = resource.GetObjectKey() ?? throw new AbpException("The resource doesn't have a key."); + + if (await PermissionChecker.IsGrantedAsync(context.User, requirement.PermissionName, resourceName, resourceKey)) + { + context.Succeed(requirement); + } + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs similarity index 76% rename from framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs rename to framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs index 5e5d3766c3..98da7878e3 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/EntityResourcePermissionStoreExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs @@ -5,25 +5,25 @@ using Volo.Abp.Domain.Entities; namespace Volo.Abp.Authorization.Permissions.Resources; -public static class EntityResourcePermissionStoreExtensions +public static class KeyedObjectResourcePermissionStoreExtensions { /// /// Retrieves an array of granted permissions for a specific entity. /// - /// The type of the entity. + /// The type of the resource. /// The resource permission store instance. - /// The entity for which the permissions are being checked. + /// The resource for which the permissions are being checked. /// An array of granted permission names as strings. - public static async Task GetGrantedPermissionsAsync( + public static async Task GetGrantedPermissionsAsync( this IResourcePermissionStore resourcePermissionStore, - TEntity entity + TResource resource ) - where TEntity : class, IEntity + where TResource : class, IEntity { Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); - Check.NotNull(entity, nameof(entity)); + Check.NotNull(resource, nameof(resource)); - return (await GetPermissionsAsync(resourcePermissionStore, entity)).Where(x => x.Value).Select(x => x.Key).ToArray(); + return (await GetPermissionsAsync(resourcePermissionStore, resource)).Where(x => x.Value).Select(x => x.Key).ToArray(); } /// @@ -44,7 +44,7 @@ public static class EntityResourcePermissionStoreExtensions return await resourcePermissionStore.GetPermissionsAsync( entity, - entity.GetResourceKey() + entity.GetObjectKey() ?? throw new AbpException("The entity doesn't have a key.") ); } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs index 5efa873226..85e0523762 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs @@ -38,6 +38,6 @@ public class AbpDddDomainModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddEntityResourcePermissionAuthorization(); + context.Services.AddKeyedObjectResourcePermissionAuthorization(); } } From c19058014b40252f2580fb0b135c2a96292e5f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 27 Nov 2025 18:16:32 +0300 Subject: [PATCH 068/130] Rename ResourcePermissionExtenstions class The class ResourcePermissionExtenstions was renamed to KeyedObjectResourcePermissionExtenstions to better reflect its purpose and usage. --- .../EntityResourcePermissionAuthorizationExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs index 3f6d2d1bfa..e65d5174de 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs @@ -3,7 +3,7 @@ using Volo.Abp.Authorization.Permissions.Resources; namespace Microsoft.Extensions.DependencyInjection; -public static class ResourcePermissionExtenstions +public static class KeyedObjectResourcePermissionExtenstions { public static IServiceCollection AddKeyedObjectResourcePermissionAuthorization(this IServiceCollection services) { From 49e79ce1dd13e49957d350f6cfccd494c4ea993f Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 5 Dec 2025 14:42:01 +0800 Subject: [PATCH 069/130] Remove redundant permission management translations --- .../Volo/Abp/PermissionManagement/Localization/Domain/ar.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/cs.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/de.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/el.json | 2 -- .../Abp/PermissionManagement/Localization/Domain/en-GB.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/en.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/es.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/fa.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/fi.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/fr.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/hi.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/hr.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/hu.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/is.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/it.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/nl.json | 2 -- .../Abp/PermissionManagement/Localization/Domain/pl-PL.json | 2 -- .../Abp/PermissionManagement/Localization/Domain/pt-BR.json | 2 -- .../Abp/PermissionManagement/Localization/Domain/ro-RO.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/ru.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/sk.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/sl.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/sv.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/tr.json | 2 -- .../Volo/Abp/PermissionManagement/Localization/Domain/vi.json | 2 -- .../Abp/PermissionManagement/Localization/Domain/zh-Hans.json | 2 -- .../Abp/PermissionManagement/Localization/Domain/zh-Hant.json | 2 -- 27 files changed, 54 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index 75f4ecc141..47b228151c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "هل أنت متأكد أنك تريد الحفظ بدون أي أذونات؟", "PermissionGroup": "مجموعة الأذونات", "Filter": "تصفية", - "Permission:PermissionManagement": "إدارة الأذونات", - "Permission:ManageResourcePermissions": "إدارة أذونات الموارد", "ResourcePermissions": "أذونات الموارد", "ResourcePermissionTarget": "الهدف", "ResourcePermissionPermissions": "الأذونات", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json index ff0ef28d13..0c71271445 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Opravdu chcete ukládat bez jakýchkoli oprávnění?", "PermissionGroup": "Skupina oprávnění", "Filter": "Filtr", - "Permission:PermissionManagement": "Správa oprávnění", - "Permission:ManageResourcePermissions": "Správa oprávnění zdrojů", "ResourcePermissions": "Oprávnění zdrojů", "ResourcePermissionTarget": "Cíl", "ResourcePermissionPermissions": "Oprávnění", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index b0db8d9404..002cd1aa2b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Sind Sie sicher, dass Sie ohne Berechtigungen speichern möchten?", "PermissionGroup": "Berechtigungsgruppe", "Filter": "Filtern", - "Permission:PermissionManagement": "Berechtigungsverwaltung", - "Permission:ManageResourcePermissions": "Ressourcenberechtigungen verwalten", "ResourcePermissions": "Ressourcenberechtigungen", "ResourcePermissionTarget": "Ziel", "ResourcePermissionPermissions": "Berechtigungen", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index 140fd52d97..1b32aa2d8d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Είστε βέβαιοι ότι θέλετε να αποθηκεύσετε χωρίς δικαιώματα;", "PermissionGroup": "Ομάδα δικαιωμάτων", "Filter": "Φίλτρο", - "Permission:PermissionManagement": "Διαχείριση δικαιωμάτων", - "Permission:ManageResourcePermissions": "Διαχείριση δικαιωμάτων πόρων", "ResourcePermissions": "Δικαιώματα πόρων", "ResourcePermissionTarget": "Στόχος", "ResourcePermissionPermissions": "Δικαιώματα", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index 5d8520e5bf..7ce464185b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Are you sure you want to save without any permissions?", "PermissionGroup": "Permission Group", "Filter": "Filter", - "Permission:PermissionManagement": "Permission Management", - "Permission:ManageResourcePermissions": "Manage resource permissions", "ResourcePermissions": "Resource permissions", "ResourcePermissionTarget": "Target", "ResourcePermissionPermissions": "Permissions", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index faa2940f7e..a9f32681f5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Are you sure you want to save without any permissions?", "PermissionGroup": "Permission Group", "Filter": "Filter", - "Permission:PermissionManagement": "Permission Management", - "Permission:ManageResourcePermissions": "Manage resource permissions", "ResourcePermissions": "Resource permissions", "ResourcePermissionTarget": "Target", "ResourcePermissionPermissions": "Permissions", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index b14aee2747..3282dc5ee3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "¿Estás seguro de que quieres guardar sin ningún permiso?", "PermissionGroup": "Grupo de permisos", "Filter": "Filtrar", - "Permission:PermissionManagement": "Gestión de permisos", - "Permission:ManageResourcePermissions": "Gestionar permisos de recursos", "ResourcePermissions": "Permisos de recursos", "ResourcePermissionTarget": "Objetivo", "ResourcePermissionPermissions": "Permisos", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index 3d2608f091..c0bedcb03e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "آیا مطمئن هستید که می خواهید بدون هیچ دسترسی ذخیره کنید؟", "PermissionGroup": "گروه دسترسی", "Filter": "فیلتر", - "Permission:PermissionManagement": "مدیریت دسترسی‌ها", - "Permission:ManageResourcePermissions": "مدیریت دسترسی‌های منابع", "ResourcePermissions": "دسترسی‌های منابع", "ResourcePermissionTarget": "هدف", "ResourcePermissionPermissions": "دسترسی‌ها", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index 0043d3fcb0..352529aab0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Haluatko varmasti tallentaa ilman käyttöoikeuksia?", "PermissionGroup": "Käyttöoikeus", "Filter": "Suodatus", - "Permission:PermissionManagement": "Käyttöoikeuksien hallinta", - "Permission:ManageResourcePermissions": "Hallitse resurssien käyttöoikeuksia", "ResourcePermissions": "Resurssien käyttöoikeudet", "ResourcePermissionTarget": "Kohde", "ResourcePermissionPermissions": "Käyttöoikeudet", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index 974c08eb40..5f0ef20310 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Êtes-vous sûr de vouloir enregistrer sans aucune autorisation ?", "PermissionGroup": "Groupe d'autorisations", "Filter": "Filtrer", - "Permission:PermissionManagement": "Gestion des autorisations", - "Permission:ManageResourcePermissions": "Gérer les autorisations des ressources", "ResourcePermissions": "Autorisations des ressources", "ResourcePermissionTarget": "Cible", "ResourcePermissionPermissions": "Autorisations", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index 3a31aae436..14356bcfa5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "क्या आप वाकई बिना किसी अनुमति के सहेजना चाहते हैं?", "PermissionGroup": "अनुमति समूह", "Filter": "फ़िल्टर", - "Permission:PermissionManagement": "अनुमति प्रबंधन", - "Permission:ManageResourcePermissions": "संसाधन अनुमतियों का प्रबंधन करें", "ResourcePermissions": "संसाधन अनुमतियाँ", "ResourcePermissionTarget": "लक्ष्य", "ResourcePermissionPermissions": "अनुमतियाँ", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index c4ce2be7da..4df3529f20 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Jeste li sigurni da želite spremiti bez ikakvih dopuštenja?", "PermissionGroup": "Grupa dozvola", "Filter": "Filtriraj", - "Permission:PermissionManagement": "Upravljanje dozvolama", - "Permission:ManageResourcePermissions": "Upravljanje dozvolama resursa", "ResourcePermissions": "Dozvole resursa", "ResourcePermissionTarget": "Cilj", "ResourcePermissionPermissions": "Dozvole", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index 91c13fba04..c83d5cdb40 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Biztos, hogy engedélyek nélkül akar menteni?", "PermissionGroup": "Engedélycsoport", "Filter": "Szűrő", - "Permission:PermissionManagement": "Engedélyek kezelése", - "Permission:ManageResourcePermissions": "Erőforrás-engedélyek kezelése", "ResourcePermissions": "Erőforrás-engedélyek", "ResourcePermissionTarget": "Cél", "ResourcePermissionPermissions": "Engedélyek", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index 502d489e41..dbd00d0452 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Ertu viss um að þú viljir vista án nokkurra heimilda?", "PermissionGroup": "Heimildahópur", "Filter": "Sía", - "Permission:PermissionManagement": "Heimildastjórnun", - "Permission:ManageResourcePermissions": "Stjórna heimildum auðlinda", "ResourcePermissions": "Heimildir auðlinda", "ResourcePermissionTarget": "Markmið", "ResourcePermissionPermissions": "Heimildir", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index 77215c6026..656d482fc2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Sei sicuro di voler salvare senza alcuna autorizzazione?", "PermissionGroup": "Gruppo di autorizzazioni", "Filter": "Filtro", - "Permission:PermissionManagement": "Gestione delle autorizzazioni", - "Permission:ManageResourcePermissions": "Gestisci le autorizzazioni delle risorse", "ResourcePermissions": "Autorizzazioni delle risorse", "ResourcePermissionTarget": "Obiettivo", "ResourcePermissionPermissions": "Autorizzazioni", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index bb01e3b60a..31f793f222 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Weet u zeker dat u zonder rechten wilt opslaan?", "PermissionGroup": "Rechtengroep", "Filter": "Filter", - "Permission:PermissionManagement": "Rechtenbeheer", - "Permission:ManageResourcePermissions": "Beheer van rechten voor bronnen", "ResourcePermissions": "Bronrechten", "ResourcePermissionTarget": "Doel", "ResourcePermissionPermissions": "Rechten", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index 11f496a7ca..b567328480 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Czy na pewno chcesz zapisać bez żadnych uprawnień?", "PermissionGroup": "Grupa uprawnień", "Filter": "Filtr", - "Permission:PermissionManagement": "Zarządzanie uprawnieniami", - "Permission:ManageResourcePermissions": "Zarządzaj uprawnieniami zasobów", "ResourcePermissions": "Uprawnienia zasobów", "ResourcePermissionTarget": "Cel", "ResourcePermissionPermissions": "Uprawnienia", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index e9ffcff835..07f6fe7ca8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Tem certeza que deseja salvar sem nenhuma permissão?", "PermissionGroup": "Grupo de permissão", "Filter": "Filtrar", - "Permission:PermissionManagement": "Gerenciamento de Permissões", - "Permission:ManageResourcePermissions": "Gerenciar permissões de recursos", "ResourcePermissions": "Permissões de recursos", "ResourcePermissionTarget": "Alvo", "ResourcePermissionPermissions": "Permissões", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index 5b0e48e287..59a94781fb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Sigur doriți să salvați fără nicio permisiune?", "PermissionGroup": "Grup de permisiuni", "Filter": "Filtru", - "Permission:PermissionManagement": "Gestionarea permisiunilor", - "Permission:ManageResourcePermissions": "Gestionați permisiunile resurselor", "ResourcePermissions": "Permisiuni pentru resurse", "ResourcePermissionTarget": "Țintă", "ResourcePermissionPermissions": "Permisiuni", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index 886de35245..05e09043b9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Вы уверены, что хотите сохранить без каких-либо разрешений?", "PermissionGroup": "Группа разрешений", "Filter": "Фильтр", - "Permission:PermissionManagement": "Управление разрешениями", - "Permission:ManageResourcePermissions": "Управление разрешениями ресурсов", "ResourcePermissions": "Разрешения ресурсов", "ResourcePermissionTarget": "Цель", "ResourcePermissionPermissions": "Разрешения", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index ee914e37c3..9d54165db3 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Naozaj chcete ukladať bez akýchkoľvek povolení?", "PermissionGroup": "Skupina oprávnení", "Filter": "Filtrovať", - "Permission:PermissionManagement": "Správa oprávnení", - "Permission:ManageResourcePermissions": "Správa oprávnení zdrojov", "ResourcePermissions": "Oprávnenia zdrojov", "ResourcePermissionTarget": "Cieľ", "ResourcePermissionPermissions": "Oprávnenia", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index 97cbf94540..10dcca8f9f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Ali ste prepričani, da želite shraniti brez kakršnih koli dovoljenj?", "PermissionGroup": "Skupina dovoljenj", "Filter": "Filtriraj", - "Permission:PermissionManagement": "Upravljanje dovoljenj", - "Permission:ManageResourcePermissions": "Upravljanje dovoljenj virov", "ResourcePermissions": "Dovoljenja virov", "ResourcePermissionTarget": "Cilj", "ResourcePermissionPermissions": "Dovoljenja", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index c633fb47ad..8f0106b1fe 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Är du säker på att du vill spara utan några behörigheter?", "PermissionGroup": "Behörighetsgrupp", "Filter": "Filtrera", - "Permission:PermissionManagement": "Behörighetshantering", - "Permission:ManageResourcePermissions": "Hantera resursbehörigheter", "ResourcePermissions": "Resursbehörigheter", "ResourcePermissionTarget": "Mål", "ResourcePermissionPermissions": "Behörigheter", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index 6135b6dcba..a88f903b8e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Hiçbir izin olmadan kaydetmek istediğinize emin misiniz?", "PermissionGroup": "İzin Grubu", "Filter": "Filtre", - "Permission:PermissionManagement": "İzin Yönetimi", - "Permission:ManageResourcePermissions": "Kaynak izinlerini yönet", "ResourcePermissions": "Kaynak izinleri", "ResourcePermissionTarget": "Hedef", "ResourcePermissionPermissions": "İzinler", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index 835232084f..a7d9cddc2c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "Bạn có chắc chắn muốn lưu mà không có bất kỳ quyền nào không?", "PermissionGroup": "Nhóm quyền", "Filter": "Lọc", - "Permission:PermissionManagement": "Quản lý quyền", - "Permission:ManageResourcePermissions": "Quản lý quyền tài nguyên", "ResourcePermissions": "Quyền tài nguyên", "ResourcePermissionTarget": "Mục tiêu", "ResourcePermissionPermissions": "Quyền", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index 7afaade31f..3fe984f0c2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "您确定要在没有任何权限的情况下保存吗?", "PermissionGroup": "权限组", "Filter": "过滤", - "Permission:PermissionManagement": "权限管理", - "Permission:ManageResourcePermissions": "管理资源权限", "ResourcePermissions": "资源权限", "ResourcePermissionTarget": "目标", "ResourcePermissionPermissions": "权限", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index c93e778a22..2b5ffe628d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -9,8 +9,6 @@ "SaveWithoutAnyPermissionsWarningMessage": "您確定要在沒有任何權限的情況下保存嗎?", "PermissionGroup": "權限組", "Filter": "過濾", - "Permission:PermissionManagement": "權限管理", - "Permission:ManageResourcePermissions": "管理資源權限", "ResourcePermissions": "資源權限", "ResourcePermissionTarget": "目標", "ResourcePermissionPermissions": "權限", From 7048d034aad77664c478c1491b9eab8a6b6e3240 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 5 Dec 2025 14:43:14 +0800 Subject: [PATCH 070/130] Remove permission definition provider and permissions classes --- ...nManagementPermissionDefinitionProvider.cs | 19 ------------------- .../PermissionManagementPermissions.cs | 15 --------------- 2 files changed, 34 deletions(-) delete mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissionDefinitionProvider.cs delete mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissions.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissionDefinitionProvider.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissionDefinitionProvider.cs deleted file mode 100644 index 74a24d1013..0000000000 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissionDefinitionProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Localization; -using Volo.Abp.PermissionManagement.Localization; - -namespace Volo.Abp.PermissionManagement; - -public class PermissionManagementPermissionDefinitionProvider : PermissionDefinitionProvider -{ - public override void Define(IPermissionDefinitionContext context) - { - var group = context.AddGroup(PermissionManagementPermissions.GroupName, L("Permission:PermissionManagement")); - var manageResourcePermissions = group.AddPermission(PermissionManagementPermissions.ManageResourcePermissions, L("Permission:ManageResourcePermissions")); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); - } -} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissions.cs deleted file mode 100644 index c4d34b23ad..0000000000 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionManagementPermissions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Volo.Abp.Reflection; - -namespace Volo.Abp.PermissionManagement; - -public static class PermissionManagementPermissions -{ - public const string GroupName = "PermissionManagement"; - - public const string ManageResourcePermissions = GroupName + ".ManageResourcePermissions"; - - public static string[] GetAll() - { - return ReflectionHelper.GetPublicConstantsRecursively(typeof(PermissionManagementPermissions)); - } -} From c7d73881b814ab75787db2bbb20952b39dfd2479 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 5 Dec 2025 18:12:04 +0800 Subject: [PATCH 071/130] feat: add management permission to resource permissions and update related logic --- .../IPermissionDefinitionContext.cs | 1 + .../Permissions/PermissionDefinition.cs | 7 ++ .../PermissionDefinitionContext.cs | 4 + ...estResourcePermissionDefinitionProvider.cs | 16 ++-- .../IPermissionAppService.cs | 4 +- .../PermissionAppService.cs | 88 ++++++++++++++----- .../PermissionDefinitionRecordConsts.cs | 2 + ...cPermissionDefinitionStoreInMemoryCache.cs | 1 + .../IResourcePermissionManager.cs | 8 ++ .../PermissionDefinitionRecord.cs | 24 +++++ .../PermissionDefinitionSerializer.cs | 1 + .../ResourcePermissionManager.cs | 9 ++ ...nagementDbContextModelBuilderExtensions.cs | 6 +- .../PermissionsClientProxy.Generated.cs | 10 ++- .../permissionManagement-generate-proxy.json | 73 +++++++++++++-- .../PermissionsController.cs | 8 +- ...esourcePermissionManagementModal.cshtml.cs | 2 +- ...esourcePermissionManagementModal.cshtml.cs | 2 +- ...dd-resource-permission-management-modal.js | 1 + .../permissionManagement-proxy.js | 8 +- .../CalculateHash_Tests.cs | 2 +- .../PermissionDefinitionSerializer_Tests.cs | 2 + ...estResourcePermissionDefinitionProvider.cs | 22 ++--- 23 files changed, 234 insertions(+), 67 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs index e6c949429d..0afe808877 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs @@ -52,6 +52,7 @@ public interface IPermissionDefinitionContext PermissionDefinition AddResourcePermission( string name, string resourceName, + string managementPermission, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true); diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index 371d880ee5..06e21b115d 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -21,6 +21,11 @@ public class PermissionDefinition : /// public string? ResourceName { get; set; } + /// + /// Management permission of the resource permission. + /// + public string? ManagementPermission { get; set; } + /// /// Parent of this permission if one exists. /// If set, this permission can be granted only if parent is granted. @@ -84,12 +89,14 @@ public class PermissionDefinition : protected internal PermissionDefinition( [NotNull] string name, string resourceName, + string managementPermission, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) : this(name, displayName, multiTenancySide, isEnabled) { ResourceName = Check.NotNull(resourceName, nameof(resourceName)); + ManagementPermission = Check.NotNull(managementPermission, nameof(managementPermission)); } protected internal PermissionDefinition( diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs index 83ffda1f80..e517c400c4 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs @@ -94,11 +94,14 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext public virtual PermissionDefinition AddResourcePermission( string name, string resourceName, + string managementPermission, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { Check.NotNull(name, nameof(name)); + Check.NotNull(resourceName, nameof(resourceName)); + Check.NotNull(managementPermission, nameof(managementPermission)); if (ResourcePermissions.ContainsKey(name)) { @@ -108,6 +111,7 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext var permission = new PermissionDefinition( name, resourceName, + managementPermission, displayName, multiTenancySide, isEnabled) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs index 5ed46f261e..c1923ea0d3 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -8,7 +8,9 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD { public override void Define(IPermissionDefinitionContext context) { - var permission1 = context.AddResourcePermission("MyResourcePermission1", resourceName: TestEntityResource.ResourceName); + context.AddGroup("TestEntityManagementPermissionGroup").AddPermission("TestEntityManagementPermission"); + + var permission1 = context.AddResourcePermission("MyResourcePermission1", resourceName: TestEntityResource.ResourceName, managementPermission: "TestEntityManagementPermission"); Assert.Throws(() => { permission1.AddChild("MyResourcePermission1.ChildPermission1"); @@ -16,12 +18,12 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD permission1.StateCheckers.Add(new TestRequireEditionPermissionSimpleStateChecker());; permission1[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName].ShouldBe(typeof(AuthorizationTestResourcePermissionDefinitionProvider).FullName); - context.AddResourcePermission("MyResourcePermission2", resourceName: typeof(TestEntityResource).FullName!); - context.AddResourcePermission("MyResourcePermission3", resourceName: typeof(TestEntityResource).FullName!); - context.AddResourcePermission("MyResourcePermission4", resourceName: typeof(TestEntityResource).FullName!); - context.AddResourcePermission("MyResourcePermission5", resourceName: typeof(TestEntityResource).FullName!); - context.AddResourcePermission("MyResourcePermission6", resourceName: typeof(TestEntityResource).FullName!).WithProviders(nameof(TestResourcePermissionValueProvider1)); - context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!).WithProviders(nameof(TestResourcePermissionValueProvider2)); + context.AddResourcePermission("MyResourcePermission2", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission3", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission4", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission5", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission6", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider1)); + context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); context.GetResourcePermissionOrNull("MyResourcePermission1").ShouldNotBeNull(); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs index ea0017a544..dd24d436be 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/IPermissionAppService.cs @@ -12,9 +12,9 @@ public interface IPermissionAppService : IApplicationService Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdatePermissionsDto input); - Task GetResourceProviderKeyLookupServicesAsync(); + Task GetResourceProviderKeyLookupServicesAsync(string resourceName); - Task SearchResourceProviderKeyAsync(string serviceName, string filter, int page); + Task SearchResourceProviderKeyAsync(string resourceName, string serviceName, string filter, int page); Task GetResourceDefinitionsAsync([NotNull] string resourceName); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 7421cf7fbd..68cdd94773 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -166,9 +166,15 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } - [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] - public virtual async Task GetResourceProviderKeyLookupServicesAsync() + public virtual async Task GetResourceProviderKeyLookupServicesAsync(string resourceName) { + var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); + if (!resourcePermissions.Any() || + !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermission!).ToArray())) + { + return new GetResourceProviderListResultDto();; + } + var lookupServices = await ResourcePermissionManager.GetProviderKeyLookupServicesAsync(); return new GetResourceProviderListResultDto { @@ -180,9 +186,15 @@ public class PermissionAppService : ApplicationService, IPermissionAppService }; } - [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] - public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter, int page) + public virtual async Task SearchResourceProviderKeyAsync(string resourceName, string serviceName, string filter, int page) { + var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); + if (!resourcePermissions.Any() || + !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermission!).ToArray())) + { + return new SearchProviderKeyListResultDto();; + } + var lookupService = await ResourcePermissionManager.GetProviderKeyLookupServiceAsync(serviceName); var keys = await lookupService.SearchAsync(filter, page); return new SearchProviderKeyListResultDto @@ -195,7 +207,6 @@ public class PermissionAppService : ApplicationService, IPermissionAppService }; } - [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task GetResourceDefinitionsAsync(string resourceName) { var result = new GetResourcePermissionDefinitionListResultDto @@ -206,17 +217,19 @@ public class PermissionAppService : ApplicationService, IPermissionAppService var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { - result.Permissions.Add(new ResourcePermissionDefinitionDto() + if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) { - Name = resourcePermission.Name, - DisplayName = resourcePermission.DisplayName?.Localize(StringLocalizerFactory), - }); + result.Permissions.Add(new ResourcePermissionDefinitionDto() + { + Name = resourcePermission.Name, + DisplayName = resourcePermission.DisplayName?.Localize(StringLocalizerFactory), + }); + } } return result; } - [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task GetResourceAsync(string resourceName, string resourceKey) { var result = new GetResourcePermissionListResultDto @@ -238,11 +251,20 @@ public class PermissionAppService : ApplicationService, IPermissionAppService foreach (var permission in resourcePermissionGrant.Permissions) { - resourcePermissionGrantInfoDto.Permissions.Add(new GrantedResourcePermissionDto() + var resourcePermission = resourcePermissions.FirstOrDefault(x => x.Name == permission); + if (resourcePermission == null) { - Name = permission, - DisplayName = resourcePermissions.FirstOrDefault(x => x.Name == permission)?.DisplayName?.Localize(StringLocalizerFactory), - }); + continue; + } + + if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) + { + resourcePermissionGrantInfoDto.Permissions.Add(new GrantedResourcePermissionDto() + { + Name = permission, + DisplayName = resourcePermission?.DisplayName.Localize(StringLocalizerFactory), + }); + } } result.Permissions.Add(resourcePermissionGrantInfoDto); @@ -251,7 +273,6 @@ public class PermissionAppService : ApplicationService, IPermissionAppService return result; } - [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task GetResourceByProviderAsync(string resourceName, string resourceKey, string providerName, string providerKey) { var result = new GetResourcePermissionWithProviderListResultDto @@ -259,35 +280,54 @@ public class PermissionAppService : ApplicationService, IPermissionAppService Permissions = new List() }; + var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); var resourcePermissionGrants = await ResourcePermissionManager.GetAllAsync(resourceName, resourceKey, providerName, providerKey); - foreach (var resourcePermission in resourcePermissionGrants) + foreach (var resourcePermissionGrant in resourcePermissionGrants) { - result.Permissions.Add(new ResourcePermissionWithProdiverGrantInfoDto + var resourcePermission = resourcePermissions.FirstOrDefault(x => x.Name == resourcePermissionGrant.Name); + if (resourcePermission == null) + { + continue; + } + + if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) { - Name = resourcePermission.Name, - DisplayName = (await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourcePermission.Name))?.DisplayName.Localize(StringLocalizerFactory), - IsGranted = resourcePermission.IsGranted - }); + result.Permissions.Add(new ResourcePermissionWithProdiverGrantInfoDto + { + Name = resourcePermissionGrant.Name, + DisplayName = resourcePermission?.DisplayName.Localize(StringLocalizerFactory), + IsGranted = resourcePermissionGrant.IsGranted + }); + } } return result; } - [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { + if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) + { + continue; + } var isGranted = !input.Permissions.IsNullOrEmpty() && input.Permissions.Any(p => p == resourcePermission.Name); await ResourcePermissionManager.SetAsync(resourcePermission.Name, resourceName, resourceKey, input.ProviderName, input.ProviderKey, isGranted); } } - [Authorize(PermissionManagementPermissions.ManageResourcePermissions)] public virtual async Task DeleteResourceAsync(string resourceName, string resourceKey, string providerName, string providerKey) { - await ResourcePermissionManager.DeleteAsync(resourceName, resourceKey, providerName, providerKey); + var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); + foreach (var resourcePermission in resourcePermissions) + { + if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) + { + await ResourcePermissionManager.DeleteAsync(resourcePermission.Name, resourceName, resourceKey, providerName, providerKey); + } + } } protected virtual async Task CheckProviderPolicy(string providerName) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs index 7d449a2f3b..def40865ff 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs @@ -14,4 +14,6 @@ public class PermissionDefinitionRecordConsts public static int MaxStateCheckersLength { get; set; } = 256; public static int MaxResourceNameLength { get; set; } = 256; + + public static int MaxManagementPermissionLength { get; set; } = 128; } 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 8ce37261af..c6f9030669 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 @@ -53,6 +53,7 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : { context.AddResourcePermission(resourcePermission.Name, resourcePermission.ResourceName, + resourcePermission.ManagementPermission, resourcePermission.DisplayName != null ? LocalizableStringSerializer.Deserialize(resourcePermission.DisplayName) : null, resourcePermission.MultiTenancySide, resourcePermission.IsEnabled); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs index 8af9cc211a..5bd2d143ca 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IResourcePermissionManager.cs @@ -66,6 +66,14 @@ public interface IResourcePermissionManager string providerKey ); + Task DeleteAsync( + string name, + string resourceName, + string resourceKey, + string providerName, + string providerKey + ); + Task DeleteAsync( string providerName, string providerKey diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs index 0ef2032452..d32e6564d9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs @@ -14,6 +14,8 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro public string ResourceName { get; set; } + public string ManagementPermission { get; set; } + public string ParentName { get; set; } public string DisplayName { get; set; } @@ -45,6 +47,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro string groupName, string name, string resourceName, + string managementPermission, string parentName, string displayName, bool isEnabled = true, @@ -60,6 +63,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro } Name = Check.NotNullOrWhiteSpace(name, nameof(name), PermissionDefinitionRecordConsts.MaxNameLength); ResourceName = resourceName; + ManagementPermission = managementPermission; ParentName = Check.Length(parentName, nameof(parentName), PermissionDefinitionRecordConsts.MaxNameLength); DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), PermissionDefinitionRecordConsts.MaxDisplayNameLength); IsEnabled = isEnabled; @@ -78,6 +82,16 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro return false; } + if (ResourceName != otherRecord.ResourceName) + { + return false; + } + + if (ManagementPermission != otherRecord.ManagementPermission) + { + return false; + } + if (GroupName != otherRecord.GroupName) { return false; @@ -128,6 +142,16 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro Name = otherRecord.Name; } + if (ResourceName != otherRecord.ResourceName) + { + ResourceName = otherRecord.ResourceName; + } + + if (ManagementPermission != otherRecord.ManagementPermission) + { + ManagementPermission = otherRecord.ManagementPermission; + } + if (GroupName != otherRecord.GroupName) { GroupName = otherRecord.GroupName; 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 e5df5201cd..723c93c847 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 @@ -87,6 +87,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I permissionGroup?.Name, permission.Name, permission.ResourceName, + permission.ManagementPermission, permission.Parent?.Name, LocalizableStringSerializer.Serialize(permission.DisplayName), permission.IsEnabled, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index c6e48db75a..515aa11918 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -332,6 +332,15 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD } } + public virtual async Task DeleteAsync(string name, string resourceName, string resourceKey, string providerName, string providerKey) + { + var permissionGrant = await ResourcePermissionGrantRepository.FindAsync(name, resourceName, resourceKey, providerName, providerKey); + if (permissionGrant != null) + { + await ResourcePermissionGrantRepository.DeleteAsync(permissionGrant, true); + } + } + public virtual async Task DeleteAsync(string providerName, string providerKey) { var permissionGrants = await ResourcePermissionGrantRepository.GetListAsync(providerName, providerKey); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs index fa9fa6de12..ae43f779a2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs @@ -72,13 +72,13 @@ public static class AbpPermissionManagementDbContextModelBuilderExtensions b.Property(x => x.GroupName).HasMaxLength(PermissionGroupDefinitionRecordConsts.MaxNameLength); b.Property(x => x.Name).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength).IsRequired(); b.Property(x => x.ResourceName).HasMaxLength(PermissionDefinitionRecordConsts.MaxResourceNameLength); + b.Property(x => x.ManagementPermission).HasMaxLength(PermissionDefinitionRecordConsts.MaxManagementPermissionLength); b.Property(x => x.ParentName).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength); - b.Property(x => x.DisplayName).HasMaxLength(PermissionDefinitionRecordConsts.MaxDisplayNameLength) - .IsRequired(); + b.Property(x => x.DisplayName).HasMaxLength(PermissionDefinitionRecordConsts.MaxDisplayNameLength).IsRequired(); b.Property(x => x.Providers).HasMaxLength(PermissionDefinitionRecordConsts.MaxProvidersLength); b.Property(x => x.StateCheckers).HasMaxLength(PermissionDefinitionRecordConsts.MaxStateCheckersLength); - b.HasIndex(x => new { x.Name }).IsUnique(); + b.HasIndex(x => new { x.ResourceName, x.Name }).IsUnique(); b.HasIndex(x => new { x.GroupName }); b.ApplyObjectExtensionMappings(); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs index fc1e851bae..bd6e2c5e8e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/Volo/Abp/PermissionManagement/PermissionsClientProxy.Generated.cs @@ -46,15 +46,19 @@ public partial class PermissionsClientProxy : ClientProxyBase GetResourceProviderKeyLookupServicesAsync() + public virtual async Task GetResourceProviderKeyLookupServicesAsync(string resourceName) { - return await RequestAsync(nameof(GetResourceProviderKeyLookupServicesAsync)); + return await RequestAsync(nameof(GetResourceProviderKeyLookupServicesAsync), new ClientProxyRequestTypeValue + { + { typeof(string), resourceName } + }); } - public virtual async Task SearchResourceProviderKeyAsync(string serviceName, string filter, int page) + public virtual async Task SearchResourceProviderKeyAsync(string resourceName, string serviceName, string filter, int page) { return await RequestAsync(nameof(SearchResourceProviderKeyAsync), new ClientProxyRequestTypeValue { + { typeof(string), resourceName }, { typeof(string), serviceName }, { typeof(string), filter }, { typeof(int), page } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json index 2907378292..8b95d9be21 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi.Client/ClientProxies/permissionManagement-generate-proxy.json @@ -181,7 +181,16 @@ }, { "name": "GetResourceProviderKeyLookupServicesAsync", - "parametersOnMethod": [], + "parametersOnMethod": [ + { + "name": "resourceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], "returnValue": { "type": "Volo.Abp.PermissionManagement.GetResourceProviderListResultDto", "typeSimple": "Volo.Abp.PermissionManagement.GetResourceProviderListResultDto" @@ -190,6 +199,14 @@ { "name": "SearchResourceProviderKeyAsync", "parametersOnMethod": [ + { + "name": "resourceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, { "name": "serviceName", "typeAsString": "System.String, System.Private.CoreLib", @@ -592,14 +609,36 @@ "allowAnonymous": null, "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" }, - "GetResourceProviderKeyLookupServicesAsync": { - "uniqueName": "GetResourceProviderKeyLookupServicesAsync", + "GetResourceProviderKeyLookupServicesAsyncByResourceName": { + "uniqueName": "GetResourceProviderKeyLookupServicesAsyncByResourceName", "name": "GetResourceProviderKeyLookupServicesAsync", "httpMethod": "GET", "url": "api/permission-management/permissions/resource-provider-key-lookup-services", "supportedVersions": [], - "parametersOnMethod": [], - "parameters": [], + "parametersOnMethod": [ + { + "name": "resourceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "resourceName", + "name": "resourceName", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + } + ], "returnValue": { "type": "Volo.Abp.PermissionManagement.GetResourceProviderListResultDto", "typeSimple": "Volo.Abp.PermissionManagement.GetResourceProviderListResultDto" @@ -607,13 +646,21 @@ "allowAnonymous": null, "implementFrom": "Volo.Abp.PermissionManagement.IPermissionAppService" }, - "SearchResourceProviderKeyAsyncByServiceNameAndFilterAndPage": { - "uniqueName": "SearchResourceProviderKeyAsyncByServiceNameAndFilterAndPage", + "SearchResourceProviderKeyAsyncByResourceNameAndServiceNameAndFilterAndPage": { + "uniqueName": "SearchResourceProviderKeyAsyncByResourceNameAndServiceNameAndFilterAndPage", "name": "SearchResourceProviderKeyAsync", "httpMethod": "GET", "url": "api/permission-management/permissions/search-resource-provider-keys", "supportedVersions": [], "parametersOnMethod": [ + { + "name": "resourceName", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + }, { "name": "serviceName", "typeAsString": "System.String, System.Private.CoreLib", @@ -640,6 +687,18 @@ } ], "parameters": [ + { + "nameOnMethod": "resourceName", + "name": "resourceName", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + }, { "nameOnMethod": "serviceName", "name": "serviceName", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs index b7a9cc2ef5..c1f65e353e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi/Volo/Abp/PermissionManagement/PermissionsController.cs @@ -36,15 +36,15 @@ public class PermissionsController : AbpControllerBase, IPermissionAppService } [HttpGet("resource-provider-key-lookup-services")] - public virtual Task GetResourceProviderKeyLookupServicesAsync() + public virtual Task GetResourceProviderKeyLookupServicesAsync(string resourceName) { - return PermissionAppService.GetResourceProviderKeyLookupServicesAsync(); + return PermissionAppService.GetResourceProviderKeyLookupServicesAsync(resourceName); } [HttpGet("search-resource-provider-keys")] - public virtual Task SearchResourceProviderKeyAsync(string serviceName, string filter, int page) + public virtual Task SearchResourceProviderKeyAsync(string resourceName, string serviceName, string filter, int page) { - return PermissionAppService.SearchResourceProviderKeyAsync(serviceName, filter, page); + return PermissionAppService.SearchResourceProviderKeyAsync(resourceName, serviceName, filter, page); } [HttpGet("resource-definitions")] diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs index 2c6c1a5474..d9828ee661 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml.cs @@ -42,7 +42,7 @@ public class AddResourcePermissionManagementModal : AbpPageModel ValidateModel(); ResourcePermissionDefinitions = await PermissionAppService.GetResourceDefinitionsAsync(ResourceName); - ResourceProviders = await PermissionAppService.GetResourceProviderKeyLookupServicesAsync(); + ResourceProviders = await PermissionAppService.GetResourceProviderKeyLookupServicesAsync(ResourceName); return Page(); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs index c4ac219428..1b1373e017 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs @@ -39,7 +39,7 @@ public class ResourcePermissionManagementModal : AbpPageModel HasAnyResourcePermission = (await PermissionAppService.GetResourceDefinitionsAsync(ResourceName)).Permissions.Any(); if (HasAnyResourcePermission) { - HasAnyResourceProviderKeyLookupService = (await PermissionAppService.GetResourceProviderKeyLookupServicesAsync()).Providers.Count > 0; + HasAnyResourceProviderKeyLookupService = (await PermissionAppService.GetResourceProviderKeyLookupServicesAsync(ResourceName)).Providers.Count > 0; } return Page(); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js index 289ac693cf..85154e4086 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -18,6 +18,7 @@ var abp = abp || {}; dataType: "json", data: function (params) { var query = {}; + query["resourceName"] = $('#ResourceName').val(); query["serviceName"] = $('input[name="AddModel.ProviderName"]:checked').val(); query["page"] = params.page || 1; query["filter"] = params.term; diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js index a0de671e4e..893c86ab97 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/wwwroot/client-proxies/permissionManagement-proxy.js @@ -34,16 +34,16 @@ }, ajaxParams)); }; - volo.abp.permissionManagement.permissions.getResourceProviderKeyLookupServices = function(ajaxParams) { + volo.abp.permissionManagement.permissions.getResourceProviderKeyLookupServices = function(resourceName, ajaxParams) { return abp.ajax($.extend(true, { - url: abp.appPath + 'api/permission-management/permissions/resource-provider-key-lookup-services', + url: abp.appPath + 'api/permission-management/permissions/resource-provider-key-lookup-services' + abp.utils.buildQueryString([{ name: 'resourceName', value: resourceName }]) + '', type: 'GET' }, ajaxParams)); }; - volo.abp.permissionManagement.permissions.searchResourceProviderKey = function(serviceName, filter, page, ajaxParams) { + volo.abp.permissionManagement.permissions.searchResourceProviderKey = function(resourceName, serviceName, filter, page, ajaxParams) { return abp.ajax($.extend(true, { - url: abp.appPath + 'api/permission-management/permissions/search-resource-provider-keys' + abp.utils.buildQueryString([{ name: 'serviceName', value: serviceName }, { name: 'filter', value: filter }, { name: 'page', value: page }]) + '', + url: abp.appPath + 'api/permission-management/permissions/search-resource-provider-keys' + abp.utils.buildQueryString([{ name: 'resourceName', value: resourceName }, { name: 'serviceName', value: serviceName }, { name: 'filter', value: filter }, { name: 'page', value: page }]) + '', type: 'GET' }, ajaxParams)); }; diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs index 5d7833ad0f..cfec0d27a2 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/CalculateHash_Tests.cs @@ -35,7 +35,7 @@ public class CalculateHash_Tests: PermissionTestBase json.ShouldNotContain(id.ToString("D")); json = JsonSerializer.Serialize(new List() { - new PermissionDefinitionRecord(id, "Test", "Test", "Test", "Test", "Test") + new PermissionDefinitionRecord(id, "Test", "Test", "Test", "Test", "Test", "Test") }, jsonSerializerOptions); json.ShouldNotContain("\"Id\""); 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 231626de72..1e15360a6a 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 @@ -88,6 +88,7 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase var resourcePermission1 = context.AddResourcePermission( "ResourcePermission1", TestEntityResource.ResourceName, + "Permission1", new LocalizableString(typeof(AbpPermissionManagementResource), "ResourcePermission1"), MultiTenancySides.Tenant ) @@ -110,6 +111,7 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase permissionRecord.Name.ShouldBe("ResourcePermission1"); permissionRecord.GroupName.ShouldBe(null); permissionRecord.ResourceName.ShouldBe(TestEntityResource.ResourceName); + permissionRecord.ManagementPermission.ShouldBe("Permission1"); permissionRecord.DisplayName.ShouldBe("L:AbpPermissionManagement,ResourcePermission1"); permissionRecord.GetProperty("CustomProperty2").ShouldBe("CustomValue2"); permissionRecord.Providers.ShouldBe("ProviderA,ProviderB"); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs index e255419313..2d5a4d5d99 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestResourcePermissionDefinitionProvider.cs @@ -7,20 +7,22 @@ public class TestResourcePermissionDefinitionProvider : PermissionDefinitionProv { public override void Define(IPermissionDefinitionContext context) { - context.AddResourcePermission("MyResourcePermission1", TestEntityResource.ResourceName); - context.AddResourcePermission("MyResourceDisabledPermission1", TestEntityResource.ResourceName, isEnabled: false); - context.AddResourcePermission("MyResourcePermission2", TestEntityResource.ResourceName); - context.AddResourcePermission("MyResourcePermission3", TestEntityResource.ResourceName, multiTenancySide: MultiTenancySides.Host); - context.AddResourcePermission("MyResourcePermission4", TestEntityResource.ResourceName, multiTenancySide: MultiTenancySides.Host).WithProviders(UserPermissionValueProvider.ProviderName); + context.AddGroup("TestEntityManagementPermissionGroup").AddPermission("TestEntityManagementPermission"); - var myPermission5 = context.AddResourcePermission("MyResourcePermission5", TestEntityResource.ResourceName); + context.AddResourcePermission("MyResourcePermission1", TestEntityResource.ResourceName, "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourceDisabledPermission1", TestEntityResource.ResourceName, "TestEntityManagementPermission", isEnabled: false); + context.AddResourcePermission("MyResourcePermission2", TestEntityResource.ResourceName, "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission3", TestEntityResource.ResourceName, "TestEntityManagementPermission", multiTenancySide: MultiTenancySides.Host); + context.AddResourcePermission("MyResourcePermission4", TestEntityResource.ResourceName, "TestEntityManagementPermission", multiTenancySide: MultiTenancySides.Host).WithProviders(UserPermissionValueProvider.ProviderName); + + var myPermission5 = context.AddResourcePermission("MyResourcePermission5", TestEntityResource.ResourceName, "TestEntityManagementPermission"); myPermission5.StateCheckers.Add(new TestRequireRolePermissionStateProvider("super-admin")); - context.AddResourcePermission("MyResourcePermission6", TestEntityResource.ResourceName); + context.AddResourcePermission("MyResourcePermission6", TestEntityResource.ResourceName, "TestEntityManagementPermission"); - context.AddResourcePermission("MyResourceDisabledPermission2", TestEntityResource.ResourceName, isEnabled: false); + context.AddResourcePermission("MyResourceDisabledPermission2", TestEntityResource.ResourceName, "TestEntityManagementPermission", isEnabled: false); - context.AddResourcePermission("MyResourcePermission7", TestEntityResource.ResourceName); - context.AddResourcePermission("MyResourcePermission8", TestEntityResource.ResourceName); + context.AddResourcePermission("MyResourcePermission7", TestEntityResource.ResourceName, "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission8", TestEntityResource.ResourceName, "TestEntityManagementPermission"); } } From 9cd2fa7617775980c1b141a845784ab3c8a253aa Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 5 Dec 2025 20:08:49 +0800 Subject: [PATCH 072/130] Refactor permission group creation in test provider --- ...uthorizationTestResourcePermissionDefinitionProvider.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs index c1923ea0d3..8c4e3e6737 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -8,7 +8,12 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD { public override void Define(IPermissionDefinitionContext context) { - context.AddGroup("TestEntityManagementPermissionGroup").AddPermission("TestEntityManagementPermission"); + var getGroup = context.GetGroupOrNull("TestGroup"); + if (getGroup == null) + { + getGroup = context.AddGroup("TestGroup"); + } + getGroup.AddPermission("TestEntityManagementPermission"); var permission1 = context.AddResourcePermission("MyResourcePermission1", resourceName: TestEntityResource.ResourceName, managementPermission: "TestEntityManagementPermission"); Assert.Throws(() => From cffd8740270261780925703ab24ffc3064aae937 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 6 Dec 2025 11:49:00 +0800 Subject: [PATCH 073/130] refactor: update resource permission methods to include resourceName parameter --- .../IPermissionDefinitionContext.cs | 4 +-- .../IPermissionDefinitionManager.cs | 4 +-- .../PermissionDefinitionContext.cs | 26 ++++++++++++------- .../AbpAuthorizationPolicyProvider.cs | 3 +-- .../IDynamicPermissionDefinitionStore.cs | 2 +- .../IStaticPermissionDefinitionStore.cs | 2 +- .../NullDynamicPermissionDefinitionStore.cs | 2 +- .../PermissionDefinitionManager.cs | 12 ++++----- .../Resources/ResourcePermissionChecker.cs | 4 +-- .../StaticPermissionDefinitionStore.cs | 14 +++++----- .../StaticPermissionDefinitionStore_Tests.cs | 5 ++-- ...estResourcePermissionDefinitionProvider.cs | 2 +- .../DynamicPermissionDefinitionStore.cs | 4 +-- ...cPermissionDefinitionStoreInMemoryCache.cs | 10 +++---- ...cPermissionDefinitionStoreInMemoryCache.cs | 2 +- .../ResourcePermissionManager.cs | 6 ++--- 16 files changed, 54 insertions(+), 48 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs index 0afe808877..56c1334c95 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs @@ -57,7 +57,7 @@ public interface IPermissionDefinitionContext MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true); - PermissionDefinition? GetResourcePermissionOrNull([NotNull] string name); + PermissionDefinition? GetResourcePermissionOrNull([NotNull] string resourceName, [NotNull] string name); - void RemoveResourcePermission([NotNull] string name); + void RemoveResourcePermission([NotNull] string resourceName, [NotNull] string name); } diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionManager.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionManager.cs index 52afcbe6eb..d9411d54fa 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionManager.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionManager.cs @@ -12,9 +12,9 @@ public interface IPermissionDefinitionManager Task GetOrNullAsync([NotNull] string name); [ItemNotNull] - Task GetResourcePermissionAsync([NotNull] string name); + Task GetResourcePermissionAsync([NotNull]string resourceName, [NotNull] string name); - Task GetResourcePermissionOrNullAsync([NotNull] string name); + Task GetResourcePermissionOrNullAsync([NotNull]string resourceName, [NotNull] string name); Task> GetPermissionsAsync(); diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs index e517c400c4..8be2af8b69 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using Volo.Abp.Localization; using Volo.Abp.MultiTenancy; @@ -12,7 +13,7 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext public Dictionary Groups { get; } - public Dictionary ResourcePermissions { get; } + public List ResourcePermissions { get; } internal IPermissionDefinitionProvider? CurrentProvider { get; set; } @@ -25,7 +26,7 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext { ServiceProvider = serviceProvider; Groups = new Dictionary(); - ResourcePermissions = new Dictionary(); + ResourcePermissions = new List(); } public virtual PermissionGroupDefinition AddGroup( @@ -103,9 +104,9 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext Check.NotNull(resourceName, nameof(resourceName)); Check.NotNull(managementPermission, nameof(managementPermission)); - if (ResourcePermissions.ContainsKey(name)) + if (ResourcePermissions.Any(x => x.ResourceName == resourceName && x.Name == name)) { - throw new AbpException($"There is already an existing resource permission with name: {name}"); + throw new AbpException($"There is already an existing resource permission with name: {name} for resource: {resourceName}"); } var permission = new PermissionDefinition( @@ -119,24 +120,29 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext [KnownPropertyNames.CurrentProviderName] = CurrentProvider?.GetType().FullName }; - ResourcePermissions[name] = permission; + ResourcePermissions.Add(permission); return permission; } - public virtual PermissionDefinition? GetResourcePermissionOrNull([NotNull] string name) + public virtual PermissionDefinition? GetResourcePermissionOrNull([NotNull] string resourceName, [NotNull] string name) { + Check.NotNull(resourceName, nameof(resourceName)); Check.NotNull(name, nameof(name)); - return ResourcePermissions.GetOrDefault(name); + + return ResourcePermissions.FirstOrDefault(p => p.ResourceName == resourceName && p.Name == name); } - public virtual void RemoveResourcePermission([NotNull] string name) + public virtual void RemoveResourcePermission([NotNull] string resourceName, [NotNull] string name) { + Check.NotNull(resourceName, nameof(resourceName)); Check.NotNull(name, nameof(name)); - if (!ResourcePermissions.Remove(name)) + var resourcePermission = GetResourcePermissionOrNull(resourceName, name); + if (resourcePermission == null) { - throw new AbpException($"Not found resource permission with name: {name}"); + throw new AbpException($"Not found resource permission with name: {name} for resource: {resourceName}"); } + ResourcePermissions.Remove(resourcePermission); } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationPolicyProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationPolicyProvider.cs index 0792044ba3..a5bd19b539 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationPolicyProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationPolicyProvider.cs @@ -40,8 +40,7 @@ public class AbpAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider return policyBuilder.Build(); } - var resourcePermission = await _permissionDefinitionManager.GetResourcePermissionOrNullAsync(policyName); - if (resourcePermission != null) + if ((await _permissionDefinitionManager.GetResourcePermissionsAsync()).Any(x => x.Name == policyName)) { //TODO: Optimize & Cache! var policyBuilder = new AuthorizationPolicyBuilder(Array.Empty()); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IDynamicPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IDynamicPermissionDefinitionStore.cs index 49f798675a..597b274035 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IDynamicPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IDynamicPermissionDefinitionStore.cs @@ -9,7 +9,7 @@ public interface IDynamicPermissionDefinitionStore Task> GetPermissionsAsync(); - Task GetResourcePermissionOrNullAsync(string name); + Task GetResourcePermissionOrNullAsync(string resourceName, string name); Task> GetResourcePermissionsAsync(); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IStaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IStaticPermissionDefinitionStore.cs index 74899574e7..da4055d771 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IStaticPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IStaticPermissionDefinitionStore.cs @@ -9,7 +9,7 @@ public interface IStaticPermissionDefinitionStore Task> GetPermissionsAsync(); - Task GetResourcePermissionOrNullAsync(string name); + Task GetResourcePermissionOrNullAsync(string resourceName, string name); Task> GetResourcePermissionsAsync(); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullDynamicPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullDynamicPermissionDefinitionStore.cs index 4ea7990abd..cf3a9820b4 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullDynamicPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullDynamicPermissionDefinitionStore.cs @@ -31,7 +31,7 @@ public class NullDynamicPermissionDefinitionStore : IDynamicPermissionDefinition return CachedPermissionsResult; } - public Task GetResourcePermissionOrNullAsync(string name) + public Task GetResourcePermissionOrNullAsync(string resourceName, string name) { return CachedResourcePermissionResult; } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs index 336e8f42fd..3dbc22fb47 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs @@ -38,23 +38,23 @@ public class PermissionDefinitionManager : IPermissionDefinitionManager, ITransi await _dynamicStore.GetOrNullAsync(name); } - public virtual async Task GetResourcePermissionAsync(string name) + public virtual async Task GetResourcePermissionAsync(string resourceName, string name) { - var permission = await GetResourcePermissionOrNullAsync(name); + var permission = await GetResourcePermissionOrNullAsync(resourceName, name); if (permission == null) { - throw new AbpException("Undefined resource permission: " + name); + throw new AbpException($"Undefined resource permission: {name} for resource: {resourceName}"); } return permission; } - public virtual async Task GetResourcePermissionOrNullAsync(string name) + public virtual async Task GetResourcePermissionOrNullAsync(string resourceName, string name) { Check.NotNull(name, nameof(name)); - return await _staticStore.GetResourcePermissionOrNullAsync(name) ?? - await _dynamicStore.GetResourcePermissionOrNullAsync(name); + return await _staticStore.GetResourcePermissionOrNullAsync(resourceName, name) ?? + await _dynamicStore.GetResourcePermissionOrNullAsync(resourceName, name); } public virtual async Task> GetPermissionsAsync() diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs index 67cf436184..7729093f69 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs @@ -45,7 +45,7 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD { Check.NotNull(name, nameof(name)); - var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(name); + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName, name); if (permission == null) { return false; @@ -115,7 +115,7 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD var permissionDefinitions = new List(); foreach (var name in names) { - var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(name); + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName, name); if (permission == null) { result.Result.Add(name, PermissionGrantResult.Prohibited); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs index 938aaf3440..5ac2257060 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs @@ -12,12 +12,12 @@ namespace Volo.Abp.Authorization.Permissions; public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, ISingletonDependency { protected IDictionary PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value.Item1; - private readonly Lazy<(Dictionary, Dictionary)> _lazyPermissionGroupDefinitions; + private readonly Lazy<(Dictionary, List)> _lazyPermissionGroupDefinitions; protected IDictionary PermissionDefinitions => _lazyPermissionDefinitions.Value; private readonly Lazy> _lazyPermissionDefinitions; - protected IDictionary ResourcePermissionDefinitions => _lazyPermissionGroupDefinitions.Value.Item2; + protected IList ResourcePermissionDefinitions => _lazyPermissionGroupDefinitions.Value.Item2; protected AbpPermissionOptions Options { get; } @@ -35,7 +35,7 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, isThreadSafe: true ); - _lazyPermissionGroupDefinitions = new Lazy<(Dictionary, Dictionary)>( + _lazyPermissionGroupDefinitions = new Lazy<(Dictionary, List)>( CreatePermissionGroupDefinitions, isThreadSafe: true ); @@ -73,7 +73,7 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, } } - protected virtual (Dictionary, Dictionary) CreatePermissionGroupDefinitions() + protected virtual (Dictionary, List) CreatePermissionGroupDefinitions() { using (var scope = _serviceProvider.CreateScope()) { @@ -120,15 +120,15 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, ); } - public virtual Task GetResourcePermissionOrNullAsync(string name) + public virtual Task GetResourcePermissionOrNullAsync(string resourceName, string name) { - return Task.FromResult(ResourcePermissionDefinitions.GetOrDefault(name)); + return Task.FromResult(ResourcePermissionDefinitions.FirstOrDefault(p => p.ResourceName == resourceName && p.Name == name)); } public virtual Task> GetResourcePermissionsAsync() { return Task.FromResult>( - ResourcePermissionDefinitions.Values.ToImmutableList() + ResourcePermissionDefinitions.ToImmutableList() ); } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs index ddec7ee451..0afaa34f10 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Shouldly; using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Authorization.TestServices.Resources; using Xunit; namespace Volo.Abp.Authorization; @@ -48,12 +49,12 @@ public class StaticPermissionDefinitionStore_Tests : AuthorizationTestBase [Fact] public async Task GetResourcePermissionOrNullAsync() { - var permission = await _store.GetResourcePermissionOrNullAsync("MyResourcePermission1"); + var permission = await _store.GetResourcePermissionOrNullAsync(TestEntityResource.ResourceName, "MyResourcePermission1"); permission.ShouldNotBeNull(); permission.Name.ShouldBe("MyResourcePermission1"); permission.StateCheckers.ShouldContain(x => x.GetType() == typeof(TestRequireEditionPermissionSimpleStateChecker)); - permission = await _store.GetResourcePermissionOrNullAsync("NotExists"); + permission = await _store.GetResourcePermissionOrNullAsync(TestEntityResource.ResourceName, "NotExists"); permission.ShouldBeNull(); } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs index 8c4e3e6737..fcb37734d8 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -30,6 +30,6 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD context.AddResourcePermission("MyResourcePermission6", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider1)); context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); - context.GetResourcePermissionOrNull("MyResourcePermission1").ShouldNotBeNull(); + context.GetResourcePermissionOrNull(TestEntityResource.ResourceName, "MyResourcePermission1").ShouldNotBeNull(); } } 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 3e70ce25b1..ff525ab263 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 @@ -72,7 +72,7 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor } } - public virtual async Task GetResourcePermissionOrNullAsync(string name) + public virtual async Task GetResourcePermissionOrNullAsync(string resourceName, string name) { if (!PermissionManagementOptions.IsDynamicPermissionStoreEnabled) { @@ -82,7 +82,7 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor using (await StoreCache.SyncSemaphore.LockAsync()) { await EnsureCacheIsUptoDateAsync(); - return StoreCache.GetResourcePermissionOrNull(name); + return StoreCache.GetResourcePermissionOrNull(resourceName, name); } } 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 c6f9030669..5e9e22d55d 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,7 +18,7 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : protected IDictionary PermissionGroupDefinitions { get; } protected IDictionary PermissionDefinitions { get; } - protected IDictionary ResourcePermissionDefinitions { get; } + protected IList ResourcePermissionDefinitions { get; } protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; } protected ILocalizableStringSerializer LocalizableStringSerializer { get; } @@ -35,7 +35,7 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : PermissionGroupDefinitions = new Dictionary(); PermissionDefinitions = new Dictionary(); - ResourcePermissionDefinitions = new Dictionary(); + ResourcePermissionDefinitions = new List(); } public Task FillAsync( @@ -101,14 +101,14 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : return PermissionGroupDefinitions.Values.ToList(); } - public PermissionDefinition GetResourcePermissionOrNull(string name) + public PermissionDefinition GetResourcePermissionOrNull(string resourceName, string name) { - return ResourcePermissionDefinitions.GetOrDefault(name); + return ResourcePermissionDefinitions.FirstOrDefault(p => p.ResourceName == resourceName && p.Name == name); } public IReadOnlyList GetResourcePermissions() { - return ResourcePermissionDefinitions.Values.ToList(); + return ResourcePermissionDefinitions.ToList(); } private void AddPermissionRecursively(ICanAddChildPermission permissionContainer, 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 b30baeb1e1..eb079f6c76 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 @@ -24,7 +24,7 @@ public interface IDynamicPermissionDefinitionStoreInMemoryCache IReadOnlyList GetGroups(); - PermissionDefinition GetResourcePermissionOrNull(string name); + PermissionDefinition GetResourcePermissionOrNull(string resourceName, string name); IReadOnlyList GetResourcePermissions(); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index 515aa11918..1561d5adac 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -101,7 +101,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD public virtual async Task GetAsync(string permissionName, string resourceName, string resourceKey, string providerName, string providerKey) { - var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName, permissionName); if (permission == null || permission.ResourceName != resourceName) { return new PermissionWithGrantedProviders(permissionName, false); @@ -123,7 +123,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD foreach (var permissionName in permissionNames) { - var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName, permissionName); if (permission != null && permission.ResourceName == resourceName) { permissions.Add(permission); @@ -241,7 +241,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD public virtual async Task SetAsync(string permissionName, string resourceName, string resourceKey, string providerName, string providerKey, bool isGranted) { - var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(permissionName); + var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName, permissionName); if (permission == null || permission.ResourceName != resourceName) { /* Silently ignore undefined permissions, From ae069750c51f16d471a0a90c83ae77847cf640ca Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 6 Dec 2025 11:57:27 +0800 Subject: [PATCH 074/130] Add tests for duplicate and multiple resource permissions --- ...horizationTestResourcePermissionDefinitionProvider.cs | 9 +++++++++ .../TestServices/Resources/TestEntityResource.cs | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs index fcb37734d8..f83fbe5ced 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -30,6 +30,15 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD context.AddResourcePermission("MyResourcePermission6", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider1)); context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); + Assert.Throws(() => + { + context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); + }).Message.ShouldBe($"There is already an existing resource permission with name: MyResourcePermission7 for resource: {typeof(TestEntityResource).FullName}"); + + context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource2).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); + context.GetResourcePermissionOrNull(TestEntityResource.ResourceName, "MyResourcePermission1").ShouldNotBeNull(); + context.GetResourcePermissionOrNull(TestEntityResource.ResourceName, "MyResourcePermission7").ShouldNotBeNull(); + context.GetResourcePermissionOrNull(TestEntityResource2.ResourceName, "MyResourcePermission7").ShouldNotBeNull(); } } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs index 81db47bf9a..7325ed26d7 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs @@ -14,3 +14,8 @@ public class TestEntityResource public static readonly string ResourceKey6 = Guid.NewGuid().ToString(); public static readonly string ResourceKey7 = Guid.NewGuid().ToString(); } + +public class TestEntityResource2 +{ + public static readonly string ResourceName = typeof(TestEntityResource2).FullName; +} From 5579fb7c543ba2c8ac9474652f5c07db25f510cb Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 6 Dec 2025 12:25:11 +0800 Subject: [PATCH 075/130] Rename ManagementPermission to ManagementPermissionName --- .../IPermissionDefinitionContext.cs | 2 +- .../Permissions/PermissionDefinition.cs | 6 +++--- .../PermissionDefinitionContext.cs | 6 +++--- ...estResourcePermissionDefinitionProvider.cs | 20 ++++++++++--------- .../PermissionAppService.cs | 14 ++++++------- .../PermissionDefinitionRecordConsts.cs | 2 +- ...cPermissionDefinitionStoreInMemoryCache.cs | 2 +- .../PermissionDefinitionRecord.cs | 12 +++++------ .../PermissionDefinitionSerializer.cs | 2 +- ...nagementDbContextModelBuilderExtensions.cs | 2 +- .../PermissionDefinitionSerializer_Tests.cs | 2 +- 11 files changed, 36 insertions(+), 34 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs index 56c1334c95..5a1bef8e1d 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs @@ -52,7 +52,7 @@ public interface IPermissionDefinitionContext PermissionDefinition AddResourcePermission( string name, string resourceName, - string managementPermission, + string managementPermissionName, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true); diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index 06e21b115d..c93992ed49 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -24,7 +24,7 @@ public class PermissionDefinition : /// /// Management permission of the resource permission. /// - public string? ManagementPermission { get; set; } + public string? ManagementPermissionName { get; set; } /// /// Parent of this permission if one exists. @@ -89,14 +89,14 @@ public class PermissionDefinition : protected internal PermissionDefinition( [NotNull] string name, string resourceName, - string managementPermission, + string managementPermissionName, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) : this(name, displayName, multiTenancySide, isEnabled) { ResourceName = Check.NotNull(resourceName, nameof(resourceName)); - ManagementPermission = Check.NotNull(managementPermission, nameof(managementPermission)); + ManagementPermissionName = Check.NotNull(managementPermissionName, nameof(managementPermissionName)); } protected internal PermissionDefinition( diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs index 8be2af8b69..c13f53866a 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs @@ -95,14 +95,14 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext public virtual PermissionDefinition AddResourcePermission( string name, string resourceName, - string managementPermission, + string managementPermissionName, ILocalizableString? displayName = null, MultiTenancySides multiTenancySide = MultiTenancySides.Both, bool isEnabled = true) { Check.NotNull(name, nameof(name)); Check.NotNull(resourceName, nameof(resourceName)); - Check.NotNull(managementPermission, nameof(managementPermission)); + Check.NotNull(managementPermissionName, nameof(managementPermissionName)); if (ResourcePermissions.Any(x => x.ResourceName == resourceName && x.Name == name)) { @@ -112,7 +112,7 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext var permission = new PermissionDefinition( name, resourceName, - managementPermission, + managementPermissionName, displayName, multiTenancySide, isEnabled) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs index f83fbe5ced..c5716532e1 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -15,7 +15,7 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD } getGroup.AddPermission("TestEntityManagementPermission"); - var permission1 = context.AddResourcePermission("MyResourcePermission1", resourceName: TestEntityResource.ResourceName, managementPermission: "TestEntityManagementPermission"); + var permission1 = context.AddResourcePermission("MyResourcePermission1", resourceName: TestEntityResource.ResourceName, "TestEntityManagementPermission"); Assert.Throws(() => { permission1.AddChild("MyResourcePermission1.ChildPermission1"); @@ -23,22 +23,24 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD permission1.StateCheckers.Add(new TestRequireEditionPermissionSimpleStateChecker());; permission1[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName].ShouldBe(typeof(AuthorizationTestResourcePermissionDefinitionProvider).FullName); - context.AddResourcePermission("MyResourcePermission2", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); - context.AddResourcePermission("MyResourcePermission3", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); - context.AddResourcePermission("MyResourcePermission4", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); - context.AddResourcePermission("MyResourcePermission5", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); - context.AddResourcePermission("MyResourcePermission6", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider1)); - context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); + context.AddResourcePermission("MyResourcePermission2", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission3", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission4", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission5", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission6", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider1)); + context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); Assert.Throws(() => { - context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, managementPermission: "TestEntityManagementPermission"); + context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission"); }).Message.ShouldBe($"There is already an existing resource permission with name: MyResourcePermission7 for resource: {typeof(TestEntityResource).FullName}"); - context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource2).FullName!, managementPermission: "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); + context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource2).FullName!, "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); context.GetResourcePermissionOrNull(TestEntityResource.ResourceName, "MyResourcePermission1").ShouldNotBeNull(); context.GetResourcePermissionOrNull(TestEntityResource.ResourceName, "MyResourcePermission7").ShouldNotBeNull(); context.GetResourcePermissionOrNull(TestEntityResource2.ResourceName, "MyResourcePermission7").ShouldNotBeNull(); + context.GetResourcePermissionOrNull(TestEntityResource.ResourceName, "MyResourcePermission9").ShouldBeNull(); + context.GetResourcePermissionOrNull(TestEntityResource2.ResourceName, "MyResourcePermission6").ShouldBeNull(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 68cdd94773..38feceb1a4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -170,7 +170,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); if (!resourcePermissions.Any() || - !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermission!).ToArray())) + !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermissionName!).ToArray())) { return new GetResourceProviderListResultDto();; } @@ -190,7 +190,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); if (!resourcePermissions.Any() || - !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermission!).ToArray())) + !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermissionName!).ToArray())) { return new SearchProviderKeyListResultDto();; } @@ -217,7 +217,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { - if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) + if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { result.Permissions.Add(new ResourcePermissionDefinitionDto() { @@ -257,7 +257,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService continue; } - if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) + if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { resourcePermissionGrantInfoDto.Permissions.Add(new GrantedResourcePermissionDto() { @@ -290,7 +290,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService continue; } - if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) + if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { result.Permissions.Add(new ResourcePermissionWithProdiverGrantInfoDto { @@ -309,7 +309,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { - if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) + if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { continue; } @@ -323,7 +323,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { - if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermission!)) + if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { await ResourcePermissionManager.DeleteAsync(resourcePermission.Name, resourceName, resourceKey, providerName, providerKey); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs index def40865ff..0aadef7940 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/PermissionDefinitionRecordConsts.cs @@ -15,5 +15,5 @@ public class PermissionDefinitionRecordConsts public static int MaxResourceNameLength { get; set; } = 256; - public static int MaxManagementPermissionLength { get; set; } = 128; + public static int MaxManagementPermissionNameLength { get; set; } = 128; } 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 5e9e22d55d..15a98c84dd 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 @@ -53,7 +53,7 @@ public class DynamicPermissionDefinitionStoreInMemoryCache : { context.AddResourcePermission(resourcePermission.Name, resourcePermission.ResourceName, - resourcePermission.ManagementPermission, + resourcePermission.ManagementPermissionName, resourcePermission.DisplayName != null ? LocalizableStringSerializer.Deserialize(resourcePermission.DisplayName) : null, resourcePermission.MultiTenancySide, resourcePermission.IsEnabled); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs index d32e6564d9..61a6ba1f14 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionRecord.cs @@ -14,7 +14,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro public string ResourceName { get; set; } - public string ManagementPermission { get; set; } + public string ManagementPermissionName { get; set; } public string ParentName { get; set; } @@ -47,7 +47,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro string groupName, string name, string resourceName, - string managementPermission, + string managementPermissionName, string parentName, string displayName, bool isEnabled = true, @@ -63,7 +63,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro } Name = Check.NotNullOrWhiteSpace(name, nameof(name), PermissionDefinitionRecordConsts.MaxNameLength); ResourceName = resourceName; - ManagementPermission = managementPermission; + ManagementPermissionName = managementPermissionName; ParentName = Check.Length(parentName, nameof(parentName), PermissionDefinitionRecordConsts.MaxNameLength); DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), PermissionDefinitionRecordConsts.MaxDisplayNameLength); IsEnabled = isEnabled; @@ -87,7 +87,7 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro return false; } - if (ManagementPermission != otherRecord.ManagementPermission) + if (ManagementPermissionName != otherRecord.ManagementPermissionName) { return false; } @@ -147,9 +147,9 @@ public class PermissionDefinitionRecord : BasicAggregateRoot, IHasExtraPro ResourceName = otherRecord.ResourceName; } - if (ManagementPermission != otherRecord.ManagementPermission) + if (ManagementPermissionName != otherRecord.ManagementPermissionName) { - ManagementPermission = otherRecord.ManagementPermission; + ManagementPermissionName = otherRecord.ManagementPermissionName; } if (GroupName != otherRecord.GroupName) 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 723c93c847..707a3e89ea 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 @@ -87,7 +87,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I permissionGroup?.Name, permission.Name, permission.ResourceName, - permission.ManagementPermission, + permission.ManagementPermissionName, permission.Parent?.Name, LocalizableStringSerializer.Serialize(permission.DisplayName), permission.IsEnabled, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs index ae43f779a2..c7bf3f2a5a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.EntityFrameworkCore/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementDbContextModelBuilderExtensions.cs @@ -72,7 +72,7 @@ public static class AbpPermissionManagementDbContextModelBuilderExtensions b.Property(x => x.GroupName).HasMaxLength(PermissionGroupDefinitionRecordConsts.MaxNameLength); b.Property(x => x.Name).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength).IsRequired(); b.Property(x => x.ResourceName).HasMaxLength(PermissionDefinitionRecordConsts.MaxResourceNameLength); - b.Property(x => x.ManagementPermission).HasMaxLength(PermissionDefinitionRecordConsts.MaxManagementPermissionLength); + b.Property(x => x.ManagementPermissionName).HasMaxLength(PermissionDefinitionRecordConsts.MaxManagementPermissionNameLength); b.Property(x => x.ParentName).HasMaxLength(PermissionDefinitionRecordConsts.MaxNameLength); b.Property(x => x.DisplayName).HasMaxLength(PermissionDefinitionRecordConsts.MaxDisplayNameLength).IsRequired(); b.Property(x => x.Providers).HasMaxLength(PermissionDefinitionRecordConsts.MaxProvidersLength); 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 1e15360a6a..4dca621a63 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 @@ -111,7 +111,7 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase permissionRecord.Name.ShouldBe("ResourcePermission1"); permissionRecord.GroupName.ShouldBe(null); permissionRecord.ResourceName.ShouldBe(TestEntityResource.ResourceName); - permissionRecord.ManagementPermission.ShouldBe("Permission1"); + permissionRecord.ManagementPermissionName.ShouldBe("Permission1"); permissionRecord.DisplayName.ShouldBe("L:AbpPermissionManagement,ResourcePermission1"); permissionRecord.GetProperty("CustomProperty2").ShouldBe("CustomValue2"); permissionRecord.Providers.ShouldBe("ProviderA,ProviderB"); From ad33653f739ef7f5c313ea1078631b47a76c8bcf Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 6 Dec 2025 13:35:59 +0800 Subject: [PATCH 076/130] Move KeyedObjectResourcePermission to Abp.Authorization --- .../KeyedObjectResourcePermissionExtenstions.cs} | 0 .../Volo/Abp/Authorization/AbpAuthorizationModule.cs | 1 + .../KeyedObjectResourcePermissionCheckerExtensions.cs | 0 .../KeyedObjectResourcePermissionRequirementHandler.cs | 3 +-- .../KeyedObjectResourcePermissionStoreExtensions.cs | 5 ++--- .../src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj | 1 - .../Volo/Abp/Domain/AbpDddDomainModule.cs | 9 +-------- 7 files changed, 5 insertions(+), 14 deletions(-) rename framework/src/{Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs => Volo.Abp.Authorization/Microsoft/Extensions/DependencyInjection/KeyedObjectResourcePermissionExtenstions.cs} (100%) rename framework/src/{Volo.Abp.Ddd.Domain => Volo.Abp.Authorization}/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionCheckerExtensions.cs (100%) rename framework/src/{Volo.Abp.Ddd.Domain => Volo.Abp.Authorization}/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs (89%) rename framework/src/{Volo.Abp.Ddd.Domain => Volo.Abp.Authorization}/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs (95%) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs b/framework/src/Volo.Abp.Authorization/Microsoft/Extensions/DependencyInjection/KeyedObjectResourcePermissionExtenstions.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/EntityResourcePermissionAuthorizationExtensions.cs rename to framework/src/Volo.Abp.Authorization/Microsoft/Extensions/DependencyInjection/KeyedObjectResourcePermissionExtenstions.cs diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs index ec2a2ddd82..65b7e1b390 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs @@ -33,6 +33,7 @@ public class AbpAuthorizationModule : AbpModule { context.Services.AddAuthorizationCore(); + context.Services.AddKeyedObjectResourcePermissionAuthorization(); context.Services.AddSingleton(); context.Services.AddSingleton(); diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionCheckerExtensions.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionCheckerExtensions.cs rename to framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionCheckerExtensions.cs diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs similarity index 89% rename from framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs rename to framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs index df09e0457b..e43b6d07b5 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionRequirementHandler.cs @@ -7,8 +7,7 @@ public class KeyedObjectResourcePermissionRequirementHandler : AuthorizationHand { protected readonly IResourcePermissionChecker PermissionChecker; - public KeyedObjectResourcePermissionRequirementHandler( - IResourcePermissionChecker permissionChecker) + public KeyedObjectResourcePermissionRequirementHandler(IResourcePermissionChecker permissionChecker) { PermissionChecker = permissionChecker; } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs similarity index 95% rename from framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs rename to framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs index 98da7878e3..3088950510 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/KeyedObjectResourcePermissionStoreExtensions.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Volo.Abp.Domain.Entities; namespace Volo.Abp.Authorization.Permissions.Resources; @@ -18,7 +17,7 @@ public static class KeyedObjectResourcePermissionStoreExtensions this IResourcePermissionStore resourcePermissionStore, TResource resource ) - where TResource : class, IEntity + where TResource : class, IKeyedObject { Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNull(resource, nameof(resource)); @@ -37,7 +36,7 @@ public static class KeyedObjectResourcePermissionStoreExtensions this IResourcePermissionStore resourcePermissionStore, TEntity entity ) - where TEntity : class, IEntity + where TEntity : class, IKeyedObject { Check.NotNull(resourcePermissionStore, nameof(resourcePermissionStore)); Check.NotNull(entity, nameof(entity)); diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj b/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj index 8666c1faa2..6506d8fe7b 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj @@ -17,7 +17,6 @@ - diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs index 85e0523762..de6632704d 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Auditing; -using Volo.Abp.Authorization; using Volo.Abp.Caching; using Volo.Abp.Data; using Volo.Abp.Domain.ChangeTracking; @@ -25,8 +24,7 @@ namespace Volo.Abp.Domain; typeof(AbpExceptionHandlingModule), typeof(AbpSpecificationsModule), typeof(AbpCachingModule), - typeof(AbpDddDomainSharedModule), - typeof(AbpAuthorizationAbstractionsModule) + typeof(AbpDddDomainSharedModule) )] public class AbpDddDomainModule : AbpModule { @@ -35,9 +33,4 @@ public class AbpDddDomainModule : AbpModule context.Services.AddConventionalRegistrar(new AbpRepositoryConventionalRegistrar()); context.Services.OnRegistered(ChangeTrackingInterceptorRegistrar.RegisterIfNeeded); } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddKeyedObjectResourcePermissionAuthorization(); - } } From 64c09acc3f0f71b48ca29b8d4af610ed9f50a648 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 6 Dec 2025 14:34:14 +0800 Subject: [PATCH 077/130] Refactor permission app service for clarity --- .../Abp/PermissionManagement/PermissionAppService.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 38feceb1a4..6091211c3c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -189,10 +189,10 @@ public class PermissionAppService : ApplicationService, IPermissionAppService public virtual async Task SearchResourceProviderKeyAsync(string resourceName, string serviceName, string filter, int page) { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); - if (!resourcePermissions.Any() || + if (resourcePermissions.IsNullOrEmpty() || !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermissionName!).ToArray())) { - return new SearchProviderKeyListResultDto();; + return new SearchProviderKeyListResultDto(); } var lookupService = await ResourcePermissionManager.GetProviderKeyLookupServiceAsync(serviceName); @@ -219,7 +219,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService { if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { - result.Permissions.Add(new ResourcePermissionDefinitionDto() + result.Permissions.Add(new ResourcePermissionDefinitionDto { Name = resourcePermission.Name, DisplayName = resourcePermission.DisplayName?.Localize(StringLocalizerFactory), @@ -238,10 +238,10 @@ public class PermissionAppService : ApplicationService, IPermissionAppService }; var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); - var resourcePermissionGrantsGroup = await ResourcePermissionManager.GetAllGroupAsync(resourceName, resourceKey); - foreach (var resourcePermissionGrant in resourcePermissionGrantsGroup) + var resourcePermissionGrants = await ResourcePermissionManager.GetAllGroupAsync(resourceName, resourceKey); + foreach (var resourcePermissionGrant in resourcePermissionGrants) { - var resourcePermissionGrantInfoDto = new ResourcePermissionGrantInfoDto() + var resourcePermissionGrantInfoDto = new ResourcePermissionGrantInfoDto { ProviderName = resourcePermissionGrant.ProviderName, ProviderKey = resourcePermissionGrant.ProviderKey, From ea32e2c96da52cdd416cb7e5c059b5f9c17a441b Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 6 Dec 2025 19:25:33 +0800 Subject: [PATCH 078/130] Enforce management permission checks for resource permissions --- .../Resources/ResourcePermissionChecker.cs | 15 ++++- .../Resources/ResourcePermissionPopulator.cs | 27 +++++---- .../ResourcePermissionChecker_Tests.cs | 6 +- .../ResourcePermissionPopulator_Test.cs | 58 +++++++++++++++++++ ...estResourcePermissionDefinitionProvider.cs | 2 + .../Resources/FakeResourcePermissionStore.cs | 2 +- .../Resources/TestEntityResource.cs | 18 +++++- .../PermissionAppService.cs | 5 +- 8 files changed, 115 insertions(+), 18 deletions(-) create mode 100644 framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs index 7729093f69..5aa6ec80b0 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs @@ -17,19 +17,22 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD protected ICurrentTenant CurrentTenant { get; } protected IResourcePermissionValueProviderManager PermissionValueProviderManager { get; } protected ISimpleStateCheckerManager StateCheckerManager { get; } + protected IPermissionChecker PermissionChecker { get; } public ResourcePermissionChecker( ICurrentPrincipalAccessor principalAccessor, IPermissionDefinitionManager permissionDefinitionManager, ICurrentTenant currentTenant, IResourcePermissionValueProviderManager permissionValueProviderManager, - ISimpleStateCheckerManager stateCheckerManager) + ISimpleStateCheckerManager stateCheckerManager, + IPermissionChecker permissionChecker) { PrincipalAccessor = principalAccessor; PermissionDefinitionManager = permissionDefinitionManager; CurrentTenant = currentTenant; PermissionValueProviderManager = permissionValueProviderManager; StateCheckerManager = stateCheckerManager; + PermissionChecker = permissionChecker; } public virtual async Task IsGrantedAsync(string name, string resourceName, string resourceKey) @@ -69,6 +72,11 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD return false; } + if (!await PermissionChecker.IsGrantedAsync(claimsPrincipal, permission.ManagementPermissionName!)) + { + return false; + } + var isGranted = false; var context = new ResourcePermissionValueCheckContext(permission, claimsPrincipal, resourceName, resourceKey); foreach (var provider in PermissionValueProviderManager.ValueProviders) @@ -96,7 +104,7 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD public async Task IsGrantedAsync(string[] names, string resourceName, string resourceKey) { - return await IsGrantedAsync(PrincipalAccessor.Principal, names, resourceName,resourceKey); + return await IsGrantedAsync(PrincipalAccessor.Principal, names, resourceName, resourceKey); } public async Task IsGrantedAsync(ClaimsPrincipal? claimsPrincipal, string[] names, string resourceName, string resourceKey) @@ -116,7 +124,8 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD foreach (var name in names) { var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName, name); - if (permission == null) + if (permission == null || + !await PermissionChecker.IsGrantedAsync(claimsPrincipal, permission.ManagementPermissionName!)) { result.Result.Add(name, PermissionGrantResult.Prohibited); continue; diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs index e3684263a0..24a20e3cca 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs @@ -9,19 +9,20 @@ namespace Volo.Abp.Authorization.Permissions.Resources; public class ResourcePermissionPopulator : ITransientDependency { protected IPermissionDefinitionManager PermissionDefinitionManager { get; } - protected IResourcePermissionChecker ResourcePermissionChecker { get; } - protected IResourcePermissionStore ResourcePermissionStore { get; } + protected IPermissionChecker PermissionChecker { get; } public ResourcePermissionPopulator( IPermissionDefinitionManager permissionDefinitionManager, IResourcePermissionChecker resourcePermissionChecker, - IResourcePermissionStore resourcePermissionStore) + IResourcePermissionStore resourcePermissionStore, + IPermissionChecker permissionChecker) { PermissionDefinitionManager = permissionDefinitionManager; ResourcePermissionChecker = resourcePermissionChecker; ResourcePermissionStore = resourcePermissionStore; + PermissionChecker = permissionChecker; } public virtual async Task PopulateAsync(TResource resource, string resourceName) @@ -36,9 +37,8 @@ public class ResourcePermissionPopulator : ITransientDependency Check.NotNull(resources, nameof(resources)); Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName)); - var resopurcePermissionNames = (await PermissionDefinitionManager.GetResourcePermissionsAsync()) + var resopurcePermissions = (await PermissionDefinitionManager.GetResourcePermissionsAsync()) .Where(x => x.ResourceName == resourceName) - .Select(x => x.Name) .ToArray(); foreach (var resource in resources) @@ -48,17 +48,22 @@ public class ResourcePermissionPopulator : ITransientDependency { throw new AbpException("Resource key can not be null or empty."); } - - var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissionNames, resourceName, resourceKey); - foreach (var resopurcePermission in resopurcePermissionNames) + + var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissions.Select(x => x.Name).ToArray(), resourceName, resourceKey); + foreach (var resopurcePermission in resopurcePermissions) { + if (!await PermissionChecker.IsGrantedAsync(resopurcePermission.ManagementPermissionName!)) + { + continue; + } + if (resource.ResourcePermissions == null) { ObjectHelper.TrySetProperty(resource, x => x.ResourcePermissions, () => new Dictionary()); } - - var hasPermission = results.Result.TryGetValue(resopurcePermission, out var granted) && granted == PermissionGrantResult.Granted; - resource.ResourcePermissions![resopurcePermission] = hasPermission; + + var hasPermission = results.Result.TryGetValue(resopurcePermission.Name, out var granted) && granted == PermissionGrantResult.Granted; + resource.ResourcePermissions![resopurcePermission.Name] = hasPermission; } } } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionChecker_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionChecker_Tests.cs index 37a624cd04..739968b603 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionChecker_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionChecker_Tests.cs @@ -21,6 +21,7 @@ public class ResourcePermissionChecker_Tests: AuthorizationTestBase { (await _resourcePermissionChecker.IsGrantedAsync("MyResourcePermission5", TestEntityResource.ResourceName, TestEntityResource.ResourceKey5)).ShouldBe(true); (await _resourcePermissionChecker.IsGrantedAsync("UndefinedResourcePermission", TestEntityResource.ResourceName, TestEntityResource.ResourceKey5)).ShouldBe(false); + (await _resourcePermissionChecker.IsGrantedAsync("MyResourcePermission8", TestEntityResource.ResourceName, TestEntityResource.ResourceKey5)).ShouldBe(false); } [Fact] @@ -33,7 +34,8 @@ public class ResourcePermissionChecker_Tests: AuthorizationTestBase "UndefinedPermission", "MyResourcePermission3", "MyResourcePermission4", - "MyResourcePermission5" + "MyResourcePermission5", + "MyResourcePermission8" }, TestEntityResource.ResourceName, TestEntityResource.ResourceKey5); result.Result["MyResourcePermission1"].ShouldBe(PermissionGrantResult.Undefined); @@ -42,11 +44,13 @@ public class ResourcePermissionChecker_Tests: AuthorizationTestBase result.Result["MyResourcePermission3"].ShouldBe(PermissionGrantResult.Granted); result.Result["MyResourcePermission4"].ShouldBe(PermissionGrantResult.Prohibited); result.Result["MyResourcePermission5"].ShouldBe(PermissionGrantResult.Granted); + result.Result["MyResourcePermission8"].ShouldBe(PermissionGrantResult.Prohibited); result = await _resourcePermissionChecker.IsGrantedAsync(new [] { "MyResourcePermission6", }, TestEntityResource.ResourceName, TestEntityResource.ResourceKey6); + result.Result["MyResourcePermission6"].ShouldBe(PermissionGrantResult.Granted); result = await _resourcePermissionChecker.IsGrantedAsync(new [] diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs new file mode 100644 index 0000000000..a4116283f8 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions.Resources; +using Volo.Abp.Authorization.TestServices.Resources; +using Xunit; + +namespace Volo.Abp.Authorization; + +public class ResourcePermissionPopulator_Tests : AuthorizationTestBase +{ + private readonly ResourcePermissionPopulator _resourcePermissionPopulator; + + public ResourcePermissionPopulator_Tests() + { + _resourcePermissionPopulator = GetRequiredService(); + } + + [Fact] + public async Task PopulateAsync() + { + var testResourceObject = new TestEntityResource(TestEntityResource.ResourceKey5); + testResourceObject.ResourcePermissions.IsNullOrEmpty().ShouldBeTrue(); + + await _resourcePermissionPopulator.PopulateAsync( + testResourceObject, + TestEntityResource.ResourceName + ); + + testResourceObject.ResourcePermissions.ShouldNotBeNull(); + testResourceObject.ResourcePermissions.Count.ShouldBe(7); // Does not include MyResourcePermission8 because current user has no TestEntityManagementPermission2 + testResourceObject.ResourcePermissions["MyResourcePermission1"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission2"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission3"].ShouldBe(true); + testResourceObject.ResourcePermissions["MyResourcePermission4"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission5"].ShouldBe(true); + testResourceObject.ResourcePermissions["MyResourcePermission6"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission7"].ShouldBe(false); + + testResourceObject = new TestEntityResource(TestEntityResource.ResourceKey6); + testResourceObject.ResourcePermissions.IsNullOrEmpty().ShouldBeTrue(); + + await _resourcePermissionPopulator.PopulateAsync( + testResourceObject, + TestEntityResource.ResourceName + ); + + testResourceObject.ResourcePermissions.ShouldNotBeNull(); + testResourceObject.ResourcePermissions.Count.ShouldBe(7); // Does not include MyResourcePermission8 because current user has no TestEntityManagementPermission2 + testResourceObject.ResourcePermissions["MyResourcePermission1"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission2"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission3"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission4"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission5"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission6"].ShouldBe(true); + testResourceObject.ResourcePermissions["MyResourcePermission7"].ShouldBe(false); + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs index c5716532e1..29a597c9b3 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/AuthorizationTestResourcePermissionDefinitionProvider.cs @@ -14,6 +14,7 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD getGroup = context.AddGroup("TestGroup"); } getGroup.AddPermission("TestEntityManagementPermission"); + getGroup.AddPermission("TestEntityManagementPermission2"); var permission1 = context.AddResourcePermission("MyResourcePermission1", resourceName: TestEntityResource.ResourceName, "TestEntityManagementPermission"); Assert.Throws(() => @@ -29,6 +30,7 @@ public class AuthorizationTestResourcePermissionDefinitionProvider : PermissionD context.AddResourcePermission("MyResourcePermission5", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission"); context.AddResourcePermission("MyResourcePermission6", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider1)); context.AddResourcePermission("MyResourcePermission7", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission").WithProviders(nameof(TestResourcePermissionValueProvider2)); + context.AddResourcePermission("MyResourcePermission8", resourceName: typeof(TestEntityResource).FullName!, "TestEntityManagementPermission2"); Assert.Throws(() => { diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs index 54ef8cd9a0..40cd542ac7 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/FakeResourcePermissionStore.cs @@ -19,7 +19,7 @@ public class FakeResourcePermissionStore : IResourcePermissionStore, ITransientD var result = new MultiplePermissionGrantResult(); foreach (var name in names) { - result.Result.Add(name, (name == "MyResourcePermission3" || name == "MyResourcePermission5" && + result.Result.Add(name, ((name == "MyResourcePermission3" || name == "MyResourcePermission5") && resourceName == TestEntityResource.ResourceName && (resourceKey == TestEntityResource.ResourceKey3 || resourceKey == TestEntityResource.ResourceKey5) ? PermissionGrantResult.Granted diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs index 7325ed26d7..f703007633 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/Resources/TestEntityResource.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; +using Volo.Abp.Authorization.Permissions.Resources; namespace Volo.Abp.Authorization.TestServices.Resources; -public class TestEntityResource +public class TestEntityResource : IHasResourcePermissions { public static readonly string ResourceName = typeof(TestEntityResource).FullName; @@ -13,6 +15,20 @@ public class TestEntityResource public static readonly string ResourceKey5 = Guid.NewGuid().ToString(); public static readonly string ResourceKey6 = Guid.NewGuid().ToString(); public static readonly string ResourceKey7 = Guid.NewGuid().ToString(); + + private string Id { get; } + + public TestEntityResource(string id) + { + Id = id; + } + + public string GetObjectKey() + { + return Id; + } + + public Dictionary ResourcePermissions { get; set; } } public class TestEntityResource2 diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 6091211c3c..b41c997723 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -267,7 +267,10 @@ public class PermissionAppService : ApplicationService, IPermissionAppService } } - result.Permissions.Add(resourcePermissionGrantInfoDto); + if(resourcePermissionGrantInfoDto.Permissions.Any()) + { + result.Permissions.Add(resourcePermissionGrantInfoDto); + } } return result; From d929e923890033f60cd0314767ba5101d76393ad Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 6 Dec 2025 20:22:20 +0800 Subject: [PATCH 079/130] Grant TestEntityManagementPermission in FakePermissionStore --- .../Abp/Authorization/TestServices/FakePermissionStore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/FakePermissionStore.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/FakePermissionStore.cs index 4582417d3b..608cf94428 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/FakePermissionStore.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/FakePermissionStore.cs @@ -8,7 +8,7 @@ public class FakePermissionStore : IPermissionStore, ITransientDependency { public Task IsGrantedAsync(string name, string providerName, string providerKey) { - return Task.FromResult(name == "MyPermission3" || name == "MyPermission5"); + return Task.FromResult(name == "MyPermission3" || name == "MyPermission5" || name == "TestEntityManagementPermission"); } public Task IsGrantedAsync(string[] names, string providerName, string providerKey) @@ -16,7 +16,7 @@ public class FakePermissionStore : IPermissionStore, ITransientDependency var result = new MultiplePermissionGrantResult(); foreach (var name in names) { - result.Result.Add(name, name == "MyPermission3" || name == "MyPermission5" + result.Result.Add(name, name == "MyPermission3" || name == "MyPermission5" || name == "TestEntityManagementPermission" ? PermissionGrantResult.Granted : PermissionGrantResult.Prohibited); } From be862df3c02110d5bb186698993caaa3dd5888f3 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 6 Dec 2025 20:50:01 +0800 Subject: [PATCH 080/130] Add test data for TestEntityManagementPermission --- .../PermissionManagement/PermissionTestDataBuilder.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs index eb345c0e0a..d0448ea5bd 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/PermissionTestDataBuilder.cs @@ -36,6 +36,15 @@ public class PermissionTestDataBuilder : ITransientDependency ) ); + await _permissionGrantRepository.InsertAsync( + new PermissionGrant( + _guidGenerator.Create(), + "TestEntityManagementPermission", + UserPermissionValueProvider.ProviderName, + User1Id.ToString() + ) + ); + await _permissionGrantRepository.InsertAsync( new PermissionGrant( _guidGenerator.Create(), From 12b50b41c5b5d2dbcc6aaa003c63e38b152cfb3b Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 8 Dec 2025 09:02:59 +0800 Subject: [PATCH 081/130] Remove management permission check from resource permissions --- .../Permissions/Resources/ResourcePermissionChecker.cs | 8 +------- .../Permissions/Resources/ResourcePermissionPopulator.cs | 5 ----- .../Abp/Authorization/ResourcePermissionPopulator_Test.cs | 6 ++++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs index 5aa6ec80b0..a920c2e464 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionChecker.cs @@ -72,11 +72,6 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD return false; } - if (!await PermissionChecker.IsGrantedAsync(claimsPrincipal, permission.ManagementPermissionName!)) - { - return false; - } - var isGranted = false; var context = new ResourcePermissionValueCheckContext(permission, claimsPrincipal, resourceName, resourceKey); foreach (var provider in PermissionValueProviderManager.ValueProviders) @@ -124,8 +119,7 @@ public class ResourcePermissionChecker : IResourcePermissionChecker, ITransientD foreach (var name in names) { var permission = await PermissionDefinitionManager.GetResourcePermissionOrNullAsync(resourceName, name); - if (permission == null || - !await PermissionChecker.IsGrantedAsync(claimsPrincipal, permission.ManagementPermissionName!)) + if (permission == null) { result.Result.Add(name, PermissionGrantResult.Prohibited); continue; diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs index 24a20e3cca..91dfb509d4 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/Resources/ResourcePermissionPopulator.cs @@ -52,11 +52,6 @@ public class ResourcePermissionPopulator : ITransientDependency var results = await ResourcePermissionChecker.IsGrantedAsync(resopurcePermissions.Select(x => x.Name).ToArray(), resourceName, resourceKey); foreach (var resopurcePermission in resopurcePermissions) { - if (!await PermissionChecker.IsGrantedAsync(resopurcePermission.ManagementPermissionName!)) - { - continue; - } - if (resource.ResourcePermissions == null) { ObjectHelper.TrySetProperty(resource, x => x.ResourcePermissions, () => new Dictionary()); diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs index a4116283f8..03dca061ac 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs @@ -28,7 +28,7 @@ public class ResourcePermissionPopulator_Tests : AuthorizationTestBase ); testResourceObject.ResourcePermissions.ShouldNotBeNull(); - testResourceObject.ResourcePermissions.Count.ShouldBe(7); // Does not include MyResourcePermission8 because current user has no TestEntityManagementPermission2 + testResourceObject.ResourcePermissions.Count.ShouldBe(8); testResourceObject.ResourcePermissions["MyResourcePermission1"].ShouldBe(false); testResourceObject.ResourcePermissions["MyResourcePermission2"].ShouldBe(false); testResourceObject.ResourcePermissions["MyResourcePermission3"].ShouldBe(true); @@ -36,6 +36,7 @@ public class ResourcePermissionPopulator_Tests : AuthorizationTestBase testResourceObject.ResourcePermissions["MyResourcePermission5"].ShouldBe(true); testResourceObject.ResourcePermissions["MyResourcePermission6"].ShouldBe(false); testResourceObject.ResourcePermissions["MyResourcePermission7"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission8"].ShouldBe(false); testResourceObject = new TestEntityResource(TestEntityResource.ResourceKey6); testResourceObject.ResourcePermissions.IsNullOrEmpty().ShouldBeTrue(); @@ -46,7 +47,7 @@ public class ResourcePermissionPopulator_Tests : AuthorizationTestBase ); testResourceObject.ResourcePermissions.ShouldNotBeNull(); - testResourceObject.ResourcePermissions.Count.ShouldBe(7); // Does not include MyResourcePermission8 because current user has no TestEntityManagementPermission2 + testResourceObject.ResourcePermissions.Count.ShouldBe(7); testResourceObject.ResourcePermissions["MyResourcePermission1"].ShouldBe(false); testResourceObject.ResourcePermissions["MyResourcePermission2"].ShouldBe(false); testResourceObject.ResourcePermissions["MyResourcePermission3"].ShouldBe(false); @@ -54,5 +55,6 @@ public class ResourcePermissionPopulator_Tests : AuthorizationTestBase testResourceObject.ResourcePermissions["MyResourcePermission5"].ShouldBe(false); testResourceObject.ResourcePermissions["MyResourcePermission6"].ShouldBe(true); testResourceObject.ResourcePermissions["MyResourcePermission7"].ShouldBe(false); + testResourceObject.ResourcePermissions["MyResourcePermission8"].ShouldBe(false); } } From 349a08c21e2338fc218d71ce265cfa6a5ea26bba Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 8 Dec 2025 09:04:43 +0800 Subject: [PATCH 082/130] Update expected resource permissions count in test --- .../Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs index 03dca061ac..2cc000c30a 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/ResourcePermissionPopulator_Test.cs @@ -47,7 +47,7 @@ public class ResourcePermissionPopulator_Tests : AuthorizationTestBase ); testResourceObject.ResourcePermissions.ShouldNotBeNull(); - testResourceObject.ResourcePermissions.Count.ShouldBe(7); + testResourceObject.ResourcePermissions.Count.ShouldBe(8); testResourceObject.ResourcePermissions["MyResourcePermission1"].ShouldBe(false); testResourceObject.ResourcePermissions["MyResourcePermission2"].ShouldBe(false); testResourceObject.ResourcePermissions["MyResourcePermission3"].ShouldBe(false); From cfea22c2e83ec160646cd8422b4211a6ca577455 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 9 Dec 2025 12:26:52 +0800 Subject: [PATCH 083/130] Add resource permission management BlazorUI. --- .../PermissionAppService.cs | 5 +- .../ResourcePermissionManagementModal.razor | 172 ++++++++++ ...ResourcePermissionManagementModal.razor.cs | 298 ++++++++++++++++++ 3 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index b41c997723..3002e681ea 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -172,7 +172,10 @@ public class PermissionAppService : ApplicationService, IPermissionAppService if (!resourcePermissions.Any() || !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermissionName!).ToArray())) { - return new GetResourceProviderListResultDto();; + return new GetResourceProviderListResultDto + { + Providers = new List() + }; } var lookupServices = await ResourcePermissionManager.GetProviderKeyLookupServicesAsync(); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor new file mode 100644 index 0000000000..75ebf86e14 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor @@ -0,0 +1,172 @@ +@using Blazorise.Components +@using Volo.Abp.BlazoriseUI.Components +@using Volo.Abp.PermissionManagement.Localization +@inherits Volo.Abp.AspNetCore.Components.AbpComponentBase +@inject AbpBlazorMessageLocalizerHelper LH + + + + @L["ResourcePermissions"] - @ResourceDisplayName + + + +
+ +
+ + + + + + + @L["Actions"] + + + + @L["Edit"] + + + @L["Delete"] + + + + + + + + @{ + @context.ProviderName + @context.ProviderDisplayName + } + + + + + @{ + foreach (var permission in context.Permissions) + { + @permission.DisplayName + } + } + + + + + @L["NoDataAvailableInDatatable"] + + +
+ + + +
+
+ + + + + + @L["AddResourcePermissions"] + + + + +
+ +

@ResourceDisplayName

+
+
+ @L["ProviderName"]: + + @foreach(var keyLookupService in ResourceProviderKeyLookupServices) + { + @keyLookupService.DisplayName + } + +
+
+ @L["ProviderKey"]: * + + + + + + + + + +
+
+
+ + @L["GrantAllResourcePermissions"] +
+ @foreach (var permission in CreateEntity.Permissions) + { + @permission.DisplayName + } +
+
+
+ + + + + +
+
+ + + +
+ + @L["UpdateResourcePermissions"] + + + + +
+ +

@ResourceDisplayName

+
+
+
+ + @L["GrantAllResourcePermissions"] +
+ @foreach (var permission in EditEntity.Permissions) + { + @permission.DisplayName + } +
+
+
+ + + + +
+
+
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs new file mode 100644 index 0000000000..9978e5b49d --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazorise; +using Blazorise.Components; +using Microsoft.AspNetCore.Components; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.PermissionManagement.Localization; + +namespace Volo.Abp.PermissionManagement.Blazor.Components; + +public partial class ResourcePermissionManagementModal +{ + [Inject] protected IPermissionAppService PermissionAppService { get; set; } + + [Inject] protected IUiMessageService UiMessageService { get; set; } + + protected Modal Modal { get; set; } + + protected string ResourceName { get; set; } + protected string ResourceKey { get; set; } + protected string ResourceDisplayName{ get; set; } + protected int PageSize { get; set; } = 10; + + protected Modal CreateModal { get; set; } + protected Validations CreateValidationsRef { get; set; } + protected CreateModel CreateEntity { get; set; } = new CreateModel + { + Permissions = [] + }; + protected Autocomplete ProviderKeyAutocompleteRef { get; set; } + protected Blazorise.Validation ProviderKeyValidationRef { get; set; } + public GetResourcePermissionDefinitionListResultDto ResourcePermissionDefinitions { get; set; } = new() + { + Permissions = [] + }; + protected string CurrentLookupService { get; set; } + protected string ProviderKey { get; set; } + protected string ProviderDisplayName { get; set; } + protected List ResourceProviderKeyLookupServices { get; set; } = new(); + protected List ProviderKeys { get; set; } = new(); + protected GetResourcePermissionListResultDto ResourcePermissionList = new() + { + Permissions = [] + }; + + protected Validations EditValidationsRef { get; set; } + protected Modal EditModal { get; set; } + protected EditModel EditEntity { get; set; } = new EditModel + { + Permissions = [] + }; + + public ResourcePermissionManagementModal() + { + LocalizationResource = typeof(AbpPermissionManagementResource); + } + + public virtual async Task OpenAsync(string resourceName, string resourceKey, string resourceDisplayName = null) + { + try + { + ResourceName = resourceName; + ResourceKey = resourceKey; + ResourceDisplayName = resourceDisplayName; + + ResourcePermissionDefinitions = await PermissionAppService.GetResourceDefinitionsAsync(ResourceName); + ResourceProviderKeyLookupServices = (await PermissionAppService.GetResourceProviderKeyLookupServicesAsync(ResourceName)).Providers; + + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName, ResourceKey); + + await Modal.Show(); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected Task CloseModal() + { + return InvokeAsync(Modal.Hide); + } + + protected virtual Task ClosingModal(ModalClosingEventArgs eventArgs) + { + eventArgs.Cancel = eventArgs.CloseReason == CloseReason.FocusLostClosing; + return Task.CompletedTask; + } + + protected virtual async Task OpenCreateModalAsync() + { + CurrentLookupService = ResourceProviderKeyLookupServices.FirstOrDefault()?.Name; + + ProviderKey = null; + ProviderDisplayName = null; + ProviderKeys = new List(); + await ProviderKeyAutocompleteRef.Clear(); + await CreateValidationsRef.ClearAll(); + + CreateEntity = new CreateModel + { + Permissions = ResourcePermissionDefinitions.Permissions.Select(x => new ResourcePermissionModel + { + Name = x.Name, + DisplayName = x.DisplayName, + IsGranted = false + }).ToList() + }; + + await CreateModal.Show(); + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task SelectedProviderKeyAsync(string value) + { + ProviderKey = value; + ProviderDisplayName = ProviderKeys.FirstOrDefault(p => p.ProviderKey == value)?.ProviderDisplayName; + await InvokeAsync(StateHasChanged); + } + + private async Task SearchProviderKeyAsync(AutocompleteReadDataEventArgs autocompleteReadDataEventArgs) + { + if ( !autocompleteReadDataEventArgs.CancellationToken.IsCancellationRequested ) + { + if (autocompleteReadDataEventArgs.SearchValue.IsNullOrWhiteSpace()) + { + ProviderKeys = new List(); + return; + } + + ProviderKeys = (await PermissionAppService.SearchResourceProviderKeyAsync(ResourceName, CurrentLookupService, autocompleteReadDataEventArgs.SearchValue, 1)).Keys; + + await InvokeAsync(StateHasChanged); + } + } + + protected virtual async Task OnPermissionCheckedChanged(ResourcePermissionModel permission, bool value) + { + permission.IsGranted = value; + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task GrantAllAsync(bool value) + { + foreach (var permission in CreateEntity.Permissions) + { + permission.IsGranted = value; + } + + foreach (var permission in EditEntity.Permissions) + { + permission.IsGranted = value; + } + + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task OpenEditModalAsync(ResourcePermissionGrantInfoDto permission) + { + var resourcePermissions = await PermissionAppService.GetResourceByProviderAsync(ResourceName, ResourceKey, permission.ProviderName, permission.ProviderKey); + EditEntity = new EditModel + { + ProviderName = permission.ProviderName, + ProviderKey = permission.ProviderKey, + Permissions = resourcePermissions.Permissions.Select(x => new ResourcePermissionModel + { + Name = x.Name, + DisplayName = x.DisplayName, + IsGranted = x.IsGranted + }).ToList() + }; + + await EditModal.Show(); + } + + protected virtual Task ClosingCreateModal(ModalClosingEventArgs eventArgs) + { + eventArgs.Cancel = eventArgs.CloseReason == CloseReason.FocusLostClosing; + return Task.CompletedTask; + } + + protected virtual Task ClosingEditModal(ModalClosingEventArgs eventArgs) + { + eventArgs.Cancel = eventArgs.CloseReason == CloseReason.FocusLostClosing; + return Task.CompletedTask; + } + + protected virtual async Task CloseCreateModalAsync() + { + await CreateModal.Hide(); + } + + protected virtual async Task CloseEditModalAsync() + { + await EditModal.Hide(); + } + + protected virtual async Task OnLookupServiceCheckedValueChanged(string value) + { + CurrentLookupService = value; + ProviderKey = null; + ProviderDisplayName = null; + await ProviderKeyAutocompleteRef.Clear(); + await CreateValidationsRef.ClearAll(); + await InvokeAsync(StateHasChanged); + } + + protected virtual void ValidateProviderKey(ValidatorEventArgs validatorEventArgs) + { + validatorEventArgs.Status = ProviderKey.IsNullOrWhiteSpace() + ? ValidationStatus.Error + : ValidationStatus.Success; + validatorEventArgs.ErrorText = L["ThisFieldIsRequired."]; + } + + protected virtual async Task CreateResourcePermissionAsync() + { + if (await CreateValidationsRef.ValidateAll()) + { + await PermissionAppService.UpdateResourceAsync( + ResourceName, + ResourceKey, + new UpdateResourcePermissionsDto + { + ProviderName = CurrentLookupService, + ProviderKey = ProviderKey, + Permissions = CreateEntity.Permissions.Where(p => p.IsGranted).Select(p => p.Name).ToList() + } + ); + + await CloseCreateModalAsync(); + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName, ResourceKey); + await InvokeAsync(StateHasChanged); + } + } + + protected virtual async Task UpdateResourcePermissionAsync() + { + if (await EditValidationsRef.ValidateAll()) + { + await PermissionAppService.UpdateResourceAsync( + ResourceName, + ResourceKey, + new UpdateResourcePermissionsDto + { + ProviderName = EditEntity.ProviderName, + ProviderKey = EditEntity.ProviderKey, + Permissions = EditEntity.Permissions.Where(p => p.IsGranted).Select(p => p.Name).ToList() + } + ); + + await CloseEditModalAsync(); + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName, ResourceKey); + await InvokeAsync(StateHasChanged); + } + } + + protected virtual async Task DeleteResourcePermissionAsync(ResourcePermissionGrantInfoDto permission) + { + if(await UiMessageService.Confirm(L["ResourcePermissionDeletionConfirmationMessage"])) + { + await PermissionAppService.DeleteResourceAsync( + ResourceName, + ResourceKey, + permission.ProviderName, + permission.ProviderKey + ); + + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName, ResourceKey); + await InvokeAsync(StateHasChanged); + } + } + + public class CreateModel + { + public List Permissions { get; set; } + } + + public class EditModel + { + public string ProviderName { get; set; } + + public string ProviderKey { get; set; } + + public List Permissions { get; set; } + } + + public class ResourcePermissionModel + { + public string Name { get; set; } + + public string DisplayName { get; set; } + + public bool IsGranted { get; set; } + } +} From c762d7508e2a13b50066aecac1c6e8471feba3de Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 9 Dec 2025 15:21:22 +0800 Subject: [PATCH 084/130] Make CloseModal method async and virtual --- .../Components/ResourcePermissionManagementModal.razor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs index 9978e5b49d..454c89acfb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs @@ -78,9 +78,9 @@ public partial class ResourcePermissionManagementModal } } - protected Task CloseModal() + protected virtual async Task CloseModal() { - return InvokeAsync(Modal.Hide); + await Modal.Hide(); } protected virtual Task ClosingModal(ModalClosingEventArgs eventArgs) From 50a4140f3d1ebdd098204896275a5e41250e7403 Mon Sep 17 00:00:00 2001 From: sumeyye Date: Wed, 10 Dec 2025 11:19:15 +0300 Subject: [PATCH 085/130] update: ssr condition for the app state refresh process --- .../core/src/lib/utils/initial-utils.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts index ece0404dcb..414f4d0a31 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts @@ -1,7 +1,7 @@ import { registerLocaleData } from '@angular/common'; import { inject, Injector } from '@angular/core'; import { tap, catchError } from 'rxjs/operators'; -import { lastValueFrom, throwError } from 'rxjs'; +import { firstValueFrom, lastValueFrom, of, throwError, timeout } from 'rxjs'; import { ABP } from '../models/common'; import { Environment } from '../models/environment'; import { CurrentTenantDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models'; @@ -10,10 +10,11 @@ import { EnvironmentService } from '../services/environment.service'; import { SessionStateService } from '../services/session-state.service'; import { CORE_OPTIONS } from '../tokens/options.token'; import { APP_INIT_ERROR_HANDLERS } from '../tokens/app-config.token'; +import { CHECK_AUTHENTICATION_STATE_FN_KEY } from '../tokens/check-authentication-state'; +import { APP_STARTED_WITH_SSR } from '../tokens/ssr-state.token'; import { getRemoteEnv } from './environment-utils'; import { parseTenantFromUrl } from './multi-tenancy-utils'; import { AuthService } from '../abstracts'; -import { CHECK_AUTHENTICATION_STATE_FN_KEY } from '../tokens/check-authentication-state'; import { noop } from './common-utils'; export async function getInitialData() { @@ -21,6 +22,7 @@ export async function getInitialData() { const environmentService = injector.get(EnvironmentService); const configState = injector.get(ConfigStateService); const options = injector.get(CORE_OPTIONS) as ABP.Root; + const appStartedWithSSR = injector.get(APP_STARTED_WITH_SSR); environmentService.setState(options.environment as Environment); await getRemoteEnv(injector, options.environment); @@ -49,8 +51,18 @@ export async function getInitialData() { return throwError(() => error); }), ); - // TODO: Not working with SSR - // await lastValueFrom(result$); + + if (appStartedWithSSR) { + await firstValueFrom( + result$.pipe( + timeout(0), + catchError(() => of(null)), + ), + ); + } else { + await lastValueFrom(result$); + } + await localeInitializer(injector); } From 0594c235234ef9f30d0e472b0eb49db9828cc0d9 Mon Sep 17 00:00:00 2001 From: sumeyye Date: Wed, 10 Dec 2025 11:20:48 +0300 Subject: [PATCH 086/130] update: app template configuration --- templates/app/angular/src/app/app.config.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/templates/app/angular/src/app/app.config.ts b/templates/app/angular/src/app/app.config.ts index 0afccfde31..1baf8e50ae 100644 --- a/templates/app/angular/src/app/app.config.ts +++ b/templates/app/angular/src/app/app.config.ts @@ -1,3 +1,9 @@ +import { + withValidationBluePrint, + provideAbpThemeShared, + provideLogo, + withEnvironmentOptions, +} from '@abp/ng.theme.shared'; import { ApplicationConfig } from '@angular/core'; import { provideRouter } from '@angular/router'; import { provideAnimations } from '@angular/platform-browser/animations'; @@ -6,9 +12,8 @@ import { appRoutes } from './app.routes'; import { APP_ROUTE_PROVIDER } from './route.provider'; import { provideAbpCore, withOptions } from '@abp/ng.core'; import { environment } from '../environments/environment'; -import { registerLocale } from '@abp/ng.core/locale'; +import { registerLocaleForEsBuild } from '@abp/ng.core/locale'; import { provideAbpOAuth } from '@abp/ng.oauth'; -import { provideAbpThemeShared, provideLogo, withEnvironmentOptions} from '@abp/ng.theme.shared'; import { provideSettingManagementConfig } from '@abp/ng.setting-management/config'; import { provideAccountConfig } from '@abp/ng.account/config'; import { provideIdentityConfig } from '@abp/ng.identity/config'; @@ -24,13 +29,12 @@ export const appConfig: ApplicationConfig = { provideAbpCore( withOptions({ environment, - registerLocaleFn: registerLocale(), + registerLocaleFn: registerLocaleForEsBuild(), }) ), provideThemeLeptonX(), provideSideMenuLayout(), provideAbpOAuth(), - provideAbpThemeShared(), provideSettingManagementConfig(), provideAccountConfig(), provideIdentityConfig(), @@ -38,5 +42,10 @@ export const appConfig: ApplicationConfig = { provideFeatureManagementConfig(), provideAnimations(), provideLogo(withEnvironmentOptions(environment)), + provideAbpThemeShared( + withValidationBluePrint({ + wrongPassword: 'Please choose 1q2w3E*', + }) + ), ], }; From d7362bfa2608fd458eaa1f1df66312c8bbd0183a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Mon, 8 Dec 2025 13:31:23 +0300 Subject: [PATCH 087/130] Add telemetry to CleanCommand execution Introduced ITelemetryService to CleanCommand and now log an activity when the clean command is executed. This enables tracking usage of the clean command for internal telemetry purposes. --- .../Volo/Abp/Cli/Commands/CleanCommand.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs index 092e996095..0f973b1ae9 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs @@ -8,6 +8,8 @@ using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; namespace Volo.Abp.Cli.Commands; @@ -17,6 +19,8 @@ public class CleanCommand : IConsoleCommand, ITransientDependency public ILogger Logger { get; set; } + public ITelemetryService TelemetryService { get; set; } + protected ICmdHelper CmdHelper { get; } public CleanCommand(ICmdHelper cmdHelper) @@ -25,8 +29,10 @@ public class CleanCommand : IConsoleCommand, ITransientDependency Logger = NullLogger.Instance; } - public Task ExecuteAsync(CommandLineArgs commandLineArgs) + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { + await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliCommandsClean); + var binEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "bin", SearchOption.AllDirectories); var objEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "obj", SearchOption.AllDirectories); @@ -49,7 +55,6 @@ public class CleanCommand : IConsoleCommand, ITransientDependency Logger.LogInformation($"'bin' and 'obj' folders removed successfully!"); Logger.LogInformation("Solution cleaned successfully!"); - return Task.CompletedTask; } public string GetUsageInfo() From 469fc986110ec1dac708ad3f393604794f1f16ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Tue, 9 Dec 2025 16:20:15 +0300 Subject: [PATCH 088/130] Integrate telemetry tracking into CLI commands Added telemetry activity tracking to AddModule, AddPackage, Clean, and ListModules commands using ITelemetryService. Introduced TelemetryCliSessionProvider to enrich telemetry session data and updated service registration to remove the default TelemetrySessionInfoEnricher. --- .../Volo/Abp/Cli/AbpCliCoreModule.cs | 4 +++ .../Volo/Abp/Cli/Commands/AddModuleCommand.cs | 13 +++++++- .../Abp/Cli/Commands/AddPackageCommand.cs | 22 ++++++++++--- .../Volo/Abp/Cli/Commands/CleanCommand.cs | 3 +- .../Abp/Cli/Commands/ListModulesCommand.cs | 5 +++ .../Telemetry/TelemetryCliSessionProvider.cs | 32 +++++++++++++++++++ 6 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 7b249f0517..5f4f6cfaf6 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Text; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Cli.Commands; @@ -10,6 +11,7 @@ using Volo.Abp.Cli.ServiceProxying.JavaScript; using Volo.Abp.Domain; using Volo.Abp.Http; using Volo.Abp.IdentityModel; +using Volo.Abp.Internal.Telemetry.Activity.Providers; using Volo.Abp.Json; using Volo.Abp.Localization; using Volo.Abp.Minify; @@ -36,6 +38,8 @@ public class AbpCliCoreModule : AbpModule { client.DefaultRequestHeaders.UserAgent.ParseAdd("MyAgent/1.0"); }); + + context.Services.RemoveAll(x => x.ImplementationType == typeof(TelemetrySessionInfoEnricher)); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs index 4756c3f99b..d32498d970 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -11,6 +11,8 @@ using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; using Volo.Abp.Cli.ProjectModification; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; namespace Volo.Abp.Cli.Commands; @@ -21,6 +23,7 @@ public class AddModuleCommand : IConsoleCommand, ITransientDependency private AddModuleInfoOutput _lastAddedModuleInfo; public ILogger Logger { get; set; } + public ITelemetryService TelemetryService { get; set; } protected SolutionModuleAdder SolutionModuleAdder { get; } public SolutionPackageVersionFinder SolutionPackageVersionFinder { get; } @@ -66,13 +69,21 @@ public class AddModuleCommand : IConsoleCommand, ITransientDependency } var newTemplate = commandLineArgs.Options.ContainsKey(Options.NewTemplate.Long); + var solutionFile = GetSolutionFile(commandLineArgs); + + await using var _ = TelemetryService.TrackActivityAsync(newTemplate + ? ActivityNameConsts.AbpCliCommandsInstallLocalModule + : ActivityNameConsts.AbpCliCommandsInstallModule, o => + { + o[ActivityPropertyNames.SolutionPath] = solutionFile; + }); + var template = commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long); var newProTemplate = !string.IsNullOrEmpty(template) && template == ModuleProTemplate.TemplateName; var withSourceCode = newTemplate || newProTemplate || commandLineArgs.Options.ContainsKey(Options.SourceCode.Long); var addSourceCodeToSolutionFile = withSourceCode && commandLineArgs.Options.ContainsKey("add-to-solution-file"); var skipOpeningDocumentation = commandLineArgs.Options.ContainsKey(Options.SkipOpeningDocumentation.Long); var skipDbMigrations = newTemplate || newProTemplate || commandLineArgs.Options.ContainsKey(Options.DbMigrations.Skip); - var solutionFile = GetSolutionFile(commandLineArgs); var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); if (version == null) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs index e9acb7ae24..cbda50602a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -9,6 +9,8 @@ using Volo.Abp.Cli.Args; using Volo.Abp.Cli.ProjectModification; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; namespace Volo.Abp.Cli.Commands; @@ -17,6 +19,8 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency public const string Name = "add-package"; public ILogger Logger { get; set; } + + public ITelemetryService TelemetryService { get; set; } protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; } @@ -51,14 +55,22 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); var withSourceCode = commandLineArgs.Options.ContainsKey(Options.SourceCode.Long); - + if (isNugetPackage) { var addSourceCodeToSolutionFile = withSourceCode && commandLineArgs.Options.ContainsKey("add-to-solution-file"); - + + var slnFile = GetSolutionFile(commandLineArgs); + var projectFile = GetProjectFile(commandLineArgs); + + await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsAddPackage, o => + { + o[ActivityPropertyNames.SolutionPath] = slnFile; + }); + await ProjectNugetPackageAdder.AddAsync( - GetSolutionFile(commandLineArgs), - GetProjectFile(commandLineArgs), + slnFile, + projectFile, commandLineArgs.Target, version, true, @@ -68,6 +80,8 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency } else if (isNpmPackage) { + await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsAddPackage); + await ProjectNpmPackageAdder.AddNpmPackageAsync( GetAngularDirectory(commandLineArgs), commandLineArgs.Target, diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs index 0f973b1ae9..886e4faa2b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs @@ -31,8 +31,7 @@ public class CleanCommand : IConsoleCommand, ITransientDependency public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { - await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliCommandsClean); - + await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsClean); var binEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "bin", SearchOption.AllDirectories); var objEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "obj", SearchOption.AllDirectories); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs index af685102d8..4496e62eeb 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using Volo.Abp.Cli.Args; using Volo.Abp.Cli.ProjectBuilding; using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; namespace Volo.Abp.Cli.Commands; @@ -16,6 +18,7 @@ public class ListModulesCommand : IConsoleCommand, ITransientDependency public ModuleInfoProvider ModuleInfoProvider { get; } public ILogger Logger { get; set; } + public ITelemetryService TelemetryService { get; set; } public ListModulesCommand(ModuleInfoProvider moduleInfoProvider) @@ -26,6 +29,8 @@ public class ListModulesCommand : IConsoleCommand, ITransientDependency public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { + await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsListModules); + var modules = await ModuleInfoProvider.GetModuleListAsync(); var freeModules = modules.Where(m => !m.IsPro).ToList(); var proModules = modules.Where(m => m.IsPro).ToList(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs new file mode 100644 index 0000000000..1934f9fc3f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Activity.Providers; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; + +namespace Volo.Abp.Cli.Telemetry; + +[ExposeServices(typeof(ITelemetryActivityEventEnricher))] +public class TelemetryCliSessionProvider : TelemetryActivityEventEnricher +{ + public TelemetryCliSessionProvider(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + public override int ExecutionOrder { get; set; } = 10; + protected override Type ReplaceParentType { get; set; } = typeof(TelemetrySessionInfoEnricher); + + protected override Task ExecuteAsync(ActivityContext context) + { + context.Current[ActivityPropertyNames.SessionType] = SessionType.AbpCli; + context.Current[ActivityPropertyNames.SessionId] = Guid.NewGuid(); + context.Current[ActivityPropertyNames.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage); + + return Task.CompletedTask; + } + +} \ No newline at end of file From b0a1c0ab0e1e21e4b9baceddbc4149db1d86faa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 09:54:17 +0300 Subject: [PATCH 089/130] Integrate telemetry tracking into CLI commands Added ITelemetryService usage to CliService, NewCommand, and UpdateCommand to track command execution and error activities. This enables better monitoring and diagnostics for CLI operations by reporting activity names and relevant metadata to the telemetry system. --- .../Volo/Abp/Cli/CliService.cs | 7 +++++++ .../Volo/Abp/Cli/Commands/NewCommand.cs | 20 +++++++++++++++++++ .../Volo/Abp/Cli/Commands/UpdateCommand.cs | 4 ++++ 3 files changed, 31 insertions(+) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs index 0a0c46b6a4..d89b68989b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -14,6 +14,8 @@ using Volo.Abp.Cli.Memory; using Volo.Abp.Cli.Version; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; namespace Volo.Abp.Cli; @@ -21,6 +23,7 @@ public class CliService : ITransientDependency { private readonly MemoryService _memoryService; public ILogger Logger { get; set; } + public ITelemetryService TelemetryService { get; set; } protected ICommandLineArgumentParser CommandLineArgumentParser { get; } protected ICommandSelector CommandSelector { get; } protected IServiceScopeFactory ServiceScopeFactory { get; } @@ -64,6 +67,7 @@ public class CliService : ITransientDependency try { + await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliRun); if (commandLineArgs.IsCommand("prompt")) { await RunPromptAsync(); @@ -80,10 +84,13 @@ public class CliService : ITransientDependency catch (CliUsageException usageException) { Logger.LogWarning(usageException.Message); + await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); Environment.ExitCode = 1; } catch (Exception ex) { + await TelemetryService.AddErrorActivityAsync(ex.Message); + await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); Logger.LogException(ex); throw; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 796a8ff757..0c8367a7c5 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -11,11 +11,16 @@ using Volo.Abp.Cli.Commands.Services; using Volo.Abp.Cli.LIbs; using Volo.Abp.Cli.ProjectBuilding; using Volo.Abp.Cli.ProjectBuilding.Events; +using Volo.Abp.Cli.ProjectBuilding.Templates.Module; +using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; using Volo.Abp.Cli.ProjectModification; using Volo.Abp.Cli.Utils; using Volo.Abp.Cli.Version; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Local; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; namespace Volo.Abp.Cli.Commands; @@ -25,6 +30,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien protected TemplateProjectBuilder TemplateProjectBuilder { get; } public ITemplateInfoProvider TemplateInfoProvider { get; } + public ITelemetryService TelemetryService { get; set; } public NewCommand( ConnectionStringProvider connectionStringProvider, @@ -94,6 +100,20 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien var result = await TemplateProjectBuilder.BuildAsync( projectArgs ); + + var activityName = ActivityNameConsts.AbpCliCommandsNewSolution; + + if (ModuleTemplateBase.IsModuleTemplate(template)) + { + activityName = ActivityNameConsts.AbpCliCommandsNewModule; + } + + await TelemetryService.AddActivityAsync(activityName, o => + { + o[ActivityPropertyNames.CreationTool] = AbpTool.OldCli; + o[ActivityPropertyNames.Template] = template; + o[ActivityPropertyNames.SolutionPath] = projectArgs.OutputFolder; + }); ExtractProjectZip(result, projectArgs.OutputFolder); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs index ae3cda8723..f46dd79f5e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs @@ -9,6 +9,8 @@ using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Args; using Volo.Abp.Cli.ProjectModification; using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; namespace Volo.Abp.Cli.Commands; @@ -17,6 +19,7 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency public const string Name = "update"; public ILogger Logger { get; set; } + public ITelemetryService TelemetryService { get; set; } private readonly VoloNugetPackagesVersionUpdater _nugetPackagesVersionUpdater; private readonly NpmPackagesUpdater _npmPackagesUpdater; @@ -32,6 +35,7 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { + await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsUpdate); var updateNpm = commandLineArgs.Options.ContainsKey(Options.Packages.Npm); var updateNuget = commandLineArgs.Options.ContainsKey(Options.Packages.NuGet); From 39ad3e6d3380f80ad771304d8cd3eecd68e7eb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 13:18:12 +0300 Subject: [PATCH 090/130] Improve telemetry solution path handling and enrichment Refactored telemetry logic to better handle and enrich solution path information, including moving TelemetryCliSessionProvider, updating activity tracking in commands, and enhancing TelemetrySolutionInfoEnricher to reliably find and set solution paths and IDs. Removed redundant service registrations and improved the order and conditions for telemetry activities. --- .../Volo/Abp/Cli/AbpCliCoreModule.cs | 2 - .../Volo/Abp/Cli/CliService.cs | 3 +- .../Abp/Cli/Commands/AddPackageCommand.cs | 10 +--- .../Volo/Abp/Cli/Commands/NewCommand.cs | 1 - .../Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs | 9 ++- .../Telemetry/TelemetryCliSessionProvider.cs | 21 ++++++- .../TelemetrySolutionInfoEnricher.cs | 57 ++++++++++++++++++- .../Activity/TelemetryJsonExtensions.cs | 10 ++++ 8 files changed, 97 insertions(+), 16 deletions(-) rename framework/src/{Volo.Abp.Cli.Core => Volo.Abp.Cli}/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs (51%) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 5f4f6cfaf6..cea7a38b8b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -38,8 +38,6 @@ public class AbpCliCoreModule : AbpModule { client.DefaultRequestHeaders.UserAgent.ParseAdd("MyAgent/1.0"); }); - - context.Services.RemoveAll(x => x.ImplementationType == typeof(TelemetrySessionInfoEnricher)); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs index d89b68989b..7f5f3224e6 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -84,7 +84,6 @@ public class CliService : ITransientDependency catch (CliUsageException usageException) { Logger.LogWarning(usageException.Message); - await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); Environment.ExitCode = 1; } catch (Exception ex) @@ -94,6 +93,8 @@ public class CliService : ITransientDependency Logger.LogException(ex); throw; } + + await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); } private async Task RunPromptAsync() diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs index cbda50602a..4cec1023b3 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -43,6 +43,9 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency GetUsageInfo() ); } + + await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsNewPackage); + await using var __ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsAddPackage); var isNpmPackage = false; var isNugetPackage = true; @@ -63,11 +66,6 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency var slnFile = GetSolutionFile(commandLineArgs); var projectFile = GetProjectFile(commandLineArgs); - await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsAddPackage, o => - { - o[ActivityPropertyNames.SolutionPath] = slnFile; - }); - await ProjectNugetPackageAdder.AddAsync( slnFile, projectFile, @@ -80,8 +78,6 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency } else if (isNpmPackage) { - await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsAddPackage); - await ProjectNpmPackageAdder.AddNpmPackageAsync( GetAngularDirectory(commandLineArgs), commandLineArgs.Target, diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 0c8367a7c5..ac932dae67 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -112,7 +112,6 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien { o[ActivityPropertyNames.CreationTool] = AbpTool.OldCli; o[ActivityPropertyNames.Template] = template; - o[ActivityPropertyNames.SolutionPath] = projectArgs.OutputFolder; }); ExtractProjectZip(result, projectArgs.OutputFolder); diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs index c782eadd52..d1b30d34b5 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs @@ -1,4 +1,6 @@ -using Volo.Abp.Autofac; +using System.Collections.Generic; +using Volo.Abp.Autofac; +using Volo.Abp.Internal.Telemetry.Activity.Providers; using Volo.Abp.Modularity; namespace Volo.Abp.Cli; @@ -9,5 +11,8 @@ namespace Volo.Abp.Cli; )] public class AbpCliModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.RemoveAll(x => x.ImplementationType == typeof(TelemetrySessionInfoEnricher)); + } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs similarity index 51% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs rename to framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs index 1934f9fc3f..807b94f5e4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -18,13 +19,31 @@ public class TelemetryCliSessionProvider : TelemetryActivityEventEnricher } public override int ExecutionOrder { get; set; } = 10; - protected override Type ReplaceParentType { get; set; } = typeof(TelemetrySessionInfoEnricher); + protected override Type? ReplaceParentType { get; set; } = typeof(TelemetrySessionInfoEnricher); protected override Task ExecuteAsync(ActivityContext context) { context.Current[ActivityPropertyNames.SessionType] = SessionType.AbpCli; context.Current[ActivityPropertyNames.SessionId] = Guid.NewGuid(); context.Current[ActivityPropertyNames.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage); + + if (context.ExtraProperties.ContainsKey(ActivityPropertyNames.SolutionPath)) + { + return Task.CompletedTask; + } + + if(context.Current.TryGetValue(ActivityPropertyNames.SolutionPath, out var existingSolutionPath) && existingSolutionPath is string) + { + context.ExtraProperties[ActivityPropertyNames.SolutionPath] = existingSolutionPath; + return Task.CompletedTask; + } + + if (context.Current.TryGetValue(ActivityPropertyNames.AdditionalProperties, out var additionalProperties) && + additionalProperties is Dictionary additionalPropertiesDict && + additionalPropertiesDict.TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath)) + { + context.ExtraProperties[ActivityPropertyNames.SolutionPath] = solutionPath; + } return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs index c57d51e6f2..f39b3babd8 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs @@ -21,12 +21,17 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri protected override Task CanExecuteAsync(ActivityContext context) { - if (context.SolutionId.HasValue && !context.SolutionPath.IsNullOrEmpty()) + if (context.SolutionPath.IsNullOrEmpty()) + { + return Task.FromResult(false); + } + + if (context.SolutionId.HasValue) { return Task.FromResult(_telemetryActivityStorage.ShouldAddSolutionInformation(context.SolutionId.Value)); } - return Task.FromResult(false); + return Task.FromResult(true); } protected override Task ExecuteAsync(ActivityContext context) @@ -37,6 +42,14 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri { return Task.CompletedTask; } + + var correctSolutionPath = FindCorrectSolutionPath(context.SolutionPath); + if (correctSolutionPath.IsNullOrEmpty()) + { + return Task.CompletedTask; + } + + context.ExtraProperties[ActivityPropertyNames.SolutionPath] = correctSolutionPath; var jsonContent = File.ReadAllText(context.SolutionPath!); using var doc = JsonDocument.Parse(jsonContent, new JsonDocumentOptions @@ -45,6 +58,15 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri }); var root = doc.RootElement; + + var solutionId = TelemetryJsonExtensions.GetGuidOrNull(root, "id"); + + if (!solutionId.HasValue) + { + return Task.CompletedTask; + } + + context.Current[ActivityPropertyNames.SolutionId] = solutionId; if (root.TryGetProperty("versions", out var versions)) { @@ -149,4 +171,35 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri var fullPath = Path.Combine(Path.GetDirectoryName(solutionPath)!, path); return File.Exists(fullPath) ? fullPath : null; } + + private static string? FindCorrectSolutionPath(string solutionPath) + { + if (solutionPath.EndsWith(".abpsln")) + { + return solutionPath; + } + + if (solutionPath.EndsWith(".sln")) + { + solutionPath = solutionPath[..^4] + ".abpsln"; + if (File.Exists(solutionPath)) + { + return solutionPath; + } + } + + var dir = Path.GetDirectoryName(solutionPath); + if (dir.IsNullOrEmpty()) + { + return null; + } + + var abpSolutionFiles = Directory.GetFiles(dir, "*.abpsln", SearchOption.TopDirectoryOnly); + + return abpSolutionFiles.Length switch + { + 1 => abpSolutionFiles[0], + _ => null + }; + } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs index 6f8b5c2a10..f822f72f18 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs @@ -31,4 +31,14 @@ static internal class TelemetryJsonExtensions return null; } + + static internal Guid? GetGuidOrNull(JsonElement element, string propertyName) + { + if (element.TryGetProperty(propertyName, out var guidProperty) && Guid.TryParse(guidProperty.GetString(), out var guidValue)) + { + return guidValue; + } + + return null; + } } \ No newline at end of file From 144d3d3f885641d80f2746a13681aee1107929c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 13:25:36 +0300 Subject: [PATCH 091/130] Improve solution file handling and cleanup unused usings Removed unused using directives in AbpCliCoreModule and NewCommand for better code clarity. Refactored AddPackageCommand to inline solution and project file retrieval. Enhanced TelemetrySolutionInfoEnricher to support '.slnx' files by mapping them to '.abpsln' if present. --- .../Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs | 2 -- .../Volo/Abp/Cli/Commands/AddPackageCommand.cs | 7 ++----- .../Volo/Abp/Cli/Commands/NewCommand.cs | 1 - .../Activity/Providers/TelemetrySolutionInfoEnricher.cs | 9 +++++++++ 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index cea7a38b8b..7b249f0517 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Cli.Commands; @@ -11,7 +10,6 @@ using Volo.Abp.Cli.ServiceProxying.JavaScript; using Volo.Abp.Domain; using Volo.Abp.Http; using Volo.Abp.IdentityModel; -using Volo.Abp.Internal.Telemetry.Activity.Providers; using Volo.Abp.Json; using Volo.Abp.Localization; using Volo.Abp.Minify; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs index 4cec1023b3..a8fbdb8840 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -63,12 +63,9 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency { var addSourceCodeToSolutionFile = withSourceCode && commandLineArgs.Options.ContainsKey("add-to-solution-file"); - var slnFile = GetSolutionFile(commandLineArgs); - var projectFile = GetProjectFile(commandLineArgs); - await ProjectNugetPackageAdder.AddAsync( - slnFile, - projectFile, + GetSolutionFile(commandLineArgs), + GetProjectFile(commandLineArgs), commandLineArgs.Target, version, true, diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index ac932dae67..4d02472d27 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -12,7 +12,6 @@ using Volo.Abp.Cli.LIbs; using Volo.Abp.Cli.ProjectBuilding; using Volo.Abp.Cli.ProjectBuilding.Events; using Volo.Abp.Cli.ProjectBuilding.Templates.Module; -using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; using Volo.Abp.Cli.ProjectModification; using Volo.Abp.Cli.Utils; using Volo.Abp.Cli.Version; diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs index f39b3babd8..38d68f3f71 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs @@ -188,6 +188,15 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri } } + if (solutionPath.EndsWith(".slnx")) + { + solutionPath = solutionPath[..^5] + ".abpsln"; + if (File.Exists(solutionPath)) + { + return solutionPath; + } + } + var dir = Path.GetDirectoryName(solutionPath); if (dir.IsNullOrEmpty()) { From b60604789913ac197267f3fb0f283fa0584055d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 13:27:58 +0300 Subject: [PATCH 092/130] Move AbpCliExit telemetry to finally block Ensures that the AbpCliExit activity is always logged by moving the telemetry call to a finally block, regardless of whether an exception occurs. --- framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs index 7f5f3224e6..c514236bd7 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -89,12 +89,13 @@ public class CliService : ITransientDependency catch (Exception ex) { await TelemetryService.AddErrorActivityAsync(ex.Message); - await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); Logger.LogException(ex); throw; } - - await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); + finally + { + await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); + } } private async Task RunPromptAsync() From d7def2007f445f0d1e7186446bce4aa5c1b708cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 13:35:31 +0300 Subject: [PATCH 093/130] Refactor solution path retrieval in telemetry context Moved solution path lookup logic from TelemetryCliSessionProvider to ActivityContext via a new TryGetValue method, simplifying property access and reducing duplication. --- .../Telemetry/TelemetryCliSessionProvider.cs | 19 --------------- .../Telemetry/Activity/ActivityContext.cs | 24 ++++++++++++++++++- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs index 807b94f5e4..0126f329ef 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -26,24 +25,6 @@ public class TelemetryCliSessionProvider : TelemetryActivityEventEnricher context.Current[ActivityPropertyNames.SessionType] = SessionType.AbpCli; context.Current[ActivityPropertyNames.SessionId] = Guid.NewGuid(); context.Current[ActivityPropertyNames.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage); - - if (context.ExtraProperties.ContainsKey(ActivityPropertyNames.SolutionPath)) - { - return Task.CompletedTask; - } - - if(context.Current.TryGetValue(ActivityPropertyNames.SolutionPath, out var existingSolutionPath) && existingSolutionPath is string) - { - context.ExtraProperties[ActivityPropertyNames.SolutionPath] = existingSolutionPath; - return Task.CompletedTask; - } - - if (context.Current.TryGetValue(ActivityPropertyNames.AdditionalProperties, out var additionalProperties) && - additionalProperties is Dictionary additionalPropertiesDict && - additionalPropertiesDict.TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath)) - { - context.ExtraProperties[ActivityPropertyNames.SolutionPath] = solutionPath; - } return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs index 8a03a66e65..7c232671e7 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs @@ -19,7 +19,7 @@ public class ActivityContext public string? DeviceId => Current.Get(ActivityPropertyNames.DeviceId); - public string? SolutionPath => ExtraProperties.TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath) + public string? SolutionPath => TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath) ? solutionPath?.ToString() : null; @@ -42,6 +42,28 @@ public class ActivityContext return new ActivityContext(activity); } + + public bool TryGetValue(string key, out object? value) + { + if (Current.TryGetValue(key, out value)) + { + return true; + } + + if (ExtraProperties.TryGetValue(key, out value)) + { + return true; + } + + if (Current.TryGetValue(ActivityPropertyNames.AdditionalProperties, out var additionalProperties) && + additionalProperties is Dictionary additionalPropertiesDict && + additionalPropertiesDict.TryGetValue(key, out value)) + { + return true; + } + + return false; + } public void Terminate() { From ba71174d5e81b8b3379539a459b81edde42ff1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 14:45:30 +0300 Subject: [PATCH 094/130] Update TelemetrySolutionInfoEnricher.cs --- .../Activity/Providers/TelemetrySolutionInfoEnricher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs index 38d68f3f71..97e487585a 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs @@ -181,7 +181,7 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri if (solutionPath.EndsWith(".sln")) { - solutionPath = solutionPath[..^4] + ".abpsln"; + solutionPath = solutionPath.RemovePostFix(".sln") + ".abpsln"; if (File.Exists(solutionPath)) { return solutionPath; @@ -190,7 +190,7 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri if (solutionPath.EndsWith(".slnx")) { - solutionPath = solutionPath[..^5] + ".abpsln"; + solutionPath = solutionPath.RemovePostFix(".slnx") + ".abpsln"; if (File.Exists(solutionPath)) { return solutionPath; From 3cab27772c940ef4384a1cb2012030f462b54127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 15:24:24 +0300 Subject: [PATCH 095/130] Add SetSolutionPath method and refactor solution path handling Introduced SetSolutionPath to ActivityContext for consistent solution path updates. Refactored TelemetrySolutionInfoEnricher to use the new method and improved logic for resolving .abpsln files based on possible extensions. --- .../Telemetry/Activity/ActivityContext.cs | 24 +++++++++++ .../TelemetrySolutionInfoEnricher.cs | 40 +++++++------------ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs index 7c232671e7..f6bb8e7e50 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs @@ -65,6 +65,30 @@ public class ActivityContext return false; } + public void SetSolutionPath(string solutionPath) + { + if (solutionPath == SolutionPath) + { + return; + } + + ExtraProperties[ActivityPropertyNames.SolutionPath] = solutionPath; + + if(Current.ContainsKey(ActivityPropertyNames.SolutionPath)) + { + Current[ActivityPropertyNames.SolutionPath] = solutionPath; + } + + if (Current.TryGetValue(ActivityPropertyNames.AdditionalProperties, out var additionalPropertiesObj) && + additionalPropertiesObj is Dictionary additionalPropertiesDict) + { + if (additionalPropertiesDict.ContainsKey(ActivityPropertyNames.SolutionPath)) + { + additionalPropertiesDict[ActivityPropertyNames.SolutionPath] = solutionPath; + } + } + } + public void Terminate() { IsTerminated = true; diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs index 97e487585a..97a2eba926 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs @@ -49,7 +49,7 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri return Task.CompletedTask; } - context.ExtraProperties[ActivityPropertyNames.SolutionPath] = correctSolutionPath; + context.SetSolutionPath(correctSolutionPath); var jsonContent = File.ReadAllText(context.SolutionPath!); using var doc = JsonDocument.Parse(jsonContent, new JsonDocumentOptions @@ -178,37 +178,27 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri { return solutionPath; } + + var possibleExtensions = new[] + { + ".sln", + ".slnx" + }; - if (solutionPath.EndsWith(".sln")) + foreach (var extension in possibleExtensions) { - solutionPath = solutionPath.RemovePostFix(".sln") + ".abpsln"; - if (File.Exists(solutionPath)) + if (!solutionPath.EndsWith(extension)) { - return solutionPath; + continue; } - } - - if (solutionPath.EndsWith(".slnx")) - { - solutionPath = solutionPath.RemovePostFix(".slnx") + ".abpsln"; - if (File.Exists(solutionPath)) + + var abpSlnPath = solutionPath.Substring(0, solutionPath.Length - extension.Length) + ".abpsln"; + if (File.Exists(abpSlnPath)) { - return solutionPath; + return abpSlnPath; } } - - var dir = Path.GetDirectoryName(solutionPath); - if (dir.IsNullOrEmpty()) - { - return null; - } - - var abpSolutionFiles = Directory.GetFiles(dir, "*.abpsln", SearchOption.TopDirectoryOnly); - return abpSolutionFiles.Length switch - { - 1 => abpSolutionFiles[0], - _ => null - }; + return null; } } \ No newline at end of file From b8abda680020cde94969200ab7f6a60bcc87f611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 15:25:28 +0300 Subject: [PATCH 096/130] Refactor telemetry activity initialization in AddModuleCommand Moved solutionFile assignment to after telemetry activity tracking and removed passing solutionFile as a telemetry property. This streamlines the activity initialization and improves code clarity. --- .../Volo/Abp/Cli/Commands/AddModuleCommand.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs index d32498d970..204882a597 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -69,14 +69,10 @@ public class AddModuleCommand : IConsoleCommand, ITransientDependency } var newTemplate = commandLineArgs.Options.ContainsKey(Options.NewTemplate.Long); - var solutionFile = GetSolutionFile(commandLineArgs); await using var _ = TelemetryService.TrackActivityAsync(newTemplate ? ActivityNameConsts.AbpCliCommandsInstallLocalModule - : ActivityNameConsts.AbpCliCommandsInstallModule, o => - { - o[ActivityPropertyNames.SolutionPath] = solutionFile; - }); + : ActivityNameConsts.AbpCliCommandsInstallModule); var template = commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long); var newProTemplate = !string.IsNullOrEmpty(template) && template == ModuleProTemplate.TemplateName; @@ -84,6 +80,7 @@ public class AddModuleCommand : IConsoleCommand, ITransientDependency var addSourceCodeToSolutionFile = withSourceCode && commandLineArgs.Options.ContainsKey("add-to-solution-file"); var skipOpeningDocumentation = commandLineArgs.Options.ContainsKey(Options.SkipOpeningDocumentation.Long); var skipDbMigrations = newTemplate || newProTemplate || commandLineArgs.Options.ContainsKey(Options.DbMigrations.Skip); + var solutionFile = GetSolutionFile(commandLineArgs); var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); if (version == null) From 7a9e5afff023ae092ebadac41e17b50bd538c661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 15:30:03 +0300 Subject: [PATCH 097/130] Remove unnecessary blank line at end of file Cleaned up TelemetryCliSessionProvider.cs by deleting an extraneous blank line at the end of the file. --- .../Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs index 0126f329ef..030a9f83db 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs @@ -28,5 +28,4 @@ public class TelemetryCliSessionProvider : TelemetryActivityEventEnricher return Task.CompletedTask; } - } \ No newline at end of file From 55a511f10fa384cb55d3a30b56aed34c99414600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 15:37:40 +0300 Subject: [PATCH 098/130] revert changes --- .../Telemetry/Activity/ActivityContext.cs | 48 +--------------- .../TelemetrySolutionInfoEnricher.cs | 56 +------------------ .../Activity/TelemetryJsonExtensions.cs | 10 ---- 3 files changed, 3 insertions(+), 111 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs index f6bb8e7e50..8a03a66e65 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs @@ -19,7 +19,7 @@ public class ActivityContext public string? DeviceId => Current.Get(ActivityPropertyNames.DeviceId); - public string? SolutionPath => TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath) + public string? SolutionPath => ExtraProperties.TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath) ? solutionPath?.ToString() : null; @@ -42,52 +42,6 @@ public class ActivityContext return new ActivityContext(activity); } - - public bool TryGetValue(string key, out object? value) - { - if (Current.TryGetValue(key, out value)) - { - return true; - } - - if (ExtraProperties.TryGetValue(key, out value)) - { - return true; - } - - if (Current.TryGetValue(ActivityPropertyNames.AdditionalProperties, out var additionalProperties) && - additionalProperties is Dictionary additionalPropertiesDict && - additionalPropertiesDict.TryGetValue(key, out value)) - { - return true; - } - - return false; - } - - public void SetSolutionPath(string solutionPath) - { - if (solutionPath == SolutionPath) - { - return; - } - - ExtraProperties[ActivityPropertyNames.SolutionPath] = solutionPath; - - if(Current.ContainsKey(ActivityPropertyNames.SolutionPath)) - { - Current[ActivityPropertyNames.SolutionPath] = solutionPath; - } - - if (Current.TryGetValue(ActivityPropertyNames.AdditionalProperties, out var additionalPropertiesObj) && - additionalPropertiesObj is Dictionary additionalPropertiesDict) - { - if (additionalPropertiesDict.ContainsKey(ActivityPropertyNames.SolutionPath)) - { - additionalPropertiesDict[ActivityPropertyNames.SolutionPath] = solutionPath; - } - } - } public void Terminate() { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs index 97a2eba926..c57d51e6f2 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs @@ -21,17 +21,12 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri protected override Task CanExecuteAsync(ActivityContext context) { - if (context.SolutionPath.IsNullOrEmpty()) - { - return Task.FromResult(false); - } - - if (context.SolutionId.HasValue) + if (context.SolutionId.HasValue && !context.SolutionPath.IsNullOrEmpty()) { return Task.FromResult(_telemetryActivityStorage.ShouldAddSolutionInformation(context.SolutionId.Value)); } - return Task.FromResult(true); + return Task.FromResult(false); } protected override Task ExecuteAsync(ActivityContext context) @@ -42,14 +37,6 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri { return Task.CompletedTask; } - - var correctSolutionPath = FindCorrectSolutionPath(context.SolutionPath); - if (correctSolutionPath.IsNullOrEmpty()) - { - return Task.CompletedTask; - } - - context.SetSolutionPath(correctSolutionPath); var jsonContent = File.ReadAllText(context.SolutionPath!); using var doc = JsonDocument.Parse(jsonContent, new JsonDocumentOptions @@ -58,15 +45,6 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri }); var root = doc.RootElement; - - var solutionId = TelemetryJsonExtensions.GetGuidOrNull(root, "id"); - - if (!solutionId.HasValue) - { - return Task.CompletedTask; - } - - context.Current[ActivityPropertyNames.SolutionId] = solutionId; if (root.TryGetProperty("versions", out var versions)) { @@ -171,34 +149,4 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri var fullPath = Path.Combine(Path.GetDirectoryName(solutionPath)!, path); return File.Exists(fullPath) ? fullPath : null; } - - private static string? FindCorrectSolutionPath(string solutionPath) - { - if (solutionPath.EndsWith(".abpsln")) - { - return solutionPath; - } - - var possibleExtensions = new[] - { - ".sln", - ".slnx" - }; - - foreach (var extension in possibleExtensions) - { - if (!solutionPath.EndsWith(extension)) - { - continue; - } - - var abpSlnPath = solutionPath.Substring(0, solutionPath.Length - extension.Length) + ".abpsln"; - if (File.Exists(abpSlnPath)) - { - return abpSlnPath; - } - } - - return null; - } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs index f822f72f18..6f8b5c2a10 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs @@ -31,14 +31,4 @@ static internal class TelemetryJsonExtensions return null; } - - static internal Guid? GetGuidOrNull(JsonElement element, string propertyName) - { - if (element.TryGetProperty(propertyName, out var guidProperty) && Guid.TryParse(guidProperty.GetString(), out var guidValue)) - { - return guidValue; - } - - return null; - } } \ No newline at end of file From 7be5a343063d1cabdaf033be42065a829d0dffb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 10 Dec 2025 15:37:43 +0300 Subject: [PATCH 099/130] Update TelemetryCliSessionProvider.cs --- .../Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs index 030a9f83db..7088e387b3 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs @@ -18,7 +18,6 @@ public class TelemetryCliSessionProvider : TelemetryActivityEventEnricher } public override int ExecutionOrder { get; set; } = 10; - protected override Type? ReplaceParentType { get; set; } = typeof(TelemetrySessionInfoEnricher); protected override Task ExecuteAsync(ActivityContext context) { From d64d4574eebf4c15a3dc749c4a9901e60d031d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Thu, 11 Dec 2025 12:32:48 +0300 Subject: [PATCH 100/130] Refactor TelemetryService usage to constructor injection Replaces property-based ITelemetryService injection with constructor injection and private readonly fields across CLI command and service classes. This change improves dependency management and aligns with best practices for service injection. --- .../src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs | 12 +++++++----- .../Volo/Abp/Cli/Commands/AddModuleCommand.cs | 9 ++++++--- .../Volo/Abp/Cli/Commands/AddPackageCommand.cs | 11 ++++++----- .../Volo/Abp/Cli/Commands/CleanCommand.cs | 9 +++++---- .../Volo/Abp/Cli/Commands/ListModulesCommand.cs | 7 ++++--- .../Volo/Abp/Cli/Commands/NewCommand.cs | 9 ++++++--- .../Volo/Abp/Cli/Commands/UpdateCommand.cs | 7 ++++--- 7 files changed, 38 insertions(+), 26 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs index c514236bd7..063ceddebf 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -22,8 +22,8 @@ namespace Volo.Abp.Cli; public class CliService : ITransientDependency { private readonly MemoryService _memoryService; + private readonly ITelemetryService _telemetryService; public ILogger Logger { get; set; } - public ITelemetryService TelemetryService { get; set; } protected ICommandLineArgumentParser CommandLineArgumentParser { get; } protected ICommandSelector CommandSelector { get; } protected IServiceScopeFactory ServiceScopeFactory { get; } @@ -38,7 +38,8 @@ public class CliService : ITransientDependency PackageVersionCheckerService nugetService, ICmdHelper cmdHelper, MemoryService memoryService, - CliVersionService cliVersionService) + CliVersionService cliVersionService, + ITelemetryService telemetryService) { _memoryService = memoryService; CommandLineArgumentParser = commandLineArgumentParser; @@ -47,6 +48,7 @@ public class CliService : ITransientDependency PackageVersionCheckerService = nugetService; CmdHelper = cmdHelper; CliVersionService = cliVersionService; + _telemetryService = telemetryService; Logger = NullLogger.Instance; } @@ -67,7 +69,7 @@ public class CliService : ITransientDependency try { - await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliRun); + await using var _ = _telemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliRun); if (commandLineArgs.IsCommand("prompt")) { await RunPromptAsync(); @@ -88,13 +90,13 @@ public class CliService : ITransientDependency } catch (Exception ex) { - await TelemetryService.AddErrorActivityAsync(ex.Message); + await _telemetryService.AddErrorActivityAsync(ex.Message); Logger.LogException(ex); throw; } finally { - await TelemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); + await _telemetryService.AddActivityAsync(ActivityNameConsts.AbpCliExit); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs index 204882a597..38483517da 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -22,8 +22,9 @@ public class AddModuleCommand : IConsoleCommand, ITransientDependency public const string Name = "add-module"; private AddModuleInfoOutput _lastAddedModuleInfo; + private readonly ITelemetryService _telemetryService; + public ILogger Logger { get; set; } - public ITelemetryService TelemetryService { get; set; } protected SolutionModuleAdder SolutionModuleAdder { get; } public SolutionPackageVersionFinder SolutionPackageVersionFinder { get; } @@ -42,11 +43,13 @@ public class AddModuleCommand : IConsoleCommand, ITransientDependency public AddModuleCommand( SolutionModuleAdder solutionModuleAdder, SolutionPackageVersionFinder solutionPackageVersionFinder, - IOptions options) + IOptions options, + ITelemetryService telemetryService) { _options = options.Value; SolutionModuleAdder = solutionModuleAdder; SolutionPackageVersionFinder = solutionPackageVersionFinder; + _telemetryService = telemetryService; Logger = NullLogger.Instance; } @@ -70,7 +73,7 @@ public class AddModuleCommand : IConsoleCommand, ITransientDependency var newTemplate = commandLineArgs.Options.ContainsKey(Options.NewTemplate.Long); - await using var _ = TelemetryService.TrackActivityAsync(newTemplate + await using var _ = _telemetryService.TrackActivityAsync(newTemplate ? ActivityNameConsts.AbpCliCommandsInstallLocalModule : ActivityNameConsts.AbpCliCommandsInstallModule); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs index a8fbdb8840..f397ef8d6e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -18,18 +18,19 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency { public const string Name = "add-package"; - public ILogger Logger { get; set; } + private readonly ITelemetryService _telemetryService; - public ITelemetryService TelemetryService { get; set; } + public ILogger Logger { get; set; } protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; } public ProjectNpmPackageAdder ProjectNpmPackageAdder { get; } - public AddPackageCommand(ProjectNugetPackageAdder projectNugetPackageAdder, ProjectNpmPackageAdder projectNpmPackageAdder) + public AddPackageCommand(ProjectNugetPackageAdder projectNugetPackageAdder, ProjectNpmPackageAdder projectNpmPackageAdder, ITelemetryService telemetryService) { ProjectNugetPackageAdder = projectNugetPackageAdder; ProjectNpmPackageAdder = projectNpmPackageAdder; + _telemetryService = telemetryService; Logger = NullLogger.Instance; } @@ -44,8 +45,8 @@ public class AddPackageCommand : IConsoleCommand, ITransientDependency ); } - await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsNewPackage); - await using var __ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsAddPackage); + await using var _ = _telemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsNewPackage); + await using var __ = _telemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsAddPackage); var isNpmPackage = false; var isNugetPackage = true; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs index 886e4faa2b..661cf7995b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs @@ -18,20 +18,21 @@ public class CleanCommand : IConsoleCommand, ITransientDependency public const string Name = "clean"; public ILogger Logger { get; set; } - - public ITelemetryService TelemetryService { get; set; } protected ICmdHelper CmdHelper { get; } + + private readonly ITelemetryService _telemetryService; - public CleanCommand(ICmdHelper cmdHelper) + public CleanCommand(ICmdHelper cmdHelper, ITelemetryService telemetryService) { CmdHelper = cmdHelper; + _telemetryService = telemetryService; Logger = NullLogger.Instance; } public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { - await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsClean); + await using var _ = _telemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsClean); var binEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "bin", SearchOption.AllDirectories); var objEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "obj", SearchOption.AllDirectories); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs index 4496e62eeb..8a9d4fd6d1 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs @@ -18,18 +18,19 @@ public class ListModulesCommand : IConsoleCommand, ITransientDependency public ModuleInfoProvider ModuleInfoProvider { get; } public ILogger Logger { get; set; } - public ITelemetryService TelemetryService { get; set; } + private readonly ITelemetryService _telemetryService; - public ListModulesCommand(ModuleInfoProvider moduleInfoProvider) + public ListModulesCommand(ModuleInfoProvider moduleInfoProvider, ITelemetryService telemetryService) { ModuleInfoProvider = moduleInfoProvider; + _telemetryService = telemetryService; Logger = NullLogger.Instance; } public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { - await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsListModules); + await using var _ = _telemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsListModules); var modules = await ModuleInfoProvider.GetModuleListAsync(); var freeModules = modules.Where(m => !m.IsPro).ToList(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 4d02472d27..149b477d5b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -29,7 +29,8 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien protected TemplateProjectBuilder TemplateProjectBuilder { get; } public ITemplateInfoProvider TemplateInfoProvider { get; } - public ITelemetryService TelemetryService { get; set; } + + private readonly ITelemetryService _telemetryService; public NewCommand( ConnectionStringProvider connectionStringProvider, @@ -45,7 +46,8 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien ITemplateInfoProvider templateInfoProvider, TemplateProjectBuilder templateProjectBuilder, AngularThemeConfigurer angularThemeConfigurer, - CliVersionService cliVersionService) : + CliVersionService cliVersionService, + ITelemetryService telemetryService) : base(connectionStringProvider, solutionPackageVersionFinder, cmdHelper, @@ -61,6 +63,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien { TemplateInfoProvider = templateInfoProvider; TemplateProjectBuilder = templateProjectBuilder; + _telemetryService = telemetryService; } public async Task ExecuteAsync(CommandLineArgs commandLineArgs) @@ -107,7 +110,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien activityName = ActivityNameConsts.AbpCliCommandsNewModule; } - await TelemetryService.AddActivityAsync(activityName, o => + await _telemetryService.AddActivityAsync(activityName, o => { o[ActivityPropertyNames.CreationTool] = AbpTool.OldCli; o[ActivityPropertyNames.Template] = template; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs index f46dd79f5e..68ab346064 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs @@ -19,23 +19,24 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency public const string Name = "update"; public ILogger Logger { get; set; } - public ITelemetryService TelemetryService { get; set; } private readonly VoloNugetPackagesVersionUpdater _nugetPackagesVersionUpdater; private readonly NpmPackagesUpdater _npmPackagesUpdater; + private readonly ITelemetryService _telemetryService; public UpdateCommand(VoloNugetPackagesVersionUpdater nugetPackagesVersionUpdater, - NpmPackagesUpdater npmPackagesUpdater) + NpmPackagesUpdater npmPackagesUpdater, ITelemetryService telemetryService) { _nugetPackagesVersionUpdater = nugetPackagesVersionUpdater; _npmPackagesUpdater = npmPackagesUpdater; + _telemetryService = telemetryService; Logger = NullLogger.Instance; } public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { - await using var _ = TelemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsUpdate); + await using var _ = _telemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsUpdate); var updateNpm = commandLineArgs.Options.ContainsKey(Options.Packages.Npm); var updateNuget = commandLineArgs.Options.ContainsKey(Options.Packages.NuGet); From ac29577569bd77c64a4fba943ec9afdd7d81aaa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Thu, 11 Dec 2025 12:57:36 +0300 Subject: [PATCH 101/130] Add OldCli flag to telemetry session context Introduces a new 'OldCli' property set to true in the telemetry session context to help identify sessions initiated by the old CLI. --- .../Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs index 7088e387b3..bc0fcc2028 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs @@ -24,6 +24,7 @@ public class TelemetryCliSessionProvider : TelemetryActivityEventEnricher context.Current[ActivityPropertyNames.SessionType] = SessionType.AbpCli; context.Current[ActivityPropertyNames.SessionId] = Guid.NewGuid(); context.Current[ActivityPropertyNames.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage); + context.Current["OldCli"] = true; return Task.CompletedTask; } From 354746ca739c78d7ac6f38ee86885ff2b62c371f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Thu, 11 Dec 2025 13:46:02 +0300 Subject: [PATCH 102/130] Add 'OldCli' flag to additional telemetry properties Ensures the 'OldCli' flag is set in the AdditionalProperties dictionary within the telemetry context, creating the dictionary if it does not exist. This provides consistent telemetry data for identifying CLI usage. --- .../Cli/Telemetry/TelemetryCliSessionProvider.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs index bc0fcc2028..17c340febd 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Telemetry/TelemetryCliSessionProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -26,6 +27,18 @@ public class TelemetryCliSessionProvider : TelemetryActivityEventEnricher context.Current[ActivityPropertyNames.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage); context.Current["OldCli"] = true; + if(context.Current.TryGetValue>(ActivityPropertyNames.AdditionalProperties, out var additionalProperties)) + { + additionalProperties["OldCli"] = true; + } + else + { + context.Current[ActivityPropertyNames.AdditionalProperties] = new Dictionary + { + { "OldCli", true } + }; + } + return Task.CompletedTask; } } \ No newline at end of file From ae4a5fbf88470f48a80d010f12342663a763f43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Thu, 11 Dec 2025 14:41:45 +0300 Subject: [PATCH 103/130] Add NullTelemetryService and telemetry config logic Introduces NullTelemetryService to provide a no-op implementation of ITelemetryService. Updates AbpCliCoreModule to conditionally configure telemetry services based on the ABP_STUDIO_ENABLE_TELEMETRY environment variable. --- .../Volo/Abp/Cli/AbpCliCoreModule.cs | 26 +++++++++++++ .../Abp/Cli/Telemetry/NullTelemetryService.cs | 39 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/NullTelemetryService.cs diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 7b249f0517..8ff8ad3206 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -1,5 +1,8 @@ +using System; +using System.Linq; using System.Text; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Cli.Commands; using Volo.Abp.Cli.Commands.Internal; using Volo.Abp.Cli.Http; @@ -7,9 +10,12 @@ using Volo.Abp.Cli.ServiceProxying; using Volo.Abp.Cli.ServiceProxying.Angular; using Volo.Abp.Cli.ServiceProxying.CSharp; using Volo.Abp.Cli.ServiceProxying.JavaScript; +using Volo.Abp.Cli.Telemetry; using Volo.Abp.Domain; using Volo.Abp.Http; using Volo.Abp.IdentityModel; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Activity.Providers; using Volo.Abp.Json; using Volo.Abp.Localization; using Volo.Abp.Minify; @@ -27,6 +33,8 @@ namespace Volo.Abp.Cli; )] public class AbpCliCoreModule : AbpModule { + private const string EnableTelemetryVariableName = "ABP_STUDIO_ENABLE_TELEMETRY"; + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddHttpClient(CliConsts.HttpClientName) @@ -82,5 +90,23 @@ public class AbpCliCoreModule : AbpModule options.Generators[AngularServiceProxyGenerator.Name] = typeof(AngularServiceProxyGenerator); options.Generators[CSharpServiceProxyGenerator.Name] = typeof(CSharpServiceProxyGenerator); }); + + ConfigureTelemetry(context.Services); + } + + private static void ConfigureTelemetry(IServiceCollection services) + { + var enableTelemetryEnvironmentVariable = Environment.GetEnvironmentVariable(EnableTelemetryVariableName , EnvironmentVariableTarget.Machine) + ?? Environment.GetEnvironmentVariable(EnableTelemetryVariableName , EnvironmentVariableTarget.User) + ?? Environment.GetEnvironmentVariable(EnableTelemetryVariableName , EnvironmentVariableTarget.Process); + + if (enableTelemetryEnvironmentVariable.IsNullOrEmpty() || !enableTelemetryEnvironmentVariable.Equals("false", StringComparison.InvariantCultureIgnoreCase)) + { + services.Remove(services.First(p => p.ImplementationType == typeof(TelemetrySessionInfoEnricher))); + } + else + { + services.Replace(ServiceDescriptor.Singleton()); + } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/NullTelemetryService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/NullTelemetryService.cs new file mode 100644 index 0000000000..4f3c695f7d --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Telemetry/NullTelemetryService.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Activity; + +namespace Volo.Abp.Cli.Telemetry; + +public class NullTelemetryService : ITelemetryService +{ + public IAsyncDisposable TrackActivity(ActivityEvent activityData) + { + return NullAsyncDisposable.Instance; + } + public IAsyncDisposable TrackActivityAsync(string activityName, Action>? additionalProperties = null) + { + return NullAsyncDisposable.Instance; + } + + public Task AddActivityAsync(string activityName, Action>? additionalProperties = null) + { + return Task.CompletedTask; + } + + public Task AddErrorActivityAsync(Action> additionalProperties) + { + return Task.CompletedTask; + } + + public Task AddErrorActivityAsync(string errorMessage) + { + return Task.CompletedTask; + } + + public Task AddErrorForActivityAsync(string failingActivity, string errorMessage) + { + return Task.CompletedTask; + } +} \ No newline at end of file From f9fe9b92cc74d4ed25953ba7b8b796059746c1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Thu, 11 Dec 2025 14:44:36 +0300 Subject: [PATCH 104/130] Update AbpCliModule.cs --- framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs index d1b30d34b5..179d4e8e38 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs @@ -11,8 +11,5 @@ namespace Volo.Abp.Cli; )] public class AbpCliModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.RemoveAll(x => x.ImplementationType == typeof(TelemetrySessionInfoEnricher)); - } + } From 51c5e1be2604ed7c237019fc4cdd91fda1ed25c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Thu, 11 Dec 2025 14:47:59 +0300 Subject: [PATCH 105/130] Update AbpCliModule.cs --- framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs index 179d4e8e38..c782eadd52 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using Volo.Abp.Autofac; -using Volo.Abp.Internal.Telemetry.Activity.Providers; +using Volo.Abp.Autofac; using Volo.Abp.Modularity; namespace Volo.Abp.Cli; From 9632923d74c047d0aedc0a74e34bdc733f68e5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 11 Dec 2025 16:11:58 +0300 Subject: [PATCH 106/130] Minor refactor --- ...ourcePermissionProviderKeyLookupService.cs | 2 +- .../PermissionAppService.cs | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs index c0e8aa548d..6b198558fc 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs @@ -31,7 +31,7 @@ public class RoleResourcePermissionProviderKeyLookupService : IResourcePermissio return roles.Select(r => new ResourcePermissionProviderKeyInfo(r.RoleName, r.RoleName)).ToList(); } - public virtual async Task> SearchAsync(string[] keys, CancellationToken cancellationToken = new CancellationToken()) + public virtual async Task> SearchAsync(string[] keys, CancellationToken cancellationToken = default) { var ids = keys .Select(key => Guid.TryParse(key, out var id) ? (Guid?)id : null) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index b41c997723..3c3815a891 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -293,15 +293,17 @@ public class PermissionAppService : ApplicationService, IPermissionAppService continue; } - if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) + if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { - result.Permissions.Add(new ResourcePermissionWithProdiverGrantInfoDto - { - Name = resourcePermissionGrant.Name, - DisplayName = resourcePermission?.DisplayName.Localize(StringLocalizerFactory), - IsGranted = resourcePermissionGrant.IsGranted - }); + continue; } + + result.Permissions.Add(new ResourcePermissionWithProdiverGrantInfoDto + { + Name = resourcePermissionGrant.Name, + DisplayName = resourcePermission?.DisplayName.Localize(StringLocalizerFactory), + IsGranted = resourcePermissionGrant.IsGranted + }); } return result; @@ -310,12 +312,14 @@ public class PermissionAppService : ApplicationService, IPermissionAppService public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); + foreach (var resourcePermission in resourcePermissions) { if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { continue; } + var isGranted = !input.Permissions.IsNullOrEmpty() && input.Permissions.Any(p => p == resourcePermission.Name); await ResourcePermissionManager.SetAsync(resourcePermission.Name, resourceName, resourceKey, input.ProviderName, input.ProviderKey, isGranted); } @@ -326,10 +330,12 @@ public class PermissionAppService : ApplicationService, IPermissionAppService var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); foreach (var resourcePermission in resourcePermissions) { - if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) + if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) { - await ResourcePermissionManager.DeleteAsync(resourcePermission.Name, resourceName, resourceKey, providerName, providerKey); + continue; } + + await ResourcePermissionManager.DeleteAsync(resourcePermission.Name, resourceName, resourceKey, providerName, providerKey); } } From 4e9322c94efdda4fa362795c007bf9c251bc0099 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 12 Dec 2025 08:57:56 +0800 Subject: [PATCH 107/130] Optimize permission checks using IPermissionChecker --- .../PermissionAppService.cs | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 3c3815a891..89e68f02de 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -18,6 +18,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService { protected PermissionManagementOptions Options { get; } protected IPermissionManager PermissionManager { get; } + protected IPermissionChecker PermissionChecker { get; } protected IResourcePermissionManager ResourcePermissionManager { get; } protected IResourcePermissionGrantRepository ResourcePermissionGrantRepository { get; } protected IPermissionDefinitionManager PermissionDefinitionManager { get; } @@ -25,6 +26,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService public PermissionAppService( IPermissionManager permissionManager, + IPermissionChecker permissionChecker, IPermissionDefinitionManager permissionDefinitionManager, IResourcePermissionManager resourcePermissionManager, IResourcePermissionGrantRepository resourcePermissionGrantRepository, @@ -36,6 +38,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService Options = options.Value; PermissionManager = permissionManager; + PermissionChecker = permissionChecker; ResourcePermissionManager = resourcePermissionManager; ResourcePermissionGrantRepository = resourcePermissionGrantRepository; PermissionDefinitionManager = permissionDefinitionManager; @@ -172,7 +175,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService if (!resourcePermissions.Any() || !await AuthorizationService.IsGrantedAnyAsync(resourcePermissions.Select(p => p.ManagementPermissionName!).ToArray())) { - return new GetResourceProviderListResultDto();; + return new GetResourceProviderListResultDto(); } var lookupServices = await ResourcePermissionManager.GetProviderKeyLookupServicesAsync(); @@ -215,16 +218,22 @@ public class PermissionAppService : ApplicationService, IPermissionAppService }; var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); + var permissionGrants = (await PermissionChecker.IsGrantedAsync(resourcePermissions + .Select(rp => rp.ManagementPermissionName!) + .Distinct().ToArray())).Result.Where(x => x.Value == PermissionGrantResult.Granted).Select(x => x.Key) + .ToHashSet(); foreach (var resourcePermission in resourcePermissions) { - if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) + if (!permissionGrants.Contains(resourcePermission.ManagementPermissionName)) { - result.Permissions.Add(new ResourcePermissionDefinitionDto - { - Name = resourcePermission.Name, - DisplayName = resourcePermission.DisplayName?.Localize(StringLocalizerFactory), - }); + continue; } + + result.Permissions.Add(new ResourcePermissionDefinitionDto + { + Name = resourcePermission.Name, + DisplayName = resourcePermission.DisplayName?.Localize(StringLocalizerFactory), + }); } return result; @@ -239,6 +248,10 @@ public class PermissionAppService : ApplicationService, IPermissionAppService var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); var resourcePermissionGrants = await ResourcePermissionManager.GetAllGroupAsync(resourceName, resourceKey); + var permissionGrants = (await PermissionChecker.IsGrantedAsync(resourcePermissions + .Select(rp => rp.ManagementPermissionName!) + .Distinct().ToArray())).Result.Where(x => x.Value == PermissionGrantResult.Granted).Select(x => x.Key) + .ToHashSet(); foreach (var resourcePermissionGrant in resourcePermissionGrants) { var resourcePermissionGrantInfoDto = new ResourcePermissionGrantInfoDto @@ -248,7 +261,6 @@ public class PermissionAppService : ApplicationService, IPermissionAppService ProviderDisplayName = resourcePermissionGrant.ProviderDisplayName, Permissions = new List() }; - foreach (var permission in resourcePermissionGrant.Permissions) { var resourcePermission = resourcePermissions.FirstOrDefault(x => x.Name == permission); @@ -257,14 +269,16 @@ public class PermissionAppService : ApplicationService, IPermissionAppService continue; } - if (await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) + if (!permissionGrants.Contains(resourcePermission.ManagementPermissionName)) { - resourcePermissionGrantInfoDto.Permissions.Add(new GrantedResourcePermissionDto() - { - Name = permission, - DisplayName = resourcePermission?.DisplayName.Localize(StringLocalizerFactory), - }); + continue; } + + resourcePermissionGrantInfoDto.Permissions.Add(new GrantedResourcePermissionDto() + { + Name = permission, + DisplayName = resourcePermission?.DisplayName.Localize(StringLocalizerFactory), + }); } if(resourcePermissionGrantInfoDto.Permissions.Any()) @@ -285,6 +299,10 @@ public class PermissionAppService : ApplicationService, IPermissionAppService var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); var resourcePermissionGrants = await ResourcePermissionManager.GetAllAsync(resourceName, resourceKey, providerName, providerKey); + var permissionGrants = (await PermissionChecker.IsGrantedAsync(resourcePermissions + .Select(rp => rp.ManagementPermissionName!) + .Distinct().ToArray())).Result.Where(x => x.Value == PermissionGrantResult.Granted).Select(x => x.Key) + .ToHashSet(); foreach (var resourcePermissionGrant in resourcePermissionGrants) { var resourcePermission = resourcePermissions.FirstOrDefault(x => x.Name == resourcePermissionGrant.Name); @@ -293,7 +311,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService continue; } - if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) + if (!permissionGrants.Contains(resourcePermission.ManagementPermissionName)) { continue; } @@ -312,14 +330,17 @@ public class PermissionAppService : ApplicationService, IPermissionAppService public virtual async Task UpdateResourceAsync(string resourceName, string resourceKey, UpdateResourcePermissionsDto input) { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); - + var permissionGrants = (await PermissionChecker.IsGrantedAsync(resourcePermissions + .Select(rp => rp.ManagementPermissionName!) + .Distinct().ToArray())).Result.Where(x => x.Value == PermissionGrantResult.Granted).Select(x => x.Key) + .ToHashSet(); foreach (var resourcePermission in resourcePermissions) { - if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) + if (!permissionGrants.Contains(resourcePermission.ManagementPermissionName)) { continue; } - + var isGranted = !input.Permissions.IsNullOrEmpty() && input.Permissions.Any(p => p == resourcePermission.Name); await ResourcePermissionManager.SetAsync(resourcePermission.Name, resourceName, resourceKey, input.ProviderName, input.ProviderKey, isGranted); } @@ -328,9 +349,13 @@ public class PermissionAppService : ApplicationService, IPermissionAppService public virtual async Task DeleteResourceAsync(string resourceName, string resourceKey, string providerName, string providerKey) { var resourcePermissions = await ResourcePermissionManager.GetAvailablePermissionsAsync(resourceName); + var permissionGrants = (await PermissionChecker.IsGrantedAsync(resourcePermissions + .Select(rp => rp.ManagementPermissionName!) + .Distinct().ToArray())).Result.Where(x => x.Value == PermissionGrantResult.Granted).Select(x => x.Key) + .ToHashSet(); foreach (var resourcePermission in resourcePermissions) { - if (!await AuthorizationService.IsGrantedAsync(resourcePermission.ManagementPermissionName!)) + if (!permissionGrants.Contains(resourcePermission.ManagementPermissionName)) { continue; } From fecd5d3f8087d53f8f60897511071b98c053592e Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 12 Dec 2025 20:11:23 +0800 Subject: [PATCH 108/130] Add MySql.Data.MySqlClient to MySQL provider detection Resolve #24404 --- .../Volo/Abp/EntityFrameworkCore/AbpDbContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 9243d4680e..f99d9130bd 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -204,6 +204,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, case "Npgsql.EntityFrameworkCore.PostgreSQL": return EfCoreDatabaseProvider.PostgreSql; case "Pomelo.EntityFrameworkCore.MySql": + case "MySql.Data.MySqlClient": return EfCoreDatabaseProvider.MySql; case "Oracle.EntityFrameworkCore": case "Devart.Data.Oracle.Entity.EFCore": From 8b4f5ba44453ac402b5527da2d5176e7b34ce83f Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 12 Dec 2025 20:35:54 +0800 Subject: [PATCH 109/130] Unify 'ResourcePermissions' translation to 'Permissions' --- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json | 2 +- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json | 2 +- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json | 2 +- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json | 2 +- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json | 2 +- framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json | 2 +- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json | 2 +- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/ar.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/cs.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/de.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/el.json | 2 +- .../Abp/PermissionManagement/Localization/Domain/en-GB.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/en.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/es.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/fa.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/fi.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/fr.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/hi.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/hr.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/hu.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/is.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/it.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/nl.json | 2 +- .../Abp/PermissionManagement/Localization/Domain/pl-PL.json | 2 +- .../Abp/PermissionManagement/Localization/Domain/pt-BR.json | 2 +- .../Abp/PermissionManagement/Localization/Domain/ro-RO.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/ru.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/sk.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/sl.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/sv.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/tr.json | 2 +- .../Volo/Abp/PermissionManagement/Localization/Domain/vi.json | 2 +- .../Abp/PermissionManagement/Localization/Domain/zh-Hans.json | 2 +- .../Abp/PermissionManagement/Localization/Domain/zh-Hant.json | 2 +- 54 files changed, 54 insertions(+), 54 deletions(-) diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json index 50c3e7fcda..eb32a44fa2 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json @@ -62,6 +62,6 @@ "Theme": "سمة", "NotAssigned": "غيرمعتمد", "EntityActionsDisabledTooltip": "ليس لديك إذن لتنفيذ أي إجراء.", - "ResourcePermissions": "أذونات المورد" + "ResourcePermissions": "أذونات" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json index 550d2a1892..27a174cb49 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json @@ -62,6 +62,6 @@ "Theme": "Téma", "NotAssigned": "Nepřiřazena", "EntityActionsDisabledTooltip": "Nemáte oprávnění provést žádnou akci.", - "ResourcePermissions": "Oprávnění ke zdrojům" + "ResourcePermissions": "Oprávnění" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json index 9fd38d88ed..869d049be1 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json @@ -62,6 +62,6 @@ "Theme": "Thema", "NotAssigned": "Nicht zugeordnet", "EntityActionsDisabledTooltip": "Sie haben keine Berechtigung, Aktionen auszuführen.", - "ResourcePermissions": "Ressourcenberechtigungen" + "ResourcePermissions": "Berechtigungen" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json index 16b21e4149..8c2dc2be1d 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json @@ -56,6 +56,6 @@ "Today": "Σήμερα", "Apply": "Ισχύουν", "EntityActionsDisabledTooltip": "Δεν έχετε δικαίωμα να εκτελέσετε καμία ενέργεια.", - "ResourcePermissions": "Δικαιώματα Πόρων", + "ResourcePermissions": "Δικαιώματα", } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json index fdb360ceb8..ce9d962d5e 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json @@ -57,6 +57,6 @@ "Today": "Today", "Apply": "Apply", "EntityActionsDisabledTooltip": "You do not have permission to perform any action.", - "ResourcePermissions": "Resource permissions" + "ResourcePermissions": "Permissions" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json index 260b846537..f328765a39 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json @@ -62,6 +62,6 @@ "Theme": "Theme", "NotAssigned": "Not Assigned", "EntityActionsDisabledTooltip": "You do not have permission to perform any action.", - "ResourcePermissions": "Resource permissions" + "ResourcePermissions": "Permissions" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json index 29efdb558d..9d341cb88f 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json @@ -62,6 +62,6 @@ "Theme": "Tema", "NotAssigned": "No asignado", "EntityActionsDisabledTooltip": "No tienes permisos para realizar ninguna acción.", - "ResourcePermissions": "Permisos de recurso" + "ResourcePermissions": "Permisos" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json index dcd7968a11..5862eab90e 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json @@ -56,6 +56,6 @@ "Today": "امروز", "Apply": "درخواست دادن", "EntityActionsDisabledTooltip": "شما دسترسی به انجام هر گونه عملیات ندارید.", - "ResourcePermissions": "مجوزهای منبع" + "ResourcePermissions": "مجوزها" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json index 1721dc369a..51e356559b 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json @@ -62,6 +62,6 @@ "Theme": "Teema", "NotAssigned": "Ei määritetty", "EntityActionsDisabledTooltip": "Sinulla ei ole oikeutta suorittaa mitään toimintoa.", - "ResourcePermissions": "Resurssin käyttöoikeudet" + "ResourcePermissions": "Käyttöoikeudet" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json index 3559421694..79a40c57f9 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json @@ -62,6 +62,6 @@ "Theme": "Thème", "NotAssigned": "Non attribué", "EntityActionsDisabledTooltip": "Vous n'avez pas les permissions pour effectuer une action.", - "ResourcePermissions": "Autorisations de ressources" + "ResourcePermissions": "Autorisations" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json index e66b51428f..10c1e426c1 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json @@ -62,6 +62,6 @@ "Theme": "विषय", "NotAssigned": "सौंपा नहीं गया है", "EntityActionsDisabledTooltip": "आपके पास कोई कार्रवाई नहीं है जो करने के लिए है।", - "ResourcePermissions": "संसाधन अनुमतियाँ" + "ResourcePermissions": "अनुमतियाँ" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json index a4b6b4b14d..c36751828c 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json @@ -62,6 +62,6 @@ "Theme": "Tema", "NotAssigned": "Nije dodijeljeno", "EntityActionsDisabledTooltip": "Nemate dozvolu za izvođenje bilo kakve akcije.", - "ResourcePermissions": "Dozvole resursa" + "ResourcePermissions": "Dozvole" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json index c23730dde8..e162a2c6c8 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json @@ -62,6 +62,6 @@ "Theme": "Téma", "NotAssigned": "Nem kijelölt", "EntityActionsDisabledTooltip": "Nincs jogosultsága bármely művelethez.", - "ResourcePermissions": "Erőforrás engedélyek" + "ResourcePermissions": "Engedélyek" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json index 4793285732..3d88d70d97 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json @@ -62,6 +62,6 @@ "Theme": "Þema", "NotAssigned": "Ekki skráður", "EntityActionsDisabledTooltip": "Þú hefur ekki aðgang að þessum aðgerðum.", - "ResourcePermissions": "Aðgangsheimildir auðlinda" + "ResourcePermissions": "Aðgangsheimildir" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json index b287522c7a..9bfffe181e 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json @@ -62,6 +62,6 @@ "Theme": "Tema", "NotAssigned": "Non assegnato", "EntityActionsDisabledTooltip": "Non hai i permessi per eseguire alcuna azione.", - "ResourcePermissions": "Autorizzazioni risorse" + "ResourcePermissions": "Autorizzazioni" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json index 7f3480dd4f..51ab0bcd31 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json @@ -62,6 +62,6 @@ "Theme": "Thema", "NotAssigned": "Niet toegekend", "EntityActionsDisabledTooltip": "U hebt geen toegang tot deze acties.", - "ResourcePermissions": "Bronmachtigingen" + "ResourcePermissions": "Machtigingen" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json index 1d90a86ffe..20b4daf7f0 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json @@ -62,6 +62,6 @@ "Theme": "Temat", "NotAssigned": "Nie przypisano", "EntityActionsDisabledTooltip": "Nie masz uprawnień do wykonania żadnej akcji.", - "ResourcePermissions": "Uprawnienia zasobów" + "ResourcePermissions": "Uprawnienia" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json index c0f11bcd63..9764d99fe6 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json @@ -62,6 +62,6 @@ "Theme": "Tema", "NotAssigned": "Não atribuído", "EntityActionsDisabledTooltip": "Você não tem permissão para executar qualquer ação.", - "ResourcePermissions": "Permissões de recurso" + "ResourcePermissions": "Permissões" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json index 71616243c8..6b7367994c 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json @@ -62,6 +62,6 @@ "Theme": "Temă", "NotAssigned": "Nealocat", "EntityActionsDisabledTooltip": "Nu aveți permisiune să efectuați nicio acțiune.", - "ResourcePermissions": "Permisiuni resurse" + "ResourcePermissions": "Permisiuni" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json index 45636606e3..d4f4acf5d6 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json @@ -62,6 +62,6 @@ "Theme": "Тема", "NotAssigned": "Не назначен", "EntityActionsDisabledTooltip": "У вас нет прав на выполнение каких-либо действий.", - "ResourcePermissions": "Разрешения ресурса" + "ResourcePermissions": "Разрешения" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json index c04c550d9c..59d8aecef4 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json @@ -62,6 +62,6 @@ "Theme": "Téma", "NotAssigned": "Nepridelené", "EntityActionsDisabledTooltip": "Nemáte oprávnenie vykonávať žiadnu akciu.", - "ResourcePermissions": "Oprávnenia zdrojov" + "ResourcePermissions": "Oprávnenia" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json index 218e51c725..89f2691a11 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json @@ -62,6 +62,6 @@ "Theme": "Tema", "NotAssigned": "Ni dodeljena", "EntityActionsDisabledTooltip": "Nimate pravic za izvajanje kakršne koli dejanje.", - "ResourcePermissions": "Dovoljenja virov" + "ResourcePermissions": "Dovoljenja" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json index bafb6252de..fe4b692cdf 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sv.json @@ -61,6 +61,6 @@ "Theme": "Tema", "NotAssigned": "Ej tilldelad", "EntityActionsDisabledTooltip": "Du har inte tillgång till dessa åtgärder.", - "ResourcePermissions": "Resurstillstånd" + "ResourcePermissions": "Behörigheter" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json index fb27d0edda..73a4bb5cb4 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json @@ -62,6 +62,6 @@ "Theme": "Tema", "NotAssigned": "Atanmadı", "EntityActionsDisabledTooltip": "Bu işlemi gerçekleştirmek için yeterli yetkiniz yok.", - "ResourcePermissions": "Kaynak izinleri" + "ResourcePermissions": "İzinler" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json index e06a2d2b51..015ed883d3 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json @@ -62,6 +62,6 @@ "Theme": "chủ đề", "NotAssigned": "Không được chỉ định", "EntityActionsDisabledTooltip": "Bạn không có quyền thực hiện bất kỳ hành động nào.", - "ResourcePermissions": "Quyền tài nguyên" + "ResourcePermissions": "Quyền" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json index 831d51fa97..b0759d7da9 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json @@ -62,6 +62,6 @@ "Theme": "主题", "NotAssigned": "未分配", "EntityActionsDisabledTooltip": "您没有权限执行任何操作。", - "ResourcePermissions": "资源权限" + "ResourcePermissions": "权限" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json index ee47b4db20..e35bae3610 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json @@ -62,6 +62,6 @@ "Theme": "主題", "NotAssigned": "未分配", "EntityActionsDisabledTooltip": "您沒有權限執行任何操作。", - "ResourcePermissions": "資源權限" + "ResourcePermissions": "權限" } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index 47b228151c..672f984f72 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "هل أنت متأكد أنك تريد الحفظ بدون أي أذونات؟", "PermissionGroup": "مجموعة الأذونات", "Filter": "تصفية", - "ResourcePermissions": "أذونات الموارد", + "ResourcePermissions": "الأذونات", "ResourcePermissionTarget": "الهدف", "ResourcePermissionPermissions": "الأذونات", "AddResourcePermission": "إضافة أذونات الموارد", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json index 0c71271445..1000b49835 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Opravdu chcete ukládat bez jakýchkoli oprávnění?", "PermissionGroup": "Skupina oprávnění", "Filter": "Filtr", - "ResourcePermissions": "Oprávnění zdrojů", + "ResourcePermissions": "Oprávnění", "ResourcePermissionTarget": "Cíl", "ResourcePermissionPermissions": "Oprávnění", "AddResourcePermission": "Přidat oprávnění zdrojů", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index 002cd1aa2b..cf3e5fa4cb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Sind Sie sicher, dass Sie ohne Berechtigungen speichern möchten?", "PermissionGroup": "Berechtigungsgruppe", "Filter": "Filtern", - "ResourcePermissions": "Ressourcenberechtigungen", + "ResourcePermissions": "Berechtigungen", "ResourcePermissionTarget": "Ziel", "ResourcePermissionPermissions": "Berechtigungen", "AddResourcePermission": "Ressourcenberechtigungen hinzufügen", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index 1b32aa2d8d..4da261e95c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Είστε βέβαιοι ότι θέλετε να αποθηκεύσετε χωρίς δικαιώματα;", "PermissionGroup": "Ομάδα δικαιωμάτων", "Filter": "Φίλτρο", - "ResourcePermissions": "Δικαιώματα πόρων", + "ResourcePermissions": "Δικαιώματα", "ResourcePermissionTarget": "Στόχος", "ResourcePermissionPermissions": "Δικαιώματα", "AddResourcePermission": "Προσθήκη δικαιωμάτων πόρων", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index 7ce464185b..a40a5d0434 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Are you sure you want to save without any permissions?", "PermissionGroup": "Permission Group", "Filter": "Filter", - "ResourcePermissions": "Resource permissions", + "ResourcePermissions": "Permissions", "ResourcePermissionTarget": "Target", "ResourcePermissionPermissions": "Permissions", "AddResourcePermission": "Add resource permissions", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index a9f32681f5..f1e358d02b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Are you sure you want to save without any permissions?", "PermissionGroup": "Permission Group", "Filter": "Filter", - "ResourcePermissions": "Resource permissions", + "ResourcePermissions": "Permissions", "ResourcePermissionTarget": "Target", "ResourcePermissionPermissions": "Permissions", "AddResourcePermission": "Add resource permissions", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index 3282dc5ee3..dd17fc919c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "¿Estás seguro de que quieres guardar sin ningún permiso?", "PermissionGroup": "Grupo de permisos", "Filter": "Filtrar", - "ResourcePermissions": "Permisos de recursos", + "ResourcePermissions": "Permisos", "ResourcePermissionTarget": "Objetivo", "ResourcePermissionPermissions": "Permisos", "AddResourcePermission": "Agregar permisos de recursos", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index c0bedcb03e..78773f29df 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "آیا مطمئن هستید که می خواهید بدون هیچ دسترسی ذخیره کنید؟", "PermissionGroup": "گروه دسترسی", "Filter": "فیلتر", - "ResourcePermissions": "دسترسی‌های منابع", + "ResourcePermissions": "دسترسی‌ها", "ResourcePermissionTarget": "هدف", "ResourcePermissionPermissions": "دسترسی‌ها", "AddResourcePermission": "افزودن دسترسی‌های منابع", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index 352529aab0..bdb5af84e4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Haluatko varmasti tallentaa ilman käyttöoikeuksia?", "PermissionGroup": "Käyttöoikeus", "Filter": "Suodatus", - "ResourcePermissions": "Resurssien käyttöoikeudet", + "ResourcePermissions": "Käyttöoikeudet", "ResourcePermissionTarget": "Kohde", "ResourcePermissionPermissions": "Käyttöoikeudet", "AddResourcePermission": "Lisää resurssien käyttöoikeudet", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index 5f0ef20310..d1e01b8966 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Êtes-vous sûr de vouloir enregistrer sans aucune autorisation ?", "PermissionGroup": "Groupe d'autorisations", "Filter": "Filtrer", - "ResourcePermissions": "Autorisations des ressources", + "ResourcePermissions": "Autorisations", "ResourcePermissionTarget": "Cible", "ResourcePermissionPermissions": "Autorisations", "AddResourcePermission": "Ajouter des autorisations de ressources", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index 14356bcfa5..d8168aca14 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "क्या आप वाकई बिना किसी अनुमति के सहेजना चाहते हैं?", "PermissionGroup": "अनुमति समूह", "Filter": "फ़िल्टर", - "ResourcePermissions": "संसाधन अनुमतियाँ", + "ResourcePermissions": "अनुमतियाँ", "ResourcePermissionTarget": "लक्ष्य", "ResourcePermissionPermissions": "अनुमतियाँ", "AddResourcePermission": "संसाधन अनुमतियाँ जोड़ें", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index 4df3529f20..415d9ccf34 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Jeste li sigurni da želite spremiti bez ikakvih dopuštenja?", "PermissionGroup": "Grupa dozvola", "Filter": "Filtriraj", - "ResourcePermissions": "Dozvole resursa", + "ResourcePermissions": "Dozvole", "ResourcePermissionTarget": "Cilj", "ResourcePermissionPermissions": "Dozvole", "AddResourcePermission": "Dodaj dozvole resursa", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index c83d5cdb40..e54de45ae2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Biztos, hogy engedélyek nélkül akar menteni?", "PermissionGroup": "Engedélycsoport", "Filter": "Szűrő", - "ResourcePermissions": "Erőforrás-engedélyek", + "ResourcePermissions": "Engedélyek", "ResourcePermissionTarget": "Cél", "ResourcePermissionPermissions": "Engedélyek", "AddResourcePermission": "Erőforrás-engedélyek hozzáadása", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index dbd00d0452..3645880e88 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Ertu viss um að þú viljir vista án nokkurra heimilda?", "PermissionGroup": "Heimildahópur", "Filter": "Sía", - "ResourcePermissions": "Heimildir auðlinda", + "ResourcePermissions": "Heimildir", "ResourcePermissionTarget": "Markmið", "ResourcePermissionPermissions": "Heimildir", "AddResourcePermission": "Bæta við heimildum auðlinda", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index 656d482fc2..bee40c0790 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Sei sicuro di voler salvare senza alcuna autorizzazione?", "PermissionGroup": "Gruppo di autorizzazioni", "Filter": "Filtro", - "ResourcePermissions": "Autorizzazioni delle risorse", + "ResourcePermissions": "Autorizzazioni", "ResourcePermissionTarget": "Obiettivo", "ResourcePermissionPermissions": "Autorizzazioni", "AddResourcePermission": "Aggiungi autorizzazioni delle risorse", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index 31f793f222..66abaefdce 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Weet u zeker dat u zonder rechten wilt opslaan?", "PermissionGroup": "Rechtengroep", "Filter": "Filter", - "ResourcePermissions": "Bronrechten", + "ResourcePermissions": "Rechten", "ResourcePermissionTarget": "Doel", "ResourcePermissionPermissions": "Rechten", "AddResourcePermission": "Bronrechten toevoegen", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index b567328480..05f07865e0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Czy na pewno chcesz zapisać bez żadnych uprawnień?", "PermissionGroup": "Grupa uprawnień", "Filter": "Filtr", - "ResourcePermissions": "Uprawnienia zasobów", + "ResourcePermissions": "Uprawnienia", "ResourcePermissionTarget": "Cel", "ResourcePermissionPermissions": "Uprawnienia", "AddResourcePermission": "Dodaj uprawnienia zasobów", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index 07f6fe7ca8..0604837090 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Tem certeza que deseja salvar sem nenhuma permissão?", "PermissionGroup": "Grupo de permissão", "Filter": "Filtrar", - "ResourcePermissions": "Permissões de recursos", + "ResourcePermissions": "Permissões", "ResourcePermissionTarget": "Alvo", "ResourcePermissionPermissions": "Permissões", "AddResourcePermission": "Adicionar permissões de recursos", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index 59a94781fb..4748b21d5a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Sigur doriți să salvați fără nicio permisiune?", "PermissionGroup": "Grup de permisiuni", "Filter": "Filtru", - "ResourcePermissions": "Permisiuni pentru resurse", + "ResourcePermissions": "Permisiuni", "ResourcePermissionTarget": "Țintă", "ResourcePermissionPermissions": "Permisiuni", "AddResourcePermission": "Adăugați permisiuni pentru resurse", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index 05e09043b9..8cd194dc79 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Вы уверены, что хотите сохранить без каких-либо разрешений?", "PermissionGroup": "Группа разрешений", "Filter": "Фильтр", - "ResourcePermissions": "Разрешения ресурсов", + "ResourcePermissions": "Разрешения", "ResourcePermissionTarget": "Цель", "ResourcePermissionPermissions": "Разрешения", "AddResourcePermission": "Добавить разрешения ресурсов", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index 9d54165db3..94993365b5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Naozaj chcete ukladať bez akýchkoľvek povolení?", "PermissionGroup": "Skupina oprávnení", "Filter": "Filtrovať", - "ResourcePermissions": "Oprávnenia zdrojov", + "ResourcePermissions": "Oprávnenia", "ResourcePermissionTarget": "Cieľ", "ResourcePermissionPermissions": "Oprávnenia", "AddResourcePermission": "Pridať oprávnenia zdrojov", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index 10dcca8f9f..9c5f004b3a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Ali ste prepričani, da želite shraniti brez kakršnih koli dovoljenj?", "PermissionGroup": "Skupina dovoljenj", "Filter": "Filtriraj", - "ResourcePermissions": "Dovoljenja virov", + "ResourcePermissions": "Dovoljenja", "ResourcePermissionTarget": "Cilj", "ResourcePermissionPermissions": "Dovoljenja", "AddResourcePermission": "Dodaj dovoljenja virov", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index 8f0106b1fe..8d3de399b1 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Är du säker på att du vill spara utan några behörigheter?", "PermissionGroup": "Behörighetsgrupp", "Filter": "Filtrera", - "ResourcePermissions": "Resursbehörigheter", + "ResourcePermissions": "Behörigheter", "ResourcePermissionTarget": "Mål", "ResourcePermissionPermissions": "Behörigheter", "AddResourcePermission": "Lägg till resursbehörigheter", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index a88f903b8e..ddddf1741a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Hiçbir izin olmadan kaydetmek istediğinize emin misiniz?", "PermissionGroup": "İzin Grubu", "Filter": "Filtre", - "ResourcePermissions": "Kaynak izinleri", + "ResourcePermissions": "İzinler", "ResourcePermissionTarget": "Hedef", "ResourcePermissionPermissions": "İzinler", "AddResourcePermission": "Kaynak izinleri ekle", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index a7d9cddc2c..1d85dd2d8a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "Bạn có chắc chắn muốn lưu mà không có bất kỳ quyền nào không?", "PermissionGroup": "Nhóm quyền", "Filter": "Lọc", - "ResourcePermissions": "Quyền tài nguyên", + "ResourcePermissions": "Quyền", "ResourcePermissionTarget": "Mục tiêu", "ResourcePermissionPermissions": "Quyền", "AddResourcePermission": "Thêm quyền tài nguyên", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index 3fe984f0c2..2895d76ad2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "您确定要在没有任何权限的情况下保存吗?", "PermissionGroup": "权限组", "Filter": "过滤", - "ResourcePermissions": "资源权限", + "ResourcePermissions": "权限", "ResourcePermissionTarget": "目标", "ResourcePermissionPermissions": "权限", "AddResourcePermission": "添加资源权限", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index 2b5ffe628d..3f56a09e82 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -9,7 +9,7 @@ "SaveWithoutAnyPermissionsWarningMessage": "您確定要在沒有任何權限的情況下保存嗎?", "PermissionGroup": "權限組", "Filter": "過濾", - "ResourcePermissions": "資源權限", + "ResourcePermissions": "權限", "ResourcePermissionTarget": "目標", "ResourcePermissionPermissions": "權限", "AddResourcePermission": "添加資源權限", From a280a8bf667c5d9d319121ef7305bae35fa6b6d7 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 12 Dec 2025 20:51:49 +0800 Subject: [PATCH 110/130] Update permission localization strings for clarity --- .../Abp/PermissionManagement/Localization/Domain/ar.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/cs.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/de.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/el.json | 8 ++++---- .../PermissionManagement/Localization/Domain/en-GB.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/en.json | 6 +++--- .../Abp/PermissionManagement/Localization/Domain/es.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/fa.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/fi.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/fr.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/hi.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/hr.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/hu.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/is.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/it.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/nl.json | 8 ++++---- .../PermissionManagement/Localization/Domain/pl-PL.json | 8 ++++---- .../PermissionManagement/Localization/Domain/pt-BR.json | 8 ++++---- .../PermissionManagement/Localization/Domain/ro-RO.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/ru.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/sk.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/sl.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/sv.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/tr.json | 8 ++++---- .../Abp/PermissionManagement/Localization/Domain/vi.json | 8 ++++---- .../PermissionManagement/Localization/Domain/zh-Hans.json | 8 ++++---- .../PermissionManagement/Localization/Domain/zh-Hant.json | 8 ++++---- 27 files changed, 107 insertions(+), 107 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index 672f984f72..dfd066511d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -12,10 +12,10 @@ "ResourcePermissions": "الأذونات", "ResourcePermissionTarget": "الهدف", "ResourcePermissionPermissions": "الأذونات", - "AddResourcePermission": "إضافة أذونات الموارد", - "ResourcePermissionDeletionConfirmationMessage": "هل أنت متأكد أنك تريد حذف أذونات الموارد هذه؟", - "AddResourcePermissions": "إضافة أذونات الموارد", - "UpdateResourcePermissions": "تحديث أذونات الموارد", + "AddResourcePermission": "إضافة إذن", + "ResourcePermissionDeletionConfirmationMessage": "هل أنت متأكد أنك تريد حذف جميع الأذونات؟", + "AddResourcePermissions": "إضافة الأذونات", + "UpdateResourcePermissions": "تحديث الأذونات", "Resource": "المورد", "ResourceName": "اسم المورد", "ResourceKey": "مفتاح المورد", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json index 1000b49835..6003fe3086 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Oprávnění", "ResourcePermissionTarget": "Cíl", "ResourcePermissionPermissions": "Oprávnění", - "AddResourcePermission": "Přidat oprávnění zdrojů", - "ResourcePermissionDeletionConfirmationMessage": "Opravdu chcete smazat tato oprávnění zdrojů?", - "AddResourcePermissions": "Přidat oprávnění zdrojů", - "UpdateResourcePermissions": "Aktualizovat oprávnění zdrojů", + "AddResourcePermission": "Přidat oprávnění", + "ResourcePermissionDeletionConfirmationMessage": "Opravdu chcete smazat všechna oprávnění?", + "AddResourcePermissions": "Přidat oprávnění", + "UpdateResourcePermissions": "Aktualizovat oprávnění", "Resource": "Zdroj", "ResourceName": "Název zdroje", "ResourceKey": "Klíč zdroje", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index cf3e5fa4cb..da3ddb5d37 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Berechtigungen", "ResourcePermissionTarget": "Ziel", "ResourcePermissionPermissions": "Berechtigungen", - "AddResourcePermission": "Ressourcenberechtigungen hinzufügen", - "ResourcePermissionDeletionConfirmationMessage": "Sind Sie sicher, dass Sie diese Ressourcenberechtigungen löschen möchten?", - "AddResourcePermissions": "Ressourcenberechtigungen hinzufügen", - "UpdateResourcePermissions": "Ressourcenberechtigungen aktualisieren", + "AddResourcePermission": "Berechtigung hinzufügen", + "ResourcePermissionDeletionConfirmationMessage": "Sind Sie sicher, dass Sie alle Berechtigungen löschen möchten?", + "AddResourcePermissions": "Berechtigungen hinzufügen", + "UpdateResourcePermissions": "Berechtigungen aktualisieren", "Resource": "Ressource", "ResourceName": "Ressourcenname", "ResourceKey": "Ressourcenschlüssel", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index 4da261e95c..e88eada2b6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Δικαιώματα", "ResourcePermissionTarget": "Στόχος", "ResourcePermissionPermissions": "Δικαιώματα", - "AddResourcePermission": "Προσθήκη δικαιωμάτων πόρων", - "ResourcePermissionDeletionConfirmationMessage": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτά τα δικαιώματα πόρων;", - "AddResourcePermissions": "Προσθήκη δικαιωμάτων πόρων", - "UpdateResourcePermissions": "Ενημέρωση δικαιωμάτων πόρων", + "AddResourcePermission": "Προσθήκη δικαιώματος", + "ResourcePermissionDeletionConfirmationMessage": "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα δικαιώματα;", + "AddResourcePermissions": "Προσθήκη δικαιωμάτων", + "UpdateResourcePermissions": "Ενημέρωση δικαιωμάτων", "Resource": "Πόρος", "ResourceName": "Όνομα πόρου", "ResourceKey": "Κλειδί πόρου", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index a40a5d0434..c0ddc241cf 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Permissions", "ResourcePermissionTarget": "Target", "ResourcePermissionPermissions": "Permissions", - "AddResourcePermission": "Add resource permissions", - "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete this resource permissions?", - "AddResourcePermissions": "Add resource permissions", - "UpdateResourcePermissions": "Update resource permissions", + "AddResourcePermission": "Add permission", + "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete all permissions?", + "AddResourcePermissions": "Add permissions", + "UpdateResourcePermissions": "Update permissions", "Resource": "Resource", "ResourceName": "Resource name", "ResourceKey": "Resource key", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index f1e358d02b..fb5c58a07a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -13,9 +13,9 @@ "ResourcePermissionTarget": "Target", "ResourcePermissionPermissions": "Permissions", "AddResourcePermission": "Add resource permissions", - "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete this resource permissions?", - "AddResourcePermissions": "Add resource permissions", - "UpdateResourcePermissions": "Update resource permissions", + "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete all permissions?", + "AddResourcePermissions": "Add permissions", + "UpdateResourcePermissions": "Update permissions", "Resource": "Resource", "ResourceName": "Resource name", "ResourceKey": "Resource key", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index dd17fc919c..110a303e2d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Permisos", "ResourcePermissionTarget": "Objetivo", "ResourcePermissionPermissions": "Permisos", - "AddResourcePermission": "Agregar permisos de recursos", - "ResourcePermissionDeletionConfirmationMessage": "¿Está seguro de que desea eliminar estos permisos de recursos?", - "AddResourcePermissions": "Agregar permisos de recursos", - "UpdateResourcePermissions": "Actualizar permisos de recursos", + "AddResourcePermission": "Agregar permiso", + "ResourcePermissionDeletionConfirmationMessage": "¿Está seguro de que desea eliminar todos los permisos?", + "AddResourcePermissions": "Agregar permisos", + "UpdateResourcePermissions": "Actualizar permisos", "Resource": "Recurso", "ResourceName": "Nombre del recurso", "ResourceKey": "Clave del recurso", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index 78773f29df..8a68b54596 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -12,10 +12,10 @@ "ResourcePermissions": "دسترسی‌ها", "ResourcePermissionTarget": "هدف", "ResourcePermissionPermissions": "دسترسی‌ها", - "AddResourcePermission": "افزودن دسترسی‌های منابع", - "ResourcePermissionDeletionConfirmationMessage": "آیا مطمئن هستید که می‌خواهید این دسترسی‌های منابع را حذف کنید؟", - "AddResourcePermissions": "افزودن دسترسی‌های منابع", - "UpdateResourcePermissions": "به‌روزرسانی دسترسی‌های منابع", + "AddResourcePermission": "افزودن مجوز", + "ResourcePermissionDeletionConfirmationMessage": "آیا مطمئن هستید که می‌خواهید همه مجوزها را حذف کنید؟", + "AddResourcePermissions": "افزودن مجوزها", + "UpdateResourcePermissions": "به‌روزرسانی مجوزها", "Resource": "منبع", "ResourceName": "نام منبع", "ResourceKey": "کلید منبع", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index bdb5af84e4..d9e6c79d74 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Käyttöoikeudet", "ResourcePermissionTarget": "Kohde", "ResourcePermissionPermissions": "Käyttöoikeudet", - "AddResourcePermission": "Lisää resurssien käyttöoikeudet", - "ResourcePermissionDeletionConfirmationMessage": "Oletko varma, että haluat poistaa nämä resurssien käyttöoikeudet?", - "AddResourcePermissions": "Lisää resurssien käyttöoikeudet", - "UpdateResourcePermissions": "Päivitä resurssien käyttöoikeudet", + "AddResourcePermission": "Lisää käyttöoikeus", + "ResourcePermissionDeletionConfirmationMessage": "Haluatko varmasti poistaa kaikki käyttöoikeudet?", + "AddResourcePermissions": "Lisää käyttöoikeudet", + "UpdateResourcePermissions": "Päivitä käyttöoikeudet", "Resource": "Resurssi", "ResourceName": "Resurssin nimi", "ResourceKey": "Resurssiavain", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index d1e01b8966..1b2b990765 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Autorisations", "ResourcePermissionTarget": "Cible", "ResourcePermissionPermissions": "Autorisations", - "AddResourcePermission": "Ajouter des autorisations de ressources", - "ResourcePermissionDeletionConfirmationMessage": "Êtes-vous sûr de vouloir supprimer ces autorisations de ressources ?", - "AddResourcePermissions": "Ajouter des autorisations de ressources", - "UpdateResourcePermissions": "Mettre à jour les autorisations de ressources", + "AddResourcePermission": "Ajouter une autorisation", + "ResourcePermissionDeletionConfirmationMessage": "Êtes-vous sûr de vouloir supprimer toutes les autorisations ?", + "AddResourcePermissions": "Ajouter des autorisations", + "UpdateResourcePermissions": "Mettre à jour les autorisations", "Resource": "Ressource", "ResourceName": "Nom de la ressource", "ResourceKey": "Clé de la ressource", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index d8168aca14..aebf6b9e17 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -12,10 +12,10 @@ "ResourcePermissions": "अनुमतियाँ", "ResourcePermissionTarget": "लक्ष्य", "ResourcePermissionPermissions": "अनुमतियाँ", - "AddResourcePermission": "संसाधन अनुमतियाँ जोड़ें", - "ResourcePermissionDeletionConfirmationMessage": "क्या आप वाकई इन संसाधन अनुमतियों को हटाना चाहते हैं?", - "AddResourcePermissions": "संसाधन अनुमतियाँ जोड़ें", - "UpdateResourcePermissions": "संसाधन अनुमतियों को अपडेट करें", + "AddResourcePermission": "अनुमति जोड़ें", + "ResourcePermissionDeletionConfirmationMessage": "क्या आप वाकई सभी अनुमतियां हटाना चाहते हैं?", + "AddResourcePermissions": "अनुमतियां जोड़ें", + "UpdateResourcePermissions": "अनुमतियां अपडेट करें", "Resource": "संसाधन", "ResourceName": "संसाधन का नाम", "ResourceKey": "संसाधन कुंजी", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index 415d9ccf34..3f0ec7a59f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Dozvole", "ResourcePermissionTarget": "Cilj", "ResourcePermissionPermissions": "Dozvole", - "AddResourcePermission": "Dodaj dozvole resursa", - "ResourcePermissionDeletionConfirmationMessage": "Jeste li sigurni da želite izbrisati ove dozvole resursa?", - "AddResourcePermissions": "Dodaj dozvole resursa", - "UpdateResourcePermissions": "Ažuriraj dozvole resursa", + "AddResourcePermission": "Dodaj dozvolu", + "ResourcePermissionDeletionConfirmationMessage": "Jeste li sigurni da želite izbrisati sve dozvole?", + "AddResourcePermissions": "Dodaj dozvole", + "UpdateResourcePermissions": "Ažuriraj dozvole", "Resource": "Resurs", "ResourceName": "Naziv resursa", "ResourceKey": "Ključ resursa", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index e54de45ae2..f62831d36e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Engedélyek", "ResourcePermissionTarget": "Cél", "ResourcePermissionPermissions": "Engedélyek", - "AddResourcePermission": "Erőforrás-engedélyek hozzáadása", - "ResourcePermissionDeletionConfirmationMessage": "Biztosan törölni szeretné ezeket az erőforrás-engedélyeket?", - "AddResourcePermissions": "Erőforrás-engedélyek hozzáadása", - "UpdateResourcePermissions": "Erőforrás-engedélyek frissítése", + "AddResourcePermission": "Engedély hozzáadása", + "ResourcePermissionDeletionConfirmationMessage": "Biztosan törölni szeretné az összes engedélyt?", + "AddResourcePermissions": "Engedélyek hozzáadása", + "UpdateResourcePermissions": "Engedélyek frissítése", "Resource": "Erőforrás", "ResourceName": "Erőforrás neve", "ResourceKey": "Erőforrás kulcs", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index 3645880e88..2c6f7b56b2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Heimildir", "ResourcePermissionTarget": "Markmið", "ResourcePermissionPermissions": "Heimildir", - "AddResourcePermission": "Bæta við heimildum auðlinda", - "ResourcePermissionDeletionConfirmationMessage": "Ertu viss um að þú viljir eyða þessum heimildum auðlinda?", - "AddResourcePermissions": "Bæta við heimildum auðlinda", - "UpdateResourcePermissions": "Uppfæra heimildir auðlinda", + "AddResourcePermission": "Bæta við heimild", + "ResourcePermissionDeletionConfirmationMessage": "Ertu viss um að þú viljir eyða öllum heimildum?", + "AddResourcePermissions": "Bæta við heimildum", + "UpdateResourcePermissions": "Uppfæra heimildir", "Resource": "Auðlind", "ResourceName": "Nafn auðlindar", "ResourceKey": "Lykill auðlindar", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index bee40c0790..61e769a56c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Autorizzazioni", "ResourcePermissionTarget": "Obiettivo", "ResourcePermissionPermissions": "Autorizzazioni", - "AddResourcePermission": "Aggiungi autorizzazioni delle risorse", - "ResourcePermissionDeletionConfirmationMessage": "Sei sicuro di voler eliminare queste autorizzazioni delle risorse?", - "AddResourcePermissions": "Aggiungi autorizzazioni delle risorse", - "UpdateResourcePermissions": "Aggiorna autorizzazioni delle risorse", + "AddResourcePermission": "Aggiungi autorizzazione", + "ResourcePermissionDeletionConfirmationMessage": "Sei sicuro di voler eliminare tutte le autorizzazioni?", + "AddResourcePermissions": "Aggiungi autorizzazioni", + "UpdateResourcePermissions": "Aggiorna autorizzazioni", "Resource": "Risorsa", "ResourceName": "Nome della risorsa", "ResourceKey": "Chiave della risorsa", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index 66abaefdce..02f6c2d76b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Rechten", "ResourcePermissionTarget": "Doel", "ResourcePermissionPermissions": "Rechten", - "AddResourcePermission": "Bronrechten toevoegen", - "ResourcePermissionDeletionConfirmationMessage": "Weet u zeker dat u deze bronrechten wilt verwijderen?", - "AddResourcePermissions": "Bronrechten toevoegen", - "UpdateResourcePermissions": "Bronrechten bijwerken", + "AddResourcePermission": "Recht toevoegen", + "ResourcePermissionDeletionConfirmationMessage": "Weet u zeker dat u alle rechten wilt verwijderen?", + "AddResourcePermissions": "Rechten toevoegen", + "UpdateResourcePermissions": "Rechten bijwerken", "Resource": "Bron", "ResourceName": "Bronnaam", "ResourceKey": "Bronsleutel", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index 05f07865e0..76dd154a05 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Uprawnienia", "ResourcePermissionTarget": "Cel", "ResourcePermissionPermissions": "Uprawnienia", - "AddResourcePermission": "Dodaj uprawnienia zasobów", - "ResourcePermissionDeletionConfirmationMessage": "Czy na pewno chcesz usunąć te uprawnienia zasobów?", - "AddResourcePermissions": "Dodaj uprawnienia zasobów", - "UpdateResourcePermissions": "Zaktualizuj uprawnienia zasobów", + "AddResourcePermission": "Dodaj uprawnienie", + "ResourcePermissionDeletionConfirmationMessage": "Czy na pewno chcesz usunąć wszystkie uprawnienia?", + "AddResourcePermissions": "Dodaj uprawnienia", + "UpdateResourcePermissions": "Zaktualizuj uprawnienia", "Resource": "Zasób", "ResourceName": "Nazwa zasobu", "ResourceKey": "Klucz zasobu", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index 0604837090..f34bd24aaf 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Permissões", "ResourcePermissionTarget": "Alvo", "ResourcePermissionPermissions": "Permissões", - "AddResourcePermission": "Adicionar permissões de recursos", - "ResourcePermissionDeletionConfirmationMessage": "Tem certeza de que deseja excluir essas permissões de recursos?", - "AddResourcePermissions": "Adicionar permissões de recursos", - "UpdateResourcePermissions": "Atualizar permissões de recursos", + "AddResourcePermission": "Adicionar permissão", + "ResourcePermissionDeletionConfirmationMessage": "Tem certeza de que deseja excluir todas as permissões?", + "AddResourcePermissions": "Adicionar permissões", + "UpdateResourcePermissions": "Atualizar permissões", "Resource": "Recurso", "ResourceName": "Nome do recurso", "ResourceKey": "Chave do recurso", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index 4748b21d5a..3944f9d497 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Permisiuni", "ResourcePermissionTarget": "Țintă", "ResourcePermissionPermissions": "Permisiuni", - "AddResourcePermission": "Adăugați permisiuni pentru resurse", - "ResourcePermissionDeletionConfirmationMessage": "Sigur doriți să ștergeți aceste permisiuni pentru resurse?", - "AddResourcePermissions": "Adăugați permisiuni pentru resurse", - "UpdateResourcePermissions": "Actualizați permisiunile pentru resurse", + "AddResourcePermission": "Adăugați permisiune", + "ResourcePermissionDeletionConfirmationMessage": "Sigur doriți să ștergeți toate permisiunile?", + "AddResourcePermissions": "Adăugați permisiuni", + "UpdateResourcePermissions": "Actualizați permisiunile", "Resource": "Resursă", "ResourceName": "Numele resursei", "ResourceKey": "Cheia resursei", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index 8cd194dc79..63b6a76bbb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Разрешения", "ResourcePermissionTarget": "Цель", "ResourcePermissionPermissions": "Разрешения", - "AddResourcePermission": "Добавить разрешения ресурсов", - "ResourcePermissionDeletionConfirmationMessage": "Вы уверены, что хотите удалить эти разрешения ресурсов?", - "AddResourcePermissions": "Добавить разрешения ресурсов", - "UpdateResourcePermissions": "Обновить разрешения ресурсов", + "AddResourcePermission": "Добавить разрешение", + "ResourcePermissionDeletionConfirmationMessage": "Вы уверены, что хотите удалить все разрешения?", + "AddResourcePermissions": "Добавить разрешения", + "UpdateResourcePermissions": "Обновить разрешения", "Resource": "Ресурс", "ResourceName": "Имя ресурса", "ResourceKey": "Ключ ресурса", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index 94993365b5..a86485d896 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Oprávnenia", "ResourcePermissionTarget": "Cieľ", "ResourcePermissionPermissions": "Oprávnenia", - "AddResourcePermission": "Pridať oprávnenia zdrojov", - "ResourcePermissionDeletionConfirmationMessage": "Naozaj chcete odstrániť tieto oprávnenia zdrojov?", - "AddResourcePermissions": "Pridať oprávnenia zdrojov", - "UpdateResourcePermissions": "Aktualizovať oprávnenia zdrojov", + "AddResourcePermission": "Pridať oprávnenie", + "ResourcePermissionDeletionConfirmationMessage": "Naozaj chcete odstrániť všetky oprávnenia?", + "AddResourcePermissions": "Pridať oprávnenia", + "UpdateResourcePermissions": "Aktualizovať oprávnenia", "Resource": "Zdroj", "ResourceName": "Názov zdroja", "ResourceKey": "Kľúč zdroja", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index 9c5f004b3a..db58926ee0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Dovoljenja", "ResourcePermissionTarget": "Cilj", "ResourcePermissionPermissions": "Dovoljenja", - "AddResourcePermission": "Dodaj dovoljenja virov", - "ResourcePermissionDeletionConfirmationMessage": "Ali ste prepričani, da želite izbrisati ta dovoljenja virov?", - "AddResourcePermissions": "Dodaj dovoljenja virov", - "UpdateResourcePermissions": "Posodobi dovoljenja virov", + "AddResourcePermission": "Dodaj dovoljenje", + "ResourcePermissionDeletionConfirmationMessage": "Ali ste prepričani, da želite izbrisati vsa dovoljenja?", + "AddResourcePermissions": "Dodaj dovoljenja", + "UpdateResourcePermissions": "Posodobi dovoljenja", "Resource": "Vir", "ResourceName": "Ime vira", "ResourceKey": "Ključ vira", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index 8d3de399b1..b9b681ce0e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Behörigheter", "ResourcePermissionTarget": "Mål", "ResourcePermissionPermissions": "Behörigheter", - "AddResourcePermission": "Lägg till resursbehörigheter", - "ResourcePermissionDeletionConfirmationMessage": "Är du säker på att du vill ta bort dessa resursbehörigheter?", - "AddResourcePermissions": "Lägg till resursbehörigheter", - "UpdateResourcePermissions": "Uppdatera resursbehörigheter", + "AddResourcePermission": "Lägg till behörighet", + "ResourcePermissionDeletionConfirmationMessage": "Är du säker på att du vill ta bort alla behörigheter?", + "AddResourcePermissions": "Lägg till behörigheter", + "UpdateResourcePermissions": "Uppdatera behörigheter", "Resource": "Resurs", "ResourceName": "Resursnamn", "ResourceKey": "Resursnyckel", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index ddddf1741a..93d87dc78e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -12,10 +12,10 @@ "ResourcePermissions": "İzinler", "ResourcePermissionTarget": "Hedef", "ResourcePermissionPermissions": "İzinler", - "AddResourcePermission": "Kaynak izinleri ekle", - "ResourcePermissionDeletionConfirmationMessage": "Bu kaynak izinlerini silmek istediğinizden emin misiniz?", - "AddResourcePermissions": "Kaynak izinleri ekle", - "UpdateResourcePermissions": "Kaynak izinlerini güncelle", + "AddResourcePermission": "İzin ekle", + "ResourcePermissionDeletionConfirmationMessage": "Tüm izinleri silmek istediğinizden emin misiniz?", + "AddResourcePermissions": "İzinler ekle", + "UpdateResourcePermissions": "İzinleri güncelle", "Resource": "Kaynak", "ResourceName": "Kaynak adı", "ResourceKey": "Kaynak anahtarı", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index 1d85dd2d8a..afb2390bc2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -12,10 +12,10 @@ "ResourcePermissions": "Quyền", "ResourcePermissionTarget": "Mục tiêu", "ResourcePermissionPermissions": "Quyền", - "AddResourcePermission": "Thêm quyền tài nguyên", - "ResourcePermissionDeletionConfirmationMessage": "Bạn có chắc chắn muốn xóa các quyền tài nguyên này không?", - "AddResourcePermissions": "Thêm quyền tài nguyên", - "UpdateResourcePermissions": "Cập nhật quyền tài nguyên", + "AddResourcePermission": "Thêm quyền", + "ResourcePermissionDeletionConfirmationMessage": "Bạn có chắc chắn muốn xóa tất cả quyền không?", + "AddResourcePermissions": "Thêm các quyền", + "UpdateResourcePermissions": "Cập nhật các quyền", "Resource": "Tài nguyên", "ResourceName": "Tên tài nguyên", "ResourceKey": "Khóa tài nguyên", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index 2895d76ad2..53a674d8bc 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -12,10 +12,10 @@ "ResourcePermissions": "权限", "ResourcePermissionTarget": "目标", "ResourcePermissionPermissions": "权限", - "AddResourcePermission": "添加资源权限", - "ResourcePermissionDeletionConfirmationMessage": "您确定要删除这些资源权限吗?", - "AddResourcePermissions": "添加资源权限", - "UpdateResourcePermissions": "更新资源权限", + "AddResourcePermission": "添加权限", + "ResourcePermissionDeletionConfirmationMessage": "您确定要删除所有权限吗?", + "AddResourcePermissions": "添加权限", + "UpdateResourcePermissions": "更新权限", "Resource": "资源", "ResourceName": "资源名称", "ResourceKey": "资源键", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index 3f56a09e82..5d44c03c35 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -12,10 +12,10 @@ "ResourcePermissions": "權限", "ResourcePermissionTarget": "目標", "ResourcePermissionPermissions": "權限", - "AddResourcePermission": "添加資源權限", - "ResourcePermissionDeletionConfirmationMessage": "您確定要刪除這些資源權限嗎?", - "AddResourcePermissions": "添加資源權限", - "UpdateResourcePermissions": "更新資源權限", + "AddResourcePermission": "添加權限", + "ResourcePermissionDeletionConfirmationMessage": "您確定要刪除所有權限嗎?", + "AddResourcePermissions": "添加權限", + "UpdateResourcePermissions": "更新權限", "Resource": "資源", "ResourceName": "資源名稱", "ResourceKey": "資源鍵", From 0d9720e0baae167c04c98b2f1eddcd2d098967ce Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 12 Dec 2025 20:53:14 +0800 Subject: [PATCH 111/130] Remove redundant resource and provider localization strings from multiple language files --- .../Abp/PermissionManagement/Localization/Domain/ar.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/cs.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/de.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/el.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/en-GB.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/en.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/es.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/fa.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/fi.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/fr.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/hi.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/hr.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/hu.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/is.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/it.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/nl.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/pl-PL.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/pt-BR.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/ro-RO.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/ru.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/sk.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/sl.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/sv.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/tr.json | 5 ----- .../Abp/PermissionManagement/Localization/Domain/vi.json | 5 ----- .../PermissionManagement/Localization/Domain/zh-Hans.json | 5 ----- .../PermissionManagement/Localization/Domain/zh-Hant.json | 5 ----- 27 files changed, 135 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index dfd066511d..abdd6c0173 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "هل أنت متأكد أنك تريد حذف جميع الأذونات؟", "AddResourcePermissions": "إضافة الأذونات", "UpdateResourcePermissions": "تحديث الأذونات", - "Resource": "المورد", - "ResourceName": "اسم المورد", - "ResourceKey": "مفتاح المورد", - "ProviderName": "اسم الموفر", - "ProviderKey": "مفتاح الموفر", "GrantAllResourcePermissions": "منح الكل", "NoResourceProviderKeyLookupServiceFound": "لم يتم العثور على خدمة البحث عن مفتاح مزود المورد", "NoResourcePermissionFound": "لا يوجد إذن مورد معرف للموارد الحالية." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json index 6003fe3086..eef2ab75fa 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Opravdu chcete smazat všechna oprávnění?", "AddResourcePermissions": "Přidat oprávnění", "UpdateResourcePermissions": "Aktualizovat oprávnění", - "Resource": "Zdroj", - "ResourceName": "Název zdroje", - "ResourceKey": "Klíč zdroje", - "ProviderName": "Název poskytovatele", - "ProviderKey": "Klíč poskytovatele", "GrantAllResourcePermissions": "Udělit vše", "NoResourceProviderKeyLookupServiceFound": "Nebyla nalezena služba pro vyhledávání klíče poskytovatele zdrojů", "NoResourcePermissionFound": "Pro aktuální prostředek není definováno žádné oprávnění." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index da3ddb5d37..b854ed86b5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Sind Sie sicher, dass Sie alle Berechtigungen löschen möchten?", "AddResourcePermissions": "Berechtigungen hinzufügen", "UpdateResourcePermissions": "Berechtigungen aktualisieren", - "Resource": "Ressource", - "ResourceName": "Ressourcenname", - "ResourceKey": "Ressourcenschlüssel", - "ProviderName": "Anbietername", - "ProviderKey": "Anbieterschlüssel", "GrantAllResourcePermissions": "Alle gewähren", "NoResourceProviderKeyLookupServiceFound": "Es wurde kein Dienst zum Nachschlagen des Ressourcenschlüsselanbieters gefunden", "NoResourcePermissionFound": "Es ist keine Ressourcenberechtigung für die aktuelle Ressource definiert." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index e88eada2b6..0d76102fb9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα δικαιώματα;", "AddResourcePermissions": "Προσθήκη δικαιωμάτων", "UpdateResourcePermissions": "Ενημέρωση δικαιωμάτων", - "Resource": "Πόρος", - "ResourceName": "Όνομα πόρου", - "ResourceKey": "Κλειδί πόρου", - "ProviderName": "Όνομα παρόχου", - "ProviderKey": "Κλειδί παρόχου", "GrantAllResourcePermissions": "Παραχώρηση όλων", "NoResourceProviderKeyLookupServiceFound": "Δεν βρέθηκε υπηρεσία αναζήτησης κλειδιού παρόχου πόρων", "NoResourcePermissionFound": "Δεν έχει οριστεί καμία άδεια πόρου για τον τρέχοντα πόρο." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index c0ddc241cf..b445d536df 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete all permissions?", "AddResourcePermissions": "Add permissions", "UpdateResourcePermissions": "Update permissions", - "Resource": "Resource", - "ResourceName": "Resource name", - "ResourceKey": "Resource key", - "ProviderName": "Provider name", - "ProviderKey": "Provider key", "GrantAllResourcePermissions": "Grant all", "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found", "NoResourcePermissionFound": "There is no resource permission defined for the current resource." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index fb5c58a07a..530a35cd7d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete all permissions?", "AddResourcePermissions": "Add permissions", "UpdateResourcePermissions": "Update permissions", - "Resource": "Resource", - "ResourceName": "Resource name", - "ResourceKey": "Resource key", - "ProviderName": "Provider name", - "ProviderKey": "Provider key", "GrantAllResourcePermissions": "Grant all", "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found", "NoResourcePermissionFound": "There is no resource permission defined for the current resource." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index 110a303e2d..59354f55b5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "¿Está seguro de que desea eliminar todos los permisos?", "AddResourcePermissions": "Agregar permisos", "UpdateResourcePermissions": "Actualizar permisos", - "Resource": "Recurso", - "ResourceName": "Nombre del recurso", - "ResourceKey": "Clave del recurso", - "ProviderName": "Nombre del proveedor", - "ProviderKey": "Clave del proveedor", "GrantAllResourcePermissions": "Conceder todos", "NoResourceProviderKeyLookupServiceFound": "No se encontró ningún servicio de búsqueda de clave de proveedor de recursos", "NoResourcePermissionFound": "No hay ningún permiso de recurso definido para el recurso actual." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index 8a68b54596..8da28b6357 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "آیا مطمئن هستید که می‌خواهید همه مجوزها را حذف کنید؟", "AddResourcePermissions": "افزودن مجوزها", "UpdateResourcePermissions": "به‌روزرسانی مجوزها", - "Resource": "منبع", - "ResourceName": "نام منبع", - "ResourceKey": "کلید منبع", - "ProviderName": "نام ارائه‌دهنده", - "ProviderKey": "کلید ارائه‌دهنده", "GrantAllResourcePermissions": "اعطای همه", "NoResourceProviderKeyLookupServiceFound": "هیچ سرویس جستجوی کلید ارائه‌دهنده منبع یافت نشد", "NoResourcePermissionFound": "هیچ مجوز منبعی برای منبع فعلی تعریف نشده است." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index d9e6c79d74..4500c0132a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Haluatko varmasti poistaa kaikki käyttöoikeudet?", "AddResourcePermissions": "Lisää käyttöoikeudet", "UpdateResourcePermissions": "Päivitä käyttöoikeudet", - "Resource": "Resurssi", - "ResourceName": "Resurssin nimi", - "ResourceKey": "Resurssiavain", - "ProviderName": "Palveluntarjoajan nimi", - "ProviderKey": "Palveluntarjoajan avain", "GrantAllResourcePermissions": "Myönnä kaikki", "NoResourceProviderKeyLookupServiceFound": "Resurssin tarjoajan avaimen hakupalvelua ei löytynyt", "NoResourcePermissionFound": "Nykyiselle resurssille ei ole määritetty käyttöoikeutta." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index 1b2b990765..7a7bd7501f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Êtes-vous sûr de vouloir supprimer toutes les autorisations ?", "AddResourcePermissions": "Ajouter des autorisations", "UpdateResourcePermissions": "Mettre à jour les autorisations", - "Resource": "Ressource", - "ResourceName": "Nom de la ressource", - "ResourceKey": "Clé de la ressource", - "ProviderName": "Nom du fournisseur", - "ProviderKey": "Clé du fournisseur", "GrantAllResourcePermissions": "Accorder tout", "NoResourceProviderKeyLookupServiceFound": "Aucun service de recherche de clé de fournisseur de ressources n'a été trouvé", "NoResourcePermissionFound": "Aucune autorisation de ressource n'est définie pour la ressource actuelle." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index aebf6b9e17..42307d4f96 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "क्या आप वाकई सभी अनुमतियां हटाना चाहते हैं?", "AddResourcePermissions": "अनुमतियां जोड़ें", "UpdateResourcePermissions": "अनुमतियां अपडेट करें", - "Resource": "संसाधन", - "ResourceName": "संसाधन का नाम", - "ResourceKey": "संसाधन कुंजी", - "ProviderName": "प्रदाता का नाम", - "ProviderKey": "प्रदाता कुंजी", "GrantAllResourcePermissions": "सभी प्रदान करें", "NoResourceProviderKeyLookupServiceFound": "कोई संसाधन प्रदाता कुंजी खोज सेवा नहीं मिली", "NoResourcePermissionFound": "वर्तमान संसाधन के लिए कोई संसाधन अनुमति परिभाषित नहीं है।" diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index 3f0ec7a59f..d7c436b798 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Jeste li sigurni da želite izbrisati sve dozvole?", "AddResourcePermissions": "Dodaj dozvole", "UpdateResourcePermissions": "Ažuriraj dozvole", - "Resource": "Resurs", - "ResourceName": "Naziv resursa", - "ResourceKey": "Ključ resursa", - "ProviderName": "Naziv pružatelja usluga", - "ProviderKey": "Ključ pružatelja usluga", "GrantAllResourcePermissions": "Dodijeli sve", "NoResourceProviderKeyLookupServiceFound": "Nije pronađena usluga za pronalaženje ključa davatelja resursa", "NoResourcePermissionFound": "Za trenutni resurs nije definirano nijedno dopuštenje resursa." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index f62831d36e..f736b10d7c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Biztosan törölni szeretné az összes engedélyt?", "AddResourcePermissions": "Engedélyek hozzáadása", "UpdateResourcePermissions": "Engedélyek frissítése", - "Resource": "Erőforrás", - "ResourceName": "Erőforrás neve", - "ResourceKey": "Erőforrás kulcs", - "ProviderName": "Szolgáltató neve", - "ProviderKey": "Szolgáltató kulcs", "GrantAllResourcePermissions": "Összes engedély megadása", "NoResourceProviderKeyLookupServiceFound": "Nem található erőforrás-szolgáltató kulcs kereső szolgáltatás", "NoResourcePermissionFound": "Az aktuális erőforráshoz nincs meghatározva erőforrás-engedély." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index 2c6f7b56b2..91c2bfd94e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Ertu viss um að þú viljir eyða öllum heimildum?", "AddResourcePermissions": "Bæta við heimildum", "UpdateResourcePermissions": "Uppfæra heimildir", - "Resource": "Auðlind", - "ResourceName": "Nafn auðlindar", - "ResourceKey": "Lykill auðlindar", - "ProviderName": "Nafn veitanda", - "ProviderKey": "Lykill veitanda", "GrantAllResourcePermissions": "Veita allt", "NoResourceProviderKeyLookupServiceFound": "Engin þjónusta fannst til að leita að lykli auðlindaveitanda", "NoResourcePermissionFound": "Engin heimild er skilgreind fyrir núverandi auðlind." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index 61e769a56c..abf0f82a2d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Sei sicuro di voler eliminare tutte le autorizzazioni?", "AddResourcePermissions": "Aggiungi autorizzazioni", "UpdateResourcePermissions": "Aggiorna autorizzazioni", - "Resource": "Risorsa", - "ResourceName": "Nome della risorsa", - "ResourceKey": "Chiave della risorsa", - "ProviderName": "Nome del provider", - "ProviderKey": "Chiave del provider", "GrantAllResourcePermissions": "Concedi tutto", "NoResourceProviderKeyLookupServiceFound": "Non è stato trovato alcun servizio di ricerca chiave provider risorse", "NoResourcePermissionFound": "Non è definita alcuna autorizzazione per la risorsa corrente." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index 02f6c2d76b..a702d4635f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Weet u zeker dat u alle rechten wilt verwijderen?", "AddResourcePermissions": "Rechten toevoegen", "UpdateResourcePermissions": "Rechten bijwerken", - "Resource": "Bron", - "ResourceName": "Bronnaam", - "ResourceKey": "Bronsleutel", - "ProviderName": "Naam van provider", - "ProviderKey": "Providersleutel", "GrantAllResourcePermissions": "Alles toekennen", "NoResourceProviderKeyLookupServiceFound": "Er is geen service gevonden voor het opzoeken van de sleutel van de bronprovider", "NoResourcePermissionFound": "Er is geen bronmachtiging gedefinieerd voor de huidige bron." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index 76dd154a05..bd1ca9cb2d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Czy na pewno chcesz usunąć wszystkie uprawnienia?", "AddResourcePermissions": "Dodaj uprawnienia", "UpdateResourcePermissions": "Zaktualizuj uprawnienia", - "Resource": "Zasób", - "ResourceName": "Nazwa zasobu", - "ResourceKey": "Klucz zasobu", - "ProviderName": "Nazwa dostawcy", - "ProviderKey": "Klucz dostawcy", "GrantAllResourcePermissions": "Przyznaj wszystko", "NoResourceProviderKeyLookupServiceFound": "Nie znaleziono usługi wyszukiwania klucza dostawcy zasobów", "NoResourcePermissionFound": "Nie zdefiniowano żadnych uprawnień do zasobów dla bieżącego zasobu." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index f34bd24aaf..833d923ec0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Tem certeza de que deseja excluir todas as permissões?", "AddResourcePermissions": "Adicionar permissões", "UpdateResourcePermissions": "Atualizar permissões", - "Resource": "Recurso", - "ResourceName": "Nome do recurso", - "ResourceKey": "Chave do recurso", - "ProviderName": "Nome do provedor", - "ProviderKey": "Chave do provedor", "GrantAllResourcePermissions": "Conceder tudo", "NoResourceProviderKeyLookupServiceFound": "Nenhum serviço de pesquisa de chave de provedor de recursos foi encontrado", "NoResourcePermissionFound": "Não há permissão de recurso definida para o recurso atual." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index 3944f9d497..edb6bb1ace 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Sigur doriți să ștergeți toate permisiunile?", "AddResourcePermissions": "Adăugați permisiuni", "UpdateResourcePermissions": "Actualizați permisiunile", - "Resource": "Resursă", - "ResourceName": "Numele resursei", - "ResourceKey": "Cheia resursei", - "ProviderName": "Numele furnizorului", - "ProviderKey": "Cheia furnizorului", "GrantAllResourcePermissions": "Acordați toate", "NoResourceProviderKeyLookupServiceFound": "Nu a fost găsit niciun serviciu de căutare a cheii furnizorului de resurse", "NoResourcePermissionFound": "Nu există nicio permisiune de resursă definită pentru resursa curentă." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index 63b6a76bbb..f64b3e9692 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Вы уверены, что хотите удалить все разрешения?", "AddResourcePermissions": "Добавить разрешения", "UpdateResourcePermissions": "Обновить разрешения", - "Resource": "Ресурс", - "ResourceName": "Имя ресурса", - "ResourceKey": "Ключ ресурса", - "ProviderName": "Имя поставщика", - "ProviderKey": "Ключ поставщика", "GrantAllResourcePermissions": "Предоставить все", "NoResourceProviderKeyLookupServiceFound": "Служба поиска ключа поставщика ресурсов не найдена", "NoResourcePermissionFound": "Для текущего ресурса не определено ни одного разрешения." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index a86485d896..465654eecc 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Naozaj chcete odstrániť všetky oprávnenia?", "AddResourcePermissions": "Pridať oprávnenia", "UpdateResourcePermissions": "Aktualizovať oprávnenia", - "Resource": "Zdroj", - "ResourceName": "Názov zdroja", - "ResourceKey": "Kľúč zdroja", - "ProviderName": "Názov poskytovateľa", - "ProviderKey": "Kľúč poskytovateľa", "GrantAllResourcePermissions": "Udeľ všetko", "NoResourceProviderKeyLookupServiceFound": "Nebola nájdená služba na vyhľadávanie kľúča poskytovateľa zdrojov", "NoResourcePermissionFound": "Pre aktuálny zdroj nie je definované žiadne povolenie." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index db58926ee0..c1e43c49cd 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Ali ste prepričani, da želite izbrisati vsa dovoljenja?", "AddResourcePermissions": "Dodaj dovoljenja", "UpdateResourcePermissions": "Posodobi dovoljenja", - "Resource": "Vir", - "ResourceName": "Ime vira", - "ResourceKey": "Ključ vira", - "ProviderName": "Ime ponudnika", - "ProviderKey": "Ključ ponudnika", "GrantAllResourcePermissions": "Dodeli vse", "NoResourceProviderKeyLookupServiceFound": "Ni bilo mogoče najti storitve za iskanje ključa ponudnika virov", "NoResourcePermissionFound": "Za trenutni vir ni določeno nobeno dovoljenje." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index b9b681ce0e..ba184d3f5a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Är du säker på att du vill ta bort alla behörigheter?", "AddResourcePermissions": "Lägg till behörigheter", "UpdateResourcePermissions": "Uppdatera behörigheter", - "Resource": "Resurs", - "ResourceName": "Resursnamn", - "ResourceKey": "Resursnyckel", - "ProviderName": "Leverantörsnamn", - "ProviderKey": "Leverantörsnyckel", "GrantAllResourcePermissions": "Bevilja alla", "NoResourceProviderKeyLookupServiceFound": "Ingen tjänst för att söka efter nyckel till resursleverantör hittades", "NoResourcePermissionFound": "Det finns ingen resursbehörighet definierad för den aktuella resursen." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index 93d87dc78e..b2badc4ee0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Tüm izinleri silmek istediğinizden emin misiniz?", "AddResourcePermissions": "İzinler ekle", "UpdateResourcePermissions": "İzinleri güncelle", - "Resource": "Kaynak", - "ResourceName": "Kaynak adı", - "ResourceKey": "Kaynak anahtarı", - "ProviderName": "Sağlayıcı adı", - "ProviderKey": "Sağlayıcı anahtarı", "GrantAllResourcePermissions": "Tümünü ver", "NoResourceProviderKeyLookupServiceFound": "Herhangi bir kaynak sağlayıcı anahtar arama hizmeti bulunamadı", "NoResourcePermissionFound": "Geçerli kaynak için tanımlı bir kaynak izni yok." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index afb2390bc2..895d8a6cb9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "Bạn có chắc chắn muốn xóa tất cả quyền không?", "AddResourcePermissions": "Thêm các quyền", "UpdateResourcePermissions": "Cập nhật các quyền", - "Resource": "Tài nguyên", - "ResourceName": "Tên tài nguyên", - "ResourceKey": "Khóa tài nguyên", - "ProviderName": "Tên nhà cung cấp", - "ProviderKey": "Khóa nhà cung cấp", "GrantAllResourcePermissions": "Cấp tất cả", "NoResourceProviderKeyLookupServiceFound": "Không tìm thấy dịch vụ tra cứu khóa nhà cung cấp tài nguyên", "NoResourcePermissionFound": "Không có quyền tài nguyên nào được định nghĩa cho tài nguyên hiện tại." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index 53a674d8bc..692ff0cea5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "您确定要删除所有权限吗?", "AddResourcePermissions": "添加权限", "UpdateResourcePermissions": "更新权限", - "Resource": "资源", - "ResourceName": "资源名称", - "ResourceKey": "资源键", - "ProviderName": "提供者名称", - "ProviderKey": "提供者键", "GrantAllResourcePermissions": "授予所有", "NoResourceProviderKeyLookupServiceFound": "未找到资源提供者键查找服务", "NoResourcePermissionFound": "当前资源未定义任何资源权限。" diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index 5d44c03c35..717cf83d33 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -16,11 +16,6 @@ "ResourcePermissionDeletionConfirmationMessage": "您確定要刪除所有權限嗎?", "AddResourcePermissions": "添加權限", "UpdateResourcePermissions": "更新權限", - "Resource": "資源", - "ResourceName": "資源名稱", - "ResourceKey": "資源鍵", - "ProviderName": "提供者名稱", - "ProviderKey": "提供者鍵", "GrantAllResourcePermissions": "授予所有", "NoResourceProviderKeyLookupServiceFound": "未找到資源提供者鍵查找服務", "NoResourcePermissionFound": "當前資源未定義任何資源權限。" From ce9859ac3feafa2c9d7298ab1fcb930fd8240eb8 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 12 Dec 2025 21:49:26 +0800 Subject: [PATCH 112/130] Update resource permission localization and modals --- .../Localization/Domain/ar.json | 4 ++-- .../Localization/Domain/de.json | 4 ++-- .../Localization/Domain/el.json | 4 ++-- .../Localization/Domain/en-GB.json | 4 ++-- .../Localization/Domain/en.json | 6 ++--- .../Localization/Domain/es.json | 4 ++-- .../Localization/Domain/fa.json | 4 ++-- .../Localization/Domain/fi.json | 4 ++-- .../Localization/Domain/fr.json | 4 ++-- .../Localization/Domain/hi.json | 4 ++-- .../Localization/Domain/hr.json | 4 ++-- .../Localization/Domain/hu.json | 4 ++-- .../Localization/Domain/is.json | 4 ++-- .../Localization/Domain/it.json | 4 ++-- .../Localization/Domain/nl.json | 4 ++-- .../Localization/Domain/pl-PL.json | 4 ++-- .../Localization/Domain/pt-BR.json | 4 ++-- .../Localization/Domain/ro-RO.json | 4 ++-- .../Localization/Domain/ru.json | 4 ++-- .../Localization/Domain/sk.json | 4 ++-- .../Localization/Domain/sl.json | 4 ++-- .../Localization/Domain/sv.json | 4 ++-- .../Localization/Domain/tr.json | 4 ++-- .../Localization/Domain/vi.json | 4 ++-- .../Localization/Domain/zh-Hans.json | 4 ++-- .../Localization/Domain/zh-Hant.json | 4 ++-- ...ddResourcePermissionManagementModal.cshtml | 24 ++++++------------- .../ResourcePermissionManagementModal.cshtml | 2 +- ...teResourcePermissionManagementModal.cshtml | 14 ++++------- 29 files changed, 65 insertions(+), 81 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index abdd6c0173..79dbec3a3e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "إضافة الأذونات", "UpdateResourcePermissions": "تحديث الأذونات", "GrantAllResourcePermissions": "منح الكل", - "NoResourceProviderKeyLookupServiceFound": "لم يتم العثور على خدمة البحث عن مفتاح مزود المورد", - "NoResourcePermissionFound": "لا يوجد إذن مورد معرف للموارد الحالية." + "NoResourceProviderKeyLookupServiceFound": "لم يتم العثور على خدمة البحث عن مفتاح المزود", + "NoResourcePermissionFound": "لا توجد أي أذونات محددة." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index b854ed86b5..d08b78e925 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Berechtigungen hinzufügen", "UpdateResourcePermissions": "Berechtigungen aktualisieren", "GrantAllResourcePermissions": "Alle gewähren", - "NoResourceProviderKeyLookupServiceFound": "Es wurde kein Dienst zum Nachschlagen des Ressourcenschlüsselanbieters gefunden", - "NoResourcePermissionFound": "Es ist keine Ressourcenberechtigung für die aktuelle Ressource definiert." + "NoResourceProviderKeyLookupServiceFound": "Es wurde kein Dienst zum Nachschlagen des Anbieterschlüssels gefunden", + "NoResourcePermissionFound": "Es ist keine Berechtigung definiert." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index 0d76102fb9..63eb0668c1 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Προσθήκη δικαιωμάτων", "UpdateResourcePermissions": "Ενημέρωση δικαιωμάτων", "GrantAllResourcePermissions": "Παραχώρηση όλων", - "NoResourceProviderKeyLookupServiceFound": "Δεν βρέθηκε υπηρεσία αναζήτησης κλειδιού παρόχου πόρων", - "NoResourcePermissionFound": "Δεν έχει οριστεί καμία άδεια πόρου για τον τρέχοντα πόρο." + "NoResourceProviderKeyLookupServiceFound": "Δεν βρέθηκε υπηρεσία αναζήτησης κλειδιού παρόχου", + "NoResourcePermissionFound": "Δεν έχει οριστεί καμία άδεια." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index b445d536df..eebae2dc62 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Add permissions", "UpdateResourcePermissions": "Update permissions", "GrantAllResourcePermissions": "Grant all", - "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found", - "NoResourcePermissionFound": "There is no resource permission defined for the current resource." + "NoResourceProviderKeyLookupServiceFound": "There is no provider key lookup service was found", + "NoResourcePermissionFound": "There is no permission defined." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index 530a35cd7d..b1ea4b37fd 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -12,12 +12,12 @@ "ResourcePermissions": "Permissions", "ResourcePermissionTarget": "Target", "ResourcePermissionPermissions": "Permissions", - "AddResourcePermission": "Add resource permissions", + "AddResourcePermission": "Add permission", "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete all permissions?", "AddResourcePermissions": "Add permissions", "UpdateResourcePermissions": "Update permissions", "GrantAllResourcePermissions": "Grant all", - "NoResourceProviderKeyLookupServiceFound": "There is no resource provider key lookup service was found", - "NoResourcePermissionFound": "There is no resource permission defined for the current resource." + "NoResourceProviderKeyLookupServiceFound": "There is no provider key lookup service was found", + "NoResourcePermissionFound": "There is no permission defined." } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index 59354f55b5..f62d7fdba0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Agregar permisos", "UpdateResourcePermissions": "Actualizar permisos", "GrantAllResourcePermissions": "Conceder todos", - "NoResourceProviderKeyLookupServiceFound": "No se encontró ningún servicio de búsqueda de clave de proveedor de recursos", - "NoResourcePermissionFound": "No hay ningún permiso de recurso definido para el recurso actual." + "NoResourceProviderKeyLookupServiceFound": "No se encontró ningún servicio de búsqueda de clave de proveedor", + "NoResourcePermissionFound": "No hay ningún permiso definido." } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index 8da28b6357..d025e2403d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "افزودن مجوزها", "UpdateResourcePermissions": "به‌روزرسانی مجوزها", "GrantAllResourcePermissions": "اعطای همه", - "NoResourceProviderKeyLookupServiceFound": "هیچ سرویس جستجوی کلید ارائه‌دهنده منبع یافت نشد", - "NoResourcePermissionFound": "هیچ مجوز منبعی برای منبع فعلی تعریف نشده است." + "NoResourceProviderKeyLookupServiceFound": "هیچ سرویس جستجوی کلید ارائه‌دهنده یافت نشد", + "NoResourcePermissionFound": "هیچ مجوزی تعریف نشده است." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index 4500c0132a..60c6f682d4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Lisää käyttöoikeudet", "UpdateResourcePermissions": "Päivitä käyttöoikeudet", "GrantAllResourcePermissions": "Myönnä kaikki", - "NoResourceProviderKeyLookupServiceFound": "Resurssin tarjoajan avaimen hakupalvelua ei löytynyt", - "NoResourcePermissionFound": "Nykyiselle resurssille ei ole määritetty käyttöoikeutta." + "NoResourceProviderKeyLookupServiceFound": "Palveluntarjoajan avaimen hakupalvelua ei löytynyt", + "NoResourcePermissionFound": "Ei käyttöoikeuksia määritetty." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index 7a7bd7501f..30bf227119 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Ajouter des autorisations", "UpdateResourcePermissions": "Mettre à jour les autorisations", "GrantAllResourcePermissions": "Accorder tout", - "NoResourceProviderKeyLookupServiceFound": "Aucun service de recherche de clé de fournisseur de ressources n'a été trouvé", - "NoResourcePermissionFound": "Aucune autorisation de ressource n'est définie pour la ressource actuelle." + "NoResourceProviderKeyLookupServiceFound": "Aucun service de recherche de clé de fournisseur n'a été trouvé", + "NoResourcePermissionFound": "Aucune autorisation n'est définie." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index 42307d4f96..4523cce321 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "अनुमतियां जोड़ें", "UpdateResourcePermissions": "अनुमतियां अपडेट करें", "GrantAllResourcePermissions": "सभी प्रदान करें", - "NoResourceProviderKeyLookupServiceFound": "कोई संसाधन प्रदाता कुंजी खोज सेवा नहीं मिली", - "NoResourcePermissionFound": "वर्तमान संसाधन के लिए कोई संसाधन अनुमति परिभाषित नहीं है।" + "NoResourceProviderKeyLookupServiceFound": "कोई प्रदाता कुंजी खोज सेवा नहीं मिली", + "NoResourcePermissionFound": "कोई अनुमति परिभाषित नहीं है।" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index d7c436b798..ff1f097592 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Dodaj dozvole", "UpdateResourcePermissions": "Ažuriraj dozvole", "GrantAllResourcePermissions": "Dodijeli sve", - "NoResourceProviderKeyLookupServiceFound": "Nije pronađena usluga za pronalaženje ključa davatelja resursa", - "NoResourcePermissionFound": "Za trenutni resurs nije definirano nijedno dopuštenje resursa." + "NoResourceProviderKeyLookupServiceFound": "Nije pronađena usluga za pronalaženje ključa pružatelja", + "NoResourcePermissionFound": "Nijedna dozvola nije definirana." } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index f736b10d7c..48ef87202d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Engedélyek hozzáadása", "UpdateResourcePermissions": "Engedélyek frissítése", "GrantAllResourcePermissions": "Összes engedély megadása", - "NoResourceProviderKeyLookupServiceFound": "Nem található erőforrás-szolgáltató kulcs kereső szolgáltatás", - "NoResourcePermissionFound": "Az aktuális erőforráshoz nincs meghatározva erőforrás-engedély." + "NoResourceProviderKeyLookupServiceFound": "Nem található szolgáltató kulcs kereső szolgáltatás", + "NoResourcePermissionFound": "Nincs meghatározva engedély." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index 91c2bfd94e..fd4b43fde5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Bæta við heimildum", "UpdateResourcePermissions": "Uppfæra heimildir", "GrantAllResourcePermissions": "Veita allt", - "NoResourceProviderKeyLookupServiceFound": "Engin þjónusta fannst til að leita að lykli auðlindaveitanda", - "NoResourcePermissionFound": "Engin heimild er skilgreind fyrir núverandi auðlind." + "NoResourceProviderKeyLookupServiceFound": "Engin þjónusta fannst til að leita að lykli veitanda", + "NoResourcePermissionFound": "Engin heimild er skilgreind." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index abf0f82a2d..39893a8dbc 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Aggiungi autorizzazioni", "UpdateResourcePermissions": "Aggiorna autorizzazioni", "GrantAllResourcePermissions": "Concedi tutto", - "NoResourceProviderKeyLookupServiceFound": "Non è stato trovato alcun servizio di ricerca chiave provider risorse", - "NoResourcePermissionFound": "Non è definita alcuna autorizzazione per la risorsa corrente." + "NoResourceProviderKeyLookupServiceFound": "Non è stato trovato alcun servizio di ricerca chiave del provider", + "NoResourcePermissionFound": "Non è definita alcuna autorizzazione." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index a702d4635f..c3eeb2bc60 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Rechten toevoegen", "UpdateResourcePermissions": "Rechten bijwerken", "GrantAllResourcePermissions": "Alles toekennen", - "NoResourceProviderKeyLookupServiceFound": "Er is geen service gevonden voor het opzoeken van de sleutel van de bronprovider", - "NoResourcePermissionFound": "Er is geen bronmachtiging gedefinieerd voor de huidige bron." + "NoResourceProviderKeyLookupServiceFound": "Er is geen service gevonden voor het opzoeken van de sleutel van de provider", + "NoResourcePermissionFound": "Er is geen machtiging gedefinieerd." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index bd1ca9cb2d..de16903dd0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Dodaj uprawnienia", "UpdateResourcePermissions": "Zaktualizuj uprawnienia", "GrantAllResourcePermissions": "Przyznaj wszystko", - "NoResourceProviderKeyLookupServiceFound": "Nie znaleziono usługi wyszukiwania klucza dostawcy zasobów", - "NoResourcePermissionFound": "Nie zdefiniowano żadnych uprawnień do zasobów dla bieżącego zasobu." + "NoResourceProviderKeyLookupServiceFound": "Nie znaleziono usługi wyszukiwania klucza dostawcy", + "NoResourcePermissionFound": "Nie zdefiniowano żadnych uprawnień." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index 833d923ec0..fbb2351f54 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Adicionar permissões", "UpdateResourcePermissions": "Atualizar permissões", "GrantAllResourcePermissions": "Conceder tudo", - "NoResourceProviderKeyLookupServiceFound": "Nenhum serviço de pesquisa de chave de provedor de recursos foi encontrado", - "NoResourcePermissionFound": "Não há permissão de recurso definida para o recurso atual." + "NoResourceProviderKeyLookupServiceFound": "Nenhum serviço de pesquisa de chave do provedor foi encontrado", + "NoResourcePermissionFound": "Nenhuma permissão foi definida." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index edb6bb1ace..87a764d599 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Adăugați permisiuni", "UpdateResourcePermissions": "Actualizați permisiunile", "GrantAllResourcePermissions": "Acordați toate", - "NoResourceProviderKeyLookupServiceFound": "Nu a fost găsit niciun serviciu de căutare a cheii furnizorului de resurse", - "NoResourcePermissionFound": "Nu există nicio permisiune de resursă definită pentru resursa curentă." + "NoResourceProviderKeyLookupServiceFound": "Nu a fost găsit niciun serviciu de căutare a cheii furnizorului", + "NoResourcePermissionFound": "Nu există nicio permisiune definită." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index f64b3e9692..dcbfeaaf08 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Добавить разрешения", "UpdateResourcePermissions": "Обновить разрешения", "GrantAllResourcePermissions": "Предоставить все", - "NoResourceProviderKeyLookupServiceFound": "Служба поиска ключа поставщика ресурсов не найдена", - "NoResourcePermissionFound": "Для текущего ресурса не определено ни одного разрешения." + "NoResourceProviderKeyLookupServiceFound": "Служба поиска ключа поставщика не найдена", + "NoResourcePermissionFound": "Не определено ни одного разрешения." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index 465654eecc..fb4425f148 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Pridať oprávnenia", "UpdateResourcePermissions": "Aktualizovať oprávnenia", "GrantAllResourcePermissions": "Udeľ všetko", - "NoResourceProviderKeyLookupServiceFound": "Nebola nájdená služba na vyhľadávanie kľúča poskytovateľa zdrojov", - "NoResourcePermissionFound": "Pre aktuálny zdroj nie je definované žiadne povolenie." + "NoResourceProviderKeyLookupServiceFound": "Nebola nájdená služba na vyhľadávanie kľúča poskytovateľa", + "NoResourcePermissionFound": "Nie je definované žiadne povolenie." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index c1e43c49cd..ca919b069b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Dodaj dovoljenja", "UpdateResourcePermissions": "Posodobi dovoljenja", "GrantAllResourcePermissions": "Dodeli vse", - "NoResourceProviderKeyLookupServiceFound": "Ni bilo mogoče najti storitve za iskanje ključa ponudnika virov", - "NoResourcePermissionFound": "Za trenutni vir ni določeno nobeno dovoljenje." + "NoResourceProviderKeyLookupServiceFound": "Ni bilo mogoče najti storitve za iskanje ključa ponudnika", + "NoResourcePermissionFound": "Nobeno dovoljenje ni določeno." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index ba184d3f5a..4896e51547 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Lägg till behörigheter", "UpdateResourcePermissions": "Uppdatera behörigheter", "GrantAllResourcePermissions": "Bevilja alla", - "NoResourceProviderKeyLookupServiceFound": "Ingen tjänst för att söka efter nyckel till resursleverantör hittades", - "NoResourcePermissionFound": "Det finns ingen resursbehörighet definierad för den aktuella resursen." + "NoResourceProviderKeyLookupServiceFound": "Ingen tjänst för att söka efter leverantörsnyckel hittades", + "NoResourcePermissionFound": "Ingen behörighet är definierad." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index b2badc4ee0..e1925fe8da 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "İzinler ekle", "UpdateResourcePermissions": "İzinleri güncelle", "GrantAllResourcePermissions": "Tümünü ver", - "NoResourceProviderKeyLookupServiceFound": "Herhangi bir kaynak sağlayıcı anahtar arama hizmeti bulunamadı", - "NoResourcePermissionFound": "Geçerli kaynak için tanımlı bir kaynak izni yok." + "NoResourceProviderKeyLookupServiceFound": "Herhangi bir sağlayıcı anahtar arama hizmeti bulunamadı", + "NoResourcePermissionFound": "Herhangi bir izin tanımlı değil." } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index 895d8a6cb9..7359377b59 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "Thêm các quyền", "UpdateResourcePermissions": "Cập nhật các quyền", "GrantAllResourcePermissions": "Cấp tất cả", - "NoResourceProviderKeyLookupServiceFound": "Không tìm thấy dịch vụ tra cứu khóa nhà cung cấp tài nguyên", - "NoResourcePermissionFound": "Không có quyền tài nguyên nào được định nghĩa cho tài nguyên hiện tại." + "NoResourceProviderKeyLookupServiceFound": "Không tìm thấy dịch vụ tra cứu khóa nhà cung cấp", + "NoResourcePermissionFound": "Không có quyền nào được định nghĩa." } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index 692ff0cea5..548d8ac2ad 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "添加权限", "UpdateResourcePermissions": "更新权限", "GrantAllResourcePermissions": "授予所有", - "NoResourceProviderKeyLookupServiceFound": "未找到资源提供者键查找服务", - "NoResourcePermissionFound": "当前资源未定义任何资源权限。" + "NoResourceProviderKeyLookupServiceFound": "未找到提供者键查找服务", + "NoResourcePermissionFound": "未定义任何权限。" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index 717cf83d33..2e114480fb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -17,7 +17,7 @@ "AddResourcePermissions": "添加權限", "UpdateResourcePermissions": "更新權限", "GrantAllResourcePermissions": "授予所有", - "NoResourceProviderKeyLookupServiceFound": "未找到資源提供者鍵查找服務", - "NoResourcePermissionFound": "當前資源未定義任何資源權限。" + "NoResourceProviderKeyLookupServiceFound": "未找到提供者鍵查找服務", + "NoResourcePermissionFound": "未定義任何權限。" } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index b56371d70d..97f9ce26b2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -16,39 +16,29 @@
- +
- -

@Model.ResourceDisplayName

-
-
-
@foreach (var provider in Model.ResourceProviders.Providers.Select((value, i) => new { i, value })) {
- +
}
-
-
- - +
-
- -
- - -
+
@L["ResourcePermissionPermissions"]
+
+ +
@foreach (var permission in Model.ResourcePermissionDefinitions.Permissions) { diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml index 54280753b3..535bee6634 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml @@ -18,7 +18,7 @@ - + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml index 87f26f1a2f..b00c53e70d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml @@ -22,17 +22,11 @@ -
- -

@Model.ResourceDisplayName

-
-
- -
- - -
+
@L["ResourcePermissionPermissions"]
+
+ +
@foreach (var permission in Model.ResourcePermissions.Permissions) { From 87632e830e21ddc01347de62cd7f0f89bbdcb575 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 13 Dec 2025 11:33:28 +0800 Subject: [PATCH 113/130] Improve resource permission modal UX and fix heading --- ...ddResourcePermissionManagementModal.cshtml | 2 +- ...teResourcePermissionManagementModal.cshtml | 2 +- ...dd-resource-permission-management-modal.js | 30 ++++++++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index 97f9ce26b2..5ed12d9d62 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -35,7 +35,7 @@
-
@L["ResourcePermissionPermissions"]
+

@L["ResourcePermissionPermissions"]

diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml index b00c53e70d..5aee69f264 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml @@ -23,7 +23,7 @@
-
@L["ResourcePermissionPermissions"]
+

@L["ResourcePermissionPermissions"]

diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js index 85154e4086..85613044a7 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -54,10 +54,38 @@ var abp = abp || {}; if ($providerKey.val()) { $permissionManagementForm.valid(); } + var providerKey = $providerKey.val(); + if (!providerKey) { + $items.prop('checked', false); + $all.prop("checked", false); + return; + } + + $items.prop('disabled', true); + var resourceName = $("#ResourceName").val(); + var resourceKey = $("#ResourceKey").val(); + var providerName = $('input[name="AddModel.ProviderName"]:checked').val(); + volo.abp.permissionManagement.permissions.getResourceByProvider(resourceName, resourceKey, providerName, providerKey).then(function (result) { + $items.prop('disabled', false); + var grantedPermissionNames = result.permissions.filter(function (p) { + return p.isGranted === true; + }).map(function (p) { + return p.name; + }); + $items.each(function () { + var $checkbox = $(this); + if (grantedPermissionNames.indexOf($checkbox.val()) >= 0) { + $checkbox.prop('checked', true); + } else { + $checkbox.prop('checked', false); + } + }); + $all.prop("checked", $items.length === $items.filter(":checked").length); + }); }); $permissionManagementForm.submit(function () { - $(this).valid(); + $(this).valid(); }); })(jQuery); From 534695d57c23679a2be32a099fa3c5a925de8e77 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 13 Dec 2025 14:01:48 +0800 Subject: [PATCH 114/130] Refactor role search methods to use names instead of IDs and update related components --- .../IIdentityUserIntegrationService.cs | 2 +- .../IdentityUserIntegrationService.cs | 4 +-- .../Volo/Abp/Identity/IUserRoleFinder.cs | 2 +- .../Abp/Identity/IIdentityRoleRepository.cs | 6 +++++ .../Volo/Abp/Identity/UserRoleFinder.cs | 4 +-- .../EfCoreIdentityRoleRepository.cs | 7 +++++ ...ityUserIntegrationClientProxy.Generated.cs | 6 ++--- .../identity-generate-proxy.json | 26 +++++++++---------- .../Abp/Identity/HttpClientUserRoleFinder.cs | 4 +-- .../IdentityUserIntegrationController.cs | 6 ++--- .../MongoDB/MongoIdentityRoleRepository.cs | 7 +++++ .../wwwroot/client-proxies/identity-proxy.js | 6 ++--- ...ourcePermissionProviderKeyLookupService.cs | 8 +----- ...ourcePermissionProviderKeyLookupService.cs | 1 + .../Volo/Abp/Identity/UserRoleFinder_Tests.cs | 4 +-- .../ResourcePermissionGrantInfoDto.cs | 2 ++ .../PermissionAppService.cs | 1 + .../PermissionProviderWithPermissions.cs | 3 +++ .../ResourcePermissionManager.cs | 6 +++-- ...ddResourcePermissionManagementModal.cshtml | 2 +- .../ResourcePermissionManagementModal.cshtml | 1 - ...esourcePermissionManagementModal.cshtml.cs | 4 --- ...dd-resource-permission-management-modal.js | 4 +-- .../resource-permission-management-modal.js | 2 +- 24 files changed, 68 insertions(+), 50 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs index 3f15309077..3a61407ddf 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Integration/IIdentityUserIntegrationService.cs @@ -23,7 +23,7 @@ public interface IIdentityUserIntegrationService : IApplicationService Task> SearchRoleAsync(RoleLookupSearchInputDto input); - Task> SearchRoleByIdsAsync(Guid[] ids); + Task> SearchRoleByNamesAsync(string[] ids); Task GetRoleCountAsync(RoleLookupCountInputDto input); } diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs index 6745bc8837..4019cd9031 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/Integration/IdentityUserIntegrationService.cs @@ -105,11 +105,11 @@ public class IdentityUserIntegrationService : IdentityAppServiceBase, IIdentityU } } - public virtual async Task> SearchRoleByIdsAsync(Guid[] ids) + public virtual async Task> SearchRoleByNamesAsync(string[] names) { using (RoleRepository.DisableTracking()) { - var roles = await RoleRepository.GetListAsync(ids); + var roles = await RoleRepository.GetListAsync(names); return new ListResultDto(roles.Select(r => new RoleData(r.Id, r.Name, r.IsDefault, r.IsStatic, r.IsPublic, r.TenantId, r.ExtraProperties)).ToList()); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs index 166d066245..75261b2cc4 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IUserRoleFinder.cs @@ -18,5 +18,5 @@ public interface IUserRoleFinder Task> SearchUserByIdsAsync(Guid[] ids); - Task> SearchRoleByIdsAsync(Guid[] ids); + Task> SearchRoleByNamesAsync(string[] names); } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs index a9eefe019e..26bd8624b9 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs @@ -31,11 +31,17 @@ public interface IIdentityRoleRepository : IBasicRepository bool includeDetails = false, CancellationToken cancellationToken = default ); + Task> GetListAsync( IEnumerable ids, CancellationToken cancellationToken = default ); + Task> GetListAsync( + IEnumerable names, + CancellationToken cancellationToken = default + ); + Task> GetDefaultOnesAsync( bool includeDetails = false, CancellationToken cancellationToken = default diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs index a958ac1456..48d8b01a73 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserRoleFinder.cs @@ -70,11 +70,11 @@ public class UserRoleFinder : IUserRoleFinder, ITransientDependency } } - public virtual async Task> SearchRoleByIdsAsync(Guid[] ids) + public virtual async Task> SearchRoleByNamesAsync(string[] names) { using (IdentityUserRepository.DisableTracking()) { - var roles = await IdentityRoleRepository.GetListAsync(ids); + var roles = await IdentityRoleRepository.GetListAsync(names); return roles.Select(user => new RoleFinderResult { Id = user.Id, diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs index ca6dc42d6f..1da7758f19 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs @@ -72,6 +72,13 @@ public class EfCoreIdentityRoleRepository : EfCoreRepository> GetListAsync(IEnumerable names, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(t => names.Contains(t.Name)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + public virtual async Task> GetDefaultOnesAsync( bool includeDetails = false, CancellationToken cancellationToken = default) { diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs index aa91f7d6cd..ddbb2cfc55 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/Volo/Abp/Identity/Integration/IdentityUserIntegrationClientProxy.Generated.cs @@ -76,11 +76,11 @@ public partial class IdentityUserIntegrationClientProxy : ClientProxyBase> SearchRoleByIdsAsync(Guid[] ids) + public virtual async Task> SearchRoleByNamesAsync(String[] names) { - return await RequestAsync>(nameof(SearchRoleByIdsAsync), new ClientProxyRequestTypeValue + return await RequestAsync>(nameof(SearchRoleByNamesAsync), new ClientProxyRequestTypeValue { - { typeof(Guid[]), ids } + { typeof(String[]), names } }); } diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json index 3977808e84..3937d99aa9 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/ClientProxies/identity-generate-proxy.json @@ -1436,12 +1436,12 @@ } }, { - "name": "SearchRoleByIdsAsync", + "name": "SearchRoleByNamesAsync", "parametersOnMethod": [ { "name": "ids", - "typeAsString": "System.Guid[], System.Private.CoreLib", - "type": "System.Guid[]", + "typeAsString": "System.String[], System.Private.CoreLib", + "type": "System.String[]", "typeSimple": "[string]", "isOptional": false, "defaultValue": null @@ -1828,17 +1828,17 @@ "allowAnonymous": null, "implementFrom": "Volo.Abp.Identity.Integration.IIdentityUserIntegrationService" }, - "SearchRoleByIdsAsyncByIds": { - "uniqueName": "SearchRoleByIdsAsyncByIds", - "name": "SearchRoleByIdsAsync", + "SearchRoleByNamesAsyncByNames": { + "uniqueName": "SearchRoleByNamesAsyncByNames", + "name": "SearchRoleByNamesAsync", "httpMethod": "GET", - "url": "integration-api/identity/users/search/roles/by-ids", + "url": "integration-api/identity/users/search/roles/by-names", "supportedVersions": [], "parametersOnMethod": [ { - "name": "ids", - "typeAsString": "System.Guid[], System.Private.CoreLib", - "type": "System.Guid[]", + "name": "names", + "typeAsString": "System.String[], System.Private.CoreLib", + "type": "System.String[]", "typeSimple": "[string]", "isOptional": false, "defaultValue": null @@ -1846,10 +1846,10 @@ ], "parameters": [ { - "nameOnMethod": "ids", - "name": "ids", + "nameOnMethod": "names", + "name": "names", "jsonName": null, - "type": "System.Guid[]", + "type": "System.String[]", "typeSimple": "[string]", "isOptional": false, "defaultValue": null, diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs index 6c32d8b11d..ed7f0f5382 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientUserRoleFinder.cs @@ -72,9 +72,9 @@ public class HttpClientUserRoleFinder : IUserRoleFinder, ITransientDependency }).ToList(); } - public virtual async Task> SearchRoleByIdsAsync(Guid[] ids) + public virtual async Task> SearchRoleByNamesAsync(string[] names) { - var roles = await _userIntegrationService.SearchRoleByIdsAsync(ids); + var roles = await _userIntegrationService.SearchRoleByNamesAsync(names); return roles.Items.Select(r => new RoleFinderResult { Id = r.Id, diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs index dba6c4f253..a30b73d5cd 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/Integration/IdentityUserIntegrationController.cs @@ -71,10 +71,10 @@ public class IdentityUserIntegrationController : AbpControllerBase, IIdentityUse } [HttpGet] - [Route("search/roles/by-ids")] - public virtual Task> SearchRoleByIdsAsync(Guid[] ids) + [Route("search/roles/by-names")] + public virtual Task> SearchRoleByNamesAsync(string[] names) { - return UserIntegrationService.SearchRoleByIdsAsync(ids); + return UserIntegrationService.SearchRoleByNamesAsync(names); } [HttpGet] diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs index 7deee94160..f613a3ee88 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs @@ -78,6 +78,13 @@ public class MongoIdentityRoleRepository : MongoDbRepository> GetListAsync(IEnumerable names, CancellationToken cancellationToken = default) + { + return await (await GetQueryableAsync(cancellationToken)) + .Where(x => names.Contains(x.Name)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + public virtual async Task> GetDefaultOnesAsync( bool includeDetails = false, CancellationToken cancellationToken = default) diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/client-proxies/identity-proxy.js b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/client-proxies/identity-proxy.js index 250abbc47b..6de3aeb8d5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/client-proxies/identity-proxy.js +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/client-proxies/identity-proxy.js @@ -20,7 +20,7 @@ volo.abp.identity.identityRole.getList = function(input, ajaxParams) { return abp.ajax($.extend(true, { - url: abp.appPath + 'api/identity/roles' + abp.utils.buildQueryString([{ name: 'filter', value: input.filter }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }]) + '', + url: abp.appPath + 'api/identity/roles' + abp.utils.buildQueryString([{ name: 'filter', value: input.filter }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }, { name: 'extraProperties', value: input.extraProperties }]) + '', type: 'GET' }, ajaxParams)); }; @@ -73,7 +73,7 @@ volo.abp.identity.identityUser.getList = function(input, ajaxParams) { return abp.ajax($.extend(true, { - url: abp.appPath + 'api/identity/users' + abp.utils.buildQueryString([{ name: 'filter', value: input.filter }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }]) + '', + url: abp.appPath + 'api/identity/users' + abp.utils.buildQueryString([{ name: 'filter', value: input.filter }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }, { name: 'extraProperties', value: input.extraProperties }]) + '', type: 'GET' }, ajaxParams)); }; @@ -163,7 +163,7 @@ volo.abp.identity.identityUserLookup.search = function(input, ajaxParams) { return abp.ajax($.extend(true, { - url: abp.appPath + 'api/identity/users/lookup/search' + abp.utils.buildQueryString([{ name: 'filter', value: input.filter }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }]) + '', + url: abp.appPath + 'api/identity/users/lookup/search' + abp.utils.buildQueryString([{ name: 'filter', value: input.filter }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }, { name: 'extraProperties', value: input.extraProperties }]) + '', type: 'GET' }, ajaxParams)); }; diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs index 6b198558fc..fa9fc31f79 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleResourcePermissionProviderKeyLookupService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -33,12 +32,7 @@ public class RoleResourcePermissionProviderKeyLookupService : IResourcePermissio public virtual async Task> SearchAsync(string[] keys, CancellationToken cancellationToken = default) { - var ids = keys - .Select(key => Guid.TryParse(key, out var id) ? (Guid?)id : null) - .Where(id => id.HasValue) - .Select(id => id.Value) - .ToArray(); - var roles = await UserRoleFinder.SearchRoleByIdsAsync(ids.ToArray()); + var roles = await UserRoleFinder.SearchRoleByNamesAsync(keys.Distinct().ToArray()); return roles.Select(r => new ResourcePermissionProviderKeyInfo(r.RoleName, r.RoleName)).ToList(); } } diff --git a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs index 3e5470f432..83f0e79099 100644 --- a/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs +++ b/modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/UserResourcePermissionProviderKeyLookupService.cs @@ -37,6 +37,7 @@ public class UserResourcePermissionProviderKeyLookupService : IResourcePermissio .Select(key => Guid.TryParse(key, out var id) ? (Guid?)id : null) .Where(id => id.HasValue) .Select(id => id.Value) + .Distinct() .ToArray(); var users = await UserRoleFinder.SearchUserByIdsAsync(ids.ToArray()); return users.Select(u => new ResourcePermissionProviderKeyInfo(u.Id.ToString(), u.UserName)).ToList(); diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/UserRoleFinder_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/UserRoleFinder_Tests.cs index cd36fb3225..56f9049b92 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/UserRoleFinder_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/UserRoleFinder_Tests.cs @@ -51,9 +51,9 @@ public class UserRoleFinder_Tests : AbpIdentityDomainTestBase } [Fact] - public async Task SearchRoleByIdsAsync() + public async Task SearchRoleByNamesAsync() { - var roleResults = await _userRoleFinder.SearchRoleByIdsAsync(new[] { _testData.RoleModeratorId, _testData.RoleManagerId }); + var roleResults = await _userRoleFinder.SearchRoleByNamesAsync(new[] { "moderator", "manager" }); roleResults.ShouldNotBeEmpty(); roleResults.Count.ShouldBe(2); roleResults.ShouldContain(x => x.RoleName == "moderator"); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs index fd7fb3a8b1..cf286815a5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/ResourcePermissionGrantInfoDto.cs @@ -10,5 +10,7 @@ public class ResourcePermissionGrantInfoDto public string ProviderDisplayName { get; set; } + public string ProviderNameDisplayName { get; set; } + public List Permissions { get; set; } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 89e68f02de..83dd815984 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -259,6 +259,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService ProviderName = resourcePermissionGrant.ProviderName, ProviderKey = resourcePermissionGrant.ProviderKey, ProviderDisplayName = resourcePermissionGrant.ProviderDisplayName, + ProviderNameDisplayName = resourcePermissionGrant.ProviderNameDisplayName?.Localize(StringLocalizerFactory), Permissions = new List() }; foreach (var permission in resourcePermissionGrant.Permissions) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs index 0443d54891..a6507ef0bc 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionProviderWithPermissions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Volo.Abp.Localization; namespace Volo.Abp.PermissionManagement; @@ -10,6 +11,8 @@ public class PermissionProviderWithPermissions public string ProviderDisplayName { get; set; } + public ILocalizableString ProviderNameDisplayName { get; set; } + public List Permissions { get; set; } public PermissionProviderWithPermissions(string providerName, string providerKey, string providerDisplayName) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs index 1561d5adac..172f326a9a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/ResourcePermissionManager.cs @@ -217,7 +217,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD { continue; } - var keys = resourcePermissionProvider.Select(rp => rp.ProviderKey).ToList(); + var keys = resourcePermissionProvider.Select(rp => rp.ProviderKey).Distinct().ToList(); providerKeyInfos.Add(resourcePermissionProvider.Key, await providerKeyLookupService.SearchAsync(keys.ToArray())); } @@ -232,6 +232,8 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD if (providerKeyInfo != null) { item.ProviderDisplayName = providerKeyInfo.ProviderDisplayName; + item.ProviderNameDisplayName = providerKeyLookupServices + .FirstOrDefault(s => s.Name == item.ProviderName)?.DisplayName; } } } @@ -374,7 +376,7 @@ public class ResourcePermissionManager : IResourcePermissionManager, ISingletonD return multiplePermissionWithGrantedProviders; } - foreach (var provider in ManagementProviders) + foreach (var provider in ManagementProviders.Where(x => x.Name == providerName)) { permissionNames = resourcePermissions.Select(x => x.Name).ToArray(); var multiplePermissionValueProviderGrantInfo = await provider.CheckAsync(permissionNames, resourceName, resourceKey, providerName, providerKey); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index 5ed12d9d62..b9b7f9340a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -34,7 +34,7 @@
-
+

@L["ResourcePermissionPermissions"]

diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml index 535bee6634..d594200866 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml @@ -21,7 +21,6 @@ -
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs index 1b1373e017..28d0afeec0 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml.cs @@ -18,10 +18,6 @@ public class ResourcePermissionManagementModal : AbpPageModel [BindProperty(SupportsGet = true)] public string ResourceKey { get; set; } - [HiddenInput] - [BindProperty(SupportsGet = true)] - public string ResourceDisplayName { get; set; } - public bool HasAnyResourcePermission { get; set; } public bool HasAnyResourceProviderKeyLookupService { get; set; } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js index 85613044a7..92cbf9620b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/add-resource-permission-management-modal.js @@ -61,12 +61,12 @@ var abp = abp || {}; return; } - $items.prop('disabled', true); + abp.ui.setBusy('#permissionList'); var resourceName = $("#ResourceName").val(); var resourceKey = $("#ResourceKey").val(); var providerName = $('input[name="AddModel.ProviderName"]:checked').val(); volo.abp.permissionManagement.permissions.getResourceByProvider(resourceName, resourceKey, providerName, providerKey).then(function (result) { - $items.prop('disabled', false); + abp.ui.clearBusy(); var grantedPermissionNames = result.permissions.filter(function (p) { return p.isGranted === true; }).map(function (p) { diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js index 713bc0ab96..8d79ca8bce 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/resource-permission-management-modal.js @@ -60,7 +60,7 @@ var abp = abp || {}; title: l("ResourcePermissionTarget"), data: 'providerName', render: function (data, type, row) { - return '' + row.providerName + '' + row.providerDisplayName; + return '' + row.providerName + '' + row.providerDisplayName; }, }, { From 960f171e5748caa3b80229c33452c5096aa4bf08 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 13 Dec 2025 14:22:29 +0800 Subject: [PATCH 115/130] Sanitize HTML id attributes by removing dots --- .../AddResourcePermissionManagementModal.cshtml | 8 ++++---- .../UpdateResourcePermissionManagementModal.cshtml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml index b9b7f9340a..be12c7cd1d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/AddResourcePermissionManagementModal.cshtml @@ -26,8 +26,8 @@ @foreach (var provider in Model.ResourceProviders.Providers.Select((value, i) => new { i, value })) {
- - + +
}
@@ -43,8 +43,8 @@ @foreach (var permission in Model.ResourcePermissionDefinitions.Permissions) {
- - + +
}
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml index 5aee69f264..59b7f55236 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml @@ -31,8 +31,8 @@ @foreach (var permission in Model.ResourcePermissions.Permissions) {
- - + +
}
From 027243439a9ca8ff7eb5972c73e48559b63f1fa2 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 13 Dec 2025 15:19:49 +0800 Subject: [PATCH 116/130] Unify resource permission modal titles and localization --- .../ResourcePermissionManagementModal.razor | 169 +++++++++--------- ...ResourcePermissionManagementModal.razor.cs | 22 ++- .../Localization/Domain/ar.json | 3 +- .../Localization/Domain/cs.json | 3 +- .../Localization/Domain/de.json | 3 +- .../Localization/Domain/el.json | 3 +- .../Localization/Domain/en-GB.json | 3 +- .../Localization/Domain/en.json | 3 +- .../Localization/Domain/es.json | 3 +- .../Localization/Domain/fa.json | 3 +- .../Localization/Domain/fi.json | 3 +- .../Localization/Domain/fr.json | 3 +- .../Localization/Domain/hi.json | 3 +- .../Localization/Domain/hr.json | 41 +++-- .../Localization/Domain/hu.json | 3 +- .../Localization/Domain/is.json | 3 +- .../Localization/Domain/it.json | 3 +- .../Localization/Domain/nl.json | 3 +- .../Localization/Domain/pl-PL.json | 3 +- .../Localization/Domain/pt-BR.json | 3 +- .../Localization/Domain/ro-RO.json | 3 +- .../Localization/Domain/ru.json | 3 +- .../Localization/Domain/sk.json | 3 +- .../Localization/Domain/sl.json | 3 +- .../Localization/Domain/sv.json | 3 +- .../Localization/Domain/tr.json | 3 +- .../Localization/Domain/vi.json | 3 +- .../Localization/Domain/zh-Hans.json | 3 +- .../Localization/Domain/zh-Hant.json | 3 +- ...teResourcePermissionManagementModal.cshtml | 2 +- 30 files changed, 153 insertions(+), 159 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor index 75ebf86e14..05a6ecb6da 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor @@ -3,67 +3,84 @@ @using Volo.Abp.PermissionManagement.Localization @inherits Volo.Abp.AspNetCore.Components.AbpComponentBase @inject AbpBlazorMessageLocalizerHelper LH + - @L["ResourcePermissions"] - @ResourceDisplayName + @L["ResourcePermissions"] -
- -
- - - - - - - @L["Actions"] - - - - @L["Edit"] - - - @L["Delete"] - - - - - - - - @{ - @context.ProviderName - @context.ProviderDisplayName - } - - - - - @{ - foreach (var permission in context.Permissions) - { - @permission.DisplayName + @if(HasAnyResourcePermission && HasAnyResourceProviderKeyLookupService) + { +
+ +
+ + + + + + + @L["Actions"] + + + + @L["Edit"] + + + @L["Delete"] + + + + + + + + @{ + @context.ProviderName + @context.ProviderDisplayName } - } - - - - - @L["NoDataAvailableInDatatable"] - - +
+
+ + + @{ + foreach (var permission in context.Permissions) + { + @permission.DisplayName + } + } + + +
+ + @L["NoDataAvailableInDatatable"] + +
+ } + else + { + + }
@@ -75,17 +92,12 @@ - @L["AddResourcePermissions"] + @L["AddResourcePermission"]
- -

@ResourceDisplayName

-
-
- @L["ProviderName"]: @@ -94,9 +106,6 @@ @keyLookupService.DisplayName } -
-
- @L["ProviderKey"]: *
-
- - @L["GrantAllResourcePermissions"] +

@L["ResourcePermissionPermissions"]

+ @L["GrantAllResourcePermissions"] +
+ @foreach (var permission in CreateEntity.Permissions) + { + @permission.DisplayName + }
- @foreach (var permission in CreateEntity.Permissions) - { - @permission.DisplayName - }
@@ -142,24 +151,20 @@ - @L["UpdateResourcePermissions"] + @L["UpdateResourcePermission"]
- -

@ResourceDisplayName

-
-
-
- - @L["GrantAllResourcePermissions"] +

@L["ResourcePermissionPermissions"]

+ @L["GrantAllResourcePermissions"] +
+ @foreach (var permission in EditEntity.Permissions) + { + @permission.DisplayName + }
- @foreach (var permission in EditEntity.Permissions) - { - @permission.DisplayName - }
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs index 454c89acfb..6f55af836d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor.cs @@ -18,9 +18,10 @@ public partial class ResourcePermissionManagementModal protected Modal Modal { get; set; } + public bool HasAnyResourcePermission { get; set; } + public bool HasAnyResourceProviderKeyLookupService { get; set; } protected string ResourceName { get; set; } protected string ResourceKey { get; set; } - protected string ResourceDisplayName{ get; set; } protected int PageSize { get; set; } = 10; protected Modal CreateModal { get; set; } @@ -57,20 +58,28 @@ public partial class ResourcePermissionManagementModal LocalizationResource = typeof(AbpPermissionManagementResource); } - public virtual async Task OpenAsync(string resourceName, string resourceKey, string resourceDisplayName = null) + public virtual async Task OpenAsync(string resourceName, string resourceKey) { try { ResourceName = resourceName; ResourceKey = resourceKey; - ResourceDisplayName = resourceDisplayName; ResourcePermissionDefinitions = await PermissionAppService.GetResourceDefinitionsAsync(ResourceName); ResourceProviderKeyLookupServices = (await PermissionAppService.GetResourceProviderKeyLookupServicesAsync(ResourceName)).Providers; + HasAnyResourcePermission = ResourcePermissionDefinitions.Permissions.Any(); + if (HasAnyResourcePermission) + { + HasAnyResourceProviderKeyLookupService = ResourceProviderKeyLookupServices.Count > 0; + } + + await InvokeAsync(StateHasChanged); + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName, ResourceKey); await Modal.Show(); + } catch (Exception ex) { @@ -117,6 +126,13 @@ public partial class ResourcePermissionManagementModal { ProviderKey = value; ProviderDisplayName = ProviderKeys.FirstOrDefault(p => p.ProviderKey == value)?.ProviderDisplayName; + + var permissionGrants = await PermissionAppService.GetResourceByProviderAsync(ResourceName, ResourceKey, CurrentLookupService, ProviderKey); + foreach (var permission in CreateEntity.Permissions) + { + permission.IsGranted = permissionGrants.Permissions.Any(p => p.Name == permission.Name && p.IsGranted); + } + await InvokeAsync(StateHasChanged); } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json index 79dbec3a3e..f234ef9068 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ar.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "الأذونات", "AddResourcePermission": "إضافة إذن", "ResourcePermissionDeletionConfirmationMessage": "هل أنت متأكد أنك تريد حذف جميع الأذونات؟", - "AddResourcePermissions": "إضافة الأذونات", - "UpdateResourcePermissions": "تحديث الأذونات", + "UpdateResourcePermission": "تحديث الإذن", "GrantAllResourcePermissions": "منح الكل", "NoResourceProviderKeyLookupServiceFound": "لم يتم العثور على خدمة البحث عن مفتاح المزود", "NoResourcePermissionFound": "لا توجد أي أذونات محددة." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json index eef2ab75fa..2083a51da2 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/cs.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Oprávnění", "AddResourcePermission": "Přidat oprávnění", "ResourcePermissionDeletionConfirmationMessage": "Opravdu chcete smazat všechna oprávnění?", - "AddResourcePermissions": "Přidat oprávnění", - "UpdateResourcePermissions": "Aktualizovat oprávnění", + "UpdateResourcePermission": "Aktualizovat oprávnění", "GrantAllResourcePermissions": "Udělit vše", "NoResourceProviderKeyLookupServiceFound": "Nebyla nalezena služba pro vyhledávání klíče poskytovatele zdrojů", "NoResourcePermissionFound": "Pro aktuální prostředek není definováno žádné oprávnění." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json index d08b78e925..65fb5b83d6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/de.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Berechtigungen", "AddResourcePermission": "Berechtigung hinzufügen", "ResourcePermissionDeletionConfirmationMessage": "Sind Sie sicher, dass Sie alle Berechtigungen löschen möchten?", - "AddResourcePermissions": "Berechtigungen hinzufügen", - "UpdateResourcePermissions": "Berechtigungen aktualisieren", + "UpdateResourcePermission": "Berechtigung aktualisieren", "GrantAllResourcePermissions": "Alle gewähren", "NoResourceProviderKeyLookupServiceFound": "Es wurde kein Dienst zum Nachschlagen des Anbieterschlüssels gefunden", "NoResourcePermissionFound": "Es ist keine Berechtigung definiert." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json index 63eb0668c1..125d64e5dc 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/el.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Δικαιώματα", "AddResourcePermission": "Προσθήκη δικαιώματος", "ResourcePermissionDeletionConfirmationMessage": "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα δικαιώματα;", - "AddResourcePermissions": "Προσθήκη δικαιωμάτων", - "UpdateResourcePermissions": "Ενημέρωση δικαιωμάτων", + "UpdateResourcePermission": "Ενημέρωση δικαιώματος", "GrantAllResourcePermissions": "Παραχώρηση όλων", "NoResourceProviderKeyLookupServiceFound": "Δεν βρέθηκε υπηρεσία αναζήτησης κλειδιού παρόχου", "NoResourcePermissionFound": "Δεν έχει οριστεί καμία άδεια." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json index eebae2dc62..75d6509523 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en-GB.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Permissions", "AddResourcePermission": "Add permission", "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete all permissions?", - "AddResourcePermissions": "Add permissions", - "UpdateResourcePermissions": "Update permissions", + "UpdateResourcePermission": "Update permission", "GrantAllResourcePermissions": "Grant all", "NoResourceProviderKeyLookupServiceFound": "There is no provider key lookup service was found", "NoResourcePermissionFound": "There is no permission defined." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json index b1ea4b37fd..f3dde4d6a6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Permissions", "AddResourcePermission": "Add permission", "ResourcePermissionDeletionConfirmationMessage": "Are you sure you want to delete all permissions?", - "AddResourcePermissions": "Add permissions", - "UpdateResourcePermissions": "Update permissions", + "UpdateResourcePermission": "Update permission", "GrantAllResourcePermissions": "Grant all", "NoResourceProviderKeyLookupServiceFound": "There is no provider key lookup service was found", "NoResourcePermissionFound": "There is no permission defined." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json index f62d7fdba0..1007ca87ef 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/es.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Permisos", "AddResourcePermission": "Agregar permiso", "ResourcePermissionDeletionConfirmationMessage": "¿Está seguro de que desea eliminar todos los permisos?", - "AddResourcePermissions": "Agregar permisos", - "UpdateResourcePermissions": "Actualizar permisos", + "UpdateResourcePermission": "Actualizar permiso", "GrantAllResourcePermissions": "Conceder todos", "NoResourceProviderKeyLookupServiceFound": "No se encontró ningún servicio de búsqueda de clave de proveedor", "NoResourcePermissionFound": "No hay ningún permiso definido." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json index d025e2403d..4d711f6542 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fa.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "دسترسی‌ها", "AddResourcePermission": "افزودن مجوز", "ResourcePermissionDeletionConfirmationMessage": "آیا مطمئن هستید که می‌خواهید همه مجوزها را حذف کنید؟", - "AddResourcePermissions": "افزودن مجوزها", - "UpdateResourcePermissions": "به‌روزرسانی مجوزها", + "UpdateResourcePermission": "به‌روزرسانی مجوز", "GrantAllResourcePermissions": "اعطای همه", "NoResourceProviderKeyLookupServiceFound": "هیچ سرویس جستجوی کلید ارائه‌دهنده یافت نشد", "NoResourcePermissionFound": "هیچ مجوزی تعریف نشده است." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json index 60c6f682d4..02bd697c43 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fi.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Käyttöoikeudet", "AddResourcePermission": "Lisää käyttöoikeus", "ResourcePermissionDeletionConfirmationMessage": "Haluatko varmasti poistaa kaikki käyttöoikeudet?", - "AddResourcePermissions": "Lisää käyttöoikeudet", - "UpdateResourcePermissions": "Päivitä käyttöoikeudet", + "UpdateResourcePermission": "Päivitä käyttöoikeus", "GrantAllResourcePermissions": "Myönnä kaikki", "NoResourceProviderKeyLookupServiceFound": "Palveluntarjoajan avaimen hakupalvelua ei löytynyt", "NoResourcePermissionFound": "Ei käyttöoikeuksia määritetty." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json index 30bf227119..d019936494 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/fr.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Autorisations", "AddResourcePermission": "Ajouter une autorisation", "ResourcePermissionDeletionConfirmationMessage": "Êtes-vous sûr de vouloir supprimer toutes les autorisations ?", - "AddResourcePermissions": "Ajouter des autorisations", - "UpdateResourcePermissions": "Mettre à jour les autorisations", + "UpdateResourcePermission": "Mettre à jour l'autorisation", "GrantAllResourcePermissions": "Accorder tout", "NoResourceProviderKeyLookupServiceFound": "Aucun service de recherche de clé de fournisseur n'a été trouvé", "NoResourcePermissionFound": "Aucune autorisation n'est définie." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json index 4523cce321..141be77ce5 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hi.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "अनुमतियाँ", "AddResourcePermission": "अनुमति जोड़ें", "ResourcePermissionDeletionConfirmationMessage": "क्या आप वाकई सभी अनुमतियां हटाना चाहते हैं?", - "AddResourcePermissions": "अनुमतियां जोड़ें", - "UpdateResourcePermissions": "अनुमतियां अपडेट करें", + "UpdateResourcePermission": "अनुमति अपडेट करें", "GrantAllResourcePermissions": "सभी प्रदान करें", "NoResourceProviderKeyLookupServiceFound": "कोई प्रदाता कुंजी खोज सेवा नहीं मिली", "NoResourcePermissionFound": "कोई अनुमति परिभाषित नहीं है।" diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json index ff1f097592..fb7129a678 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hr.json @@ -1,23 +1,22 @@ { - "culture": "hr", - "texts": { - "Permissions": "Dozvole", - "OnlyProviderPermissons": "Samo ovaj pružatelj usluga", - "All": "Svi", - "SelectAllInAllTabs": "Dodijelite sva dopuštenja", - "SelectAllInThisTab": "Odaberi sve", - "SaveWithoutAnyPermissionsWarningMessage": "Jeste li sigurni da želite spremiti bez ikakvih dopuštenja?", - "PermissionGroup": "Grupa dozvola", - "Filter": "Filtriraj", - "ResourcePermissions": "Dozvole", - "ResourcePermissionTarget": "Cilj", - "ResourcePermissionPermissions": "Dozvole", - "AddResourcePermission": "Dodaj dozvolu", - "ResourcePermissionDeletionConfirmationMessage": "Jeste li sigurni da želite izbrisati sve dozvole?", - "AddResourcePermissions": "Dodaj dozvole", - "UpdateResourcePermissions": "Ažuriraj dozvole", - "GrantAllResourcePermissions": "Dodijeli sve", - "NoResourceProviderKeyLookupServiceFound": "Nije pronađena usluga za pronalaženje ključa pružatelja", - "NoResourcePermissionFound": "Nijedna dozvola nije definirana." - } + "culture": "hr", + "texts": { + "Permissions": "Dozvole", + "OnlyProviderPermissons": "Samo ovaj pružatelj usluga", + "All": "Svi", + "SelectAllInAllTabs": "Dodijelite sva dopuštenja", + "SelectAllInThisTab": "Odaberi sve", + "SaveWithoutAnyPermissionsWarningMessage": "Jeste li sigurni da želite spremiti bez ikakvih dopuštenja?", + "PermissionGroup": "Grupa dozvola", + "Filter": "Filtriraj", + "ResourcePermissions": "Dozvole", + "ResourcePermissionTarget": "Cilj", + "ResourcePermissionPermissions": "Dozvole", + "AddResourcePermission": "Dodaj dozvolu", + "ResourcePermissionDeletionConfirmationMessage": "Jeste li sigurni da želite izbrisati sve dozvole?", + "UpdateResourcePermission": "Ažuriraj dozvolu", + "GrantAllResourcePermissions": "Dodijeli sve", + "NoResourceProviderKeyLookupServiceFound": "Nije pronađena usluga za pronalaženje ključa pružatelja", + "NoResourcePermissionFound": "Nijedna dozvola nije definirana." + } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json index 48ef87202d..9ef8822f63 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/hu.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Engedélyek", "AddResourcePermission": "Engedély hozzáadása", "ResourcePermissionDeletionConfirmationMessage": "Biztosan törölni szeretné az összes engedélyt?", - "AddResourcePermissions": "Engedélyek hozzáadása", - "UpdateResourcePermissions": "Engedélyek frissítése", + "UpdateResourcePermission": "Engedély frissítése", "GrantAllResourcePermissions": "Összes engedély megadása", "NoResourceProviderKeyLookupServiceFound": "Nem található szolgáltató kulcs kereső szolgáltatás", "NoResourcePermissionFound": "Nincs meghatározva engedély." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json index fd4b43fde5..0510d8a1b8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/is.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Heimildir", "AddResourcePermission": "Bæta við heimild", "ResourcePermissionDeletionConfirmationMessage": "Ertu viss um að þú viljir eyða öllum heimildum?", - "AddResourcePermissions": "Bæta við heimildum", - "UpdateResourcePermissions": "Uppfæra heimildir", + "UpdateResourcePermission": "Uppfæra heimild", "GrantAllResourcePermissions": "Veita allt", "NoResourceProviderKeyLookupServiceFound": "Engin þjónusta fannst til að leita að lykli veitanda", "NoResourcePermissionFound": "Engin heimild er skilgreind." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json index 39893a8dbc..644eae25ee 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/it.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Autorizzazioni", "AddResourcePermission": "Aggiungi autorizzazione", "ResourcePermissionDeletionConfirmationMessage": "Sei sicuro di voler eliminare tutte le autorizzazioni?", - "AddResourcePermissions": "Aggiungi autorizzazioni", - "UpdateResourcePermissions": "Aggiorna autorizzazioni", + "UpdateResourcePermission": "Aggiorna autorizzazione", "GrantAllResourcePermissions": "Concedi tutto", "NoResourceProviderKeyLookupServiceFound": "Non è stato trovato alcun servizio di ricerca chiave del provider", "NoResourcePermissionFound": "Non è definita alcuna autorizzazione." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json index c3eeb2bc60..b9af1b227b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/nl.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Rechten", "AddResourcePermission": "Recht toevoegen", "ResourcePermissionDeletionConfirmationMessage": "Weet u zeker dat u alle rechten wilt verwijderen?", - "AddResourcePermissions": "Rechten toevoegen", - "UpdateResourcePermissions": "Rechten bijwerken", + "UpdateResourcePermission": "Recht bijwerken", "GrantAllResourcePermissions": "Alles toekennen", "NoResourceProviderKeyLookupServiceFound": "Er is geen service gevonden voor het opzoeken van de sleutel van de provider", "NoResourcePermissionFound": "Er is geen machtiging gedefinieerd." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index de16903dd0..9ba0bd5690 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Uprawnienia", "AddResourcePermission": "Dodaj uprawnienie", "ResourcePermissionDeletionConfirmationMessage": "Czy na pewno chcesz usunąć wszystkie uprawnienia?", - "AddResourcePermissions": "Dodaj uprawnienia", - "UpdateResourcePermissions": "Zaktualizuj uprawnienia", + "UpdateResourcePermission": "Zaktualizuj uprawnienie", "GrantAllResourcePermissions": "Przyznaj wszystko", "NoResourceProviderKeyLookupServiceFound": "Nie znaleziono usługi wyszukiwania klucza dostawcy", "NoResourcePermissionFound": "Nie zdefiniowano żadnych uprawnień." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json index fbb2351f54..d9ceca3317 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pt-BR.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Permissões", "AddResourcePermission": "Adicionar permissão", "ResourcePermissionDeletionConfirmationMessage": "Tem certeza de que deseja excluir todas as permissões?", - "AddResourcePermissions": "Adicionar permissões", - "UpdateResourcePermissions": "Atualizar permissões", + "UpdateResourcePermission": "Atualizar permissão", "GrantAllResourcePermissions": "Conceder tudo", "NoResourceProviderKeyLookupServiceFound": "Nenhum serviço de pesquisa de chave do provedor foi encontrado", "NoResourcePermissionFound": "Nenhuma permissão foi definida." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json index 87a764d599..25553f693b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ro-RO.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Permisiuni", "AddResourcePermission": "Adăugați permisiune", "ResourcePermissionDeletionConfirmationMessage": "Sigur doriți să ștergeți toate permisiunile?", - "AddResourcePermissions": "Adăugați permisiuni", - "UpdateResourcePermissions": "Actualizați permisiunile", + "UpdateResourcePermission": "Actualizați permisiunea", "GrantAllResourcePermissions": "Acordați toate", "NoResourceProviderKeyLookupServiceFound": "Nu a fost găsit niciun serviciu de căutare a cheii furnizorului", "NoResourcePermissionFound": "Nu există nicio permisiune definită." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json index dcbfeaaf08..409460e2dd 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/ru.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Разрешения", "AddResourcePermission": "Добавить разрешение", "ResourcePermissionDeletionConfirmationMessage": "Вы уверены, что хотите удалить все разрешения?", - "AddResourcePermissions": "Добавить разрешения", - "UpdateResourcePermissions": "Обновить разрешения", + "UpdateResourcePermission": "Обновить разрешение", "GrantAllResourcePermissions": "Предоставить все", "NoResourceProviderKeyLookupServiceFound": "Служба поиска ключа поставщика не найдена", "NoResourcePermissionFound": "Не определено ни одного разрешения." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json index fb4425f148..09394f87a9 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sk.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Oprávnenia", "AddResourcePermission": "Pridať oprávnenie", "ResourcePermissionDeletionConfirmationMessage": "Naozaj chcete odstrániť všetky oprávnenia?", - "AddResourcePermissions": "Pridať oprávnenia", - "UpdateResourcePermissions": "Aktualizovať oprávnenia", + "UpdateResourcePermission": "Aktualizovať oprávnenie", "GrantAllResourcePermissions": "Udeľ všetko", "NoResourceProviderKeyLookupServiceFound": "Nebola nájdená služba na vyhľadávanie kľúča poskytovateľa", "NoResourcePermissionFound": "Nie je definované žiadne povolenie." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json index ca919b069b..60ee9af67a 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sl.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Dovoljenja", "AddResourcePermission": "Dodaj dovoljenje", "ResourcePermissionDeletionConfirmationMessage": "Ali ste prepričani, da želite izbrisati vsa dovoljenja?", - "AddResourcePermissions": "Dodaj dovoljenja", - "UpdateResourcePermissions": "Posodobi dovoljenja", + "UpdateResourcePermission": "Posodobi dovoljenje", "GrantAllResourcePermissions": "Dodeli vse", "NoResourceProviderKeyLookupServiceFound": "Ni bilo mogoče najti storitve za iskanje ključa ponudnika", "NoResourcePermissionFound": "Nobeno dovoljenje ni določeno." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json index 4896e51547..a5c1260438 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/sv.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Behörigheter", "AddResourcePermission": "Lägg till behörighet", "ResourcePermissionDeletionConfirmationMessage": "Är du säker på att du vill ta bort alla behörigheter?", - "AddResourcePermissions": "Lägg till behörigheter", - "UpdateResourcePermissions": "Uppdatera behörigheter", + "UpdateResourcePermission": "Uppdatera behörighet", "GrantAllResourcePermissions": "Bevilja alla", "NoResourceProviderKeyLookupServiceFound": "Ingen tjänst för att söka efter leverantörsnyckel hittades", "NoResourcePermissionFound": "Ingen behörighet är definierad." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json index e1925fe8da..b96baa2383 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/tr.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "İzinler", "AddResourcePermission": "İzin ekle", "ResourcePermissionDeletionConfirmationMessage": "Tüm izinleri silmek istediğinizden emin misiniz?", - "AddResourcePermissions": "İzinler ekle", - "UpdateResourcePermissions": "İzinleri güncelle", + "UpdateResourcePermission": "İzni güncelle", "GrantAllResourcePermissions": "Tümünü ver", "NoResourceProviderKeyLookupServiceFound": "Herhangi bir sağlayıcı anahtar arama hizmeti bulunamadı", "NoResourcePermissionFound": "Herhangi bir izin tanımlı değil." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json index 7359377b59..2a5aca86e1 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/vi.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "Quyền", "AddResourcePermission": "Thêm quyền", "ResourcePermissionDeletionConfirmationMessage": "Bạn có chắc chắn muốn xóa tất cả quyền không?", - "AddResourcePermissions": "Thêm các quyền", - "UpdateResourcePermissions": "Cập nhật các quyền", + "UpdateResourcePermission": "Cập nhật quyền", "GrantAllResourcePermissions": "Cấp tất cả", "NoResourceProviderKeyLookupServiceFound": "Không tìm thấy dịch vụ tra cứu khóa nhà cung cấp", "NoResourcePermissionFound": "Không có quyền nào được định nghĩa." diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json index 548d8ac2ad..bbd53f9606 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hans.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "权限", "AddResourcePermission": "添加权限", "ResourcePermissionDeletionConfirmationMessage": "您确定要删除所有权限吗?", - "AddResourcePermissions": "添加权限", - "UpdateResourcePermissions": "更新权限", + "UpdateResourcePermission": "更新权限", "GrantAllResourcePermissions": "授予所有", "NoResourceProviderKeyLookupServiceFound": "未找到提供者键查找服务", "NoResourcePermissionFound": "未定义任何权限。" diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json index 2e114480fb..c081c54f20 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/zh-Hant.json @@ -14,8 +14,7 @@ "ResourcePermissionPermissions": "權限", "AddResourcePermission": "添加權限", "ResourcePermissionDeletionConfirmationMessage": "您確定要刪除所有權限嗎?", - "AddResourcePermissions": "添加權限", - "UpdateResourcePermissions": "更新權限", + "UpdateResourcePermission": "更新權限", "GrantAllResourcePermissions": "授予所有", "NoResourceProviderKeyLookupServiceFound": "未找到提供者鍵查找服務", "NoResourcePermissionFound": "未定義任何權限。" diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml index 59b7f55236..3e90319e02 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/UpdateResourcePermissionManagementModal.cshtml @@ -16,7 +16,7 @@ - + From 2a9a261229475f15996e908c61e3fcb2093b7bb8 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 13 Dec 2025 15:32:43 +0800 Subject: [PATCH 117/130] Replace tooltip span with Tooltip component --- .../Components/ResourcePermissionManagementModal.razor | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor index 05a6ecb6da..b0c3609309 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/ResourcePermissionManagementModal.razor @@ -47,7 +47,9 @@ @{ - @context.ProviderName + + @context.ProviderName + @context.ProviderDisplayName } From 1e59092ef5690e59522905e95a938420083d7d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Sat, 13 Dec 2025 11:40:15 +0300 Subject: [PATCH 118/130] Add community article on dynamic XML sitemaps with ABP --- .../cover.png | Bin 0 -> 578568 bytes .../images/sitemap-architecture.svg | 122 +++++ .../post.md | 475 ++++++++++++++++++ .../summary.md | 1 + 4 files changed, 598 insertions(+) create mode 100644 docs/en/Community-Articles/2025-12-13-Building-Dynamic-XML-Sitemaps-With-ABP-Framework/cover.png create mode 100644 docs/en/Community-Articles/2025-12-13-Building-Dynamic-XML-Sitemaps-With-ABP-Framework/images/sitemap-architecture.svg create mode 100644 docs/en/Community-Articles/2025-12-13-Building-Dynamic-XML-Sitemaps-With-ABP-Framework/post.md create mode 100644 docs/en/Community-Articles/2025-12-13-Building-Dynamic-XML-Sitemaps-With-ABP-Framework/summary.md diff --git a/docs/en/Community-Articles/2025-12-13-Building-Dynamic-XML-Sitemaps-With-ABP-Framework/cover.png b/docs/en/Community-Articles/2025-12-13-Building-Dynamic-XML-Sitemaps-With-ABP-Framework/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b9c07505be40b437dde13a5837b0bde7465f3e GIT binary patch literal 578568 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%0Dj0}E*041U-dwSs|3M*1k$f#55eNs?R8^>l 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�)Sw*0h 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

=36SR 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�c}HXtQC;Y@Kxrc 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

!z9_#a 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