Skip to content

Commit c6748e1

Browse files
committed
Add excluded user ids for rateLimiting & early retrn in should ratelimit request
1 parent 38f3aec commit c6748e1

File tree

6 files changed

+150
-1
lines changed

6 files changed

+150
-1
lines changed

aikido_zen/background_process/cloud_connection_manager/update_service_config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ def update_service_config(connection_manager, res):
2323
received_any_stats=res.get("receivedAnyStats", True),
2424
)
2525

26+
# Handle excluded user IDs from rate limiting
27+
excluded_user_ids = res.get("excludedUserIdsFromRateLimiting")
28+
if isinstance(excluded_user_ids, list):
29+
connection_manager.conf.update_excluded_user_ids_from_rate_limiting(
30+
excluded_user_ids
31+
)
32+
2633
# Handle outbound request blocking configuration
2734
if "blockNewOutgoingRequests" in res:
2835
connection_manager.conf.set_block_new_outgoing_requests(

aikido_zen/background_process/cloud_connection_manager/update_service_config_test.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,52 @@ def test_update_service_config_block_new_outgoing_requests_only():
234234
assert connection_manager.conf.outbound_domains == {
235235
"existing.com": "allow"
236236
} # Not changed
237+
238+
239+
def test_update_service_config_excluded_user_ids_from_rate_limiting():
240+
"""Test that update_service_config handles excludedUserIdsFromRateLimiting"""
241+
connection_manager = MagicMock()
242+
connection_manager.conf = ServiceConfig(
243+
endpoints=[],
244+
last_updated_at=0,
245+
blocked_uids=set(),
246+
bypassed_ips=[],
247+
received_any_stats=False,
248+
)
249+
connection_manager.block = False
250+
251+
res = {
252+
"success": True,
253+
"excludedUserIdsFromRateLimiting": ["user1", "user2"],
254+
}
255+
256+
update_service_config(connection_manager, res)
257+
258+
assert connection_manager.conf.is_user_excluded_from_rate_limiting("user1") is True
259+
assert connection_manager.conf.is_user_excluded_from_rate_limiting("user2") is True
260+
assert connection_manager.conf.is_user_excluded_from_rate_limiting("user3") is False
261+
262+
263+
def test_update_service_config_excluded_user_ids_not_array():
264+
"""Test that update_service_config ignores non-array excludedUserIdsFromRateLimiting"""
265+
connection_manager = MagicMock()
266+
connection_manager.conf = ServiceConfig(
267+
endpoints=[],
268+
last_updated_at=0,
269+
blocked_uids=set(),
270+
bypassed_ips=[],
271+
received_any_stats=False,
272+
)
273+
connection_manager.block = False
274+
275+
res = {
276+
"success": True,
277+
"excludedUserIdsFromRateLimiting": "not-an-array",
278+
}
279+
280+
update_service_config(connection_manager, res)
281+
282+
assert (
283+
connection_manager.conf.is_user_excluded_from_rate_limiting("not-an-array")
284+
is False
285+
)

aikido_zen/background_process/service_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def __init__(
2424
)
2525
self.block_new_outgoing_requests = False
2626
self.outbound_domains = {}
27+
self.excluded_user_ids_from_rate_limiting = set()
2728

2829
def update(
2930
self,
@@ -75,6 +76,14 @@ def is_bypassed_ip(self, ip):
7576
"""Checks if the IP is on the bypass list"""
7677
return self.bypassed_ips.has(ip)
7778

79+
def update_excluded_user_ids_from_rate_limiting(self, user_ids):
80+
"""Replaces the set of user IDs excluded from rate limiting"""
81+
self.excluded_user_ids_from_rate_limiting = set(user_ids)
82+
83+
def is_user_excluded_from_rate_limiting(self, user_id):
84+
"""Checks if the user ID is excluded from rate limiting"""
85+
return str(user_id) in self.excluded_user_ids_from_rate_limiting
86+
7887
def update_outbound_domains(self, domains):
7988
self.outbound_domains = {
8089
domain["hostname"]: domain["mode"] for domain in domains

aikido_zen/background_process/service_config_test.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,31 @@ def test_service_config_with_empty_allowlist():
319319
assert admin_endpoint["route"] == "/admin"
320320
assert isinstance(admin_endpoint["allowedIPAddresses"], list)
321321
assert len(admin_endpoint["allowedIPAddresses"]) == 0
322+
323+
324+
def test_excluded_user_ids_from_rate_limiting():
325+
config = ServiceConfig(
326+
endpoints=[],
327+
last_updated_at=0,
328+
blocked_uids=set(),
329+
bypassed_ips=[],
330+
received_any_stats=False,
331+
)
332+
333+
# Initially empty
334+
assert config.is_user_excluded_from_rate_limiting("user1") is False
335+
336+
# Update with user IDs
337+
config.update_excluded_user_ids_from_rate_limiting(["user1", "user2"])
338+
assert config.is_user_excluded_from_rate_limiting("user1") is True
339+
assert config.is_user_excluded_from_rate_limiting("user2") is True
340+
assert config.is_user_excluded_from_rate_limiting("user3") is False
341+
342+
# Update replaces the set
343+
config.update_excluded_user_ids_from_rate_limiting(["user3"])
344+
assert config.is_user_excluded_from_rate_limiting("user1") is False
345+
assert config.is_user_excluded_from_rate_limiting("user3") is True
346+
347+
# Empty list clears all
348+
config.update_excluded_user_ids_from_rate_limiting([])
349+
assert config.is_user_excluded_from_rate_limiting("user3") is False

aikido_zen/ratelimiting/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ def should_ratelimit_request(
2121
if is_bypassed_ip:
2222
return {"block": False}
2323

24+
if user and connection_manager.conf.is_user_excluded_from_rate_limiting(user["id"]):
25+
return {"block": False}
26+
2427
max_requests = int(endpoint["rateLimiting"]["maxRequests"])
2528
windows_size_in_ms = int(endpoint["rateLimiting"]["windowSizeInMS"])
2629

aikido_zen/ratelimiting/init_test.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ def user():
1515
return {"id": "user123"}
1616

1717

18-
def create_connection_manager(endpoints=[], bypassed_ips=[]):
18+
def create_connection_manager(
19+
endpoints=[], bypassed_ips=[], excluded_user_ids_from_rate_limiting=[]
20+
):
1921
cm = MagicMock()
2022
cm.conf = ServiceConfig(
2123
endpoints=endpoints,
@@ -24,6 +26,9 @@ def create_connection_manager(endpoints=[], bypassed_ips=[]):
2426
bypassed_ips=bypassed_ips,
2527
received_any_stats=True,
2628
)
29+
cm.conf.update_excluded_user_ids_from_rate_limiting(
30+
excluded_user_ids_from_rate_limiting
31+
)
2732
cm.rate_limiter = RateLimiter(
2833
max_items=5000, time_to_live_in_ms=120 * 60 * 1000 # 120 minutes
2934
)
@@ -511,3 +516,51 @@ def test_rate_limits_by_group_if_user_is_not_set():
511516
"block": True,
512517
"trigger": "group",
513518
}
519+
520+
521+
def test_does_not_rate_limit_excluded_users():
522+
cm = create_connection_manager(
523+
[
524+
{
525+
"method": "POST",
526+
"route": "/login",
527+
"forceProtectionOff": False,
528+
"rateLimiting": {
529+
"enabled": True,
530+
"maxRequests": 3,
531+
"windowSizeInMS": 1000,
532+
},
533+
},
534+
],
535+
excluded_user_ids_from_rate_limiting=["excluded-user-id"],
536+
)
537+
route_metadata = create_route_metadata()
538+
excluded_user = {"id": "excluded-user-id"}
539+
for _ in range(5):
540+
assert should_ratelimit_request(
541+
route_metadata, "1.2.3.4", excluded_user, cm
542+
) == {"block": False}
543+
544+
545+
def test_does_not_rate_limit_excluded_users_in_group():
546+
cm = create_connection_manager(
547+
[
548+
{
549+
"method": "POST",
550+
"route": "/login",
551+
"forceProtectionOff": False,
552+
"rateLimiting": {
553+
"enabled": True,
554+
"maxRequests": 3,
555+
"windowSizeInMS": 1000,
556+
},
557+
},
558+
],
559+
excluded_user_ids_from_rate_limiting=["excluded-user-id"],
560+
)
561+
route_metadata = create_route_metadata()
562+
excluded_user = {"id": "excluded-user-id"}
563+
for _ in range(5):
564+
assert should_ratelimit_request(
565+
route_metadata, "1.2.3.4", excluded_user, cm, "group1"
566+
) == {"block": False}

0 commit comments

Comments
 (0)