Browse Source

refactor: improve operation rate limit rule handling and add multi-tenancy support in policy builder tests

pull/25024/head
maliming 3 weeks ago
parent
commit
4c3448be32
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 4
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/FixedWindowOperationRateLimitRule.cs
  2. 15
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitPolicyBuilder.cs
  3. 23
      framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitRuleBuilder.cs
  4. 48
      framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/OperationRateLimitPolicyBuilder_Tests.cs

4
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/FixedWindowOperationRateLimitRule.cs

@ -24,7 +24,7 @@ public class FixedWindowOperationRateLimitRule : IOperationRateLimitRule
IOperationRateLimitStore store, IOperationRateLimitStore store,
ICurrentUser currentUser, ICurrentUser currentUser,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IClientIpAddressProvider clientInfoProvider) IClientIpAddressProvider clientIpAddressProvider)
{ {
PolicyName = policyName; PolicyName = policyName;
RuleIndex = ruleIndex; RuleIndex = ruleIndex;
@ -32,7 +32,7 @@ public class FixedWindowOperationRateLimitRule : IOperationRateLimitRule
Store = store; Store = store;
CurrentUser = currentUser; CurrentUser = currentUser;
CurrentTenant = currentTenant; CurrentTenant = currentTenant;
ClientIpAddressProvider = clientInfoProvider; ClientIpAddressProvider = clientIpAddressProvider;
} }
public virtual async Task<OperationRateLimitRuleResult> AcquireAsync( public virtual async Task<OperationRateLimitRuleResult> AcquireAsync(

15
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitPolicyBuilder.cs

@ -22,9 +22,12 @@ public class OperationRateLimitPolicyBuilder
public OperationRateLimitPolicyBuilder AddRule( public OperationRateLimitPolicyBuilder AddRule(
Action<OperationRateLimitRuleBuilder> configure) Action<OperationRateLimitRuleBuilder> configure)
{ {
var builder = new OperationRateLimitRuleBuilder(); var builder = new OperationRateLimitRuleBuilder(this);
configure(builder); configure(builder);
_rules.Add(builder.Build()); if (!builder.IsCommitted)
{
_rules.Add(builder.Build());
}
return this; return this;
} }
@ -74,15 +77,17 @@ public class OperationRateLimitPolicyBuilder
} }
var duplicate = _rules var duplicate = _rules
.GroupBy(r => (r.Duration, r.MaxCount, r.PartitionType)) .Where(r => r.PartitionType != OperationRateLimitPartitionType.Custom)
.GroupBy(r => (r.Duration, r.MaxCount, r.PartitionType, r.IsMultiTenant))
.FirstOrDefault(g => g.Count() > 1); .FirstOrDefault(g => g.Count() > 1);
if (duplicate != null) if (duplicate != null)
{ {
var (duration, maxCount, partitionType) = duplicate.Key; var (duration, maxCount, partitionType, isMultiTenant) = duplicate.Key;
throw new AbpException( throw new AbpException(
$"Operation rate limit policy '{_name}' has duplicate rules with the same " + $"Operation rate limit policy '{_name}' has duplicate rules with the same " +
$"Duration ({duration}), MaxCount ({maxCount}), and PartitionType ({partitionType}). " + $"Duration ({duration}), MaxCount ({maxCount}), PartitionType ({partitionType}), " +
$"and IsMultiTenant ({isMultiTenant}). " +
"Each rule in a policy must have a unique combination of these properties."); "Each rule in a policy must have a unique combination of these properties.");
} }

23
framework/src/Volo.Abp.OperationRateLimit/Volo/Abp/OperationRateLimit/OperationRateLimitRuleBuilder.cs

@ -4,16 +4,14 @@ namespace Volo.Abp.OperationRateLimit;
public class OperationRateLimitRuleBuilder public class OperationRateLimitRuleBuilder
{ {
private readonly OperationRateLimitPolicyBuilder? _policyBuilder; private readonly OperationRateLimitPolicyBuilder _policyBuilder;
private TimeSpan _duration; private TimeSpan _duration;
private int _maxCount; private int _maxCount;
private OperationRateLimitPartitionType? _partitionType; private OperationRateLimitPartitionType? _partitionType;
private Func<OperationRateLimitContext, string>? _customPartitionKeyResolver; private Func<OperationRateLimitContext, string>? _customPartitionKeyResolver;
private bool _isMultiTenant; private bool _isMultiTenant;
public OperationRateLimitRuleBuilder() internal bool IsCommitted { get; private set; }
{
}
internal OperationRateLimitRuleBuilder(OperationRateLimitPolicyBuilder policyBuilder) internal OperationRateLimitRuleBuilder(OperationRateLimitPolicyBuilder policyBuilder)
{ {
@ -41,7 +39,7 @@ public class OperationRateLimitRuleBuilder
{ {
_partitionType = OperationRateLimitPartitionType.Parameter; _partitionType = OperationRateLimitPartitionType.Parameter;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder!; return _policyBuilder;
} }
/// <summary> /// <summary>
@ -51,7 +49,7 @@ public class OperationRateLimitRuleBuilder
{ {
_partitionType = OperationRateLimitPartitionType.CurrentUser; _partitionType = OperationRateLimitPartitionType.CurrentUser;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder!; return _policyBuilder;
} }
/// <summary> /// <summary>
@ -61,7 +59,7 @@ public class OperationRateLimitRuleBuilder
{ {
_partitionType = OperationRateLimitPartitionType.CurrentTenant; _partitionType = OperationRateLimitPartitionType.CurrentTenant;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder!; return _policyBuilder;
} }
/// <summary> /// <summary>
@ -71,7 +69,7 @@ public class OperationRateLimitRuleBuilder
{ {
_partitionType = OperationRateLimitPartitionType.ClientIp; _partitionType = OperationRateLimitPartitionType.ClientIp;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder!; return _policyBuilder;
} }
/// <summary> /// <summary>
@ -82,7 +80,7 @@ public class OperationRateLimitRuleBuilder
{ {
_partitionType = OperationRateLimitPartitionType.Email; _partitionType = OperationRateLimitPartitionType.Email;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder!; return _policyBuilder;
} }
/// <summary> /// <summary>
@ -93,7 +91,7 @@ public class OperationRateLimitRuleBuilder
{ {
_partitionType = OperationRateLimitPartitionType.PhoneNumber; _partitionType = OperationRateLimitPartitionType.PhoneNumber;
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder!; return _policyBuilder;
} }
/// <summary> /// <summary>
@ -105,12 +103,13 @@ public class OperationRateLimitRuleBuilder
_partitionType = OperationRateLimitPartitionType.Custom; _partitionType = OperationRateLimitPartitionType.Custom;
_customPartitionKeyResolver = Check.NotNull(keyResolver, nameof(keyResolver)); _customPartitionKeyResolver = Check.NotNull(keyResolver, nameof(keyResolver));
CommitToPolicyBuilder(); CommitToPolicyBuilder();
return _policyBuilder!; return _policyBuilder;
} }
protected virtual void CommitToPolicyBuilder() protected virtual void CommitToPolicyBuilder()
{ {
_policyBuilder?.AddRuleDefinition(Build()); _policyBuilder.AddRuleDefinition(Build());
IsCommitted = true;
} }
internal OperationRateLimitRuleDefinition Build() internal OperationRateLimitRuleDefinition Build()

48
framework/test/Volo.Abp.OperationRateLimit.Tests/Volo/Abp/OperationRateLimit/OperationRateLimitPolicyBuilder_Tests.cs

@ -206,4 +206,52 @@ public class OperationRateLimitPolicyBuilder_Tests
exception.Message.ShouldContain("maxCount >= 0"); exception.Message.ShouldContain("maxCount >= 0");
} }
[Fact]
public void Should_Allow_Same_Rule_With_Different_MultiTenancy()
{
var options = new AbpOperationRateLimitOptions();
// Same Duration/MaxCount/PartitionType but different IsMultiTenant should be allowed
options.AddPolicy("MultiTenancyPolicy", policy =>
{
policy.AddRule(rule => rule
.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5)
.PartitionByParameter());
policy.AddRule(rule => rule
.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5)
.WithMultiTenancy()
.PartitionByParameter());
});
var policy = options.Policies["MultiTenancyPolicy"];
policy.Rules.Count.ShouldBe(2);
policy.Rules[0].IsMultiTenant.ShouldBeFalse();
policy.Rules[1].IsMultiTenant.ShouldBeTrue();
}
[Fact]
public void Should_Allow_Multiple_Custom_Partition_Rules()
{
var options = new AbpOperationRateLimitOptions();
// Multiple custom partition rules with same Duration/MaxCount should be allowed
// because they may use different key resolvers
options.AddPolicy("MultiCustomPolicy", policy =>
{
policy.AddRule(rule => rule
.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5)
.PartitionBy(ctx => $"by-ip:{ctx.Parameter}"));
policy.AddRule(rule => rule
.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5)
.PartitionBy(ctx => $"by-device:{ctx.Parameter}"));
});
var policy = options.Policies["MultiCustomPolicy"];
policy.Rules.Count.ShouldBe(2);
policy.Rules[0].PartitionType.ShouldBe(OperationRateLimitPartitionType.Custom);
policy.Rules[1].PartitionType.ShouldBe(OperationRateLimitPartitionType.Custom);
}
} }

Loading…
Cancel
Save