mirror of
https://github.com/nextcloud/server.git
synced 2026-06-10 17:23:59 -04:00
Merge pull request #56346 from nextcloud/feat/noid/allow-overwriting-rate-limit
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Integration sqlite / changes (push) Waiting to run
Integration sqlite / integration-sqlite (master, 8.4, main, --tags ~@large files_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, capabilities_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, collaboration_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, comments_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, dav_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, federation_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, file_conversions) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, filesdrop_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, ldap_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, openldap_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, openldap_numerical_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, remoteapi_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, routing_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, setup_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, sharees_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, sharing_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, theming_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, videoverification_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite-summary (push) Blocked by required conditions
Psalm static code analysis / static-code-analysis (push) Waiting to run
Psalm static code analysis / static-code-analysis-security (push) Waiting to run
Psalm static code analysis / static-code-analysis-ocp (push) Waiting to run
Psalm static code analysis / static-code-analysis-ncu (push) Waiting to run
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Integration sqlite / changes (push) Waiting to run
Integration sqlite / integration-sqlite (master, 8.4, main, --tags ~@large files_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, capabilities_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, collaboration_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, comments_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, dav_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, federation_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, file_conversions) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, filesdrop_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, ldap_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, openldap_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, openldap_numerical_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, remoteapi_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, routing_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, setup_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, sharees_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, sharing_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, theming_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (master, 8.4, main, videoverification_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite-summary (push) Blocked by required conditions
Psalm static code analysis / static-code-analysis (push) Waiting to run
Psalm static code analysis / static-code-analysis-security (push) Waiting to run
Psalm static code analysis / static-code-analysis-ocp (push) Waiting to run
Psalm static code analysis / static-code-analysis-ncu (push) Waiting to run
feat(rate-limit): Allow overwriting the rate limit
This commit is contained in:
commit
49324bcb21
3 changed files with 79 additions and 3 deletions
|
|
@ -473,6 +473,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');
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@ use OC\Security\RateLimiting\Limiter;
|
|||
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\AppFramework\Middleware\Security\Mock\RateLimitingMiddlewareController;
|
||||
use Test\TestCase;
|
||||
|
||||
|
|
@ -33,7 +35,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 {
|
||||
|
|
@ -45,7 +49,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,
|
||||
|
|
@ -54,7 +60,9 @@ class RateLimitingMiddlewareTest extends TestCase {
|
|||
$this->limiter,
|
||||
$this->session,
|
||||
$this->appConfig,
|
||||
$this->serverConfig,
|
||||
$this->bruteForceAllowList,
|
||||
$this->logger
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue