From 1839af015da6e8665225087c2dac7f4c9ffd9f45 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Tue, 14 Apr 2026 04:56:43 -0300 Subject: [PATCH] Fix ratelimit cache key prefix: strip Django version/prefix mangling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit django-ratelimit (the @ratelimit view decorator) writes its counters through Django's cache framework. Django's default key function produces '{KEY_PREFIX}:{VERSION}:{key}', so with KEY_PREFIX='' and VERSION=1 (defaults) the decorator keys appear in Redis as ':1:rl:{hash}' — an ugly leading colon that makes them look distinct from the clean 'rl:*' keys written by RateLimitMiddleware via get_redis_connection(). Add make_ratelimit_cache_key() to sapl/middleware/ratelimit.py (a simple pass-through) and wire it into the 'ratelimit' cache config via KEY_FUNCTION. Both key families now share the same 'rl:*' namespace: decorator keys → rl:{hash} middleware keys → rl:ip:{ip}:reqs rl:{ns}:user:{uid}:reqs rl:{ns}:ip:{ip}:w:{bucket} Co-Authored-By: Claude Sonnet 4.6 --- sapl/middleware/ratelimit.py | 16 ++++++++++++++++ sapl/settings.py | 7 ++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/sapl/middleware/ratelimit.py b/sapl/middleware/ratelimit.py index 1af9a27ae..38e29110c 100644 --- a/sapl/middleware/ratelimit.py +++ b/sapl/middleware/ratelimit.py @@ -77,6 +77,22 @@ _INCR_LUA = """ """ +def make_ratelimit_cache_key(key, key_prefix, version): + """ + Pass-through cache key function for the 'ratelimit' Django cache backend. + + Django's default key function produces '{KEY_PREFIX}:{VERSION}:{key}', + which turns django-ratelimit's own keys (already prefixed 'rl:{hash}') + into ':1:rl:{hash}' — an ugly leading colon and version number that does + not match the clean 'rl:*' keys written directly by RateLimitMiddleware. + + Setting KEY_FUNCTION to this function makes both key namespaces consistent: + django-ratelimit decorator keys → rl:{hash} + RateLimitMiddleware keys → rl:ip:{ip}:reqs / rl:{ns}:user:{uid}:reqs / … + """ + return key + + def _sha256(s): return hashlib.sha256(s.encode()).hexdigest() diff --git a/sapl/settings.py b/sapl/settings.py index 299fed34f..1fae74235 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -252,13 +252,18 @@ CACHES = { } ), }, - # DB1 — rate-limiter counters (raw keys, no KEY_PREFIX) + # DB1 — rate-limiter counters (raw keys, no KEY_PREFIX / version mangling) 'ratelimit': { 'BACKEND': ( 'django_redis.cache.RedisCache' if _redis_ready else 'django.core.cache.backends.filebased.FileBasedCache' ), 'LOCATION': REDIS_URL + '/1' if _redis_ready else '/var/tmp/django_ratelimit_cache', + # Pass-through key function so django-ratelimit decorator keys ('rl:{hash}') + # are stored as-is, matching the 'rl:*' keys written directly by + # RateLimitMiddleware via get_redis_connection(). Without this, Django's + # default key function would produce ':1:rl:{hash}' (empty prefix + version). + 'KEY_FUNCTION': 'sapl.middleware.ratelimit.make_ratelimit_cache_key', **( { 'OPTIONS': {