diff --git a/sapl/middleware/ratelimit.py b/sapl/middleware/ratelimit.py index 6d65f17b7..c44738abf 100644 --- a/sapl/middleware/ratelimit.py +++ b/sapl/middleware/ratelimit.py @@ -3,6 +3,8 @@ RateLimitMiddleware — cross-pod rate limiting backed by shared Redis. Decision flow (per request): 0. /api/ path AND consumer daily/weekly quota exceeded? → 429 + Anonymous /api/ (quota not exceeded): pass immediately — no IP counter, + no block key. nginx sapl_api zone (60r/m) is the burst gate. 1. Known bot UA? → 429 (Python list — substring match) 1b. Redis UA deny list? → 429 (runtime SET — token hash match, refreshed every 60 s) 2. Anonymous AND IP in blocked set? → 429 (authenticated users skip — have per-user limit at 3c) @@ -298,6 +300,16 @@ class RateLimitMiddleware: response['X-RateLimit-Reason'] = f'quota_{exceeded}' return response + # Anonymous /api/ requests: quota + nginx sapl_api zone are the only + # controls. Skip _evaluate so anonymous API traffic never increments + # the global IP counter or writes a block key — a misbehaving script + # behind a NAT must not lock out the org's page requests. + # Authenticated /api/ falls through to _evaluate_authenticated normally + # (per-user counter, NAT-safe). + user = getattr(request, 'user', None) + if not (user and user.is_authenticated): + return self.get_response(request) + decision = self._evaluate(request) if decision['action'] == 'block': logger.warning(