Skip to content

Commit 6b31497

Browse files
committed
Harden limiter lifecycle and key formatting
1 parent 98e9d3a commit 6b31497

25 files changed

Lines changed: 257 additions & 53 deletions

ManagedCode.Orleans.RateLimiting.Client/Extensions/HttpRequestExtensions.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ namespace ManagedCode.Orleans.RateLimiting.Client.Extensions;
77

88
public static class HttpRequestExtensions
99
{
10+
private const char CsvSeparator = ',';
11+
private const string RealIpHeaderName = "X-Real-IP";
12+
private const string ForwardedForHeaderName = "X-Forwarded-For";
13+
private const string RemoteAddressHeaderName = "REMOTE_ADDR";
14+
1015
private static readonly string[] DefaultIpHeaders =
1116
[
12-
"X-Real-IP",
13-
"X-Forwarded-For",
14-
"REMOTE_ADDR"
17+
RealIpHeaderName,
18+
ForwardedForHeaderName,
19+
RemoteAddressHeaderName
1520
];
1621

1722
public static string GetClientIpAddress(this HttpRequest request)
@@ -59,6 +64,6 @@ private static IEnumerable<string> SplitCsv(string? csvList)
5964
if (string.IsNullOrWhiteSpace(csvList))
6065
return Enumerable.Empty<string>();
6166

62-
return csvList.TrimEnd(',').Split(',').Select(s => s.Trim());
67+
return csvList.TrimEnd(CsvSeparator).Split(CsvSeparator).Select(s => s.Trim());
6368
}
6469
}

ManagedCode.Orleans.RateLimiting.Client/Middlewares/OrleansBaseRateLimitingMiddleware.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using ManagedCode.Orleans.RateLimiting.Core.Extensions;
77
using ManagedCode.Orleans.RateLimiting.Core.Models;
88
using ManagedCode.Orleans.RateLimiting.Core.Models.Holders;
9+
using ManagedCode.Orleans.RateLimiting.Core.Models.Orchestration;
910
using Microsoft.AspNetCore.Http;
1011
using Microsoft.AspNetCore.Mvc.Controllers;
1112
using Microsoft.Extensions.DependencyInjection;
@@ -89,6 +90,6 @@ protected static (T attribute, string? postfix)? TryGetAttribute<T>(HttpContext
8990

9091
protected static string CreateKey(params string[] parts)
9192
{
92-
return string.Join(RateLimitMiddlewareConstants.KeySeparator, parts);
93+
return RateLimitPartitionKeyFormatter.Join(parts);
9394
}
9495
}

ManagedCode.Orleans.RateLimiting.Client/Middlewares/OrleansUserRateLimitingMiddleware.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Security.Claims;
23
using ManagedCode.Orleans.RateLimiting.Client.Attributes;
34
using ManagedCode.Orleans.RateLimiting.Client.Extensions;
45
using ManagedCode.Orleans.RateLimiting.Core.Models.Holders;
@@ -45,7 +46,7 @@ private bool AddAuthorizedRateLimiter(HttpContext httpContext, GroupLimiterHolde
4546
{
4647
var attribute = TryGetAttribute<AuthorizedIpRateLimiterAttribute>(httpContext);
4748
if (attribute.HasValue)
48-
return holder.AddLimiter(TryGetLimiterHolder(CreateKey(httpContext.Request.GetClientIpAddress(), httpContext.User.Identity.Name ?? "rate-user-name", attribute.Value.postfix!), attribute.Value.attribute.ConfigurationName));
49+
return holder.AddLimiter(TryGetLimiterHolder(CreateKey(httpContext.Request.GetClientIpAddress(), GetAuthenticatedUserKey(httpContext), attribute.Value.postfix!), attribute.Value.attribute.ConfigurationName));
4950
}
5051

5152
return false;
@@ -56,9 +57,16 @@ private bool AddInRoleRateLimiter(HttpContext httpContext, GroupLimiterHolder ho
5657
var attribute = TryGetAttribute<InRoleIpRateLimiterAttribute>(httpContext);
5758
if (attribute.HasValue)
5859
if (httpContext.User?.Identity?.IsAuthenticated is true && httpContext.User.IsInRole(attribute.Value.attribute.Role))
59-
return holder.AddLimiter(TryGetLimiterHolder(CreateKey(httpContext.Request.GetClientIpAddress(), httpContext.User.Identity.Name!, attribute.Value.attribute.Role, attribute.Value.postfix!),
60+
return holder.AddLimiter(TryGetLimiterHolder(CreateKey(httpContext.Request.GetClientIpAddress(), GetAuthenticatedUserKey(httpContext), attribute.Value.attribute.Role, attribute.Value.postfix!),
6061
attribute.Value.attribute.ConfigurationName));
6162

6263
return false;
6364
}
64-
}
65+
66+
private static string GetAuthenticatedUserKey(HttpContext httpContext)
67+
{
68+
return httpContext.User.Identity?.Name
69+
?? httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)
70+
?? RateLimitMiddlewareConstants.UnknownAuthenticatedUserKey;
71+
}
72+
}

ManagedCode.Orleans.RateLimiting.Client/Middlewares/RateLimitMiddlewareConstants.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ namespace ManagedCode.Orleans.RateLimiting.Client.Middlewares;
33
internal static class RateLimitMiddlewareConstants
44
{
55
public const string DefaultPath = "/";
6-
public const string KeySeparator = ":";
6+
public const string UnknownAuthenticatedUserKey = "rate-user-name";
77
public const string OperationNameSeparator = " ";
88
public const string ResourceSeparator = ".";
99
public const string TooManyRequestsError = "Too many requests";
@@ -17,4 +17,5 @@ internal static class RateLimitMiddlewareConstants
1717
public const string ConfigurationMetadataKey = "configuration";
1818
public const string PartitionMetadataKey = "partition";
1919
public const string PathMetadataKey = "path";
20+
public const string SignalRRateLimitedLogMessage = "SignalR invocation {HubMethodName} was rate limited: {Reason}";
2021
}

ManagedCode.Orleans.RateLimiting.Client/Middlewares/RateLimitResponseWriter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Threading.Tasks;
2+
using System.Globalization;
23
using ManagedCode.Orleans.RateLimiting.Core.Models;
34
using Microsoft.AspNetCore.Http;
45

@@ -17,7 +18,7 @@ await httpContext.Response.WriteAsJsonAsync(new RateLimitRejectedResponse(
1718
StatusCodes.Status429TooManyRequests,
1819
RateLimitMiddlewareConstants.TooManyRequestsError,
1920
lease.Reason,
20-
lease.RetryAfter.ToString()));
21+
lease.RetryAfter.ToString(null, CultureInfo.InvariantCulture)));
2122
}
2223

2324
private sealed record RateLimitRejectedResponse(int StatusCode, string Error, string Reason, string RetryAfter);

ManagedCode.Orleans.RateLimiting.Client/Middlewares/RateLimitingHubFilter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public RateLimitingHubFilter(
3434
var lease = await holder.AcquireAsync();
3535
if (lease is not null)
3636
{
37-
_logger.LogInformation("SignalR invocation {HubMethodName} was rate limited: {Reason}", invocationContext.HubMethodName, lease.Reason);
37+
_logger.LogInformation(RateLimitMiddlewareConstants.SignalRRateLimitedLogMessage, invocationContext.HubMethodName, lease.Reason);
3838
throw new RateLimitExceededException(lease.Reason, lease.RetryAfter);
3939
}
4040

ManagedCode.Orleans.RateLimiting.Core/Exceptions/RateLimitConfigurationNotFoundException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public RateLimitConfigurationNotFoundException()
99
}
1010

1111
public RateLimitConfigurationNotFoundException(string configurationName)
12-
: base($"Rate limiter configuration '{configurationName}' was not found.")
12+
: base(RateLimiterExceptionMessages.ConfigurationNotFound(configurationName))
1313
{
1414
ConfigurationName = configurationName;
1515
}

ManagedCode.Orleans.RateLimiting.Core/Exceptions/RateLimitPartitionKeyNotFoundException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public RateLimitPartitionKeyNotFoundException()
1010
}
1111

1212
public RateLimitPartitionKeyNotFoundException(RateLimitPartitionKind kind)
13-
: base($"Rate limit partition key for '{kind}' was not found.")
13+
: base(RateLimiterExceptionMessages.PartitionKeyNotFound(kind))
1414
{
1515
Kind = kind;
1616
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace ManagedCode.Orleans.RateLimiting.Core.Exceptions;
2+
3+
public static class RateLimiterExceptionMessages
4+
{
5+
private const string ConfigurationNotFoundPrefix = "Rate limiter configuration '";
6+
private const string PartitionKeyNotFoundPrefix = "Rate limit partition key for '";
7+
private const string NotFoundSuffix = "' was not found.";
8+
9+
public static string ConfigurationNotFound(string configurationName)
10+
{
11+
return string.Concat(ConfigurationNotFoundPrefix, configurationName, NotFoundSuffix);
12+
}
13+
14+
public static string PartitionKeyNotFound(object kind)
15+
{
16+
return string.Concat(PartitionKeyNotFoundPrefix, kind, NotFoundSuffix);
17+
}
18+
}

ManagedCode.Orleans.RateLimiting.Core/Extensions/GrainFactoryExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public static ILimiterHolder GetRateLimiter<T>(this IGrainFactory factory, strin
1919
var type when type == typeof(ISlidingWindowRateLimiterGrain) => factory.GetSlidingWindowRateLimiter(key),
2020
var type when type == typeof(ITokenBucketRateLimiterGrain) => factory.GetTokenBucketRateLimiter(key),
2121

22-
_ => throw new System.NotSupportedException($"Rate limiter grain type '{typeof(T).FullName}' is not supported.")
22+
_ => throw new System.NotSupportedException(RateLimiterLogMessages.UnsupportedGrainType(typeof(T).FullName))
2323
};
2424
}
2525

0 commit comments

Comments
 (0)