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': {