From d3b9edaefaf049a0e90366d5463aa4988f190441 Mon Sep 17 00:00:00 2001
From: Thomas <37584838+tomfp@users.noreply.github.com>
Date: Thu, 3 Aug 2023 14:04:37 +0100
Subject: [PATCH 1/2] Allow Client or IP rate limiting to be disabled, without
removing configuration options
---
.../Middleware/RateLimitMiddleware.cs | 2 +-
.../Models/RateLimitOptions.cs | 5 +
.../appsettings.EnabledFalse.json | 205 ++++++++++++++++++
.../appsettings.Regex.json | 2 +
.../AspNetCoreRateLimit.Demo/appsettings.json | 2 +
.../BaseClassFixture.cs | 20 ++
.../ClientRateLimitTests.cs | 25 +++
.../Enums/ClientType.cs | 3 +-
.../IpRateLimitTests.cs | 24 ++
9 files changed, 286 insertions(+), 2 deletions(-)
create mode 100644 test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json
diff --git a/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs b/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs
index 3b6f63fc..33a2bff3 100644
--- a/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs
+++ b/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs
@@ -31,7 +31,7 @@ protected RateLimitMiddleware(
public async Task Invoke(HttpContext context)
{
// check if rate limiting is enabled
- if (_options == null)
+ if (_options == null || _options.RateLimitingEnabled == false)
{
await _next.Invoke(context);
return;
diff --git a/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs b/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs
index 89d2cf73..29b8a9f0 100644
--- a/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs
+++ b/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs
@@ -7,6 +7,11 @@ namespace AspNetCoreRateLimit
{
public class RateLimitOptions
{
+ ///
+ /// Gets or sets whether Rate Limiting is enabled
+ ///
+ public bool RateLimitingEnabled { get; set; } = true; // default for backward compatibility
+
public List GeneralRules { get; set; }
public List EndpointWhitelist { get; set; }
diff --git a/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json b/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json
new file mode 100644
index 00000000..b774037e
--- /dev/null
+++ b/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json
@@ -0,0 +1,205 @@
+{
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ },
+
+ "IpRateLimiting": {
+ "EnableEndpointRateLimiting": false,
+ "EnableRegexRuleMatching": true,
+ "StackBlockedRequests": false,
+ "RealIpHeader": "X-Real-IP",
+ "ClientIdHeader": "X-ClientId",
+ "HttpStatusCode": 429,
+ "IpWhitelist": [ "::1/10", "192.168.0.0/24" ],
+ "EndpointWhitelist": [ "delete:/api/values", ":/api/clients", ":/api/ClientRateLimit", ":/api/IpRateLimit", "get:/" ],
+ "ClientWhitelist": [ "cl-key-1", "cl-key-2" ],
+ "QuotaExceededResponse": {
+ "Content": "{{ \"message\": \"Whoa! Calm down, cowboy!\", \"details\": \"Quota exceeded. Maximum allowed: {0} per {1}. Please try again in {2} second(s).\" }}",
+ "ContentType": "application/json"
+ },
+ "GeneralRules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1s",
+ "Limit": 2
+ },
+ {
+ "Endpoint": ".+",
+ "Period": "1m",
+ "Limit": 5
+ }
+ ]
+ },
+
+ "IpRateLimitPolicies": {
+ "IpRules": [
+ {
+ "Ip": "84.247.85.224",
+ "Rules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1s",
+ "Limit": 10
+ },
+ {
+ "Endpoint": ".+",
+ "Period": "1m",
+ "Limit": 2
+ },
+ {
+ "Endpoint": "post:/api/values",
+ "Period": "1m",
+ "Limit": 5
+ }
+ ]
+ },
+ {
+ "Ip": "84.247.85.225",
+ "Rules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1s",
+ "Limit": 10
+ },
+ {
+ "Endpoint": ".+",
+ "Period": "1m",
+ "Limit": 5
+ },
+ {
+ "Endpoint": ".+",
+ "Period": "1h",
+ "Limit": 2
+ }
+ ]
+ },
+ {
+ "Ip": "84.247.85.226",
+ "Rules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1s",
+ "Limit": 10
+ },
+ {
+ "Endpoint": ".+",
+ "Period": "1m",
+ "Limit": 5
+ },
+ {
+ "Endpoint": ".+",
+ "Period": "1d",
+ "Limit": 2
+ }
+ ]
+ },
+ {
+ "Ip": "84.247.85.231",
+ "Rules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1m",
+ "Limit": 0
+ }
+ ]
+ },
+ {
+ "Ip": "84.247.85.232",
+ "Rules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1m",
+ "Limit": 1,
+ "MonitorMode": true
+ }
+ ]
+ }
+ ]
+ },
+
+ "ClientRateLimiting": {
+ "EnableEndpointRateLimiting": false,
+ "EnableRegexRuleMatching": true,
+ "ClientIdHeader": "X-ClientId",
+ "HttpStatusCode": 429,
+ "EndpointWhitelist": [ "((post)|(put)|(get)|(delete)):/api/values", "delete:/api/clients" ],
+ "ClientWhitelist": [ "cl-key-a", "cl-key-b" ],
+ "GeneralRules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1s",
+ "Limit": 2
+ },
+ {
+ "Endpoint": ".+",
+ "Period": "1m",
+ "Limit": 5
+ },
+ {
+ "Endpoint": "post:/api/clients",
+ "Period": "5m",
+ "Limit": 3
+ }
+ ]
+ },
+
+ "ClientRateLimitPolicies": {
+ "ClientRules": [
+ {
+ "ClientId": "cl-key-1",
+ "Rules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1s",
+ "Limit": 10
+ },
+ {
+ "Endpoint": "get:/api/clients",
+ "Period": "1m",
+ "Limit": 2
+ },
+ {
+ "Endpoint": "put:/api/clients",
+ "Period": "5m",
+ "Limit": 2
+ }
+ ]
+ },
+ {
+ "ClientId": "cl-key-2",
+ "Rules": [
+ {
+ "Endpoint": ".+",
+ "Period": "1s",
+ "Limit": 10
+ },
+ {
+ "Endpoint": "get:/api/clients",
+ "Period": "1m",
+ "Limit": 0
+ },
+ {
+ "Endpoint": "post:/api/clients",
+ "Period": "5m",
+ "Limit": 50
+ }
+ ]
+ },
+ {
+ "ClientId": "cl-key-3",
+ "Rules": [
+ {
+ "Endpoint": "post:/api/clients",
+ "Period": "1s",
+ "Limit": 3
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/test/AspNetCoreRateLimit.Demo/appsettings.Regex.json b/test/AspNetCoreRateLimit.Demo/appsettings.Regex.json
index 0a6b3597..edd1190c 100644
--- a/test/AspNetCoreRateLimit.Demo/appsettings.Regex.json
+++ b/test/AspNetCoreRateLimit.Demo/appsettings.Regex.json
@@ -9,6 +9,7 @@
},
"IpRateLimiting": {
+ "RateLimitingEnabled": true,
"EnableEndpointRateLimiting": true,
"EnableRegexRuleMatching": true,
"StackBlockedRequests": false,
@@ -123,6 +124,7 @@
},
"ClientRateLimiting": {
+ "RateLimitingEnabled": true,
"EnableEndpointRateLimiting": true,
"EnableRegexRuleMatching": true,
"ClientIdHeader": "X-ClientId",
diff --git a/test/AspNetCoreRateLimit.Demo/appsettings.json b/test/AspNetCoreRateLimit.Demo/appsettings.json
index 8c587347..b07ee6da 100644
--- a/test/AspNetCoreRateLimit.Demo/appsettings.json
+++ b/test/AspNetCoreRateLimit.Demo/appsettings.json
@@ -9,6 +9,7 @@
},
"IpRateLimiting": {
+ "RateLimitingEnabled": true,
"EnableEndpointRateLimiting": true,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
@@ -130,6 +131,7 @@
},
"ClientRateLimiting": {
+ "RateLimitingEnabled": true,
"EnableEndpointRateLimiting": true,
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
diff --git a/test/AspNetCoreRateLimit.Tests/BaseClassFixture.cs b/test/AspNetCoreRateLimit.Tests/BaseClassFixture.cs
index 15dc1afe..8b7893d2 100644
--- a/test/AspNetCoreRateLimit.Tests/BaseClassFixture.cs
+++ b/test/AspNetCoreRateLimit.Tests/BaseClassFixture.cs
@@ -11,6 +11,7 @@ public abstract class BaseClassFixture : IClassFixture
+ {
+ builder.ConfigureAppConfiguration((context, conf) =>
+ {
+ conf.AddJsonFile("appsettings.EnabledFalse.json");
+ });
+ }).CreateClient(options: new WebApplicationFactoryClientOptions
+ {
+ BaseAddress = new System.Uri("https://localhost:44304"),
+
+ });
}
///
@@ -43,9 +56,16 @@ protected HttpClient GetClient(ClientType clientType)
return _wildcardClient;
case ClientType.Regex:
return _regexClient;
+ case ClientType.EnabledFalse:
+ return _enabledFalseClient;
default:
throw new ArgumentOutOfRangeException(nameof(clientType), clientType, "Unexpected client type.");
}
}
+
+ protected void SetOptions(RateLimitOptions options)
+ {
+
+ }
}
}
\ No newline at end of file
diff --git a/test/AspNetCoreRateLimit.Tests/ClientRateLimitTests.cs b/test/AspNetCoreRateLimit.Tests/ClientRateLimitTests.cs
index 0b26c325..a7128347 100644
--- a/test/AspNetCoreRateLimit.Tests/ClientRateLimitTests.cs
+++ b/test/AspNetCoreRateLimit.Tests/ClientRateLimitTests.cs
@@ -1,5 +1,6 @@
using AspNetCoreRateLimit.Tests.Enums;
using System.Net.Http;
+using System.Text;
using System.Threading.Tasks;
using Xunit;
@@ -224,5 +225,29 @@ public async Task UpdateOptions(ClientType clientType)
// Assert
Assert.Contains(keyword, content);
}
+
+ [Theory]
+ [InlineData(ClientType.EnabledFalse)]
+ public async Task RulesDisabled(ClientType clientType)
+ {
+ // Arrange
+ var clientId = "cl-key-3";
+ int responseStatusCode = 0;
+
+ // Act
+ for (int i = 0; i < 4; i++)
+ {
+ var request = new HttpRequestMessage(HttpMethod.Post, apiPath);
+ request.Headers.Add("X-ClientId", clientId);
+ request.Headers.Add("X-Real-IP", ip);
+ request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
+
+ var response = await GetClient(clientType).SendAsync(request);
+ responseStatusCode = (int)response.StatusCode;
+ }
+
+ // Assert
+ Assert.Equal(200, responseStatusCode);
+ }
}
}
\ No newline at end of file
diff --git a/test/AspNetCoreRateLimit.Tests/Enums/ClientType.cs b/test/AspNetCoreRateLimit.Tests/Enums/ClientType.cs
index b95d09ef..7b84cf12 100644
--- a/test/AspNetCoreRateLimit.Tests/Enums/ClientType.cs
+++ b/test/AspNetCoreRateLimit.Tests/Enums/ClientType.cs
@@ -3,6 +3,7 @@
public enum ClientType
{
Wildcard,
- Regex
+ Regex,
+ EnabledFalse
}
}
\ No newline at end of file
diff --git a/test/AspNetCoreRateLimit.Tests/IpRateLimitTests.cs b/test/AspNetCoreRateLimit.Tests/IpRateLimitTests.cs
index 8400c675..01170c3c 100644
--- a/test/AspNetCoreRateLimit.Tests/IpRateLimitTests.cs
+++ b/test/AspNetCoreRateLimit.Tests/IpRateLimitTests.cs
@@ -1,5 +1,6 @@
using AspNetCoreRateLimit.Tests.Enums;
using System.Net.Http;
+using System.Text;
using System.Threading.Tasks;
using Xunit;
@@ -265,5 +266,28 @@ public async Task SpecificIpRuleMonitorActive(ClientType clientType, string ip)
// Assert
Assert.Equal(200, responseStatusCode);
}
+
+ [Theory]
+ [InlineData(ClientType.EnabledFalse, "84.247.85.232")]
+ public async Task RulesDisabled(ClientType clientType, string ip)
+ {
+ // Arrange
+ int responseStatusCode = 0;
+
+ // Act
+ for (int i = 0; i < 2; i++)
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, apiValuesPath);
+ request.Headers.Add("X-Real-IP", ip);
+ request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
+
+ var response = await GetClient(clientType).SendAsync(request);
+ responseStatusCode = (int)response.StatusCode;
+ }
+
+ // Assert
+ Assert.Equal(200, responseStatusCode);
+ }
+
}
}
\ No newline at end of file
From 92cc030b9032f11972a9e5b0c43f9cd57b06b10c Mon Sep 17 00:00:00 2001
From: Thomas <37584838+tomfp@users.noreply.github.com>
Date: Thu, 3 Aug 2023 14:14:07 +0100
Subject: [PATCH 2/2] false setting
---
test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json b/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json
index b774037e..6e7c157b 100644
--- a/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json
+++ b/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json
@@ -9,7 +9,8 @@
},
"IpRateLimiting": {
- "EnableEndpointRateLimiting": false,
+ "RateLimitingEnabled": false,
+ "EnableEndpointRateLimiting": true,
"EnableRegexRuleMatching": true,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
@@ -123,7 +124,8 @@
},
"ClientRateLimiting": {
- "EnableEndpointRateLimiting": false,
+ "RateLimitingEnabled": false,
+ "EnableEndpointRateLimiting": true,
"EnableRegexRuleMatching": true,
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,