diff options
author | Joas Schilling <coding@schilljs.com> | 2023-08-10 11:30:19 +0200 |
---|---|---|
committer | Joas Schilling <coding@schilljs.com> | 2023-08-10 11:30:19 +0200 |
commit | ffaa82589e3b1eb63f4d932b20234ffb2b63498a (patch) | |
tree | 62b183b2ef491929c9c7fba0735f70c1331304e5 | |
parent | ca4c993ca35664e154990c1322b69ae0a7900f17 (diff) | |
download | nextcloud-server-feat/noid/ratelimit-header.tar.gz nextcloud-server-feat/noid/ratelimit-header.zip |
feat(request): Add RateLimit and RateLimit-Policy headersfeat/noid/ratelimit-header
Signed-off-by: Joas Schilling <coding@schilljs.com>
-rw-r--r-- | lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php | 30 | ||||
-rw-r--r-- | lib/private/Security/RateLimiting/Limiter.php | 12 |
2 files changed, 34 insertions, 8 deletions
diff --git a/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php b/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php index 6f84a0c94d0..21b55cd6450 100644 --- a/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php @@ -65,6 +65,10 @@ use ReflectionMethod; * @package OC\AppFramework\Middleware\Security */ class RateLimitingMiddleware extends Middleware { + protected int $limit; + protected int $period; + protected int $attempts; + public function __construct( protected IRequest $request, protected IUserSession $userSession, @@ -85,7 +89,9 @@ class RateLimitingMiddleware extends Middleware { $rateLimit = $this->readLimitFromAnnotationOrAttribute($controller, $methodName, 'UserRateThrottle', UserRateLimit::class); if ($rateLimit !== null) { - $this->limiter->registerUserRequest( + $this->limit = $rateLimit->getLimit(); + $this->period = $rateLimit->getPeriod(); + $this->attempts = $this->limiter->registerUserRequest( $rateLimitIdentifier, $rateLimit->getLimit(), $rateLimit->getPeriod(), @@ -100,7 +106,9 @@ class RateLimitingMiddleware extends Middleware { $rateLimit = $this->readLimitFromAnnotationOrAttribute($controller, $methodName, 'AnonRateThrottle', AnonRateLimit::class); if ($rateLimit !== null) { - $this->limiter->registerAnonRequest( + $this->limit = $rateLimit->getLimit(); + $this->period = $rateLimit->getPeriod(); + $this->attempts = $this->limiter->registerAnonRequest( $rateLimitIdentifier, $rateLimit->getLimit(), $rateLimit->getPeriod(), @@ -143,6 +151,17 @@ class RateLimitingMiddleware extends Middleware { /** * {@inheritDoc} */ + public function afterController(Controller $controller, string $methodName, Response $response): Response { + $response->setHeaders([ + 'RateLimit' => 'limit=' . $this->limit . ', remaining=' . max(0, $this->limit - $this->attempts) . ', reset=' . $this->period, + 'RateLimit-Policy' => $this->limit . ';w=' . $this->period, + ]); + return $response; + } + + /** + * {@inheritDoc} + */ public function afterException(Controller $controller, string $methodName, \Exception $exception): Response { if ($exception instanceof RateLimitExceededException) { if (stripos($this->request->getHeader('Accept'), 'html') === false) { @@ -154,9 +173,14 @@ class RateLimitingMiddleware extends Middleware { [], TemplateResponse::RENDER_AS_GUEST ); - $response->setStatus($exception->getCode()); } + $response->setStatus($exception->getCode()); + $response->setHeaders([ + 'RateLimit' => 'limit=' . $this->limit . ', remaining=' . max(0, $this->limit - $this->attempts) . ', reset=' . $this->period, + 'RateLimit-Policy' => $this->limit . ';w=' . $this->period, + ]); + return $response; } diff --git a/lib/private/Security/RateLimiting/Limiter.php b/lib/private/Security/RateLimiting/Limiter.php index c8c0e2ce101..484e0c65096 100644 --- a/lib/private/Security/RateLimiting/Limiter.php +++ b/lib/private/Security/RateLimiting/Limiter.php @@ -46,13 +46,15 @@ class Limiter { string $userIdentifier, int $period, int $limit, - ): void { + ): int { $existingAttempts = $this->backend->getAttempts($methodIdentifier, $userIdentifier); if ($existingAttempts >= $limit) { throw new RateLimitExceededException(); } $this->backend->registerAttempt($methodIdentifier, $userIdentifier, $period); + + return $existingAttempts; } /** @@ -66,11 +68,11 @@ class Limiter { int $anonLimit, int $anonPeriod, string $ip, - ): void { + ): int { $ipSubnet = (new IpAddress($ip))->getSubnet(); $anonHashIdentifier = hash('sha512', 'anon::' . $identifier . $ipSubnet); - $this->register($identifier, $anonHashIdentifier, $anonPeriod, $anonLimit); + return $this->register($identifier, $anonHashIdentifier, $anonPeriod, $anonLimit); } /** @@ -84,8 +86,8 @@ class Limiter { int $userLimit, int $userPeriod, IUser $user, - ): void { + ): int { $userHashIdentifier = hash('sha512', 'user::' . $identifier . $user->getUID()); - $this->register($identifier, $userHashIdentifier, $userPeriod, $userLimit); + return $this->register($identifier, $userHashIdentifier, $userPeriod, $userLimit); } } |