mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
Add database ratelimiting backend
In case no distributed memory cache is specified this adds a database backend for ratelimit purposes. Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
This commit is contained in:
parent
33a0b75c83
commit
d4f97affc1
9 changed files with 290 additions and 11 deletions
47
core/Migrations/Version23000Date20210906132259.php
Normal file
47
core/Migrations/Version23000Date20210906132259.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OC\Core\Migrations;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
/**
|
||||
* Auto-generated migration step: Please modify to your needs!
|
||||
*/
|
||||
class Version23000Date20210906132259 extends SimpleMigrationStep {
|
||||
private const TABLE_NAME = 'ratelimit_entries';
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
$hasTable = $schema->hasTable(self::TABLE_NAME);
|
||||
|
||||
if(!$hasTable)
|
||||
{
|
||||
$table = $schema->createTable(self::TABLE_NAME);
|
||||
$table->addColumn('hash', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 128,
|
||||
]);
|
||||
$table->addColumn('timestamp', 'datetime', [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addIndex(['hash'], 'ratelimit_hash_idx');
|
||||
$table->addIndex(['timestamp'], 'ratelimit_timestamp_idx');
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,10 @@ $vendorDir = dirname(dirname(__FILE__));
|
|||
$baseDir = dirname(dirname($vendorDir));
|
||||
|
||||
return array(
|
||||
'Bamarni\\Composer\\Bin\\BinCommand' => $vendorDir . '/bamarni/composer-bin-plugin/src/BinCommand.php',
|
||||
'Bamarni\\Composer\\Bin\\CommandProvider' => $vendorDir . '/bamarni/composer-bin-plugin/src/CommandProvider.php',
|
||||
'Bamarni\\Composer\\Bin\\Config' => $vendorDir . '/bamarni/composer-bin-plugin/src/Config.php',
|
||||
'Bamarni\\Composer\\Bin\\Plugin' => $vendorDir . '/bamarni/composer-bin-plugin/src/Plugin.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'OCP\\Accounts\\IAccount' => $baseDir . '/lib/public/Accounts/IAccount.php',
|
||||
'OCP\\Accounts\\IAccountManager' => $baseDir . '/lib/public/Accounts/IAccountManager.php',
|
||||
|
|
@ -969,6 +973,7 @@ return array(
|
|||
'OC\\Core\\Migrations\\Version21000Date20210309185126' => $baseDir . '/core/Migrations/Version21000Date20210309185126.php',
|
||||
'OC\\Core\\Migrations\\Version21000Date20210309185127' => $baseDir . '/core/Migrations/Version21000Date20210309185127.php',
|
||||
'OC\\Core\\Migrations\\Version22000Date20210216080825' => $baseDir . '/core/Migrations/Version22000Date20210216080825.php',
|
||||
'OC\\Core\\Migrations\\Version23000Date20210906132259' => $baseDir . '/core/Migrations/Version23000Date20210906132259.php',
|
||||
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
|
||||
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
|
||||
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
|
||||
|
|
@ -1365,6 +1370,7 @@ return array(
|
|||
'OC\\Security\\IdentityProof\\Manager' => $baseDir . '/lib/private/Security/IdentityProof/Manager.php',
|
||||
'OC\\Security\\IdentityProof\\Signer' => $baseDir . '/lib/private/Security/IdentityProof/Signer.php',
|
||||
'OC\\Security\\Normalizer\\IpAddress' => $baseDir . '/lib/private/Security/Normalizer/IpAddress.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\DatabaseBackend' => $baseDir . '/lib/private/Security/RateLimiting/Backend/DatabaseBackend.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\IBackend' => $baseDir . '/lib/private/Security/RateLimiting/Backend/IBackend.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\MemoryCache' => $baseDir . '/lib/private/Security/RateLimiting/Backend/MemoryCache.php',
|
||||
'OC\\Security\\RateLimiting\\Exception\\RateLimitExceededException' => $baseDir . '/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php',
|
||||
|
|
|
|||
|
|
@ -9,5 +9,6 @@ return array(
|
|||
'OC\\Core\\' => array($baseDir . '/core'),
|
||||
'OC\\' => array($baseDir . '/lib/private'),
|
||||
'OCP\\' => array($baseDir . '/lib/public'),
|
||||
'Bamarni\\Composer\\Bin\\' => array($vendorDir . '/bamarni/composer-bin-plugin/src'),
|
||||
'' => array($baseDir . '/lib/private/legacy'),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\' => 3,
|
||||
'OCP\\' => 4,
|
||||
),
|
||||
'B' =>
|
||||
array (
|
||||
'Bamarni\\Composer\\Bin\\' => 21,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
|
|
@ -28,6 +32,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
array (
|
||||
0 => __DIR__ . '/../../..' . '/lib/public',
|
||||
),
|
||||
'Bamarni\\Composer\\Bin\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $fallbackDirsPsr4 = array (
|
||||
|
|
@ -35,6 +43,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Bamarni\\Composer\\Bin\\BinCommand' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/BinCommand.php',
|
||||
'Bamarni\\Composer\\Bin\\CommandProvider' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/CommandProvider.php',
|
||||
'Bamarni\\Composer\\Bin\\Config' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Config.php',
|
||||
'Bamarni\\Composer\\Bin\\Plugin' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Plugin.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'OCP\\Accounts\\IAccount' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccount.php',
|
||||
'OCP\\Accounts\\IAccountManager' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountManager.php',
|
||||
|
|
@ -998,6 +1010,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Core\\Migrations\\Version21000Date20210309185126' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20210309185126.php',
|
||||
'OC\\Core\\Migrations\\Version21000Date20210309185127' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20210309185127.php',
|
||||
'OC\\Core\\Migrations\\Version22000Date20210216080825' => __DIR__ . '/../../..' . '/core/Migrations/Version22000Date20210216080825.php',
|
||||
'OC\\Core\\Migrations\\Version23000Date20210906132259' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210906132259.php',
|
||||
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
|
||||
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
|
||||
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
|
||||
|
|
@ -1394,6 +1407,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Security\\IdentityProof\\Manager' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Manager.php',
|
||||
'OC\\Security\\IdentityProof\\Signer' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Signer.php',
|
||||
'OC\\Security\\Normalizer\\IpAddress' => __DIR__ . '/../../..' . '/lib/private/Security/Normalizer/IpAddress.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\DatabaseBackend' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/DatabaseBackend.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\IBackend' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/IBackend.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\MemoryCache' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/MemoryCache.php',
|
||||
'OC\\Security\\RateLimiting\\Exception\\RateLimitExceededException' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,61 @@
|
|||
{
|
||||
"packages": [],
|
||||
"dev": false,
|
||||
"dev-package-names": []
|
||||
"packages": [
|
||||
{
|
||||
"name": "bamarni/composer-bin-plugin",
|
||||
"version": "1.4.1",
|
||||
"version_normalized": "1.4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bamarni/composer-bin-plugin.git",
|
||||
"reference": "9329fb0fbe29e0e1b2db8f4639a193e4f5406225"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/9329fb0fbe29e0e1b2db8f4639a193e4f5406225",
|
||||
"reference": "9329fb0fbe29e0e1b2db8f4639a193e4f5406225",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "^1.0 || ^2.0",
|
||||
"php": "^5.5.9 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^1.0 || ^2.0",
|
||||
"symfony/console": "^2.5 || ^3.0 || ^4.0"
|
||||
},
|
||||
"time": "2020-05-03T08:27:20+00:00",
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": "Bamarni\\Composer\\Bin\\Plugin"
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Bamarni\\Composer\\Bin\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "No conflicts for your bin dependencies",
|
||||
"keywords": [
|
||||
"composer",
|
||||
"conflict",
|
||||
"dependency",
|
||||
"executable",
|
||||
"isolation",
|
||||
"tool"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bamarni/composer-bin-plugin/issues",
|
||||
"source": "https://github.com/bamarni/composer-bin-plugin/tree/master"
|
||||
},
|
||||
"install-path": "../bamarni/composer-bin-plugin"
|
||||
}
|
||||
],
|
||||
"dev": true,
|
||||
"dev-package-names": [
|
||||
"bamarni/composer-bin-plugin"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../../',
|
||||
'aliases' => array(),
|
||||
'reference' => '66144c300395458ff38b86e50cd92174443cd85e',
|
||||
'reference' => '33a0b75c83a1c56fa84b98d3a07a26b5c4932b65',
|
||||
'name' => '__root__',
|
||||
'dev' => false,
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
|
|
@ -16,8 +16,17 @@
|
|||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../../',
|
||||
'aliases' => array(),
|
||||
'reference' => '66144c300395458ff38b86e50cd92174443cd85e',
|
||||
'reference' => '33a0b75c83a1c56fa84b98d3a07a26b5c4932b65',
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'bamarni/composer-bin-plugin' => array(
|
||||
'pretty_version' => '1.4.1',
|
||||
'version' => '1.4.1.0',
|
||||
'type' => 'composer-plugin',
|
||||
'install_path' => __DIR__ . '/../bamarni/composer-bin-plugin',
|
||||
'aliases' => array(),
|
||||
'reference' => '9329fb0fbe29e0e1b2db8f4639a193e4f5406225',
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
136
lib/private/Security/RateLimiting/Backend/DatabaseBackend.php
Normal file
136
lib/private/Security/RateLimiting/Backend/DatabaseBackend.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Lukas Reschke <lukas@statuscode.ch>
|
||||
*
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OC\Security\RateLimiting\Backend;
|
||||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/**
|
||||
* Class DatabaseBackend uses the database for storing rate limiting data.
|
||||
*
|
||||
* @package OC\Security\RateLimiting\Backend
|
||||
*/
|
||||
class DatabaseBackend implements IBackend {
|
||||
private const TABLE_NAME = 'ratelimit_entries';
|
||||
|
||||
/** @var IDBConnection */
|
||||
private $dbConnection;
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
|
||||
/**
|
||||
* @param IDBConnection $dbConnection
|
||||
* @param ITimeFactory $timeFactory
|
||||
*/
|
||||
public function __construct(
|
||||
IDBConnection $dbConnection,
|
||||
ITimeFactory $timeFactory
|
||||
) {
|
||||
$this->dbConnection = $dbConnection;
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $methodIdentifier
|
||||
* @param string $userIdentifier
|
||||
* @return string
|
||||
*/
|
||||
private function hash(string $methodIdentifier,
|
||||
string $userIdentifier): string {
|
||||
return hash('sha512', $methodIdentifier . $userIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $identifier
|
||||
* @param int $seconds
|
||||
* @return int
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
private function getExistingAttemptCount(
|
||||
string $identifier,
|
||||
int $seconds
|
||||
): int {
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
$notOlderThan = $this->timeFactory->getDateTime()->sub(new \DateInterval("PT{$seconds}S"));
|
||||
|
||||
$qb->selectAlias($qb->createFunction('COUNT(*)'), 'count')
|
||||
->from(self::TABLE_NAME)
|
||||
->where(
|
||||
$qb->expr()->eq('hash', $qb->createNamedParameter($identifier, IQueryBuilder::PARAM_STR))
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->gte('timestamp', $qb->createParameter('notOlderThan'))
|
||||
)
|
||||
->setParameter('notOlderThan', $notOlderThan, 'datetime');
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
$row = $cursor->fetch();
|
||||
$cursor->closeCursor();
|
||||
|
||||
return (int)$row['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getAttempts(string $methodIdentifier,
|
||||
string $userIdentifier,
|
||||
int $seconds): int {
|
||||
$identifier = $this->hash($methodIdentifier, $userIdentifier);
|
||||
return $this->getExistingAttemptCount($identifier, $seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function registerAttempt(string $methodIdentifier,
|
||||
string $userIdentifier,
|
||||
int $period) {
|
||||
$identifier = $this->hash($methodIdentifier, $userIdentifier);
|
||||
$currentTime = $this->timeFactory->getDateTime();
|
||||
$notOlderThan = $this->timeFactory->getDateTime('@' . $period);
|
||||
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
$qb->delete(self::TABLE_NAME)
|
||||
->where(
|
||||
$qb->expr()->eq('hash', $qb->createNamedParameter($identifier, IQueryBuilder::PARAM_STR))
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->lt('timestamp', $qb->createParameter('notOlderThan'))
|
||||
)
|
||||
->setParameter('notOlderThan', $notOlderThan, 'datetime')
|
||||
->executeStatement();
|
||||
|
||||
$qb->insert(self::TABLE_NAME)
|
||||
->values([
|
||||
'hash' => $qb->createNamedParameter($identifier, IQueryBuilder::PARAM_STR),
|
||||
'timestamp' => $qb->createNamedParameter($currentTime, IQueryBuilder::PARAM_DATE),
|
||||
])
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
|
|
@ -785,10 +785,20 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
$this->registerDeprecatedAlias('Search', ISearch::class);
|
||||
|
||||
$this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) {
|
||||
return new \OC\Security\RateLimiting\Backend\MemoryCache(
|
||||
$this->get(ICacheFactory::class),
|
||||
new \OC\AppFramework\Utility\TimeFactory()
|
||||
);
|
||||
$cacheFactory = $c->get(ICacheFactory::class);
|
||||
if ($cacheFactory->isAvailable()) {
|
||||
$backend = new \OC\Security\RateLimiting\Backend\MemoryCache(
|
||||
$this->get(ICacheFactory::class),
|
||||
new \OC\AppFramework\Utility\TimeFactory()
|
||||
);
|
||||
} else {
|
||||
$backend = new \OC\Security\RateLimiting\Backend\DatabaseBackend(
|
||||
$c->get(IDBConnection::class),
|
||||
new \OC\AppFramework\Utility\TimeFactory()
|
||||
);
|
||||
}
|
||||
|
||||
return $backend;
|
||||
});
|
||||
|
||||
$this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
|
||||
// when updating major/minor version number.
|
||||
|
||||
$OC_Version = [23, 0, 0, 0];
|
||||
$OC_Version = [23, 0, 0, 1];
|
||||
|
||||
// The human readable string
|
||||
$OC_VersionString = '23.0.0 alpha';
|
||||
|
|
|
|||
Loading…
Reference in a new issue