mirror of
https://github.com/nextcloud/server.git
synced 2026-04-21 22:27:31 -04:00
feat(rate-limit): Allow overwriting the rate limit
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
parent
72dd55e53e
commit
826fe1a918
3 changed files with 79 additions and 3 deletions
|
|
@ -463,6 +463,30 @@ $CONFIG = [
|
|||
*/
|
||||
'ratelimit.protection.enabled' => true,
|
||||
|
||||
/**
|
||||
* Overwrite the individual rate limit for a specific route
|
||||
*
|
||||
* From time to time it can be necessary to extend the rate limit of a specific route,
|
||||
* depending on your usage pattern or when you script some actions.
|
||||
* Instead of completely disabling the rate limit or excluding an IP address from the
|
||||
* rate limit, the following config allows to overwrite the rate limit duration and period.
|
||||
*
|
||||
* The first level key is the name of the route. You can find the route name from a URL
|
||||
* using the ``occ router:list`` command of your server.
|
||||
*
|
||||
* You can also specify different limits for logged-in users with the ``user`` key
|
||||
* and not-logged-in users with the ``anon`` key. However, if there is no specific ``user`` limit,
|
||||
* the ``anon`` limit is also applied for logged-in users.
|
||||
*
|
||||
* Defaults to empty array ``[]``
|
||||
*/
|
||||
'ratelimit_overwrite' => [
|
||||
'profile.profilepage.index' => [
|
||||
'user' => ['limit' => 300, 'period' => 3600],
|
||||
'anon' => ['limit' => 1, 'period' => 300],
|
||||
]
|
||||
],
|
||||
|
||||
/**
|
||||
* Size of subnet used to normalize IPv6
|
||||
*
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ use OCP\AppFramework\Http\Response;
|
|||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Middleware;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IUserSession;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
|
|
@ -56,7 +58,9 @@ class RateLimitingMiddleware extends Middleware {
|
|||
protected Limiter $limiter,
|
||||
protected ISession $session,
|
||||
protected IAppConfig $appConfig,
|
||||
protected IConfig $serverConfig,
|
||||
protected BruteforceAllowList $bruteForceAllowList,
|
||||
protected LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +78,13 @@ class RateLimitingMiddleware extends Middleware {
|
|||
}
|
||||
|
||||
if ($this->userSession->isLoggedIn()) {
|
||||
$rateLimit = $this->readLimitFromAnnotationOrAttribute($controller, $methodName, 'UserRateThrottle', UserRateLimit::class);
|
||||
$rateLimit = $this->readLimitFromAnnotationOrAttribute(
|
||||
$controller,
|
||||
$methodName,
|
||||
'UserRateThrottle',
|
||||
UserRateLimit::class,
|
||||
'user',
|
||||
);
|
||||
|
||||
if ($rateLimit !== null) {
|
||||
if ($this->appConfig->getValueBool('bruteforcesettings', 'apply_allowlist_to_ratelimit')
|
||||
|
|
@ -94,7 +104,13 @@ class RateLimitingMiddleware extends Middleware {
|
|||
// If not user specific rate limit is found the Anon rate limit applies!
|
||||
}
|
||||
|
||||
$rateLimit = $this->readLimitFromAnnotationOrAttribute($controller, $methodName, 'AnonRateThrottle', AnonRateLimit::class);
|
||||
$rateLimit = $this->readLimitFromAnnotationOrAttribute(
|
||||
$controller,
|
||||
$methodName,
|
||||
'AnonRateThrottle',
|
||||
AnonRateLimit::class,
|
||||
'anon',
|
||||
);
|
||||
|
||||
if ($rateLimit !== null) {
|
||||
$this->limiter->registerAnonRequest(
|
||||
|
|
@ -115,7 +131,35 @@ class RateLimitingMiddleware extends Middleware {
|
|||
* @param class-string<T> $attributeClass
|
||||
* @return ?ARateLimit
|
||||
*/
|
||||
protected function readLimitFromAnnotationOrAttribute(Controller $controller, string $methodName, string $annotationName, string $attributeClass): ?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');
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@ use OCP\AppFramework\Http\Attribute\UserRateLimit;
|
|||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
class TestRateLimitController extends Controller {
|
||||
|
|
@ -64,7 +66,9 @@ class RateLimitingMiddlewareTest extends TestCase {
|
|||
private Limiter|MockObject $limiter;
|
||||
private ISession|MockObject $session;
|
||||
private IAppConfig|MockObject $appConfig;
|
||||
private IConfig|MockObject $serverConfig;
|
||||
private BruteforceAllowList|MockObject $bruteForceAllowList;
|
||||
private LoggerInterface|MockObject $logger;
|
||||
private RateLimitingMiddleware $rateLimitingMiddleware;
|
||||
|
||||
protected function setUp(): void {
|
||||
|
|
@ -76,7 +80,9 @@ class RateLimitingMiddlewareTest extends TestCase {
|
|||
$this->limiter = $this->createMock(Limiter::class);
|
||||
$this->session = $this->createMock(ISession::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->serverConfig = $this->createMock(IConfig::class);
|
||||
$this->bruteForceAllowList = $this->createMock(BruteforceAllowList::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->rateLimitingMiddleware = new RateLimitingMiddleware(
|
||||
$this->request,
|
||||
|
|
@ -85,7 +91,9 @@ class RateLimitingMiddlewareTest extends TestCase {
|
|||
$this->limiter,
|
||||
$this->session,
|
||||
$this->appConfig,
|
||||
$this->serverConfig,
|
||||
$this->bruteForceAllowList,
|
||||
$this->logger
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue