userSession instanceof Session && $this->userSession->getSession()->get('app_api') === true && $this->userSession->getUser() === null) { // if userId is not specified and the request is authenticated by AppAPI, we skip the rate limit return; } if ($this->userSession->isLoggedIn()) { $rateLimit = $this->readLimitFromAnnotationOrAttribute( $controller, $methodName, 'UserRateThrottle', UserRateLimit::class, 'user', ); if ($rateLimit !== null) { if ($this->appConfig->getValueBool('bruteforcesettings', 'apply_allowlist_to_ratelimit') && $this->bruteForceAllowList->isBypassListed($this->request->getRemoteAddress())) { return; } $this->limiter->registerUserRequest( $rateLimitIdentifier, $rateLimit->getLimit(), $rateLimit->getPeriod(), $this->userSession->getUser() ); return; } // If not user specific rate limit is found the Anon rate limit applies! } $rateLimit = $this->readLimitFromAnnotationOrAttribute( $controller, $methodName, 'AnonRateThrottle', AnonRateLimit::class, 'anon', ); if ($rateLimit !== null) { $this->limiter->registerAnonRequest( $rateLimitIdentifier, $rateLimit->getLimit(), $rateLimit->getPeriod(), $this->request->getRemoteAddress() ); } } /** * @template T of ARateLimit * * @param Controller $controller * @param string $methodName * @param string $annotationName * @param class-string $attributeClass * @return ?ARateLimit */ protected function readLimitFromAnnotationOrAttribute(Controller $controller, string $methodName, string $annotationName, string $attributeClass, string $overwriteKey): ?ARateLimit { $rateLimitOverwrite = $this->serverConfig->getSystemValue('ratelimit_overwrite', []); if (!empty($rateLimitOverwrite)) { $controllerRef = new \ReflectionClass($controller); $appName = $controllerRef->getProperty('appName')->getValue($controller); $controllerName = substr($controller::class, strrpos($controller::class, '\\') + 1); $controllerName = substr($controllerName, 0, 0 - strlen('Controller')); $overwriteConfig = strtolower($appName . '.' . $controllerName . '.' . $methodName); $rateLimitOverwriteForActionAndType = $rateLimitOverwrite[$overwriteConfig][$overwriteKey] ?? null; if ($rateLimitOverwriteForActionAndType !== null) { $isValid = isset($rateLimitOverwriteForActionAndType['limit'], $rateLimitOverwriteForActionAndType['period']) && $rateLimitOverwriteForActionAndType['limit'] > 0 && $rateLimitOverwriteForActionAndType['period'] > 0; if ($isValid) { return new $attributeClass( (int)$rateLimitOverwriteForActionAndType['limit'], (int)$rateLimitOverwriteForActionAndType['period'], ); } $this->logger->warning('Rate limit overwrite on controller "{overwriteConfig}" for "{overwriteKey}" is invalid', [ 'overwriteConfig' => $overwriteConfig, 'overwriteKey' => $overwriteKey, ]); } } $annotationLimit = $this->reflector->getAnnotationParameter($annotationName, 'limit'); $annotationPeriod = $this->reflector->getAnnotationParameter($annotationName, 'period'); if ($annotationLimit !== '' && $annotationPeriod !== '') { return new $attributeClass( (int)$annotationLimit, (int)$annotationPeriod, ); } $reflectionMethod = new ReflectionMethod($controller, $methodName); $attributes = $reflectionMethod->getAttributes($attributeClass); $attribute = current($attributes); if ($attribute !== false) { return $attribute->newInstance(); } return null; } /** * {@inheritDoc} */ public function afterException(Controller $controller, string $methodName, \Exception $exception): Response { if ($exception instanceof RateLimitExceededException) { if (stripos($this->request->getHeader('Accept'), 'html') === false) { $response = new DataResponse([], $exception->getCode()); } else { $response = new TemplateResponse( 'core', '429', [], TemplateResponse::RENDER_AS_GUEST ); $response->setStatus($exception->getCode()); } return $response; } throw $exception; } }