Skip to content

Commit

Permalink
Add database ratelimiting backend
Browse files Browse the repository at this point in the history
In case no distributed memory cache is specified this adds
a database backend for ratelimit purposes.

Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
  • Loading branch information
LukasReschke committed Sep 6, 2021
1 parent 33a0b75 commit d4f97af
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 11 deletions.
47 changes: 47 additions & 0 deletions core/Migrations/Version23000Date20210906132259.php
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
$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',
Expand Down Expand Up @@ -969,6 +973,7 @@
'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',
Expand Down Expand Up @@ -1365,6 +1370,7 @@
'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',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_psr4.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
'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'),
);
14 changes: 14 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\' => 3,
'OCP\\' => 4,
),
'B' =>
array (
'Bamarni\\Composer\\Bin\\' => 21,
),
);

public static $prefixDirsPsr4 = array (
Expand All @@ -28,13 +32,21 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
array (
0 => __DIR__ . '/../../..' . '/lib/public',
),
'Bamarni\\Composer\\Bin\\' =>
array (
0 => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src',
),
);

public static $fallbackDirsPsr4 = array (
0 => __DIR__ . '/../../..' . '/lib/private/legacy',
);

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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
62 changes: 59 additions & 3 deletions lib/composer/composer/installed.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
15 changes: 12 additions & 3 deletions lib/composer/composer/installed.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 changes: 136 additions & 0 deletions lib/private/Security/RateLimiting/Backend/DatabaseBackend.php
Original file line number Diff line number Diff line change
@@ -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();
}
}
18 changes: 14 additions & 4 deletions lib/private/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -785,10 +785,20 @@ public function __construct($webRoot, \OC\Config $config) {
$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);
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down

0 comments on commit d4f97af

Please sign in to comment.