Signed-off-by: Joas Schilling <coding@schilljs.com>tags/v27.0.0beta1
/** | /** | ||||
* The interval at which token activity should be updated. | * The interval at which token activity should be updated. | ||||
* Increasing this value means that the last activty on the security page gets | |||||
* Increasing this value means that the last activity on the security page gets | |||||
* more outdated. | * more outdated. | ||||
* | * | ||||
* Tokens are still checked every 5 minutes for validity | * Tokens are still checked every 5 minutes for validity | ||||
*/ | */ | ||||
'auth.bruteforce.protection.enabled' => true, | 'auth.bruteforce.protection.enabled' => true, | ||||
/** | |||||
* Whether the rate limit protection shipped with Nextcloud should be enabled or not. | |||||
* | |||||
* Disabling this is discouraged for security reasons. | |||||
* | |||||
* Defaults to ``true`` | |||||
*/ | |||||
'ratelimit.protection.enabled' => true, | |||||
/** | /** | ||||
* By default, WebAuthn is available, but it can be explicitly disabled by admins | * By default, WebAuthn is available, but it can be explicitly disabled by admins | ||||
*/ | */ |
declare(strict_types=1); | declare(strict_types=1); | ||||
/** | /** | ||||
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com> | |||||
* @copyright Copyright (c) 2021 Lukas Reschke <lukas@statuscode.ch> | * @copyright Copyright (c) 2021 Lukas Reschke <lukas@statuscode.ch> | ||||
* | * | ||||
* @author Joas Schilling <coding@schilljs.com> | |||||
* @author Lukas Reschke <lukas@statuscode.ch> | * @author Lukas Reschke <lukas@statuscode.ch> | ||||
* | * | ||||
* @license GNU AGPL version 3 or any later version | * @license GNU AGPL version 3 or any later version | ||||
use OCP\AppFramework\Utility\ITimeFactory; | use OCP\AppFramework\Utility\ITimeFactory; | ||||
use OCP\DB\QueryBuilder\IQueryBuilder; | use OCP\DB\QueryBuilder\IQueryBuilder; | ||||
use OCP\IConfig; | |||||
use OCP\IDBConnection; | use OCP\IDBConnection; | ||||
class DatabaseBackend implements IBackend { | class DatabaseBackend implements IBackend { | ||||
private const TABLE_NAME = 'ratelimit_entries'; | private const TABLE_NAME = 'ratelimit_entries'; | ||||
/** @var IConfig */ | |||||
private $config; | |||||
/** @var IDBConnection */ | /** @var IDBConnection */ | ||||
private $dbConnection; | private $dbConnection; | ||||
/** @var ITimeFactory */ | /** @var ITimeFactory */ | ||||
private $timeFactory; | private $timeFactory; | ||||
/** | |||||
* @param IDBConnection $dbConnection | |||||
* @param ITimeFactory $timeFactory | |||||
*/ | |||||
public function __construct( | public function __construct( | ||||
IConfig $config, | |||||
IDBConnection $dbConnection, | IDBConnection $dbConnection, | ||||
ITimeFactory $timeFactory | ITimeFactory $timeFactory | ||||
) { | ) { | ||||
$this->config = $config; | |||||
$this->dbConnection = $dbConnection; | $this->dbConnection = $dbConnection; | ||||
$this->timeFactory = $timeFactory; | $this->timeFactory = $timeFactory; | ||||
} | } | ||||
->values([ | ->values([ | ||||
'hash' => $qb->createNamedParameter($identifier, IQueryBuilder::PARAM_STR), | 'hash' => $qb->createNamedParameter($identifier, IQueryBuilder::PARAM_STR), | ||||
'delete_after' => $qb->createNamedParameter($deleteAfter, IQueryBuilder::PARAM_DATE), | 'delete_after' => $qb->createNamedParameter($deleteAfter, IQueryBuilder::PARAM_DATE), | ||||
]) | |||||
->executeStatement(); | |||||
]); | |||||
if (!$this->config->getSystemValueBool('ratelimit.protection.enabled', true)) { | |||||
return; | |||||
} | |||||
$qb->executeStatement(); | |||||
} | } | ||||
} | } |
declare(strict_types=1); | declare(strict_types=1); | ||||
/** | /** | ||||
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com> | |||||
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> | * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> | ||||
* | * | ||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> | * @author Christoph Wurst <christoph@winzerhof-wurst.at> | ||||
* @author Joas Schilling <coding@schilljs.com> | |||||
* @author Lukas Reschke <lukas@statuscode.ch> | * @author Lukas Reschke <lukas@statuscode.ch> | ||||
* @author Morris Jobke <hey@morrisjobke.de> | * @author Morris Jobke <hey@morrisjobke.de> | ||||
* @author Roeland Jago Douma <roeland@famdouma.nl> | * @author Roeland Jago Douma <roeland@famdouma.nl> | ||||
use OCP\AppFramework\Utility\ITimeFactory; | use OCP\AppFramework\Utility\ITimeFactory; | ||||
use OCP\ICache; | use OCP\ICache; | ||||
use OCP\ICacheFactory; | use OCP\ICacheFactory; | ||||
use OCP\IConfig; | |||||
/** | /** | ||||
* Class MemoryCacheBackend uses the configured distributed memory cache for storing | * Class MemoryCacheBackend uses the configured distributed memory cache for storing | ||||
* @package OC\Security\RateLimiting\Backend | * @package OC\Security\RateLimiting\Backend | ||||
*/ | */ | ||||
class MemoryCacheBackend implements IBackend { | class MemoryCacheBackend implements IBackend { | ||||
/** @var IConfig */ | |||||
private $config; | |||||
/** @var ICache */ | /** @var ICache */ | ||||
private $cache; | private $cache; | ||||
/** @var ITimeFactory */ | /** @var ITimeFactory */ | ||||
private $timeFactory; | private $timeFactory; | ||||
/** | |||||
* @param ICacheFactory $cacheFactory | |||||
* @param ITimeFactory $timeFactory | |||||
*/ | |||||
public function __construct(ICacheFactory $cacheFactory, | |||||
ITimeFactory $timeFactory) { | |||||
public function __construct( | |||||
IConfig $config, | |||||
ICacheFactory $cacheFactory, | |||||
ITimeFactory $timeFactory) { | |||||
$this->config = $config; | |||||
$this->cache = $cacheFactory->createDistributed(__CLASS__); | $this->cache = $cacheFactory->createDistributed(__CLASS__); | ||||
$this->timeFactory = $timeFactory; | $this->timeFactory = $timeFactory; | ||||
} | } | ||||
// Store the new attempt | // Store the new attempt | ||||
$existingAttempts[] = (string)($currentTime + $period); | $existingAttempts[] = (string)($currentTime + $period); | ||||
if (!$this->config->getSystemValueBool('ratelimit.protection.enabled', true)) { | |||||
return; | |||||
} | |||||
$this->cache->set($identifier, json_encode($existingAttempts)); | $this->cache->set($identifier, json_encode($existingAttempts)); | ||||
} | } | ||||
} | } |
$cacheFactory = $c->get(ICacheFactory::class); | $cacheFactory = $c->get(ICacheFactory::class); | ||||
if ($cacheFactory->isAvailable()) { | if ($cacheFactory->isAvailable()) { | ||||
$backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend( | $backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend( | ||||
$c->get(AllConfig::class), | |||||
$this->get(ICacheFactory::class), | $this->get(ICacheFactory::class), | ||||
new \OC\AppFramework\Utility\TimeFactory() | new \OC\AppFramework\Utility\TimeFactory() | ||||
); | ); | ||||
} else { | } else { | ||||
$backend = new \OC\Security\RateLimiting\Backend\DatabaseBackend( | $backend = new \OC\Security\RateLimiting\Backend\DatabaseBackend( | ||||
$c->get(AllConfig::class), | |||||
$c->get(IDBConnection::class), | $c->get(IDBConnection::class), | ||||
new \OC\AppFramework\Utility\TimeFactory() | new \OC\AppFramework\Utility\TimeFactory() | ||||
); | ); |
use OCP\AppFramework\Utility\ITimeFactory; | use OCP\AppFramework\Utility\ITimeFactory; | ||||
use OCP\ICache; | use OCP\ICache; | ||||
use OCP\ICacheFactory; | use OCP\ICacheFactory; | ||||
use OCP\IConfig; | |||||
use Test\TestCase; | use Test\TestCase; | ||||
class MemoryCacheBackendTest extends TestCase { | class MemoryCacheBackendTest extends TestCase { | ||||
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ | |||||
private $config; | |||||
/** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */ | /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */ | ||||
private $cacheFactory; | private $cacheFactory; | ||||
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ | /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ | ||||
protected function setUp(): void { | protected function setUp(): void { | ||||
parent::setUp(); | parent::setUp(); | ||||
$this->config = $this->createMock(IConfig::class); | |||||
$this->cacheFactory = $this->createMock(ICacheFactory::class); | $this->cacheFactory = $this->createMock(ICacheFactory::class); | ||||
$this->timeFactory = $this->createMock(ITimeFactory::class); | $this->timeFactory = $this->createMock(ITimeFactory::class); | ||||
$this->cache = $this->createMock(ICache::class); | $this->cache = $this->createMock(ICache::class); | ||||
->with('OC\Security\RateLimiting\Backend\MemoryCacheBackend') | ->with('OC\Security\RateLimiting\Backend\MemoryCacheBackend') | ||||
->willReturn($this->cache); | ->willReturn($this->cache); | ||||
$this->config->method('getSystemValueBool') | |||||
->with('ratelimit.protection.enabled') | |||||
->willReturn(true); | |||||
$this->memoryCache = new MemoryCacheBackend( | $this->memoryCache = new MemoryCacheBackend( | ||||
$this->config, | |||||
$this->cacheFactory, | $this->cacheFactory, | ||||
$this->timeFactory | $this->timeFactory | ||||
); | ); |