Skip to content

Commit 422c819

Browse files
Merge pull request #37553 from nextcloud/backport/37542/stable24
[stable24] feat(security): Allow to opt-out of ratelimit protection, e.g. for te…
2 parents 24b92b3 + 0dc8c73 commit 422c819

File tree

5 files changed

+50
-13
lines changed

5 files changed

+50
-13
lines changed

config/config.sample.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@
284284

285285
/**
286286
* The interval at which token activity should be updated.
287-
* Increasing this value means that the last activty on the security page gets
287+
* Increasing this value means that the last activity on the security page gets
288288
* more outdated.
289289
*
290290
* Tokens are still checked every 5 minutes for validity
@@ -303,6 +303,15 @@
303303
*/
304304
'auth.bruteforce.protection.enabled' => true,
305305

306+
/**
307+
* Whether the rate limit protection shipped with Nextcloud should be enabled or not.
308+
*
309+
* Disabling this is discouraged for security reasons.
310+
*
311+
* Defaults to ``true``
312+
*/
313+
'ratelimit.protection.enabled' => true,
314+
306315
/**
307316
* By default WebAuthn is available but it can be explicitly disabled by admins
308317
*/

lib/private/Security/RateLimiting/Backend/DatabaseBackend.php

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
declare(strict_types=1);
44

55
/**
6+
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
67
* @copyright Copyright (c) 2021 Lukas Reschke <lukas@statuscode.ch>
78
*
9+
* @author Joas Schilling <coding@schilljs.com>
810
* @author Lukas Reschke <lukas@statuscode.ch>
911
*
1012
* @license GNU AGPL version 3 or any later version
@@ -27,24 +29,25 @@
2729

2830
use OCP\AppFramework\Utility\ITimeFactory;
2931
use OCP\DB\QueryBuilder\IQueryBuilder;
32+
use OCP\IConfig;
3033
use OCP\IDBConnection;
3134

3235
class DatabaseBackend implements IBackend {
3336
private const TABLE_NAME = 'ratelimit_entries';
3437

38+
/** @var IConfig */
39+
private $config;
3540
/** @var IDBConnection */
3641
private $dbConnection;
3742
/** @var ITimeFactory */
3843
private $timeFactory;
3944

40-
/**
41-
* @param IDBConnection $dbConnection
42-
* @param ITimeFactory $timeFactory
43-
*/
4445
public function __construct(
46+
IConfig $config,
4547
IDBConnection $dbConnection,
4648
ITimeFactory $timeFactory
4749
) {
50+
$this->config = $config;
4851
$this->dbConnection = $dbConnection;
4952
$this->timeFactory = $timeFactory;
5053
}
@@ -115,7 +118,12 @@ public function registerAttempt(string $methodIdentifier,
115118
->values([
116119
'hash' => $qb->createNamedParameter($identifier, IQueryBuilder::PARAM_STR),
117120
'delete_after' => $qb->createNamedParameter($deleteAfter, IQueryBuilder::PARAM_DATE),
118-
])
119-
->executeStatement();
121+
]);
122+
123+
if (!$this->config->getSystemValueBool('ratelimit.protection.enabled', true)) {
124+
return;
125+
}
126+
127+
$qb->executeStatement();
120128
}
121129
}

lib/private/Security/RateLimiting/Backend/MemoryCacheBackend.php

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
declare(strict_types=1);
44

55
/**
6+
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
67
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
78
*
89
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
10+
* @author Joas Schilling <coding@schilljs.com>
911
* @author Lukas Reschke <lukas@statuscode.ch>
1012
* @author Morris Jobke <hey@morrisjobke.de>
1113
* @author Roeland Jago Douma <roeland@famdouma.nl>
@@ -31,6 +33,7 @@
3133
use OCP\AppFramework\Utility\ITimeFactory;
3234
use OCP\ICache;
3335
use OCP\ICacheFactory;
36+
use OCP\IConfig;
3437

3538
/**
3639
* Class MemoryCacheBackend uses the configured distributed memory cache for storing
@@ -39,17 +42,18 @@
3942
* @package OC\Security\RateLimiting\Backend
4043
*/
4144
class MemoryCacheBackend implements IBackend {
45+
/** @var IConfig */
46+
private $config;
4247
/** @var ICache */
4348
private $cache;
4449
/** @var ITimeFactory */
4550
private $timeFactory;
4651

47-
/**
48-
* @param ICacheFactory $cacheFactory
49-
* @param ITimeFactory $timeFactory
50-
*/
51-
public function __construct(ICacheFactory $cacheFactory,
52-
ITimeFactory $timeFactory) {
52+
public function __construct(
53+
IConfig $config,
54+
ICacheFactory $cacheFactory,
55+
ITimeFactory $timeFactory) {
56+
$this->config = $config;
5357
$this->cache = $cacheFactory->createDistributed(__CLASS__);
5458
$this->timeFactory = $timeFactory;
5559
}
@@ -121,6 +125,11 @@ public function registerAttempt(string $methodIdentifier,
121125

122126
// Store the new attempt
123127
$existingAttempts[] = (string)($currentTime + $period);
128+
129+
if (!$this->config->getSystemValueBool('ratelimit.protection.enabled', true)) {
130+
return;
131+
}
132+
124133
$this->cache->set($identifier, json_encode($existingAttempts));
125134
}
126135
}

lib/private/Server.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,11 +832,13 @@ public function __construct($webRoot, \OC\Config $config) {
832832
$cacheFactory = $c->get(ICacheFactory::class);
833833
if ($cacheFactory->isAvailable()) {
834834
$backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend(
835+
$c->get(AllConfig::class),
835836
$this->get(ICacheFactory::class),
836837
new \OC\AppFramework\Utility\TimeFactory()
837838
);
838839
} else {
839840
$backend = new \OC\Security\RateLimiting\Backend\DatabaseBackend(
841+
$c->get(AllConfig::class),
840842
$c->get(IDBConnection::class),
841843
new \OC\AppFramework\Utility\TimeFactory()
842844
);

tests/lib/Security/RateLimiting/Backend/MemoryCacheBackendTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@
2828
use OCP\AppFramework\Utility\ITimeFactory;
2929
use OCP\ICache;
3030
use OCP\ICacheFactory;
31+
use OCP\IConfig;
3132
use Test\TestCase;
3233

3334
class MemoryCacheBackendTest extends TestCase {
35+
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
36+
private $config;
3437
/** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */
3538
private $cacheFactory;
3639
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
@@ -43,6 +46,7 @@ class MemoryCacheBackendTest extends TestCase {
4346
protected function setUp(): void {
4447
parent::setUp();
4548

49+
$this->config = $this->createMock(IConfig::class);
4650
$this->cacheFactory = $this->createMock(ICacheFactory::class);
4751
$this->timeFactory = $this->createMock(ITimeFactory::class);
4852
$this->cache = $this->createMock(ICache::class);
@@ -53,7 +57,12 @@ protected function setUp(): void {
5357
->with('OC\Security\RateLimiting\Backend\MemoryCacheBackend')
5458
->willReturn($this->cache);
5559

60+
$this->config->method('getSystemValueBool')
61+
->with('ratelimit.protection.enabled')
62+
->willReturn(true);
63+
5664
$this->memoryCache = new MemoryCacheBackend(
65+
$this->config,
5766
$this->cacheFactory,
5867
$this->timeFactory
5968
);

0 commit comments

Comments
 (0)