mirror of https://github.com/abpframework/abp.git
committed by
GitHub
124 changed files with 4626 additions and 380 deletions
@ -0,0 +1,14 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
public interface ICanAddChildPermission |
||||
|
{ |
||||
|
PermissionDefinition AddPermission( |
||||
|
[NotNull] string name, |
||||
|
ILocalizableString displayName = null, |
||||
|
MultiTenancySides multiTenancySide = MultiTenancySides.Both, |
||||
|
bool isEnabled = true); |
||||
|
} |
||||
@ -1,17 +1,18 @@ |
|||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
using JetBrains.Annotations; |
using JetBrains.Annotations; |
||||
|
|
||||
namespace Volo.Abp.Authorization.Permissions; |
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
public interface IPermissionDefinitionManager |
public interface IPermissionDefinitionManager |
||||
{ |
{ |
||||
[NotNull] |
[ItemNotNull] |
||||
PermissionDefinition Get([NotNull] string name); |
Task<PermissionDefinition> GetAsync([NotNull] string name); |
||||
|
|
||||
[CanBeNull] |
[ItemCanBeNull] |
||||
PermissionDefinition GetOrNull([NotNull] string name); |
Task<PermissionDefinition> GetOrNullAsync([NotNull] string name); |
||||
|
|
||||
IReadOnlyList<PermissionDefinition> GetPermissions(); |
Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync(); |
||||
|
|
||||
IReadOnlyList<PermissionGroupDefinition> GetGroups(); |
Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync(); |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,38 @@ |
|||||
|
using System.Text.Json.Nodes; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
public class AuthenticatedSimpleStateCheckerSerializerContributor : |
||||
|
ISimpleStateCheckerSerializerContributor, |
||||
|
ISingletonDependency |
||||
|
{ |
||||
|
public const string CheckerShortName = "A"; |
||||
|
|
||||
|
public string SerializeToJson<TState>(ISimpleStateChecker<TState> checker) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (checker is not RequireAuthenticatedSimpleStateChecker<TState>) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var jsonObject = new JsonObject { |
||||
|
["T"] = CheckerShortName |
||||
|
}; |
||||
|
|
||||
|
return jsonObject.ToJsonString(); |
||||
|
} |
||||
|
|
||||
|
public ISimpleStateChecker<TState> Deserialize<TState>(JsonObject jsonObject, TState state) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (jsonObject["T"]?.ToString() != CheckerShortName) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return new RequireAuthenticatedSimpleStateChecker<TState>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
public interface IDynamicPermissionDefinitionStore |
||||
|
{ |
||||
|
Task<PermissionDefinition> GetOrNullAsync(string name); |
||||
|
|
||||
|
Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync(); |
||||
|
|
||||
|
Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync(); |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
public interface IStaticPermissionDefinitionStore |
||||
|
{ |
||||
|
Task<PermissionDefinition> GetOrNullAsync(string name); |
||||
|
|
||||
|
Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync(); |
||||
|
|
||||
|
Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync(); |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
public class NullDynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStore, ISingletonDependency |
||||
|
{ |
||||
|
private readonly static Task<PermissionDefinition> CachedPermissionResult = Task.FromResult((PermissionDefinition)null); |
||||
|
|
||||
|
private readonly static Task<IReadOnlyList<PermissionDefinition>> CachedPermissionsResult = |
||||
|
Task.FromResult((IReadOnlyList<PermissionDefinition>)Array.Empty<PermissionDefinition>().ToImmutableList()); |
||||
|
|
||||
|
private readonly static Task<IReadOnlyList<PermissionGroupDefinition>> CachedGroupsResult = |
||||
|
Task.FromResult((IReadOnlyList<PermissionGroupDefinition>)Array.Empty<PermissionGroupDefinition>().ToImmutableList()); |
||||
|
|
||||
|
public Task<PermissionDefinition> GetOrNullAsync(string name) |
||||
|
{ |
||||
|
return CachedPermissionResult; |
||||
|
} |
||||
|
|
||||
|
public Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync() |
||||
|
{ |
||||
|
return CachedPermissionsResult; |
||||
|
} |
||||
|
|
||||
|
public Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync() |
||||
|
{ |
||||
|
return CachedGroupsResult; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
using System.Linq; |
||||
|
using System.Text.Json.Nodes; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
namespace Volo.Abp.GlobalFeatures; |
||||
|
|
||||
|
public class PermissionsSimpleStateCheckerSerializerContributor : |
||||
|
ISimpleStateCheckerSerializerContributor, |
||||
|
ISingletonDependency |
||||
|
{ |
||||
|
public const string CheckerShortName = "P"; |
||||
|
|
||||
|
public string SerializeToJson<TState>(ISimpleStateChecker<TState> checker) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (checker is not RequirePermissionsSimpleStateChecker<TState> permissionsSimpleStateChecker) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var jsonObject = new JsonObject { |
||||
|
["T"] = CheckerShortName, |
||||
|
["A"] = permissionsSimpleStateChecker.RequiresAll |
||||
|
}; |
||||
|
|
||||
|
var nameArray = new JsonArray(); |
||||
|
foreach (var permissionName in permissionsSimpleStateChecker.PermissionNames) |
||||
|
{ |
||||
|
nameArray.Add(permissionName); |
||||
|
} |
||||
|
|
||||
|
jsonObject["N"] = nameArray; |
||||
|
return jsonObject.ToJsonString(); |
||||
|
} |
||||
|
|
||||
|
public ISimpleStateChecker<TState> Deserialize<TState>( |
||||
|
JsonObject jsonObject, |
||||
|
TState state) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (jsonObject["T"]?.ToString() != CheckerShortName) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var nameArray = jsonObject["N"] as JsonArray; |
||||
|
if (nameArray == null) |
||||
|
{ |
||||
|
throw new AbpException("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject.ToJsonString()); |
||||
|
} |
||||
|
|
||||
|
return new RequirePermissionsSimpleStateChecker<TState>( |
||||
|
new RequirePermissionsSimpleBatchStateCheckerModel<TState>( |
||||
|
state, |
||||
|
nameArray.Select(x => x.ToString()).ToArray(), |
||||
|
(bool?)jsonObject["A"] ?? false |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,122 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, ISingletonDependency |
||||
|
{ |
||||
|
protected IDictionary<string, PermissionGroupDefinition> PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value; |
||||
|
private readonly Lazy<Dictionary<string, PermissionGroupDefinition>> _lazyPermissionGroupDefinitions; |
||||
|
|
||||
|
protected IDictionary<string, PermissionDefinition> PermissionDefinitions => _lazyPermissionDefinitions.Value; |
||||
|
private readonly Lazy<Dictionary<string, PermissionDefinition>> _lazyPermissionDefinitions; |
||||
|
|
||||
|
protected AbpPermissionOptions Options { get; } |
||||
|
|
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
public StaticPermissionDefinitionStore( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IOptions<AbpPermissionOptions> options) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
Options = options.Value; |
||||
|
|
||||
|
_lazyPermissionDefinitions = new Lazy<Dictionary<string, PermissionDefinition>>( |
||||
|
CreatePermissionDefinitions, |
||||
|
isThreadSafe: true |
||||
|
); |
||||
|
|
||||
|
_lazyPermissionGroupDefinitions = new Lazy<Dictionary<string, PermissionGroupDefinition>>( |
||||
|
CreatePermissionGroupDefinitions, |
||||
|
isThreadSafe: true |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
protected virtual Dictionary<string, PermissionDefinition> CreatePermissionDefinitions() |
||||
|
{ |
||||
|
var permissions = new Dictionary<string, PermissionDefinition>(); |
||||
|
|
||||
|
foreach (var groupDefinition in PermissionGroupDefinitions.Values) |
||||
|
{ |
||||
|
foreach (var permission in groupDefinition.Permissions) |
||||
|
{ |
||||
|
AddPermissionToDictionaryRecursively(permissions, permission); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return permissions; |
||||
|
} |
||||
|
|
||||
|
protected virtual void AddPermissionToDictionaryRecursively( |
||||
|
Dictionary<string, PermissionDefinition> permissions, |
||||
|
PermissionDefinition permission) |
||||
|
{ |
||||
|
if (permissions.ContainsKey(permission.Name)) |
||||
|
{ |
||||
|
throw new AbpException("Duplicate permission name: " + permission.Name); |
||||
|
} |
||||
|
|
||||
|
permissions[permission.Name] = permission; |
||||
|
|
||||
|
foreach (var child in permission.Children) |
||||
|
{ |
||||
|
AddPermissionToDictionaryRecursively(permissions, child); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual Dictionary<string, PermissionGroupDefinition> CreatePermissionGroupDefinitions() |
||||
|
{ |
||||
|
using (var scope = _serviceProvider.CreateScope()) |
||||
|
{ |
||||
|
var context = new PermissionDefinitionContext(scope.ServiceProvider); |
||||
|
|
||||
|
var providers = Options |
||||
|
.DefinitionProviders |
||||
|
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider) |
||||
|
.ToList(); |
||||
|
|
||||
|
foreach (var provider in providers) |
||||
|
{ |
||||
|
provider.PreDefine(context); |
||||
|
} |
||||
|
|
||||
|
foreach (var provider in providers) |
||||
|
{ |
||||
|
provider.Define(context); |
||||
|
} |
||||
|
|
||||
|
foreach (var provider in providers) |
||||
|
{ |
||||
|
provider.PostDefine(context); |
||||
|
} |
||||
|
|
||||
|
return context.Groups; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task<PermissionDefinition> GetOrNullAsync(string name) |
||||
|
{ |
||||
|
return Task.FromResult(PermissionDefinitions.GetOrDefault(name)); |
||||
|
} |
||||
|
|
||||
|
public virtual Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync() |
||||
|
{ |
||||
|
return Task.FromResult<IReadOnlyList<PermissionDefinition>>( |
||||
|
PermissionDefinitions.Values.ToImmutableList() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync() |
||||
|
{ |
||||
|
return Task.FromResult<IReadOnlyList<PermissionGroupDefinition>>( |
||||
|
PermissionGroupDefinitions.Values.ToImmutableList() |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The root service provider of the application.
|
||||
|
/// Be careful to use the root service provider since there is no way
|
||||
|
/// to release/dispose objects resolved from the root service provider.
|
||||
|
/// So, always create a new scope if you need to resolve any service.
|
||||
|
/// </summary>
|
||||
|
public interface IRootServiceProvider : IServiceProvider |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
[ExposeServices(typeof(IRootServiceProvider))] |
||||
|
public class RootServiceProvider : IRootServiceProvider, ISingletonDependency |
||||
|
{ |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public RootServiceProvider(IObjectAccessor<IServiceProvider> objectAccessor) |
||||
|
{ |
||||
|
ServiceProvider = objectAccessor.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual object GetService(Type serviceType) |
||||
|
{ |
||||
|
return ServiceProvider.GetService(serviceType); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Volo.Abp; |
||||
|
|
||||
|
public interface IApplicationNameAccessor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Name of the application.
|
||||
|
/// This is useful for systems with multiple applications, to distinguish
|
||||
|
/// resources of the applications located together.
|
||||
|
/// </summary>
|
||||
|
string ApplicationName { get; } |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System.Text.Json.Nodes; |
||||
|
|
||||
|
namespace Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
public interface ISimpleStateCheckerSerializer |
||||
|
{ |
||||
|
public string Serialize<TState>(ISimpleStateChecker<TState> checker) |
||||
|
where TState : IHasSimpleStateCheckers<TState>; |
||||
|
|
||||
|
public ISimpleStateChecker<TState> Deserialize<TState>(JsonObject jsonObject, TState state) |
||||
|
where TState : IHasSimpleStateCheckers<TState>; |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Text.Json.Nodes; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
public interface ISimpleStateCheckerSerializerContributor |
||||
|
{ |
||||
|
[CanBeNull] |
||||
|
public string SerializeToJson<TState>(ISimpleStateChecker<TState> checker) |
||||
|
where TState : IHasSimpleStateCheckers<TState>; |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public ISimpleStateChecker<TState> Deserialize<TState>(JsonObject jsonObject, TState state) |
||||
|
where TState : IHasSimpleStateCheckers<TState>; |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Text.Json.Nodes; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
public class SimpleStateCheckerSerializer : |
||||
|
ISimpleStateCheckerSerializer, |
||||
|
ISingletonDependency |
||||
|
{ |
||||
|
private readonly IEnumerable<ISimpleStateCheckerSerializerContributor> _contributors; |
||||
|
|
||||
|
public SimpleStateCheckerSerializer(IEnumerable<ISimpleStateCheckerSerializerContributor> contributors) |
||||
|
{ |
||||
|
_contributors = contributors; |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public string Serialize<TState>(ISimpleStateChecker<TState> checker) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
foreach (var contributor in _contributors) |
||||
|
{ |
||||
|
var result = contributor.SerializeToJson(checker); |
||||
|
if (result != null) |
||||
|
{ |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public ISimpleStateChecker<TState> Deserialize<TState>(JsonObject jsonObject, TState state) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
foreach (var contributor in _contributors) |
||||
|
{ |
||||
|
var result = contributor.Deserialize(jsonObject, state); |
||||
|
if (result != null) |
||||
|
{ |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text.Json.Nodes; |
||||
|
|
||||
|
namespace Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
public static class SimpleStateCheckerSerializerExtensions |
||||
|
{ |
||||
|
public static string Serialize<TState>( |
||||
|
this ISimpleStateCheckerSerializer serializer, |
||||
|
IList<ISimpleStateChecker<TState>> stateCheckers) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
switch (stateCheckers.Count) |
||||
|
{ |
||||
|
case 0: |
||||
|
return null; |
||||
|
case 1: |
||||
|
var serializedChecker = serializer.Serialize(stateCheckers.Single()); |
||||
|
return serializedChecker != null |
||||
|
? $"[{serializedChecker}]" |
||||
|
: null; |
||||
|
default: |
||||
|
var serializedCheckers = new List<string>(stateCheckers.Count); |
||||
|
|
||||
|
foreach (var stateChecker in stateCheckers) |
||||
|
{ |
||||
|
var serialized = serializer.Serialize(stateChecker); |
||||
|
if (serialized != null) |
||||
|
{ |
||||
|
serializedCheckers.Add(serialized); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return serializedCheckers.Any() |
||||
|
? $"[{serializedCheckers.JoinAsString(",")}]" |
||||
|
: null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static ISimpleStateChecker<TState>[] DeserializeArray<TState>( |
||||
|
this ISimpleStateCheckerSerializer serializer, |
||||
|
string value, |
||||
|
TState state) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (value.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
return Array.Empty<ISimpleStateChecker<TState>>(); |
||||
|
} |
||||
|
|
||||
|
var array = JsonNode.Parse(value) as JsonArray; |
||||
|
if (array == null || array.Count == 0) |
||||
|
{ |
||||
|
return Array.Empty<ISimpleStateChecker<TState>>(); |
||||
|
} |
||||
|
|
||||
|
if (array.Count == 1) |
||||
|
{ |
||||
|
var jsonObject = array[0] as JsonObject; |
||||
|
if (jsonObject == null) |
||||
|
{ |
||||
|
throw new AbpException("JSON value is not an array of objects: " + value); |
||||
|
} |
||||
|
|
||||
|
var checker = serializer.Deserialize(jsonObject, state); |
||||
|
if (checker == null) |
||||
|
{ |
||||
|
return Array.Empty<ISimpleStateChecker<TState>>(); |
||||
|
} |
||||
|
|
||||
|
return new[] { checker }; |
||||
|
} |
||||
|
|
||||
|
var checkers = new List<ISimpleStateChecker<TState>>(); |
||||
|
|
||||
|
for (var i = 0; i < array.Count; i++) |
||||
|
{ |
||||
|
if (array[i] is not JsonObject jsonObject) |
||||
|
{ |
||||
|
throw new AbpException("JSON value is not an array of objects: " + value); |
||||
|
} |
||||
|
|
||||
|
checkers.Add(serializer.Deserialize(jsonObject, state)); |
||||
|
} |
||||
|
|
||||
|
return checkers.Where(x => x != null).ToArray(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System.Linq; |
||||
|
using System.Text.Json.Nodes; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
namespace Volo.Abp.Features; |
||||
|
|
||||
|
public class FeaturesSimpleStateCheckerSerializerContributor : |
||||
|
ISimpleStateCheckerSerializerContributor, |
||||
|
ISingletonDependency |
||||
|
{ |
||||
|
public const string CheckerShortName = "F"; |
||||
|
|
||||
|
public string SerializeToJson<TState>(ISimpleStateChecker<TState> checker) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (checker is not RequireFeaturesSimpleStateChecker<TState> featuresSimpleStateChecker) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var jsonObject = new JsonObject { |
||||
|
["T"] = CheckerShortName, |
||||
|
["A"] = featuresSimpleStateChecker.RequiresAll |
||||
|
}; |
||||
|
|
||||
|
var nameArray = new JsonArray(); |
||||
|
foreach (var featureName in featuresSimpleStateChecker.FeatureNames) |
||||
|
{ |
||||
|
nameArray.Add(featureName); |
||||
|
} |
||||
|
|
||||
|
jsonObject["N"] = nameArray; |
||||
|
return jsonObject.ToJsonString(); |
||||
|
} |
||||
|
|
||||
|
public ISimpleStateChecker<TState> Deserialize<TState>(JsonObject jsonObject, TState state) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (jsonObject["T"]?.ToString() != CheckerShortName) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var nameArray = jsonObject["N"] as JsonArray; |
||||
|
if (nameArray == null) |
||||
|
{ |
||||
|
throw new AbpException("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject.ToJsonString()); |
||||
|
} |
||||
|
|
||||
|
return new RequireFeaturesSimpleStateChecker<TState>( |
||||
|
(bool?)jsonObject["A"] ?? false, |
||||
|
nameArray.Select(x => x.ToString()).ToArray() |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System.Linq; |
||||
|
using System.Text.Json.Nodes; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
namespace Volo.Abp.GlobalFeatures; |
||||
|
|
||||
|
public class GlobalFeaturesSimpleStateCheckerSerializerContributor : |
||||
|
ISimpleStateCheckerSerializerContributor, |
||||
|
ISingletonDependency |
||||
|
{ |
||||
|
public const string CheckerShortName = "G"; |
||||
|
|
||||
|
public string SerializeToJson<TState>(ISimpleStateChecker<TState> checker) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (checker is not RequireGlobalFeaturesSimpleStateChecker<TState> globalFeaturesSimpleStateChecker) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var jsonObject = new JsonObject { |
||||
|
["T"] = CheckerShortName, |
||||
|
["A"] = globalFeaturesSimpleStateChecker.RequiresAll |
||||
|
}; |
||||
|
|
||||
|
var nameArray = new JsonArray(); |
||||
|
foreach (var globalFeatureName in globalFeaturesSimpleStateChecker.GlobalFeatureNames) |
||||
|
{ |
||||
|
nameArray.Add(globalFeatureName); |
||||
|
} |
||||
|
|
||||
|
jsonObject["N"] = nameArray; |
||||
|
return jsonObject.ToJsonString(); |
||||
|
} |
||||
|
|
||||
|
public ISimpleStateChecker<TState> Deserialize<TState>(JsonObject jsonObject, TState state) |
||||
|
where TState : IHasSimpleStateCheckers<TState> |
||||
|
{ |
||||
|
if (jsonObject["T"]?.ToString() != CheckerShortName) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var nameArray = jsonObject["N"] as JsonArray; |
||||
|
if (nameArray == null) |
||||
|
{ |
||||
|
throw new AbpException("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject.ToJsonString()); |
||||
|
} |
||||
|
|
||||
|
return new RequireGlobalFeaturesSimpleStateChecker<TState>( |
||||
|
(bool?)jsonObject["A"] ?? false, |
||||
|
nameArray.Select(x => x.ToString()).ToArray() |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace Volo.Abp.Localization; |
||||
|
|
||||
|
public interface ILocalizableStringSerializer |
||||
|
{ |
||||
|
string Serialize(ILocalizableString localizableString); |
||||
|
|
||||
|
ILocalizableString Deserialize(string value); |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
using System; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Localization; |
||||
|
|
||||
|
public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransientDependency |
||||
|
{ |
||||
|
protected AbpLocalizationOptions LocalizationOptions { get; } |
||||
|
|
||||
|
public LocalizableStringSerializer(IOptions<AbpLocalizationOptions> localizationOptions) |
||||
|
{ |
||||
|
LocalizationOptions = localizationOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual string Serialize(ILocalizableString localizableString) |
||||
|
{ |
||||
|
if (localizableString is LocalizableString realLocalizableString) |
||||
|
{ |
||||
|
return $"L:{LocalizationResourceNameAttribute.GetName(realLocalizableString.ResourceType)},{realLocalizableString.Name}"; |
||||
|
} |
||||
|
|
||||
|
if (localizableString is FixedLocalizableString fixedLocalizableString) |
||||
|
{ |
||||
|
return $"F:{fixedLocalizableString.Value}"; |
||||
|
} |
||||
|
|
||||
|
throw new AbpException($"Unknown {nameof(ILocalizableString)} type: {localizableString.GetType().FullName}"); |
||||
|
} |
||||
|
|
||||
|
public virtual ILocalizableString Deserialize(string value) |
||||
|
{ |
||||
|
if (value.IsNullOrEmpty() || |
||||
|
value.Length < 3 || |
||||
|
value[1] != ':') |
||||
|
{ |
||||
|
return new FixedLocalizableString(value); |
||||
|
} |
||||
|
|
||||
|
var type = value[0]; |
||||
|
switch (type) |
||||
|
{ |
||||
|
case 'F': |
||||
|
return new FixedLocalizableString(value.Substring(2)); |
||||
|
case 'L': |
||||
|
var commaPosition = value.IndexOf(',', 2); |
||||
|
if (commaPosition == -1) |
||||
|
{ |
||||
|
throw new AbpException("Invalid LocalizableString value: " + value); |
||||
|
} |
||||
|
|
||||
|
var resourceName = value.Substring(2, commaPosition - 2); |
||||
|
var name = value.Substring(commaPosition + 1); |
||||
|
if (name.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
throw new AbpException("Invalid LocalizableString value: " + value); |
||||
|
} |
||||
|
|
||||
|
var resourceType = LocalizationOptions.Resources.GetOrNull(resourceName)?.ResourceType; |
||||
|
|
||||
|
return new LocalizableString( |
||||
|
resourceType, |
||||
|
name |
||||
|
); |
||||
|
default: |
||||
|
return new FixedLocalizableString(value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Auditing; |
||||
|
|
||||
|
public class AbpAuditingOptions_Tests : AbpAuditingTestBase |
||||
|
{ |
||||
|
private const string ApplicationName = "TEST_APP_NAME"; |
||||
|
|
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
base.SetAbpApplicationCreationOptions(options); |
||||
|
options.ApplicationName = ApplicationName; |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Set_Application_Name_From_Global_Application_Name_By_Default() |
||||
|
{ |
||||
|
var options = GetRequiredService<IOptions<AbpAuditingOptions>>().Value; |
||||
|
options.ApplicationName.ShouldBe(ApplicationName); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Text.Json.Nodes; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization; |
||||
|
|
||||
|
public class AuthenticatedSimpleStateCheckerSerializerContributor_Tests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Serialize_RequireGlobalFeaturesSimpleStateChecker() |
||||
|
{ |
||||
|
var serializer = new AuthenticatedSimpleStateCheckerSerializerContributor(); |
||||
|
|
||||
|
var result = serializer.SerializeToJson( |
||||
|
new RequireAuthenticatedSimpleStateChecker<MyState>() |
||||
|
); |
||||
|
|
||||
|
result.ShouldBe("{\"T\":\"A\"}"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Deserialize_RequireGlobalFeaturesSimpleStateChecker() |
||||
|
{ |
||||
|
var serializer = new AuthenticatedSimpleStateCheckerSerializerContributor(); |
||||
|
|
||||
|
var jsonObject = (JsonObject)JsonNode.Parse("{\"T\":\"A\"}"); |
||||
|
var checker = serializer.Deserialize(jsonObject, new MyState()); |
||||
|
|
||||
|
checker.ShouldBeOfType<RequireAuthenticatedSimpleStateChecker<MyState>>(); |
||||
|
var globalFeaturesSimpleStateChecker = checker as RequireAuthenticatedSimpleStateChecker<MyState>; |
||||
|
globalFeaturesSimpleStateChecker.ShouldNotBeNull(); |
||||
|
} |
||||
|
|
||||
|
private class MyState : IHasSimpleStateCheckers<MyState> |
||||
|
{ |
||||
|
public List<ISimpleStateChecker<MyState>> StateCheckers { get; } = new(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Text.Json.Nodes; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Features; |
||||
|
|
||||
|
public class FeaturesSimpleStateCheckerSerializerContributor_Tests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Serialize_RequireGlobalFeaturesSimpleStateChecker() |
||||
|
{ |
||||
|
var serializer = new FeaturesSimpleStateCheckerSerializerContributor(); |
||||
|
|
||||
|
var result = serializer.SerializeToJson( |
||||
|
new RequireFeaturesSimpleStateChecker<MyState>( |
||||
|
"FeatureA", |
||||
|
"FeatureB" |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
result.ShouldBe("{\"T\":\"F\",\"A\":true,\"N\":[\"FeatureA\",\"FeatureB\"]}"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Deserialize_RequireGlobalFeaturesSimpleStateChecker() |
||||
|
{ |
||||
|
var serializer = new FeaturesSimpleStateCheckerSerializerContributor(); |
||||
|
|
||||
|
var jsonObject = (JsonObject)JsonNode.Parse("{\"T\":\"F\",\"A\":true,\"N\":[\"FeatureA\",\"FeatureB\"]}"); |
||||
|
var checker = serializer.Deserialize<MyState>(jsonObject, new MyState()); |
||||
|
|
||||
|
checker.ShouldBeOfType<RequireFeaturesSimpleStateChecker<MyState>>(); |
||||
|
var globalFeaturesSimpleStateChecker = checker as RequireFeaturesSimpleStateChecker<MyState>; |
||||
|
globalFeaturesSimpleStateChecker.ShouldNotBeNull(); |
||||
|
globalFeaturesSimpleStateChecker.RequiresAll.ShouldBeTrue(); |
||||
|
globalFeaturesSimpleStateChecker.FeatureNames[0].ShouldBe("FeatureA"); |
||||
|
globalFeaturesSimpleStateChecker.FeatureNames[1].ShouldBe("FeatureB"); |
||||
|
} |
||||
|
|
||||
|
private class MyState : IHasSimpleStateCheckers<MyState> |
||||
|
{ |
||||
|
public List<ISimpleStateChecker<MyState>> StateCheckers { get; } = new(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Text.Json.Nodes; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.GlobalFeatures; |
||||
|
|
||||
|
public class GlobalFeaturesSimpleStateCheckerSerializerContributor_Tests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Serialize_RequireGlobalFeaturesSimpleStateChecker() |
||||
|
{ |
||||
|
var serializer = new GlobalFeaturesSimpleStateCheckerSerializerContributor(); |
||||
|
|
||||
|
var result = serializer.SerializeToJson( |
||||
|
new RequireGlobalFeaturesSimpleStateChecker<MyState>( |
||||
|
"FeatureA", |
||||
|
"FeatureB" |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
result.ShouldBe("{\"T\":\"G\",\"A\":true,\"N\":[\"FeatureA\",\"FeatureB\"]}"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Deserialize_RequireGlobalFeaturesSimpleStateChecker() |
||||
|
{ |
||||
|
var serializer = new GlobalFeaturesSimpleStateCheckerSerializerContributor(); |
||||
|
|
||||
|
var jsonObject = (JsonObject)JsonNode.Parse("{\"T\":\"G\",\"A\":true,\"N\":[\"FeatureA\",\"FeatureB\"]}"); |
||||
|
var checker = serializer.Deserialize<MyState>(jsonObject, new MyState()); |
||||
|
|
||||
|
checker.ShouldBeOfType<RequireGlobalFeaturesSimpleStateChecker<MyState>>(); |
||||
|
var globalFeaturesSimpleStateChecker = checker as RequireGlobalFeaturesSimpleStateChecker<MyState>; |
||||
|
globalFeaturesSimpleStateChecker.ShouldNotBeNull(); |
||||
|
globalFeaturesSimpleStateChecker.RequiresAll.ShouldBeTrue(); |
||||
|
globalFeaturesSimpleStateChecker.GlobalFeatureNames[0].ShouldBe("FeatureA"); |
||||
|
globalFeaturesSimpleStateChecker.GlobalFeatureNames[1].ShouldBe("FeatureB"); |
||||
|
} |
||||
|
|
||||
|
private class MyState : IHasSimpleStateCheckers<MyState> |
||||
|
{ |
||||
|
public List<ISimpleStateChecker<MyState>> StateCheckers { get; } = new(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Volo.Abp.Localization.TestResources.Base.CountryNames; |
||||
|
using Volo.Abp.Localization.TestResources.Base.Validation; |
||||
|
using Volo.Abp.Localization.TestResources.Source; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace Volo.Abp.Localization; |
||||
|
|
||||
|
[DependsOn(typeof(AbpTestBaseModule))] |
||||
|
[DependsOn(typeof(AbpLocalizationModule))] |
||||
|
public class AbpLocalizationTestModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpLocalizationTestModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<LocalizationTestValidationResource>("en") |
||||
|
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Base/Validation"); |
||||
|
|
||||
|
options.Resources |
||||
|
.Add<LocalizationTestCountryNamesResource>("en") |
||||
|
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Base/CountryNames"); |
||||
|
|
||||
|
options.Resources |
||||
|
.Add<LocalizationTestResource>("en") |
||||
|
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Source"); |
||||
|
|
||||
|
options.Resources |
||||
|
.Get<LocalizationTestResource>() |
||||
|
.AddVirtualJson("/Volo/Abp/Localization/TestResources/SourceExt"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
using Shouldly; |
||||
|
using Volo.Abp.Localization.TestResources.Source; |
||||
|
using Volo.Abp.Testing; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Localization; |
||||
|
|
||||
|
public class LocalizableStringSerializer_Tests : AbpIntegratedTest<AbpLocalizationTestModule> |
||||
|
{ |
||||
|
private readonly ILocalizableStringSerializer _serializer; |
||||
|
|
||||
|
public LocalizableStringSerializer_Tests() |
||||
|
{ |
||||
|
_serializer = GetRequiredService<ILocalizableStringSerializer>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Serialize_FixedLocalizableString() |
||||
|
{ |
||||
|
_serializer |
||||
|
.Serialize(new FixedLocalizableString("")) |
||||
|
.ShouldBe("F:"); |
||||
|
|
||||
|
_serializer |
||||
|
.Serialize(new FixedLocalizableString("Hello World")) |
||||
|
.ShouldBe("F:Hello World"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Serialize_LocalizableString() |
||||
|
{ |
||||
|
_serializer |
||||
|
.Serialize(new LocalizableString(typeof(LocalizationTestResource),"Car")) |
||||
|
.ShouldBe("L:Test,Car"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Deserialize_FixedLocalizableString() |
||||
|
{ |
||||
|
_serializer |
||||
|
.Deserialize("") |
||||
|
.ShouldBeOfType<FixedLocalizableString>() |
||||
|
.Value.ShouldBe(""); |
||||
|
|
||||
|
_serializer |
||||
|
.Deserialize("Hello") |
||||
|
.ShouldBeOfType<FixedLocalizableString>() |
||||
|
.Value.ShouldBe("Hello"); |
||||
|
|
||||
|
_serializer |
||||
|
.Deserialize("F:Hello") |
||||
|
.ShouldBeOfType<FixedLocalizableString>() |
||||
|
.Value.ShouldBe("Hello"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Deserialize_LocalizableString() |
||||
|
{ |
||||
|
var localizableString = _serializer |
||||
|
.Deserialize("L:Test,Car") |
||||
|
.ShouldBeOfType<LocalizableString>(); |
||||
|
localizableString.ResourceType.ShouldBe(typeof(LocalizationTestResource)); |
||||
|
localizableString.Name.ShouldBe("Car"); |
||||
|
|
||||
|
Assert.Throws<AbpException>(() => |
||||
|
{ |
||||
|
_serializer.Deserialize("L:Test"); |
||||
|
}); |
||||
|
|
||||
|
Assert.Throws<AbpException>(() => |
||||
|
{ |
||||
|
_serializer.Deserialize("L:Test, "); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public class PermissionDefinitionRecordConsts |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Default value: 128
|
||||
|
/// </summary>
|
||||
|
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; |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public class PermissionGroupDefinitionRecordConsts |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Default value: 128
|
||||
|
/// </summary>
|
||||
|
public static int MaxNameLength { get; set; } = 128; |
||||
|
|
||||
|
public static int MaxDisplayNameLength { get; set; } = 256; |
||||
|
} |
||||
@ -0,0 +1,171 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Caching.Distributed; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DistributedLocking; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStore, ITransientDependency |
||||
|
{ |
||||
|
protected IPermissionGroupDefinitionRecordRepository PermissionGroupRepository { get; } |
||||
|
protected IPermissionDefinitionRecordRepository PermissionRepository { get; } |
||||
|
protected IPermissionDefinitionSerializer PermissionDefinitionSerializer { get; } |
||||
|
protected IDynamicPermissionDefinitionStoreInMemoryCache StoreCache { get; } |
||||
|
protected IDistributedCache DistributedCache { get; } |
||||
|
protected IAbpDistributedLock DistributedLock { get; } |
||||
|
public PermissionManagementOptions PermissionManagementOptions { get; } |
||||
|
protected AbpDistributedCacheOptions CacheOptions { get; } |
||||
|
|
||||
|
public DynamicPermissionDefinitionStore( |
||||
|
IPermissionGroupDefinitionRecordRepository permissionGroupRepository, |
||||
|
IPermissionDefinitionRecordRepository permissionRepository, |
||||
|
IPermissionDefinitionSerializer permissionDefinitionSerializer, |
||||
|
IDynamicPermissionDefinitionStoreInMemoryCache storeCache, |
||||
|
IDistributedCache distributedCache, |
||||
|
IOptions<AbpDistributedCacheOptions> cacheOptions, |
||||
|
IOptions<PermissionManagementOptions> permissionManagementOptions, |
||||
|
IAbpDistributedLock distributedLock) |
||||
|
{ |
||||
|
PermissionGroupRepository = permissionGroupRepository; |
||||
|
PermissionRepository = permissionRepository; |
||||
|
PermissionDefinitionSerializer = permissionDefinitionSerializer; |
||||
|
StoreCache = storeCache; |
||||
|
DistributedCache = distributedCache; |
||||
|
DistributedLock = distributedLock; |
||||
|
PermissionManagementOptions = permissionManagementOptions.Value; |
||||
|
CacheOptions = cacheOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<PermissionDefinition> GetOrNullAsync(string name) |
||||
|
{ |
||||
|
if (!PermissionManagementOptions.IsDynamicPermissionStoreEnabled) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
using (await StoreCache.SyncSemaphore.LockAsync()) |
||||
|
{ |
||||
|
await EnsureCacheIsUptoDateAsync(); |
||||
|
return StoreCache.GetPermissionOrNull(name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync() |
||||
|
{ |
||||
|
if (!PermissionManagementOptions.IsDynamicPermissionStoreEnabled) |
||||
|
{ |
||||
|
return Array.Empty<PermissionDefinition>(); |
||||
|
} |
||||
|
|
||||
|
using (await StoreCache.SyncSemaphore.LockAsync()) |
||||
|
{ |
||||
|
await EnsureCacheIsUptoDateAsync(); |
||||
|
return StoreCache.GetPermissions().ToImmutableList(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync() |
||||
|
{ |
||||
|
if (!PermissionManagementOptions.IsDynamicPermissionStoreEnabled) |
||||
|
{ |
||||
|
return Array.Empty<PermissionGroupDefinition>(); |
||||
|
} |
||||
|
|
||||
|
using (await StoreCache.SyncSemaphore.LockAsync()) |
||||
|
{ |
||||
|
await EnsureCacheIsUptoDateAsync(); |
||||
|
return StoreCache.GetGroups().ToImmutableList(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task EnsureCacheIsUptoDateAsync() |
||||
|
{ |
||||
|
if (StoreCache.LastCheckTime.HasValue && |
||||
|
DateTime.Now.Subtract(StoreCache.LastCheckTime.Value).TotalSeconds < 30) |
||||
|
{ |
||||
|
/* We get the latest permission with a small delay for optimization */ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var stampInDistributedCache = await GetOrSetStampInDistributedCache(); |
||||
|
|
||||
|
if (stampInDistributedCache == StoreCache.CacheStamp) |
||||
|
{ |
||||
|
StoreCache.LastCheckTime = DateTime.Now; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await UpdateInMemoryStoreCache(); |
||||
|
|
||||
|
StoreCache.CacheStamp = stampInDistributedCache; |
||||
|
StoreCache.LastCheckTime = DateTime.Now; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task UpdateInMemoryStoreCache() |
||||
|
{ |
||||
|
var permissionGroupRecords = await PermissionGroupRepository.GetListAsync(); |
||||
|
var permissionRecords = await PermissionRepository.GetListAsync(); |
||||
|
|
||||
|
await StoreCache.FillAsync(permissionGroupRecords, permissionRecords); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<string> GetOrSetStampInDistributedCache() |
||||
|
{ |
||||
|
var cacheKey = GetCommonStampCacheKey(); |
||||
|
|
||||
|
var stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey); |
||||
|
if (stampInDistributedCache != null) |
||||
|
{ |
||||
|
return stampInDistributedCache; |
||||
|
} |
||||
|
|
||||
|
await using (var commonLockHandle = await DistributedLock |
||||
|
.TryAcquireAsync(GetCommonDistributedLockKey(), TimeSpan.FromMinutes(2))) |
||||
|
{ |
||||
|
if (commonLockHandle == null) |
||||
|
{ |
||||
|
/* This request will fail */ |
||||
|
throw new AbpException( |
||||
|
"Could not acquire distributed lock for permission definition common stamp check!" |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey); |
||||
|
if (stampInDistributedCache != null) |
||||
|
{ |
||||
|
return stampInDistributedCache; |
||||
|
} |
||||
|
|
||||
|
stampInDistributedCache = Guid.NewGuid().ToString(); |
||||
|
|
||||
|
await DistributedCache.SetStringAsync( |
||||
|
cacheKey, |
||||
|
stampInDistributedCache, |
||||
|
new DistributedCacheEntryOptions |
||||
|
{ |
||||
|
SlidingExpiration = TimeSpan.FromDays(30) //TODO: Make it configurable?
|
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return stampInDistributedCache; |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetCommonStampCacheKey() |
||||
|
{ |
||||
|
return $"{CacheOptions.KeyPrefix}_AbpInMemoryPermissionCacheStamp"; |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetCommonDistributedLockKey() |
||||
|
{ |
||||
|
return $"{CacheOptions.KeyPrefix}_Common_AbpPermissionUpdateLock"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,127 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public class DynamicPermissionDefinitionStoreInMemoryCache : |
||||
|
IDynamicPermissionDefinitionStoreInMemoryCache, |
||||
|
ISingletonDependency |
||||
|
{ |
||||
|
public string CacheStamp { get; set; } |
||||
|
|
||||
|
protected IDictionary<string, PermissionGroupDefinition> PermissionGroupDefinitions { get; } |
||||
|
protected IDictionary<string, PermissionDefinition> PermissionDefinitions { get; } |
||||
|
protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; } |
||||
|
protected ILocalizableStringSerializer LocalizableStringSerializer { get; } |
||||
|
|
||||
|
public SemaphoreSlim SyncSemaphore { get; } = new(1, 1); |
||||
|
|
||||
|
public DateTime? LastCheckTime { get; set; } |
||||
|
|
||||
|
public DynamicPermissionDefinitionStoreInMemoryCache( |
||||
|
ISimpleStateCheckerSerializer stateCheckerSerializer, |
||||
|
ILocalizableStringSerializer localizableStringSerializer) |
||||
|
{ |
||||
|
StateCheckerSerializer = stateCheckerSerializer; |
||||
|
LocalizableStringSerializer = localizableStringSerializer; |
||||
|
|
||||
|
PermissionGroupDefinitions = new Dictionary<string, PermissionGroupDefinition>(); |
||||
|
PermissionDefinitions = new Dictionary<string, PermissionDefinition>(); |
||||
|
} |
||||
|
|
||||
|
public Task FillAsync( |
||||
|
List<PermissionGroupDefinitionRecord> permissionGroupRecords, |
||||
|
List<PermissionDefinitionRecord> permissionRecords) |
||||
|
{ |
||||
|
PermissionGroupDefinitions.Clear(); |
||||
|
PermissionDefinitions.Clear(); |
||||
|
|
||||
|
var context = new PermissionDefinitionContext(null); |
||||
|
|
||||
|
foreach (var permissionGroupRecord in permissionGroupRecords) |
||||
|
{ |
||||
|
var permissionGroup = context.AddGroup( |
||||
|
permissionGroupRecord.Name, |
||||
|
LocalizableStringSerializer.Deserialize(permissionGroupRecord.DisplayName) |
||||
|
); |
||||
|
|
||||
|
PermissionGroupDefinitions[permissionGroup.Name] = permissionGroup; |
||||
|
|
||||
|
foreach (var property in permissionGroupRecord.ExtraProperties) |
||||
|
{ |
||||
|
permissionGroup[property.Key] = property.Value; |
||||
|
} |
||||
|
|
||||
|
var permissionRecordsInThisGroup = permissionRecords |
||||
|
.Where(p => p.GroupName == permissionGroup.Name); |
||||
|
|
||||
|
foreach (var permissionRecord in permissionRecordsInThisGroup.Where(x => x.ParentName == null)) |
||||
|
{ |
||||
|
AddPermissionRecursively(permissionGroup, permissionRecord, permissionRecords); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public PermissionDefinition GetPermissionOrNull(string name) |
||||
|
{ |
||||
|
return PermissionDefinitions.GetOrDefault(name); |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyList<PermissionDefinition> GetPermissions() |
||||
|
{ |
||||
|
return PermissionDefinitions.Values.ToList(); |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyList<PermissionGroupDefinition> GetGroups() |
||||
|
{ |
||||
|
return PermissionGroupDefinitions.Values.ToList(); |
||||
|
} |
||||
|
|
||||
|
private void AddPermissionRecursively(ICanAddChildPermission permissionContainer, |
||||
|
PermissionDefinitionRecord permissionRecord, |
||||
|
List<PermissionDefinitionRecord> allPermissionRecords) |
||||
|
{ |
||||
|
var permission = permissionContainer.AddPermission( |
||||
|
permissionRecord.Name, |
||||
|
LocalizableStringSerializer.Deserialize(permissionRecord.DisplayName), |
||||
|
permissionRecord.MultiTenancySide, |
||||
|
permissionRecord.IsEnabled |
||||
|
); |
||||
|
|
||||
|
PermissionDefinitions[permission.Name] = permission; |
||||
|
|
||||
|
if (!permissionRecord.Providers.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
permission.Providers.AddRange(permissionRecord.Providers.Split(',')); |
||||
|
} |
||||
|
|
||||
|
if (!permissionRecord.StateCheckers.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
var checkers = StateCheckerSerializer |
||||
|
.DeserializeArray( |
||||
|
permissionRecord.StateCheckers, |
||||
|
permission |
||||
|
); |
||||
|
permission.StateCheckers.AddRange(checkers); |
||||
|
} |
||||
|
|
||||
|
foreach (var property in permissionRecord.ExtraProperties) |
||||
|
{ |
||||
|
permission[property.Key] = property.Value; |
||||
|
} |
||||
|
|
||||
|
foreach (var subPermission in allPermissionRecords.Where(p => p.ParentName == permissionRecord.Name)) |
||||
|
{ |
||||
|
AddPermissionRecursively(permission, subPermission, allPermissionRecords); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public interface IDynamicPermissionDefinitionStoreInMemoryCache |
||||
|
{ |
||||
|
string CacheStamp { get; set; } |
||||
|
|
||||
|
SemaphoreSlim SyncSemaphore { get; } |
||||
|
|
||||
|
DateTime? LastCheckTime { get; set; } |
||||
|
|
||||
|
Task FillAsync( |
||||
|
List<PermissionGroupDefinitionRecord> permissionGroupRecords, |
||||
|
List<PermissionDefinitionRecord> permissionRecords); |
||||
|
|
||||
|
PermissionDefinition GetPermissionOrNull(string name); |
||||
|
|
||||
|
IReadOnlyList<PermissionDefinition> GetPermissions(); |
||||
|
|
||||
|
IReadOnlyList<PermissionGroupDefinition> GetGroups(); |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public interface IPermissionDefinitionRecordRepository : IBasicRepository<PermissionDefinitionRecord, Guid> |
||||
|
{ |
||||
|
Task<PermissionDefinitionRecord> FindByNameAsync( |
||||
|
string name, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public interface IPermissionDefinitionSerializer |
||||
|
{ |
||||
|
Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> |
||||
|
SerializeAsync(IEnumerable<PermissionGroupDefinition> permissionGroups); |
||||
|
|
||||
|
Task<PermissionGroupDefinitionRecord> SerializeAsync( |
||||
|
PermissionGroupDefinition permissionGroup); |
||||
|
|
||||
|
Task<PermissionDefinitionRecord> SerializeAsync( |
||||
|
PermissionDefinition permission, |
||||
|
[CanBeNull] PermissionGroupDefinition permissionGroup); |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public interface IPermissionGroupDefinitionRecordRepository : IBasicRepository<PermissionGroupDefinitionRecord, Guid> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public interface IStaticPermissionSaver |
||||
|
{ |
||||
|
Task SaveAsync(); |
||||
|
} |
||||
@ -0,0 +1,175 @@ |
|||||
|
using System; |
||||
|
using System.Text.Json.Serialization; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public class PermissionDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraProperties |
||||
|
{ |
||||
|
/* Ignoring Id because it is different whenever we create an instance of |
||||
|
* this class, and we are using Json Serialize, than Hash to understand |
||||
|
* if permission definitions have changed (in StaticPermissionSaver.CalculateHash()). |
||||
|
*/ |
||||
|
[JsonIgnore] |
||||
|
public override Guid Id { get; protected set; } |
||||
|
|
||||
|
public string GroupName { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string ParentName { get; set; } |
||||
|
|
||||
|
public string DisplayName { get; set; } |
||||
|
|
||||
|
public bool IsEnabled { get; set; } |
||||
|
|
||||
|
public MultiTenancySides MultiTenancySide { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Comma separated list of provider names.
|
||||
|
/// </summary>
|
||||
|
public string Providers { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Serialized string to store info about the state checkers.
|
||||
|
/// </summary>
|
||||
|
public string StateCheckers { get; set; } |
||||
|
|
||||
|
public ExtraPropertyDictionary ExtraProperties { get; protected set; } |
||||
|
|
||||
|
public PermissionDefinitionRecord() |
||||
|
{ |
||||
|
ExtraProperties = new ExtraPropertyDictionary(); |
||||
|
this.SetDefaultsForExtraProperties(); |
||||
|
} |
||||
|
|
||||
|
public PermissionDefinitionRecord( |
||||
|
Guid id, |
||||
|
string groupName, |
||||
|
string name, |
||||
|
string parentName, |
||||
|
string displayName, |
||||
|
bool isEnabled = true, |
||||
|
MultiTenancySides multiTenancySide = MultiTenancySides.Both, |
||||
|
string providers = null, |
||||
|
string stateCheckers = null) |
||||
|
: base(id) |
||||
|
{ |
||||
|
GroupName = Check.NotNullOrWhiteSpace(groupName, nameof(groupName), PermissionGroupDefinitionRecordConsts.MaxNameLength); |
||||
|
Name = Check.NotNullOrWhiteSpace(name, nameof(name), PermissionDefinitionRecordConsts.MaxNameLength); |
||||
|
ParentName = Check.Length(parentName, nameof(parentName), PermissionDefinitionRecordConsts.MaxNameLength); |
||||
|
DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), PermissionDefinitionRecordConsts.MaxDisplayNameLength); |
||||
|
IsEnabled = isEnabled; |
||||
|
MultiTenancySide = multiTenancySide; |
||||
|
Providers = providers; |
||||
|
StateCheckers = stateCheckers; |
||||
|
|
||||
|
ExtraProperties = new ExtraPropertyDictionary(); |
||||
|
this.SetDefaultsForExtraProperties(); |
||||
|
} |
||||
|
|
||||
|
public bool HasSameData(PermissionDefinitionRecord otherRecord) |
||||
|
{ |
||||
|
if (Name != otherRecord.Name) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (GroupName != otherRecord.GroupName) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (ParentName != otherRecord.ParentName) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (DisplayName != otherRecord.DisplayName) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (IsEnabled != otherRecord.IsEnabled) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (MultiTenancySide != otherRecord.MultiTenancySide) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (Providers != otherRecord.Providers) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (StateCheckers != otherRecord.StateCheckers) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!this.HasSameExtraProperties(otherRecord)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public void Patch(PermissionDefinitionRecord otherRecord) |
||||
|
{ |
||||
|
if (Name != otherRecord.Name) |
||||
|
{ |
||||
|
Name = otherRecord.Name; |
||||
|
} |
||||
|
|
||||
|
if (GroupName != otherRecord.GroupName) |
||||
|
{ |
||||
|
GroupName = otherRecord.GroupName; |
||||
|
} |
||||
|
|
||||
|
if (ParentName != otherRecord.ParentName) |
||||
|
{ |
||||
|
ParentName = otherRecord.ParentName; |
||||
|
} |
||||
|
|
||||
|
if (DisplayName != otherRecord.DisplayName) |
||||
|
{ |
||||
|
DisplayName = otherRecord.DisplayName; |
||||
|
} |
||||
|
|
||||
|
if (IsEnabled != otherRecord.IsEnabled) |
||||
|
{ |
||||
|
IsEnabled = otherRecord.IsEnabled; |
||||
|
} |
||||
|
|
||||
|
if (MultiTenancySide != otherRecord.MultiTenancySide) |
||||
|
{ |
||||
|
MultiTenancySide = otherRecord.MultiTenancySide; |
||||
|
} |
||||
|
|
||||
|
if (Providers != otherRecord.Providers) |
||||
|
{ |
||||
|
Providers = otherRecord.Providers; |
||||
|
} |
||||
|
|
||||
|
if (StateCheckers != otherRecord.StateCheckers) |
||||
|
{ |
||||
|
StateCheckers = otherRecord.StateCheckers; |
||||
|
} |
||||
|
|
||||
|
if (!this.HasSameExtraProperties(otherRecord)) |
||||
|
{ |
||||
|
this.ExtraProperties.Clear(); |
||||
|
|
||||
|
foreach (var property in otherRecord.ExtraProperties) |
||||
|
{ |
||||
|
this.ExtraProperties.Add(property.Key, property.Value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.SimpleStateChecking; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, ITransientDependency |
||||
|
{ |
||||
|
protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; } |
||||
|
protected IGuidGenerator GuidGenerator { get; } |
||||
|
protected ILocalizableStringSerializer LocalizableStringSerializer { get; } |
||||
|
|
||||
|
public PermissionDefinitionSerializer( |
||||
|
IGuidGenerator guidGenerator, |
||||
|
ISimpleStateCheckerSerializer stateCheckerSerializer, |
||||
|
ILocalizableStringSerializer localizableStringSerializer) |
||||
|
{ |
||||
|
StateCheckerSerializer = stateCheckerSerializer; |
||||
|
LocalizableStringSerializer = localizableStringSerializer; |
||||
|
GuidGenerator = guidGenerator; |
||||
|
} |
||||
|
|
||||
|
public async Task<(PermissionGroupDefinitionRecord[], PermissionDefinitionRecord[])> |
||||
|
SerializeAsync(IEnumerable<PermissionGroupDefinition> permissionGroups) |
||||
|
{ |
||||
|
var permissionGroupRecords = new List<PermissionGroupDefinitionRecord>(); |
||||
|
var permissionRecords = new List<PermissionDefinitionRecord>(); |
||||
|
|
||||
|
foreach (var permissionGroup in permissionGroups) |
||||
|
{ |
||||
|
permissionGroupRecords.Add(await SerializeAsync(permissionGroup)); |
||||
|
|
||||
|
foreach (var permission in permissionGroup.GetPermissionsWithChildren()) |
||||
|
{ |
||||
|
permissionRecords.Add(await SerializeAsync(permission, permissionGroup)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return (permissionGroupRecords.ToArray(), permissionRecords.ToArray()); |
||||
|
} |
||||
|
|
||||
|
public Task<PermissionGroupDefinitionRecord> SerializeAsync(PermissionGroupDefinition permissionGroup) |
||||
|
{ |
||||
|
using (CultureHelper.Use(CultureInfo.InvariantCulture)) |
||||
|
{ |
||||
|
var permissionGroupRecord = new PermissionGroupDefinitionRecord( |
||||
|
GuidGenerator.Create(), |
||||
|
permissionGroup.Name, |
||||
|
LocalizableStringSerializer.Serialize(permissionGroup.DisplayName) |
||||
|
); |
||||
|
|
||||
|
foreach (var property in permissionGroup.Properties) |
||||
|
{ |
||||
|
permissionGroupRecord.SetProperty(property.Key, property.Value); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult(permissionGroupRecord); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task<PermissionDefinitionRecord> SerializeAsync( |
||||
|
PermissionDefinition permission, |
||||
|
PermissionGroupDefinition permissionGroup) |
||||
|
{ |
||||
|
using (CultureHelper.Use(CultureInfo.InvariantCulture)) |
||||
|
{ |
||||
|
var permissionRecord = new PermissionDefinitionRecord( |
||||
|
GuidGenerator.Create(), |
||||
|
permissionGroup?.Name, |
||||
|
permission.Name, |
||||
|
permission.Parent?.Name, |
||||
|
LocalizableStringSerializer.Serialize(permission.DisplayName), |
||||
|
permission.IsEnabled, |
||||
|
permission.MultiTenancySide, |
||||
|
SerializeProviders(permission.Providers), |
||||
|
SerializeStateCheckers(permission.StateCheckers) |
||||
|
); |
||||
|
|
||||
|
foreach (var property in permission.Properties) |
||||
|
{ |
||||
|
permissionRecord.SetProperty(property.Key, property.Value); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult(permissionRecord); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual string SerializeProviders(ICollection<string> providers) |
||||
|
{ |
||||
|
return providers.Any() |
||||
|
? providers.JoinAsString(",") |
||||
|
: null; |
||||
|
} |
||||
|
|
||||
|
protected virtual string SerializeStateCheckers(List<ISimpleStateChecker<PermissionDefinition>> stateCheckers) |
||||
|
{ |
||||
|
return StateCheckerSerializer.Serialize(stateCheckers); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
using System; |
||||
|
using System.Text.Json.Serialization; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public class PermissionGroupDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraProperties |
||||
|
{ |
||||
|
/* Ignoring Id because it is different whenever we create an instance of |
||||
|
* this class, and we are using Json Serialize, than Hash to understand |
||||
|
* if permission definitions have changed (in StaticPermissionSaver.CalculateHash()). |
||||
|
*/ |
||||
|
[JsonIgnore] |
||||
|
public override Guid Id { get; protected set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string DisplayName { get; set; } |
||||
|
|
||||
|
public ExtraPropertyDictionary ExtraProperties { get; protected set; } |
||||
|
|
||||
|
public PermissionGroupDefinitionRecord() |
||||
|
{ |
||||
|
ExtraProperties = new ExtraPropertyDictionary(); |
||||
|
this.SetDefaultsForExtraProperties(); |
||||
|
} |
||||
|
|
||||
|
public PermissionGroupDefinitionRecord( |
||||
|
Guid id, |
||||
|
string name, |
||||
|
string displayName) |
||||
|
: base(id) |
||||
|
{ |
||||
|
Name = Check.NotNullOrWhiteSpace(name, nameof(name), PermissionGroupDefinitionRecordConsts.MaxNameLength); |
||||
|
DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), PermissionGroupDefinitionRecordConsts.MaxDisplayNameLength);; |
||||
|
|
||||
|
ExtraProperties = new ExtraPropertyDictionary(); |
||||
|
this.SetDefaultsForExtraProperties(); |
||||
|
} |
||||
|
|
||||
|
public bool HasSameData(PermissionGroupDefinitionRecord otherRecord) |
||||
|
{ |
||||
|
if (Name != otherRecord.Name) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (DisplayName != otherRecord.DisplayName) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!this.HasSameExtraProperties(otherRecord)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public void Patch(PermissionGroupDefinitionRecord otherRecord) |
||||
|
{ |
||||
|
if (Name != otherRecord.Name) |
||||
|
{ |
||||
|
Name = otherRecord.Name; |
||||
|
} |
||||
|
|
||||
|
if (DisplayName != otherRecord.DisplayName) |
||||
|
{ |
||||
|
DisplayName = otherRecord.DisplayName; |
||||
|
} |
||||
|
|
||||
|
if (!this.HasSameExtraProperties(otherRecord)) |
||||
|
{ |
||||
|
this.ExtraProperties.Clear(); |
||||
|
|
||||
|
foreach (var property in otherRecord.ExtraProperties) |
||||
|
{ |
||||
|
this.ExtraProperties.Add(property.Key, property.Value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,294 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Caching.Distributed; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DistributedLocking; |
||||
|
using Volo.Abp.Threading; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement; |
||||
|
|
||||
|
public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependency |
||||
|
{ |
||||
|
protected IStaticPermissionDefinitionStore StaticStore { get; } |
||||
|
protected IPermissionGroupDefinitionRecordRepository PermissionGroupRepository { get; } |
||||
|
protected IPermissionDefinitionRecordRepository PermissionRepository { get; } |
||||
|
protected IPermissionDefinitionSerializer PermissionSerializer { get; } |
||||
|
protected IDistributedCache Cache { get; } |
||||
|
protected IApplicationNameAccessor ApplicationNameAccessor { get; } |
||||
|
protected IAbpDistributedLock DistributedLock { get; } |
||||
|
protected PermissionManagementOptions PermissionManagementOptions { get; } |
||||
|
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
||||
|
protected AbpDistributedCacheOptions CacheOptions { get; } |
||||
|
|
||||
|
public StaticPermissionSaver( |
||||
|
IStaticPermissionDefinitionStore staticStore, |
||||
|
IPermissionGroupDefinitionRecordRepository permissionGroupRepository, |
||||
|
IPermissionDefinitionRecordRepository permissionRepository, |
||||
|
IPermissionDefinitionSerializer permissionSerializer, |
||||
|
IDistributedCache cache, |
||||
|
IOptions<AbpDistributedCacheOptions> cacheOptions, |
||||
|
IApplicationNameAccessor applicationNameAccessor, |
||||
|
IAbpDistributedLock distributedLock, |
||||
|
IOptions<PermissionManagementOptions> permissionManagementOptions, |
||||
|
ICancellationTokenProvider cancellationTokenProvider) |
||||
|
{ |
||||
|
StaticStore = staticStore; |
||||
|
PermissionGroupRepository = permissionGroupRepository; |
||||
|
PermissionRepository = permissionRepository; |
||||
|
PermissionSerializer = permissionSerializer; |
||||
|
Cache = cache; |
||||
|
ApplicationNameAccessor = applicationNameAccessor; |
||||
|
DistributedLock = distributedLock; |
||||
|
CancellationTokenProvider = cancellationTokenProvider; |
||||
|
PermissionManagementOptions = permissionManagementOptions.Value; |
||||
|
CacheOptions = cacheOptions.Value; |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public virtual async Task SaveAsync() |
||||
|
{ |
||||
|
await using var applicationLockHandle = await DistributedLock.TryAcquireAsync( |
||||
|
GetApplicationDistributedLockKey() |
||||
|
); |
||||
|
|
||||
|
if (applicationLockHandle == null) |
||||
|
{ |
||||
|
/* Another application instance is already doing it */ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
/* NOTE: This can be further optimized by using 4 cache values for: |
||||
|
* Groups, permissions, deleted groups and deleted permissions. |
||||
|
* But the code would be more complex. This is enough for now. |
||||
|
*/ |
||||
|
|
||||
|
var cacheKey = GetApplicationHashCacheKey(); |
||||
|
var cachedHash = await Cache.GetStringAsync(cacheKey, CancellationTokenProvider.Token); |
||||
|
|
||||
|
var (permissionGroupRecords, permissionRecords) = await PermissionSerializer.SerializeAsync( |
||||
|
await StaticStore.GetGroupsAsync() |
||||
|
); |
||||
|
|
||||
|
var currentHash = CalculateHash( |
||||
|
permissionGroupRecords, |
||||
|
permissionRecords, |
||||
|
PermissionManagementOptions.DeletedPermissionGroups, |
||||
|
PermissionManagementOptions.DeletedPermissions |
||||
|
); |
||||
|
|
||||
|
if (cachedHash == currentHash) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await using (var commonLockHandle = await DistributedLock.TryAcquireAsync( |
||||
|
GetCommonDistributedLockKey(), |
||||
|
TimeSpan.FromMinutes(5))) |
||||
|
{ |
||||
|
if (commonLockHandle == null) |
||||
|
{ |
||||
|
/* It will re-try */ |
||||
|
throw new AbpException("Could not acquire distributed lock for saving static permissions!"); |
||||
|
} |
||||
|
|
||||
|
var hasChangesInGroups = await UpdateChangedPermissionGroupsAsync(permissionGroupRecords); |
||||
|
var hasChangesInPermissions = await UpdateChangedPermissionsAsync(permissionRecords); |
||||
|
|
||||
|
if (hasChangesInGroups ||hasChangesInPermissions) |
||||
|
{ |
||||
|
await Cache.SetStringAsync( |
||||
|
GetCommonStampCacheKey(), |
||||
|
Guid.NewGuid().ToString(), |
||||
|
new DistributedCacheEntryOptions { |
||||
|
SlidingExpiration = TimeSpan.FromDays(30) //TODO: Make it configurable?
|
||||
|
}, |
||||
|
CancellationTokenProvider.Token |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await Cache.SetStringAsync( |
||||
|
cacheKey, |
||||
|
currentHash, |
||||
|
new DistributedCacheEntryOptions { |
||||
|
SlidingExpiration = TimeSpan.FromDays(30) //TODO: Make it configurable?
|
||||
|
}, |
||||
|
CancellationTokenProvider.Token |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private async Task<bool> UpdateChangedPermissionGroupsAsync( |
||||
|
IEnumerable<PermissionGroupDefinitionRecord> permissionGroupRecords) |
||||
|
{ |
||||
|
var newRecords = new List<PermissionGroupDefinitionRecord>(); |
||||
|
var changedRecords = new List<PermissionGroupDefinitionRecord>(); |
||||
|
|
||||
|
var permissionGroupRecordsInDatabase = (await PermissionGroupRepository.GetListAsync()) |
||||
|
.ToDictionary(x => x.Name); |
||||
|
|
||||
|
foreach (var permissionGroupRecord in permissionGroupRecords) |
||||
|
{ |
||||
|
var permissionGroupRecordInDatabase = permissionGroupRecordsInDatabase.GetOrDefault(permissionGroupRecord.Name); |
||||
|
if (permissionGroupRecordInDatabase == null) |
||||
|
{ |
||||
|
/* New group */ |
||||
|
newRecords.Add(permissionGroupRecord); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (permissionGroupRecord.HasSameData(permissionGroupRecordInDatabase)) |
||||
|
{ |
||||
|
/* Not changed */ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
/* Changed */ |
||||
|
permissionGroupRecordInDatabase.Patch(permissionGroupRecord); |
||||
|
changedRecords.Add(permissionGroupRecordInDatabase); |
||||
|
} |
||||
|
|
||||
|
/* Deleted */ |
||||
|
var deletedRecords = PermissionManagementOptions.DeletedPermissionGroups.Any() |
||||
|
? permissionGroupRecordsInDatabase.Values |
||||
|
.Where(x => PermissionManagementOptions.DeletedPermissionGroups.Contains(x.Name)) |
||||
|
.ToArray() |
||||
|
: Array.Empty<PermissionGroupDefinitionRecord>(); |
||||
|
|
||||
|
if (newRecords.Any()) |
||||
|
{ |
||||
|
await PermissionGroupRepository.InsertManyAsync(newRecords); |
||||
|
} |
||||
|
|
||||
|
if (changedRecords.Any()) |
||||
|
{ |
||||
|
await PermissionGroupRepository.UpdateManyAsync(changedRecords); |
||||
|
} |
||||
|
|
||||
|
if (deletedRecords.Any()) |
||||
|
{ |
||||
|
await PermissionGroupRepository.DeleteManyAsync(deletedRecords); |
||||
|
} |
||||
|
|
||||
|
return newRecords.Any() || changedRecords.Any() || deletedRecords.Any(); |
||||
|
} |
||||
|
|
||||
|
private async Task<bool> UpdateChangedPermissionsAsync( |
||||
|
IEnumerable<PermissionDefinitionRecord> permissionRecords) |
||||
|
{ |
||||
|
var newRecords = new List<PermissionDefinitionRecord>(); |
||||
|
var changedRecords = new List<PermissionDefinitionRecord>(); |
||||
|
|
||||
|
var permissionRecordsInDatabase = (await PermissionRepository.GetListAsync()) |
||||
|
.ToDictionary(x => x.Name); |
||||
|
|
||||
|
foreach (var permissionRecord in permissionRecords) |
||||
|
{ |
||||
|
var permissionRecordInDatabase = permissionRecordsInDatabase.GetOrDefault(permissionRecord.Name); |
||||
|
if (permissionRecordInDatabase == null) |
||||
|
{ |
||||
|
/* New group */ |
||||
|
newRecords.Add(permissionRecord); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (permissionRecord.HasSameData(permissionRecordInDatabase)) |
||||
|
{ |
||||
|
/* Not changed */ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
/* Changed */ |
||||
|
permissionRecordInDatabase.Patch(permissionRecord); |
||||
|
changedRecords.Add(permissionRecordInDatabase); |
||||
|
} |
||||
|
|
||||
|
/* Deleted */ |
||||
|
var deletedRecords = new List<PermissionDefinitionRecord>(); |
||||
|
|
||||
|
if (PermissionManagementOptions.DeletedPermissions.Any()) |
||||
|
{ |
||||
|
deletedRecords.AddRange( |
||||
|
permissionRecordsInDatabase.Values |
||||
|
.Where(x => PermissionManagementOptions.DeletedPermissions.Contains(x.Name)) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (PermissionManagementOptions.DeletedPermissionGroups.Any()) |
||||
|
{ |
||||
|
deletedRecords.AddIfNotContains( |
||||
|
permissionRecordsInDatabase.Values |
||||
|
.Where(x => PermissionManagementOptions.DeletedPermissionGroups.Contains(x.GroupName)) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (newRecords.Any()) |
||||
|
{ |
||||
|
await PermissionRepository.InsertManyAsync(newRecords); |
||||
|
} |
||||
|
|
||||
|
if (changedRecords.Any()) |
||||
|
{ |
||||
|
await PermissionRepository.UpdateManyAsync(changedRecords); |
||||
|
} |
||||
|
|
||||
|
if (deletedRecords.Any()) |
||||
|
{ |
||||
|
await PermissionRepository.DeleteManyAsync(deletedRecords); |
||||
|
} |
||||
|
|
||||
|
return newRecords.Any() || changedRecords.Any() || deletedRecords.Any(); |
||||
|
} |
||||
|
|
||||
|
private string GetApplicationDistributedLockKey() |
||||
|
{ |
||||
|
return $"{CacheOptions.KeyPrefix}_{ApplicationNameAccessor.ApplicationName}_AbpPermissionUpdateLock"; |
||||
|
} |
||||
|
|
||||
|
private string GetCommonDistributedLockKey() |
||||
|
{ |
||||
|
return $"{CacheOptions.KeyPrefix}_Common_AbpPermissionUpdateLock"; |
||||
|
} |
||||
|
|
||||
|
private string GetApplicationHashCacheKey() |
||||
|
{ |
||||
|
return $"{CacheOptions.KeyPrefix}_{ApplicationNameAccessor.ApplicationName}_AbpPermissionsHash"; |
||||
|
} |
||||
|
|
||||
|
private string GetCommonStampCacheKey() |
||||
|
{ |
||||
|
return $"{CacheOptions.KeyPrefix}_AbpInMemoryPermissionCacheStamp"; |
||||
|
} |
||||
|
|
||||
|
private static string CalculateHash( |
||||
|
PermissionGroupDefinitionRecord[] permissionGroupRecords, |
||||
|
PermissionDefinitionRecord[] permissionRecords, |
||||
|
IEnumerable<string> deletedPermissionGroups, |
||||
|
IEnumerable<string> deletedPermissions) |
||||
|
{ |
||||
|
var stringBuilder = new StringBuilder(); |
||||
|
|
||||
|
stringBuilder.Append("PermissionGroupRecords:"); |
||||
|
stringBuilder.AppendLine(JsonSerializer.Serialize(permissionGroupRecords)); |
||||
|
|
||||
|
stringBuilder.Append("PermissionRecords:"); |
||||
|
stringBuilder.AppendLine(JsonSerializer.Serialize(permissionRecords)); |
||||
|
|
||||
|
stringBuilder.Append("DeletedPermissionGroups:"); |
||||
|
stringBuilder.AppendLine(deletedPermissionGroups.JoinAsString(",")); |
||||
|
|
||||
|
stringBuilder.Append("DeletedPermission:"); |
||||
|
stringBuilder.Append(deletedPermissions.JoinAsString(",")); |
||||
|
|
||||
|
return stringBuilder |
||||
|
.ToString() |
||||
|
.ToMd5(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System; |
||||
|
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 EfCorePermissionDefinitionRecordRepository : |
||||
|
EfCoreRepository<IPermissionManagementDbContext, PermissionDefinitionRecord, Guid>, |
||||
|
IPermissionDefinitionRecordRepository |
||||
|
{ |
||||
|
public EfCorePermissionDefinitionRecordRepository( |
||||
|
IDbContextProvider<IPermissionManagementDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<PermissionDefinitionRecord> FindByNameAsync( |
||||
|
string name, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.OrderBy(x => x.Id) |
||||
|
.FirstOrDefaultAsync(r => r.Name == name, cancellationToken); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement.EntityFrameworkCore; |
||||
|
|
||||
|
public class EfCorePermissionGroupDefinitionRecordRepository : |
||||
|
EfCoreRepository<IPermissionManagementDbContext, PermissionGroupDefinitionRecord, Guid>, |
||||
|
IPermissionGroupDefinitionRecordRepository |
||||
|
{ |
||||
|
public EfCorePermissionGroupDefinitionRecordRepository( |
||||
|
IDbContextProvider<IPermissionManagementDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using System; |
||||
|
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 MongoPermissionDefinitionRecordRepository : |
||||
|
MongoDbRepository<IPermissionManagementMongoDbContext, PermissionDefinitionRecord, Guid>, |
||||
|
IPermissionDefinitionRecordRepository |
||||
|
{ |
||||
|
public MongoPermissionDefinitionRecordRepository( |
||||
|
IMongoDbContextProvider<IPermissionManagementMongoDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<PermissionDefinitionRecord> FindByNameAsync( |
||||
|
string name, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
cancellationToken = GetCancellationToken(cancellationToken); |
||||
|
return await (await GetMongoQueryableAsync(cancellationToken)) |
||||
|
.OrderBy(x => x.Id) |
||||
|
.FirstOrDefaultAsync( |
||||
|
s => s.Name == name, |
||||
|
cancellationToken |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Repositories.MongoDB; |
||||
|
using Volo.Abp.MongoDB; |
||||
|
|
||||
|
namespace Volo.Abp.PermissionManagement.MongoDB; |
||||
|
|
||||
|
public class MongoPermissionGroupDefinitionRecordRepository : |
||||
|
MongoDbRepository<IPermissionManagementMongoDbContext, PermissionGroupDefinitionRecord, Guid>, |
||||
|
IPermissionGroupDefinitionRecordRepository |
||||
|
{ |
||||
|
public MongoPermissionGroupDefinitionRecordRepository( |
||||
|
IMongoDbContextProvider<IPermissionManagementMongoDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue