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.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Authorization.Permissions; |
|||
|
|||
public interface IPermissionDefinitionManager |
|||
{ |
|||
[NotNull] |
|||
PermissionDefinition Get([NotNull] string name); |
|||
[ItemNotNull] |
|||
Task<PermissionDefinition> GetAsync([NotNull] string name); |
|||
|
|||
[CanBeNull] |
|||
PermissionDefinition GetOrNull([NotNull] string name); |
|||
[ItemCanBeNull] |
|||
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