Browse Source

refactor: update partition key handling to support async resolution in operation rate limiting

pull/25024/head
maliming 7 months ago
parent
commit
46b283e338
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 5
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingContext.cs
  2. 15
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleBuilder.cs
  3. 3
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleDefinition.cs
  4. 21
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs
  5. 3
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingTestModule.cs
  6. 7
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingPolicyBuilder_Tests.cs

5
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingContext.cs

@ -8,8 +8,9 @@ public class OperationRateLimitingContext
{
/// <summary>
/// Optional parameter passed by the caller.
/// Used by rules configured with PartitionByParameter().
/// Can be email, phone number, resource id, or any string.
/// Used as the partition key by PartitionByParameter() (required),
/// and as a fallback by PartitionByEmail() and PartitionByPhoneNumber().
/// Can be email, phone number, user id, resource id, or any string.
/// </summary>
public string? Parameter { get; set; }

15
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleBuilder.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimiting;
@ -8,7 +9,7 @@ public class OperationRateLimitingRuleBuilder
private TimeSpan _duration;
private int _maxCount;
private OperationRateLimitingPartitionType? _partitionType;
private Func<OperationRateLimitingContext, string>? _customPartitionKeyResolver;
private Func<OperationRateLimitingContext, Task<string>>? _customPartitionKeyResolver;
private bool _isMultiTenant;
internal bool IsCommitted { get; private set; }
@ -43,7 +44,8 @@ public class OperationRateLimitingRuleBuilder
}
/// <summary>
/// Auto resolve from ICurrentUser.Id.
/// Partition by the current authenticated user (ICurrentUser.Id).
/// Use PartitionByParameter() if you need to specify the user ID explicitly.
/// </summary>
public OperationRateLimitingPolicyBuilder PartitionByCurrentUser()
{
@ -53,7 +55,7 @@ public class OperationRateLimitingRuleBuilder
}
/// <summary>
/// Auto resolve from ICurrentTenant.Id.
/// Partition by the current tenant (ICurrentTenant.Id). Uses "host" when no tenant is active.
/// </summary>
public OperationRateLimitingPolicyBuilder PartitionByCurrentTenant()
{
@ -63,7 +65,8 @@ public class OperationRateLimitingRuleBuilder
}
/// <summary>
/// Auto resolve from IWebClientInfoProvider.ClientIpAddress.
/// Partition by the client IP address (IWebClientInfoProvider.ClientIpAddress).
/// Use PartitionByParameter() if you need to specify the IP explicitly.
/// </summary>
public OperationRateLimitingPolicyBuilder PartitionByClientIp()
{
@ -95,10 +98,10 @@ public class OperationRateLimitingRuleBuilder
}
/// <summary>
/// Custom partition key resolver from context.
/// Custom async partition key resolver from context.
/// </summary>
public OperationRateLimitingPolicyBuilder PartitionBy(
Func<OperationRateLimitingContext, string> keyResolver)
Func<OperationRateLimitingContext, Task<string>> keyResolver)
{
_partitionType = OperationRateLimitingPartitionType.Custom;
_customPartitionKeyResolver = Check.NotNull(keyResolver, nameof(keyResolver));

3
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleDefinition.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp.OperationRateLimiting;
@ -10,7 +11,7 @@ public class OperationRateLimitingRuleDefinition
public OperationRateLimitingPartitionType PartitionType { get; set; }
public Func<OperationRateLimitingContext, string>? CustomPartitionKeyResolver { get; set; }
public Func<OperationRateLimitingContext, Task<string>>? CustomPartitionKeyResolver { get; set; }
public bool IsMultiTenant { get; set; }
}

21
framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs

@ -38,7 +38,7 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule
public virtual async Task<OperationRateLimitingRuleResult> AcquireAsync(
OperationRateLimitingContext context)
{
var partitionKey = ResolvePartitionKey(context);
var partitionKey = await ResolvePartitionKeyAsync(context);
var storeKey = BuildStoreKey(partitionKey);
var storeResult = await Store.IncrementAsync(storeKey, Definition.Duration, Definition.MaxCount);
@ -48,7 +48,7 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule
public virtual async Task<OperationRateLimitingRuleResult> CheckAsync(
OperationRateLimitingContext context)
{
var partitionKey = ResolvePartitionKey(context);
var partitionKey = await ResolvePartitionKeyAsync(context);
var storeKey = BuildStoreKey(partitionKey);
var storeResult = await Store.GetAsync(storeKey, Definition.Duration, Definition.MaxCount);
@ -57,12 +57,12 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule
public virtual async Task ResetAsync(OperationRateLimitingContext context)
{
var partitionKey = ResolvePartitionKey(context);
var partitionKey = await ResolvePartitionKeyAsync(context);
var storeKey = BuildStoreKey(partitionKey);
await Store.ResetAsync(storeKey);
}
protected virtual string ResolvePartitionKey(OperationRateLimitingContext context)
protected virtual async Task<string> ResolvePartitionKeyAsync(OperationRateLimitingContext context)
{
return Definition.PartitionType switch
{
@ -71,17 +71,20 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule
$"OperationRateLimitingContext.Parameter is required for policy '{PolicyName}' (PartitionByParameter)."),
OperationRateLimitingPartitionType.CurrentUser =>
CurrentUser.Id?.ToString() ?? throw new AbpException(
$"Current user is not authenticated. Policy '{PolicyName}' requires PartitionByCurrentUser."),
CurrentUser.Id?.ToString()
?? throw new AbpException(
$"Current user is not authenticated. Policy '{PolicyName}' requires PartitionByCurrentUser. " +
"Use PartitionByParameter() if you need to specify the user ID explicitly."),
OperationRateLimitingPartitionType.CurrentTenant =>
CurrentTenant.Id?.ToString() ?? HostTenantKey,
CurrentTenant.Id?.ToString()
?? HostTenantKey,
OperationRateLimitingPartitionType.ClientIp =>
WebClientInfoProvider.ClientIpAddress
?? throw new AbpException(
$"Client IP address could not be determined. Policy '{PolicyName}' requires PartitionByClientIp. " +
"Ensure IWebClientInfoProvider is properly configured."),
"Ensure IWebClientInfoProvider is properly configured or use PartitionByParameter() to pass the IP explicitly."),
OperationRateLimitingPartitionType.Email =>
context.Parameter
@ -96,7 +99,7 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule
$"Phone number is required for policy '{PolicyName}' (PartitionByPhoneNumber). Provide it via context.Parameter or ensure the user has a phone number."),
OperationRateLimitingPartitionType.Custom =>
Definition.CustomPartitionKeyResolver!(context),
await Definition.CustomPartitionKeyResolver!(context),
_ => throw new AbpException($"Unknown partition type: {Definition.PartitionType}")
};

3
framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingTestModule.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Volo.Abp.AspNetCore.WebClientInfo;
@ -157,7 +158,7 @@ public class AbpOperationRateLimitingTestModule : AbpModule
options.AddPolicy("TestCustomResolver", policy =>
{
policy.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 2)
.PartitionBy(ctx => $"action:{ctx.Parameter}");
.PartitionBy(ctx => Task.FromResult($"action:{ctx.Parameter}"));
});
// Multi-tenant: ByParameter with tenant isolation - same param, different tenants = different counters

7
framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingPolicyBuilder_Tests.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
@ -74,7 +75,7 @@ public class OperationRateLimitingPolicyBuilder_Tests
{
policy.AddRule(rule => rule
.WithFixedWindow(TimeSpan.FromMinutes(30), maxCount: 5)
.PartitionBy(ctx => $"custom:{ctx.Parameter}"));
.PartitionBy(ctx => Task.FromResult($"custom:{ctx.Parameter}")));
});
var policy = options.Policies["CustomPolicy"];
@ -242,11 +243,11 @@ public class OperationRateLimitingPolicyBuilder_Tests
{
policy.AddRule(rule => rule
.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5)
.PartitionBy(ctx => $"by-ip:{ctx.Parameter}"));
.PartitionBy(ctx => Task.FromResult($"by-ip:{ctx.Parameter}")));
policy.AddRule(rule => rule
.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5)
.PartitionBy(ctx => $"by-device:{ctx.Parameter}"));
.PartitionBy(ctx => Task.FromResult($"by-device:{ctx.Parameter}")));
});
var policy = options.Policies["MultiCustomPolicy"];

Loading…
Cancel
Save