Browse Source

Merge pull request #25424 from abpframework/fix/distribution-batch-state-checker-projection

Serialize batch state checkers per permission in dynamic distribution
pull/25430/head
Ma Liming 5 days ago
committed by GitHub
parent
commit
da24e5e15b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AuthenticatedSimpleStateCheckerSerializerContributor.cs
  2. 24
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionsSimpleStateCheckerSerializerContributor.cs
  3. 15
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs
  4. 4
      framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializer.cs
  5. 3
      framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializerContributor.cs
  6. 17
      framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializer.cs
  7. 25
      framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializerExtensions.cs
  8. 24
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeaturesSimpleStateCheckerSerializerContributor.cs
  9. 15
      framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs
  10. 4
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeaturesSimpleStateCheckerSerializerContributor.cs
  11. 52
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs
  12. 8
      modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs
  13. 68
      modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs

6
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AuthenticatedSimpleStateCheckerSerializerContributor.cs

@ -10,7 +10,7 @@ public class AuthenticatedSimpleStateCheckerSerializerContributor :
{
public const string CheckerShortName = "A";
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker)
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker)
where TState : IHasSimpleStateCheckers<TState>
{
if (checker is not RequireAuthenticatedSimpleStateChecker<TState>)
@ -25,6 +25,10 @@ public class AuthenticatedSimpleStateCheckerSerializerContributor :
return jsonObject.ToJsonString();
}
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker, TState state)
where TState : IHasSimpleStateCheckers<TState>
=> SerializeToJson(checker);
public ISimpleStateChecker<TState>? Deserialize<TState>(JsonObject jsonObject, TState state)
where TState : IHasSimpleStateCheckers<TState>
{

24
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionsSimpleStateCheckerSerializerContributor.cs

@ -20,13 +20,31 @@ public class PermissionsSimpleStateCheckerSerializerContributor :
return null;
}
var jsonObject = new JsonObject {
return BuildJson(permissionsSimpleStateChecker.RequiresAll, permissionsSimpleStateChecker.PermissionNames);
}
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker, TState state)
where TState : IHasSimpleStateCheckers<TState>
{
if (checker is RequirePermissionsSimpleBatchStateChecker<TState> batch)
{
var model = batch.GetModelOrNull(state);
return model == null ? null : BuildJson(model.RequiresAll, model.Permissions);
}
return SerializeToJson(checker);
}
private static string BuildJson(bool requiresAll, string[] permissionNames)
{
var jsonObject = new JsonObject
{
["T"] = CheckerShortName,
["A"] = permissionsSimpleStateChecker.RequiresAll
["A"] = requiresAll
};
var nameArray = new JsonArray();
foreach (var permissionName in permissionsSimpleStateChecker.PermissionNames)
foreach (var permissionName in permissionNames)
{
nameArray.Add(permissionName);
}

15
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs

@ -27,9 +27,12 @@ public class RequirePermissionsSimpleBatchStateChecker<TState> : SimpleBatchStat
private readonly List<RequirePermissionsSimpleBatchStateCheckerModel<TState>> _models;
private readonly Dictionary<TState, RequirePermissionsSimpleBatchStateCheckerModel<TState>> _modelsByState;
public RequirePermissionsSimpleBatchStateChecker()
{
_models = new List<RequirePermissionsSimpleBatchStateCheckerModel<TState>>();
_modelsByState = new Dictionary<TState, RequirePermissionsSimpleBatchStateCheckerModel<TState>>();
}
public RequirePermissionsSimpleBatchStateChecker<TState> AddCheckModels(params RequirePermissionsSimpleBatchStateCheckerModel<TState>[] models)
@ -37,6 +40,13 @@ public class RequirePermissionsSimpleBatchStateChecker<TState> : SimpleBatchStat
Check.NotNullOrEmpty(models, nameof(models));
_models.AddRange(models);
foreach (var model in models)
{
if (!_modelsByState.ContainsKey(model.State))
{
_modelsByState[model.State] = model;
}
}
return this;
}
@ -47,6 +57,11 @@ public class RequirePermissionsSimpleBatchStateChecker<TState> : SimpleBatchStat
return new DisposeAction(() => _current.Value = previousValue);
}
public virtual RequirePermissionsSimpleBatchStateCheckerModel<TState>? GetModelOrNull(TState state)
{
return _modelsByState.TryGetValue(state, out var model) ? model : null;
}
public override async Task<SimpleStateCheckerResult<TState>> IsEnabledAsync(SimpleBatchStateCheckerContext<TState> context)
{
var permissionChecker = context.ServiceProvider.GetRequiredService<IPermissionChecker>();

4
framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializer.cs

@ -7,6 +7,10 @@ public interface ISimpleStateCheckerSerializer
public string? Serialize<TState>(ISimpleStateChecker<TState> checker)
where TState : IHasSimpleStateCheckers<TState>;
public string? Serialize<TState>(ISimpleStateChecker<TState> checker, TState state)
where TState : IHasSimpleStateCheckers<TState>;
public ISimpleStateChecker<TState>? Deserialize<TState>(JsonObject jsonObject, TState state)
where TState : IHasSimpleStateCheckers<TState>;
}

3
framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializerContributor.cs

@ -7,6 +7,9 @@ public interface ISimpleStateCheckerSerializerContributor
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker)
where TState : IHasSimpleStateCheckers<TState>;
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker, TState state)
where TState : IHasSimpleStateCheckers<TState>;
public ISimpleStateChecker<TState>? Deserialize<TState>(JsonObject jsonObject, TState state)
where TState : IHasSimpleStateCheckers<TState>;
}

17
framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializer.cs

@ -15,7 +15,7 @@ public class SimpleStateCheckerSerializer :
_contributors = contributors;
}
public string? Serialize<TState>(ISimpleStateChecker<TState> checker)
public string? Serialize<TState>(ISimpleStateChecker<TState> checker)
where TState : IHasSimpleStateCheckers<TState>
{
foreach (var contributor in _contributors)
@ -30,6 +30,21 @@ public class SimpleStateCheckerSerializer :
return null;
}
public string? Serialize<TState>(ISimpleStateChecker<TState> checker, TState state)
where TState : IHasSimpleStateCheckers<TState>
{
foreach (var contributor in _contributors)
{
var result = contributor.SerializeToJson(checker, state);
if (result != null)
{
return result;
}
}
return null;
}
public ISimpleStateChecker<TState>? Deserialize<TState>(JsonObject jsonObject, TState state)
where TState : IHasSimpleStateCheckers<TState>
{

25
framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializerExtensions.cs

@ -8,25 +8,42 @@ namespace Volo.Abp.SimpleStateChecking;
public static class SimpleStateCheckerSerializerExtensions
{
public static string? Serialize<TState>(
this ISimpleStateCheckerSerializer serializer,
this ISimpleStateCheckerSerializer serializer,
IList<ISimpleStateChecker<TState>> stateCheckers)
where TState : IHasSimpleStateCheckers<TState>
{
return SerializeCore(stateCheckers, serializer.Serialize);
}
public static string? Serialize<TState>(
this ISimpleStateCheckerSerializer serializer,
IList<ISimpleStateChecker<TState>> stateCheckers,
TState state)
where TState : IHasSimpleStateCheckers<TState>
{
return SerializeCore(stateCheckers, c => serializer.Serialize(c, state));
}
private static string? SerializeCore<TState>(
IList<ISimpleStateChecker<TState>> stateCheckers,
Func<ISimpleStateChecker<TState>, string?> serializeChecker)
where TState : IHasSimpleStateCheckers<TState>
{
switch (stateCheckers.Count)
{
case 0:
return null;
case 1:
var serializedChecker = serializer.Serialize(stateCheckers.Single());
var serializedChecker = serializeChecker(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);
var serialized = serializeChecker(stateChecker);
if (serialized != null)
{
serializedCheckers.Add(serialized);

24
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeaturesSimpleStateCheckerSerializerContributor.cs

@ -19,13 +19,31 @@ public class FeaturesSimpleStateCheckerSerializerContributor :
return null;
}
var jsonObject = new JsonObject {
return BuildJson(featuresSimpleStateChecker.RequiresAll, featuresSimpleStateChecker.FeatureNames);
}
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker, TState state)
where TState : IHasSimpleStateCheckers<TState>
{
if (checker is RequireFeaturesSimpleBatchStateChecker<TState> batch)
{
var model = batch.GetModelOrNull(state);
return model == null ? null : BuildJson(model.RequiresAll, model.FeatureNames);
}
return SerializeToJson(checker);
}
private static string BuildJson(bool requiresAll, string[] featureNames)
{
var jsonObject = new JsonObject
{
["T"] = CheckerShortName,
["A"] = featuresSimpleStateChecker.RequiresAll
["A"] = requiresAll
};
var nameArray = new JsonArray();
foreach (var featureName in featuresSimpleStateChecker.FeatureNames)
foreach (var featureName in featureNames)
{
nameArray.Add(featureName);
}

15
framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs

@ -27,9 +27,12 @@ public class RequireFeaturesSimpleBatchStateChecker<TState> : SimpleBatchStateCh
private readonly List<RequireFeaturesSimpleBatchStateCheckerModel<TState>> _models;
private readonly Dictionary<TState, RequireFeaturesSimpleBatchStateCheckerModel<TState>> _modelsByState;
public RequireFeaturesSimpleBatchStateChecker()
{
_models = new List<RequireFeaturesSimpleBatchStateCheckerModel<TState>>();
_modelsByState = new Dictionary<TState, RequireFeaturesSimpleBatchStateCheckerModel<TState>>();
}
public RequireFeaturesSimpleBatchStateChecker<TState> AddCheckModels(
@ -38,6 +41,13 @@ public class RequireFeaturesSimpleBatchStateChecker<TState> : SimpleBatchStateCh
Check.NotNullOrEmpty(models, nameof(models));
_models.AddRange(models);
foreach (var model in models)
{
if (!_modelsByState.ContainsKey(model.State))
{
_modelsByState[model.State] = model;
}
}
return this;
}
@ -48,6 +58,11 @@ public class RequireFeaturesSimpleBatchStateChecker<TState> : SimpleBatchStateCh
return new DisposeAction(() => _current.Value = previousValue);
}
public virtual RequireFeaturesSimpleBatchStateCheckerModel<TState>? GetModelOrNull(TState state)
{
return _modelsByState.TryGetValue(state, out var model) ? model : null;
}
public override async Task<SimpleStateCheckerResult<TState>> IsEnabledAsync(
SimpleBatchStateCheckerContext<TState> context)
{

4
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeaturesSimpleStateCheckerSerializerContributor.cs

@ -34,6 +34,10 @@ public class GlobalFeaturesSimpleStateCheckerSerializerContributor :
return jsonObject.ToJsonString();
}
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker, TState state)
where TState : IHasSimpleStateCheckers<TState>
=> SerializeToJson(checker);
public ISimpleStateChecker<TState>? Deserialize<TState>(JsonObject jsonObject, TState state)
where TState : IHasSimpleStateCheckers<TState>
{

52
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs

@ -85,6 +85,58 @@ public class RequireFeaturesSimpleBatchStateChecker_Tests : FeatureTestBase
}
}
[Fact]
public void GetModelOrNull_Returns_First_Win_When_Same_State_Registered_Twice()
{
// Mirrors IsEnabledAsync's modelLookup behaviour: when the same state is registered
// multiple times, the first registration wins. Backed by the dictionary index.
var checker = new RequireFeaturesSimpleBatchStateChecker<NamedState>();
var state = new NamedState("A");
checker.AddCheckModels(
new RequireFeaturesSimpleBatchStateCheckerModel<NamedState>(state, new[] { "First" }, true));
checker.AddCheckModels(
new RequireFeaturesSimpleBatchStateCheckerModel<NamedState>(state, new[] { "Second" }, true));
checker.GetModelOrNull(state)!.FeatureNames.ShouldBe(new[] { "First" });
}
[Fact]
public void GetModelOrNull_Uses_Same_Equality_As_Runtime()
{
// The runtime path (IsEnabledAsync) looks up models via HashSet<TState>(context.States),
// i.e. EqualityComparer<TState>.Default. GetModelOrNull must use the same semantics or
// a custom TState.Equals would make the runtime gate and the serializer disagree.
var checker = new RequireFeaturesSimpleBatchStateChecker<NamedState>();
var stateA1 = new NamedState("A");
var stateA2 = new NamedState("A"); // distinct instance, equal by Name
var stateB = new NamedState("B");
checker.AddCheckModels(
new RequireFeaturesSimpleBatchStateCheckerModel<NamedState>(stateA1, new[] { "F1" }, true),
new RequireFeaturesSimpleBatchStateCheckerModel<NamedState>(stateB, new[] { "F2" }, true));
// Same equality semantics as the runtime: A2 hits A1's model.
checker.GetModelOrNull(stateA1).ShouldNotBeNull();
checker.GetModelOrNull(stateA2).ShouldNotBeNull();
checker.GetModelOrNull(stateA2)!.FeatureNames.ShouldBe(new[] { "F1" });
checker.GetModelOrNull(new NamedState("missing")).ShouldBeNull();
}
private sealed class NamedState : IHasSimpleStateCheckers<NamedState>, IEquatable<NamedState>
{
public string Name { get; }
public List<ISimpleStateChecker<NamedState>> StateCheckers { get; } = new();
public NamedState(string name) => Name = name;
public bool Equals(NamedState? other) => other is not null && other.Name == Name;
public override bool Equals(object? obj) => obj is NamedState other && Equals(other);
public override int GetHashCode() => Name.GetHashCode();
}
[Fact]
public async Task Current_Should_Not_Be_Null_In_Fresh_ExecutionContext()
{

8
modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs

@ -93,7 +93,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I
permission.IsEnabled,
permission.MultiTenancySide,
SerializeProviders(permission.Providers),
SerializeStateCheckers(permission.StateCheckers)
SerializeStateCheckers(permission, permission.StateCheckers)
);
foreach (var property in permission.Properties)
@ -112,8 +112,10 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I
: null;
}
protected virtual string SerializeStateCheckers(List<ISimpleStateChecker<PermissionDefinition>> stateCheckers)
protected virtual string SerializeStateCheckers(
PermissionDefinition permission,
List<ISimpleStateChecker<PermissionDefinition>> stateCheckers)
{
return StateCheckerSerializer.Serialize(stateCheckers);
return StateCheckerSerializer.Serialize(stateCheckers, permission);
}
}

68
modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs

@ -79,6 +79,74 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase
}
[Fact]
public async Task Serialize_Permission_With_Default_Batch_RequireFeatures()
{
var context = new PermissionDefinitionContext(null);
var group = context.AddGroup("AbpAuditLogging");
var permission = group.AddPermission(
"AbpAuditLogging.Settings",
new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1"))
.RequireFeatures("AbpAuditLogging.SettingManagement");
var record = await _serializer.SerializeAsync(permission, group);
record.StateCheckers.ShouldBe(
"[{\"T\":\"F\",\"A\":true,\"N\":[\"AbpAuditLogging.SettingManagement\"]}]");
}
[Fact]
public async Task Serialize_Permission_With_Default_Batch_RequireFeatures_Picks_Per_State_Model()
{
var context = new PermissionDefinitionContext(null);
var group = context.AddGroup("Group1");
var permA = group.AddPermission("PermA", new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1"))
.RequireFeatures("FeatureForA");
var permB = group.AddPermission("PermB", new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1"))
.RequireFeatures("FeatureForB1", "FeatureForB2");
var recordA = await _serializer.SerializeAsync(permA, group);
var recordB = await _serializer.SerializeAsync(permB, group);
recordA.StateCheckers.ShouldBe("[{\"T\":\"F\",\"A\":true,\"N\":[\"FeatureForA\"]}]");
recordB.StateCheckers.ShouldBe("[{\"T\":\"F\",\"A\":true,\"N\":[\"FeatureForB1\",\"FeatureForB2\"]}]");
}
[Fact]
public async Task Serialize_Permission_With_Default_Batch_RequirePermissions()
{
var context = new PermissionDefinitionContext(null);
var group = context.AddGroup("Group1");
var permission = group.AddPermission(
"Permission1",
new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1"))
.RequirePermissions("OtherPermission1", "OtherPermission2");
var record = await _serializer.SerializeAsync(permission, group);
record.StateCheckers.ShouldBe(
"[{\"T\":\"P\",\"A\":true,\"N\":[\"OtherPermission1\",\"OtherPermission2\"]}]");
}
[Fact]
public async Task Serialize_Permission_With_Default_Batch_RequirePermissions_Picks_Per_State_Model()
{
var context = new PermissionDefinitionContext(null);
var group = context.AddGroup("Group1");
var permA = group.AddPermission("PermA", new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1"))
.RequirePermissions("OtherForA");
var permB = group.AddPermission("PermB", new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1"))
.RequirePermissions(requiresAll: false, "OtherForB1", "OtherForB2");
var recordA = await _serializer.SerializeAsync(permA, group);
var recordB = await _serializer.SerializeAsync(permB, group);
recordA.StateCheckers.ShouldBe("[{\"T\":\"P\",\"A\":true,\"N\":[\"OtherForA\"]}]");
recordB.StateCheckers.ShouldBe("[{\"T\":\"P\",\"A\":false,\"N\":[\"OtherForB1\",\"OtherForB2\"]}]");
}
[Fact]
public async Task Serialize_Complex_Resource_Permission_Definition()
{

Loading…
Cancel
Save