Skip to content
Snippets Groups Projects
Verified Commit a6b03e36 authored by Pavel Břoušek's avatar Pavel Břoušek
Browse files

fix: use flaps correctly

parent 0e09cd0d
No related branches found
No related tags found
No related merge requests found
Pipeline #315590 passed with warnings
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace SimpleSAML\Module\privacyidea\Auth; namespace SimpleSAML\Module\privacyidea\Auth;
use Exception; use Exception;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Client; use MongoDB\Client;
use MongoDB\Collection; use MongoDB\Collection;
use BehEh\Flaps\StorageInterface; use BehEh\Flaps\StorageInterface;
...@@ -33,7 +34,7 @@ class MongoDBStorage implements StorageInterface ...@@ -33,7 +34,7 @@ class MongoDBStorage implements StorageInterface
public function setValue($key, $value) public function setValue($key, $value)
{ {
$this->collection->updateOne( $this->collection->updateOne(
['_id' => $key], ['key' => $key],
['$set' => ['value' => $value]], ['$set' => ['value' => $value]],
['upsert' => true] ['upsert' => true]
); );
...@@ -42,7 +43,7 @@ class MongoDBStorage implements StorageInterface ...@@ -42,7 +43,7 @@ class MongoDBStorage implements StorageInterface
public function incrementValue($key) public function incrementValue($key)
{ {
$result = $this->collection->findOneAndUpdate( $result = $this->collection->findOneAndUpdate(
['_id' => $key], ['key' => $key],
['$inc' => ['value' => 1]], ['$inc' => ['value' => 1]],
['upsert' => true, 'returnDocument' => Collection::RETURN_DOCUMENT_AFTER] ['upsert' => true, 'returnDocument' => Collection::RETURN_DOCUMENT_AFTER]
); );
...@@ -52,34 +53,50 @@ class MongoDBStorage implements StorageInterface ...@@ -52,34 +53,50 @@ class MongoDBStorage implements StorageInterface
public function getValue($key) public function getValue($key)
{ {
$result = $this->collection->findOne(['_id' => $key]); $result = $this->collection->findOne(['key' => $key]);
$result = $this->collection->findOne( $result = $this->collection->findOne(
['$or' => ['$and' => ['_id' => $key, 'expireAt' => ['$or' => [
['$gt' => gmdate('Y-m-d H:i:s')]]], ['$and' => ['_id' => $key, 'expire' => null]]] [
'$and' =>
[
['key' => $key],
['expireAt' => ['$gt' => self::createMongoDate()]],
]
],
[
'$and' =>
[
['key' => $key],
['expireAt' => null],
]
]
]]
); );
return $result ? $result['value'] : 0;
} }
public function setTimestamp($key, $timestamp) public function setTimestamp($key, $timestamp)
{ {
$this->collection->updateOne( $this->collection->updateOne(
['_id' => $key], ['key' => $key],
['$set' => ['timestamp' => $timestamp]], ['$set' => ['timestamp' => new UTCDateTime($timestamp * 1000)]],
['upsert' => true] ['upsert' => true]
); );
} }
public function getTimestamp($key) public function getTimestamp($key)
{ {
$result = $this->collection->findOne(['_id' => $key]); $result = $this->collection->findOne(['key' => $key]);
return $result ? $result['timestamp'] : 0; return $result ? $result['timestamp']->toDateTime()->getTimestamp() : 0;
} }
public function expire($key) public function expire($key)
{ {
$this->collection->deleteOne(['_id' => $key]); $this->collection->deleteOne(['key' => $key]);
$this->collection->deleteMany(['expireAt' => ['$lt' => gmdate('Y-m-d H:i:s')]]); $this->removeExpiredRecords();
} }
/** /**
...@@ -88,10 +105,22 @@ class MongoDBStorage implements StorageInterface ...@@ -88,10 +105,22 @@ class MongoDBStorage implements StorageInterface
public function expireIn($key, $seconds) public function expireIn($key, $seconds)
{ {
$this->collection->updateOne( $this->collection->updateOne(
['_id' => $key], ['key' => $key],
['$set' => ['expireAt' => new DateTimeImmutable('+ ' . $seconds . ' seconds')]], ['$set' => ['expireAt' => self::createMongoDate($seconds)]],
['upsert' => true] ['upsert' => true]
); );
$this->collection->deleteMany(['expireAt' => ['$lt' => gmdate('Y-m-d H:i:s')]]); $this->removeExpiredRecords();
}
private function removeExpiredRecords()
{
$this->collection->deleteMany(['expireAt' => ['$lt' => self::createMongoDate()]]);
}
private static function createMongoDate($secondsFromNow = 0)
{
return new UTCDateTime(
(new \DateTimeImmutable('+' . $secondsFromNow . ' seconds'))->getTimestamp() * 1000
);
} }
} }
...@@ -314,4 +314,4 @@ if (!empty($this->data['links'])) { ...@@ -314,4 +314,4 @@ if (!empty($this->data['links'])) {
<?php <?php
$this->includeAtTemplateBase('includes/footer.php'); $this->includeAtTemplateBase('includes/footer.php');
?> ?>
\ No newline at end of file
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
use BehEh\Flaps\Flaps; use BehEh\Flaps\Flaps;
use BehEh\Flaps\Throttling\LeakyBucketStrategy;
use BehEh\Flaps\Violation\PassiveViolationHandler; use BehEh\Flaps\Violation\PassiveViolationHandler;
use SimpleSAML\Auth\Source; use SimpleSAML\Auth\Source;
use SimpleSAML\Auth\State; use SimpleSAML\Auth\State;
...@@ -24,14 +25,16 @@ if (empty($stateId)) { ...@@ -24,14 +25,16 @@ if (empty($stateId)) {
$state = State::loadState($stateId, 'privacyidea:privacyidea'); $state = State::loadState($stateId, 'privacyidea:privacyidea');
if (!empty($state['privacyidea:privacyidea']['rate_limiting'])) { if (!empty($state['privacyidea:privacyidea']['rate_limiting'])) {
$config = $state['privacyidea:privacyidea']['rate_limiting']; $config = $state['privacyidea:privacyidea']['rate_limiting'];
$usernameStorage = new MongoDBStorage( $flapsStorage = new MongoDBStorage(
array_merge($config, ['collection_name' => $config['collection_prefix'] . 'username']) array_merge($config, ['collection_name' => $config['collection_prefix'] . 'flaps'])
); );
$ipStorage = new MongoDBStorage(array_merge($config, ['collection_name' => $config['collection_prefix'] . 'ip'])); $flaps = new Flaps($flapsStorage);
$usernameFlaps = new Flaps($usernameStorage); $flaps->setDefaultViolationHandler(new PassiveViolationHandler());
$ipFlaps = new Flaps($ipStorage); $flapsUsername = $flaps->username;
$usernameFlaps->setViolationHandler(new PassiveViolationHandler()); $flapsUsername->pushThrottlingStrategy(new LeakyBucketStrategy(5, '60s'));
$ipFlaps->setViolationHandler(new PassiveViolationHandler()); $flapsIP = $flaps->ip;
$flapsIP->pushThrottlingStrategy(new LeakyBucketStrategy(2, '1s'));
$ip = $_SERVER['REMOTE_ADDR']; $ip = $_SERVER['REMOTE_ADDR'];
} }
...@@ -52,12 +55,12 @@ if (array_key_exists('username', $_REQUEST)) { ...@@ -52,12 +55,12 @@ if (array_key_exists('username', $_REQUEST)) {
if (!empty($state['privacyidea:privacyidea']['rate_limiting'])) { if (!empty($state['privacyidea:privacyidea']['rate_limiting'])) {
// Limit the requests based on UID and IP // Limit the requests based on UID and IP
if (!$usernameFlaps->login->limit($username)) { if (!$flapsUsername->limit($username)) {
Logger::warning('Rate limit exceeded for username ' . $username); Logger::warning('Rate limit exceeded for username ' . $username);
$e = new \SimpleSAML\Error\Error('BADREQUEST', null, 429); $e = new \SimpleSAML\Error\Error('BADREQUEST', null, 429);
throw new \SimpleSAML\Error\Exception('Rate limit exceeded', 429, $e); throw new \SimpleSAML\Error\Exception('Rate limit exceeded', 429, $e);
} }
if (!$ipFlaps->login->limit($ip)) { if (!$flapsIP->limit($ip)) {
Logger::warning('Rate limit exceeded for IP address ' . $ip); Logger::warning('Rate limit exceeded for IP address ' . $ip);
$e = new \SimpleSAML\Error\Error('BADREQUEST', null, 429); $e = new \SimpleSAML\Error\Error('BADREQUEST', null, 429);
throw new \SimpleSAML\Error\Exception('Rate limit exceeded', 429, $e); throw new \SimpleSAML\Error\Exception('Rate limit exceeded', 429, $e);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment