In case no distributed memory cache is specified this adds a database backend for ratelimit purposes. Signed-off-by: Lukas Reschke <lukas@statuscode.ch>tags/v23.0.0beta1
@@ -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, | |||
), | |||
), | |||
); |
@@ -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'; |