aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoas Schilling <coding@schilljs.com>2023-08-10 11:30:19 +0200
committerJoas Schilling <coding@schilljs.com>2023-08-10 11:30:19 +0200
commitffaa82589e3b1eb63f4d932b20234ffb2b63498a (patch)
tree62b183b2ef491929c9c7fba0735f70c1331304e5
parentca4c993ca35664e154990c1322b69ae0a7900f17 (diff)
downloadnextcloud-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.php30
-rw-r--r--lib/private/Security/RateLimiting/Limiter.php12
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);
}
}