From 4dcf52dda7f33ac51a33d2279543233270cb78b3 Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 14:06:26 +0300 Subject: [PATCH 01/24] Add block period into annotation --- Annotation/RateLimit.php | 21 +++++++++++++++++++++ Tests/Annotation/RateLimitTest.php | 7 +++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Annotation/RateLimit.php b/Annotation/RateLimit.php index 5f252c6..9497817 100644 --- a/Annotation/RateLimit.php +++ b/Annotation/RateLimit.php @@ -26,6 +26,11 @@ class RateLimit extends ConfigurationAnnotation */ protected $period = 3600; + /** + * @var int Amount of seconds when the calls aren't available + */ + protected $blockPeriod = 0; + /** * Returns the alias name for an annotated configuration. * @@ -93,4 +98,20 @@ public function setPeriod($period) { $this->period = $period; } + + /** + * @return int + */ + public function getBlockPeriod() + { + return $this->blockPeriod; + } + + /** + * @param int $blockPeriod + */ + public function setBlockPeriod($blockPeriod) + { + $this->blockPeriod = $blockPeriod; + } } diff --git a/Tests/Annotation/RateLimitTest.php b/Tests/Annotation/RateLimitTest.php index 33dfeba..3e822c1 100644 --- a/Tests/Annotation/RateLimitTest.php +++ b/Tests/Annotation/RateLimitTest.php @@ -18,19 +18,22 @@ public function testConstruction() $this->assertEquals(-1, $annot->getLimit()); $this->assertEmpty($annot->getMethods()); $this->assertEquals(3600, $annot->getPeriod()); + $this->assertEquals(0, $annot->getBlockPeriod()); } public function testConstructionWithValues() { - $annot = new RateLimit(array('limit' => 1234, 'period' => 1000)); + $annot = new RateLimit(array('limit' => 1234, 'period' => 1000, 'blockPeriod' => 7200)); $this->assertEquals(1234, $annot->getLimit()); $this->assertEquals(1000, $annot->getPeriod()); + $this->assertEquals(7200, $annot->getBlockPeriod()); - $annot = new RateLimit(array('methods' => 'POST', 'limit' => 1234, 'period' => 1000)); + $annot = new RateLimit(array('methods' => 'POST', 'limit' => 1234, 'period' => 1000, 'blockPeriod' => 7200)); $this->assertEquals(1234, $annot->getLimit()); $this->assertEquals(1000, $annot->getPeriod()); $this->assertEquals(['POST'], $annot->getMethods()); + $this->assertEquals(7200, $annot->getBlockPeriod()); } public function testConstructionWithMethods() From e858246e3dcfeab6063393413d6c6110a1c66a81 Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 14:30:28 +0300 Subject: [PATCH 02/24] Add an ability to get block period from config file --- DependencyInjection/Configuration.php | 4 ++++ Tests/DependencyInjection/ConfigurationTest.php | 15 ++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index f6944c4..4177da5 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -119,6 +119,10 @@ public function getConfigTreeBuilder() ->isRequired() ->min(0) ->end() + ->integerNode('block_period') + ->defaultValue(0) + ->min(0) + ->end() ->end() ->end() ->end() diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index 016b0b3..8fdb8a7 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -71,7 +71,8 @@ public function testPathLimitConfiguration() 'path' => 'api/', 'methods' => array('GET'), 'limit' => 100, - 'period' => 60 + 'period' => 60, + 'block_period' => 120 ) ); @@ -90,13 +91,15 @@ public function testMultiplePathLimitConfiguration() 'path' => 'api/', 'methods' => array('GET', 'POST'), 'limit' => 200, - 'period' => 10 + 'period' => 10, + 'block_period' => 20 ), 'api2' => array( 'path' => 'api2/', 'methods' => array('*'), 'limit' => 1000, - 'period' => 15 + 'period' => 15, + 'block_period' => 0 ) ); @@ -115,12 +118,14 @@ public function testDefaultPathLimitMethods() 'path' => 'api/', 'methods' => array('GET', 'POST'), 'limit' => 200, - 'period' => 10 + 'period' => 10, + 'block_period' => 0 ), 'api2' => array( 'path' => 'api2/', 'limit' => 1000, - 'period' => 15 + 'period' => 15, + 'block_period' => 0, ) ); From f70c74d4d12d83cd31eff823d2e7c1374425b1bd Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 17:41:05 +0300 Subject: [PATCH 03/24] Add predis/predis package for development We added the package for more convenient development. It allows an IDE make auto complete. --- composer.json | 3 ++- composer.lock | 54 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 300c169..1a18f79 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "sensio/framework-extra-bundle": "^2.3|^3.0|^4.0|^5.0" }, "require-dev": { - "phpunit/phpunit": "^4.8|^5.0" + "phpunit/phpunit": "^4.8|^5.0", + "predis/predis": "^1.1" }, "suggest": { "snc/redis-bundle": "Use Redis as a storage engine.", diff --git a/composer.lock b/composer.lock index e1c3469..e47a040 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "32cbc74722517d7b5ac280f74b7d57f6", + "content-hash": "4520ecda7c366289886edf91f6fbf86d", "packages": [ { "name": "doctrine/annotations", @@ -2141,6 +2141,56 @@ ], "time": "2017-06-30T09:13:00+00:00" }, + { + "name": "predis/predis", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2016-06-16T16:22:20+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", From b52562d1952d4629b3148d6586f23d8b6d7073b7 Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 19:34:27 +0300 Subject: [PATCH 04/24] Add block status into RateLimitInfo We will check this field and will discard requests if it is set up blocked. --- Service/RateLimitInfo.php | 25 +++++++++++++++++++++++++ Tests/Service/RateLimitInfoTest.php | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/Service/RateLimitInfo.php b/Service/RateLimitInfo.php index 49eb5b0..54fbe7e 100644 --- a/Service/RateLimitInfo.php +++ b/Service/RateLimitInfo.php @@ -8,6 +8,11 @@ class RateLimitInfo protected $calls; protected $resetTimestamp; + /** + * @var bool + */ + protected $blocked = false; + /** * @return mixed */ @@ -55,4 +60,24 @@ public function setResetTimestamp($resetTimestamp) { $this->resetTimestamp = $resetTimestamp; } + + /** + * Return true if the action is blocked + * + * @return bool + */ + public function isBlocked() + { + return $this->blocked; + } + + /** + * Set block the action + * + * @param bool $blocked + */ + public function setBlocked($blocked) + { + $this->blocked = $blocked; + } } diff --git a/Tests/Service/RateLimitInfoTest.php b/Tests/Service/RateLimitInfoTest.php index 7412913..e4a0130 100644 --- a/Tests/Service/RateLimitInfoTest.php +++ b/Tests/Service/RateLimitInfoTest.php @@ -23,6 +23,11 @@ public function testRateInfoSetters() $rateInfo->setResetTimestamp(100000); $this->assertEquals(100000, $rateInfo->getResetTimestamp()); + + $this->assertFalse($rateInfo->isBlocked()); + + $rateInfo->setBlocked(true); + $this->assertTrue($rateInfo->isBlocked()); } } From c39d49fc28f1d35cd841fa2d5a5d4c2546d9deca Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 19:51:50 +0300 Subject: [PATCH 05/24] Add the key of rate limit into RateLimitInfo We added the key for more convenient handling of RateLimitInfo in service storages. --- Service/RateLimitInfo.php | 27 ++++++++++++++++++++++++++- Tests/Service/RateLimitInfoTest.php | 3 +++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Service/RateLimitInfo.php b/Service/RateLimitInfo.php index 54fbe7e..f165d65 100644 --- a/Service/RateLimitInfo.php +++ b/Service/RateLimitInfo.php @@ -13,6 +13,11 @@ class RateLimitInfo */ protected $blocked = false; + /** + * @var string the key of rate limit + */ + protected $key; + /** * @return mixed */ @@ -78,6 +83,26 @@ public function isBlocked() */ public function setBlocked($blocked) { - $this->blocked = $blocked; + $this->blocked = (bool)$blocked; + } + + /** + * Return the key of rate limit + * + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * Set the key into rate limit + * + * @param string $key + */ + public function setKey($key) + { + $this->key = (string)$key; } } diff --git a/Tests/Service/RateLimitInfoTest.php b/Tests/Service/RateLimitInfoTest.php index e4a0130..35659c8 100644 --- a/Tests/Service/RateLimitInfoTest.php +++ b/Tests/Service/RateLimitInfoTest.php @@ -28,6 +28,9 @@ public function testRateInfoSetters() $rateInfo->setBlocked(true); $this->assertTrue($rateInfo->isBlocked()); + + $rateInfo->setKey('test'); + $this->assertEquals('test', $rateInfo->getKey()); } } From 8a7b7b237f4411d6f7e1bc4374e07de9fbbd9d84 Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 20:46:07 +0300 Subject: [PATCH 06/24] Add setting of block into all available storages --- Service/Storage/DoctrineCache.php | 23 +++++++++++++++++++ Service/Storage/Memcache.php | 24 ++++++++++++++++++++ Service/Storage/PhpRedis.php | 15 +++++++++++++ Service/Storage/Redis.php | 15 +++++++++++++ Service/Storage/StorageInterface.php | 10 +++++++++ Tests/EventListener/MockStorage.php | 18 +++++++++++++++ Tests/Service/Storage/DoctrineCacheTest.php | 23 +++++++++++++++++++ Tests/Service/Storage/MemcacheTest.php | 18 +++++++++++++++ Tests/Service/Storage/PhpRedisTest.php | 25 +++++++++++++++++++++ Tests/Service/Storage/RedisTest.php | 25 +++++++++++++++++++++ 10 files changed, 196 insertions(+) diff --git a/Service/Storage/DoctrineCache.php b/Service/Storage/DoctrineCache.php index cc422b7..b01d3a3 100644 --- a/Service/Storage/DoctrineCache.php +++ b/Service/Storage/DoctrineCache.php @@ -54,4 +54,27 @@ public function resetRate($key) { $this->client->delete($key); return true; } + + /** + * @inheritDoc + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) + { + $resetTimestamp = time() + $periodBlock; + $this->client->save( + $rateLimitInfo->getKey(), + array( + 'limit' => $rateLimitInfo->getLimit(), + 'calls' => $rateLimitInfo->getCalls(), + 'reset' => $resetTimestamp, + 'blocked' => 1, + ), + $periodBlock + ); + + $rateLimitInfo->setBlocked(true); + $rateLimitInfo->setResetTimestamp($resetTimestamp); + + return true; + } } diff --git a/Service/Storage/Memcache.php b/Service/Storage/Memcache.php index 225ce50..0f0f5c5 100644 --- a/Service/Storage/Memcache.php +++ b/Service/Storage/Memcache.php @@ -60,4 +60,28 @@ public function resetRate($key) $this->client->delete($key); return true; } + + /** + * @inheritDoc + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) + { + $resetTimestamp = time() + $periodBlock; + + $this->client->set( + $rateLimitInfo->getKey(), + [ + 'limit' => $rateLimitInfo->getLimit(), + 'calls' => $rateLimitInfo->getCalls(), + 'reset' => $resetTimestamp, + 'blocked' => 1, + ], + $periodBlock + ); + + $rateLimitInfo->setBlocked(true); + $rateLimitInfo->setResetTimestamp($resetTimestamp); + + return true; + } } diff --git a/Service/Storage/PhpRedis.php b/Service/Storage/PhpRedis.php index 37d135f..60919f2 100644 --- a/Service/Storage/PhpRedis.php +++ b/Service/Storage/PhpRedis.php @@ -69,4 +69,19 @@ public function resetRate($key) return true; } + /** + * @inheritDoc + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) + { + $resetTimestamp = time() + $periodBlock; + $this->client->hset($rateLimitInfo->getKey(), 'blocked', 1); + $this->client->hset($rateLimitInfo->getKey(), 'reset', $resetTimestamp); + $this->client->expire($rateLimitInfo->getKey(), $periodBlock); + + $rateLimitInfo->setBlocked(true); + $rateLimitInfo->setResetTimestamp($resetTimestamp); + + return true; + } } diff --git a/Service/Storage/Redis.php b/Service/Storage/Redis.php index 270cb76..eb8e6b4 100644 --- a/Service/Storage/Redis.php +++ b/Service/Storage/Redis.php @@ -68,4 +68,19 @@ public function resetRate($key) return true; } + /** + * @inheritDoc + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) + { + $resetTimestamp = time() + $periodBlock; + $this->client->hset($rateLimitInfo->getKey(), 'blocked', 1); + $this->client->hset($rateLimitInfo->getKey(), 'reset', $resetTimestamp); + $this->client->expire($rateLimitInfo->getKey(), $periodBlock); + + $rateLimitInfo->setBlocked(true); + $rateLimitInfo->setResetTimestamp($resetTimestamp); + + return true; + } } diff --git a/Service/Storage/StorageInterface.php b/Service/Storage/StorageInterface.php index 5691c3e..a49e192 100644 --- a/Service/Storage/StorageInterface.php +++ b/Service/Storage/StorageInterface.php @@ -37,4 +37,14 @@ public function createRate($key, $limit, $period); * @param $key */ public function resetRate($key); + + /** + * Set block for the call + * + * @param RateLimitInfo $rateLimitInfo + * @param integer $periodBlock + * + * @return bool + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock); } diff --git a/Tests/EventListener/MockStorage.php b/Tests/EventListener/MockStorage.php index 56d8a92..1b35000 100644 --- a/Tests/EventListener/MockStorage.php +++ b/Tests/EventListener/MockStorage.php @@ -71,4 +71,22 @@ public function createMockRate($key, $limit, $period, $calls) $this->rates[$key] = array('calls' => $calls, 'limit' => $limit, 'reset' => (time() + $period)); return $this->getRateInfo($key); } + + /** + * @inheritDoc + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) + { + $resetTimestamp = time() + $periodBlock; + $this->rates[$rateLimitInfo->getKey()] = array_merge( + $this->rates[$rateLimitInfo->getKey()], + array( + 'reset' => $resetTimestamp, + 'blocked' => 1 + ) + ); + + $rateLimitInfo->setBlocked(1); + $rateLimitInfo->setResetTimestamp($resetTimestamp); + } } diff --git a/Tests/Service/Storage/DoctrineCacheTest.php b/Tests/Service/Storage/DoctrineCacheTest.php index 50e22c0..0551e1c 100644 --- a/Tests/Service/Storage/DoctrineCacheTest.php +++ b/Tests/Service/Storage/DoctrineCacheTest.php @@ -3,6 +3,7 @@ namespace Noxlogic\RateLimitBundle\Tests\Service\Storage; +use Noxlogic\RateLimitBundle\Service\RateLimitInfo; use Noxlogic\RateLimitBundle\Service\Storage\DoctrineCache; use Noxlogic\RateLimitBundle\Tests\TestCase; @@ -94,4 +95,26 @@ public function testresetRate() $storage = new DoctrineCache($client); $this->assertTrue($storage->resetRate('foo')); } + + public function testSetBlock() + { + $client = $this->getMockBuilder('Doctrine\\Common\\Cache\\ArrayCache') + ->setMethods(array('save')) + ->getMock(); + $client->expects(self::once()) + ->method('save') + ->with('foo', ['limit' => 5, 'calls' => 6, 'reset' => time() + 100, 'blocked' => 1,], 100) + ->willReturn(true); + + $rateLimitInfo = new RateLimitInfo(); + $rateLimitInfo->setKey('foo'); + $rateLimitInfo->setResetTimestamp(10); + $rateLimitInfo->setLimit(5); + $rateLimitInfo->setCalls(6); + + $storage = new DoctrineCache($client); + self::assertTrue($storage->setBlock($rateLimitInfo, 100)); + self::assertTrue($rateLimitInfo->isBlocked()); + self::assertGreaterThan(100, $rateLimitInfo->getResetTimestamp()); + } } diff --git a/Tests/Service/Storage/MemcacheTest.php b/Tests/Service/Storage/MemcacheTest.php index 255aa53..f2685d9 100644 --- a/Tests/Service/Storage/MemcacheTest.php +++ b/Tests/Service/Storage/MemcacheTest.php @@ -2,6 +2,7 @@ namespace Noxlogic\RateLimitBundle\Tests\Service\Storage; +use Noxlogic\RateLimitBundle\Service\RateLimitInfo; use Noxlogic\RateLimitBundle\Service\Storage\Memcache; use Noxlogic\RateLimitBundle\Tests\TestCase; @@ -96,4 +97,21 @@ public function testresetRate() $this->assertTrue($storage->resetRate('foo')); } + public function testSetBlock() + { + $client = @$this->getMockBuilder('\\Memcached') + ->setMethods(array('set')) + ->getMock(); + $client->expects(self::once()) + ->method('set') + ->with('foo', ['limit' => null, 'calls' => null, 'reset' => time() + 100, 'blocked' => 1], 100); + + $rateLimitInfo = new RateLimitInfo(); + $rateLimitInfo->setKey('foo'); + + $storage = new Memcache($client); + self::assertTrue($storage->setBlock($rateLimitInfo, 100)); + self::assertTrue($rateLimitInfo->isBlocked()); + self::assertGreaterThan(10, $rateLimitInfo->getResetTimestamp()); + } } diff --git a/Tests/Service/Storage/PhpRedisTest.php b/Tests/Service/Storage/PhpRedisTest.php index eac7091..b7eec73 100644 --- a/Tests/Service/Storage/PhpRedisTest.php +++ b/Tests/Service/Storage/PhpRedisTest.php @@ -2,6 +2,7 @@ namespace Noxlogic\RateLimitBundle\Tests\Service\Storage; +use Noxlogic\RateLimitBundle\Service\RateLimitInfo; use Noxlogic\RateLimitBundle\Service\Storage\PhpRedis; use Noxlogic\RateLimitBundle\Service\Storage\Redis; use Noxlogic\RateLimitBundle\Tests\TestCase; @@ -105,4 +106,28 @@ public function testresetRate() $this->assertTrue($storage->resetRate('foo')); } + public function testSetBlock() + { + $client = $this->getMockBuilder('\Redis') + ->setMethods(array('hset', 'expire')) + ->getMock(); + $client->expects(self::exactly(2)) + ->method('hset') + ->withConsecutive( + array('foo', 'blocked', 1), + array('foo', 'reset', time() + 100) + ); + $client->expects(self::once()) + ->method('expire') + ->with('foo', 100); + + $rateLimitInfo = new RateLimitInfo(); + $rateLimitInfo->setKey('foo'); + $rateLimitInfo->setResetTimestamp(10); + + $storage = new PhpRedis($client); + self::assertTrue($storage->setBlock($rateLimitInfo, 100)); + self::assertTrue($rateLimitInfo->isBlocked()); + self::assertGreaterThan(10, $rateLimitInfo->getResetTimestamp()); + } } diff --git a/Tests/Service/Storage/RedisTest.php b/Tests/Service/Storage/RedisTest.php index ca1684b..c7b8b1f 100644 --- a/Tests/Service/Storage/RedisTest.php +++ b/Tests/Service/Storage/RedisTest.php @@ -2,6 +2,7 @@ namespace Noxlogic\RateLimitBundle\Tests\Service\Storage; +use Noxlogic\RateLimitBundle\Service\RateLimitInfo; use Noxlogic\RateLimitBundle\Service\Storage\Redis; use Noxlogic\RateLimitBundle\Tests\TestCase; @@ -104,4 +105,28 @@ public function testresetRate() $this->assertTrue($storage->resetRate('foo')); } + public function testSetBlock() + { + $client = $this->getMockBuilder('Predis\\Client') + ->setMethods(array('hset', 'expire')) + ->getMock(); + $client->expects(self::exactly(2)) + ->method('hset') + ->withConsecutive( + array('foo', 'blocked', 1), + array('foo', 'reset', time() + 100) + ); + $client->expects(self::once()) + ->method('expire') + ->with('foo', 100); + + $rateLimitInfo = new RateLimitInfo(); + $rateLimitInfo->setKey('foo'); + $rateLimitInfo->setResetTimestamp(10); + + $storage = new Redis($client); + self::assertTrue($storage->setBlock($rateLimitInfo, 100)); + self::assertTrue($rateLimitInfo->isBlocked()); + self::assertGreaterThan(10, $rateLimitInfo->getResetTimestamp()); + } } From ec310dd1500e5b311c0db0b5cc4835be4dbfd112 Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 20:51:28 +0300 Subject: [PATCH 07/24] Fix test --- Tests/DependencyInjection/NoxlogicRateLimitExtensionTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/DependencyInjection/NoxlogicRateLimitExtensionTest.php b/Tests/DependencyInjection/NoxlogicRateLimitExtensionTest.php index 959dfa0..02b54b3 100644 --- a/Tests/DependencyInjection/NoxlogicRateLimitExtensionTest.php +++ b/Tests/DependencyInjection/NoxlogicRateLimitExtensionTest.php @@ -42,7 +42,8 @@ public function testPathLimitsParameter() 'path' => 'api/', 'methods' => array('GET'), 'limit' => 100, - 'period' => 60 + 'period' => 60, + 'block_period' => 0 ) ); From ed273808339085057a383f261ebd7a3216320794 Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 21:41:09 +0300 Subject: [PATCH 08/24] Change set block method of mock storage --- Tests/EventListener/MockStorage.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Tests/EventListener/MockStorage.php b/Tests/EventListener/MockStorage.php index 1b35000..96b73f9 100644 --- a/Tests/EventListener/MockStorage.php +++ b/Tests/EventListener/MockStorage.php @@ -78,12 +78,11 @@ public function createMockRate($key, $limit, $period, $calls) public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) { $resetTimestamp = time() + $periodBlock; - $this->rates[$rateLimitInfo->getKey()] = array_merge( - $this->rates[$rateLimitInfo->getKey()], - array( - 'reset' => $resetTimestamp, - 'blocked' => 1 - ) + $this->rates[$rateLimitInfo->getKey()] = array( + 'calls' => $rateLimitInfo->getCalls(), + 'limit' => $rateLimitInfo->getLimit(), + 'reset' => $resetTimestamp, + 'blocked' => 1 ); $rateLimitInfo->setBlocked(1); From 86d4a20ef34d980be5cf7cd5d411a1fa0e9ad06f Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 21:45:06 +0300 Subject: [PATCH 09/24] Add calling of setBlock method of storages into rate limit service --- Service/RateLimitService.php | 13 +++++++++++++ Tests/Service/RateLimitServiceTest.php | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Service/RateLimitService.php b/Service/RateLimitService.php index 1dcf891..b863340 100644 --- a/Service/RateLimitService.php +++ b/Service/RateLimitService.php @@ -54,4 +54,17 @@ public function resetRate($key) { return $this->storage->resetRate($key); } + + /** + * Set block for the call + * + * @param RateLimitInfo $rateLimitInfo + * @param integer $blockPeriod + * + * @return bool + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $blockPeriod) + { + return $this->storage->setBlock($rateLimitInfo, $blockPeriod); + } } diff --git a/Tests/Service/RateLimitServiceTest.php b/Tests/Service/RateLimitServiceTest.php index 39c109b..ca57a71 100644 --- a/Tests/Service/RateLimitServiceTest.php +++ b/Tests/Service/RateLimitServiceTest.php @@ -70,4 +70,20 @@ public function testResetRate() $service->setStorage($mockStorage); $service->resetRate('testkey'); } + + public function testSetBlock() + { + $mockStorage = $this->getMockBuilder('Noxlogic\\RateLimitBundle\\Service\\Storage\\StorageInterface')->getMock(); + + $rateLimitInfo = $this->createMock(RateLimitInfo::class); + + $mockStorage + ->expects(self::once()) + ->method('setBlock') + ->with($rateLimitInfo, 100); + + $service = new RateLimitService(); + $service->setStorage($mockStorage); + $service->setBlock($rateLimitInfo, 100); + } } From 28de195fe0b0c98bb78e02db5736c7b240d36ea8 Mon Sep 17 00:00:00 2001 From: Aleksandr Ivanov Date: Wed, 27 Jun 2018 22:09:45 +0300 Subject: [PATCH 10/24] Add passing blocked value of rate limit from a storage into the object --- Service/Storage/DoctrineCache.php | 10 ++++++---- Service/Storage/Memcache.php | 2 ++ Service/Storage/PhpRedis.php | 6 +++++- Service/Storage/Redis.php | 7 ++++++- Tests/EventListener/MockStorage.php | 2 ++ Tests/Service/Storage/DoctrineCacheTest.php | 3 ++- Tests/Service/Storage/MemcacheTest.php | 3 ++- Tests/Service/Storage/PhpRedisTest.php | 18 ++++++++++++------ Tests/Service/Storage/RedisTest.php | 18 ++++++++++++------ 9 files changed, 49 insertions(+), 20 deletions(-) diff --git a/Service/Storage/DoctrineCache.php b/Service/Storage/DoctrineCache.php index b01d3a3..5fe7bad 100644 --- a/Service/Storage/DoctrineCache.php +++ b/Service/Storage/DoctrineCache.php @@ -20,6 +20,7 @@ public function getRateInfo($key) { $rateLimitInfo->setLimit($info['limit']); $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); + $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); return $rateLimitInfo; } @@ -40,10 +41,11 @@ public function limitRate($key) { } public function createRate($key, $limit, $period) { - $info = array(); - $info['limit'] = $limit; - $info['calls'] = 1; - $info['reset'] = time() + $period; + $info = array(); + $info['limit'] = $limit; + $info['calls'] = 1; + $info['reset'] = time() + $period; + $info['blocked'] = 0; $this->client->save($key, $info, $period); diff --git a/Service/Storage/Memcache.php b/Service/Storage/Memcache.php index 0f0f5c5..7bd1920 100644 --- a/Service/Storage/Memcache.php +++ b/Service/Storage/Memcache.php @@ -23,6 +23,7 @@ public function getRateInfo($key) $rateLimitInfo->setLimit($info['limit']); $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); + $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); return $rateLimitInfo; } @@ -49,6 +50,7 @@ public function createRate($key, $limit, $period) $info['limit'] = $limit; $info['calls'] = 1; $info['reset'] = time() + $period; + $info['blocked'] = 0; $this->client->set($key, $info, $period); diff --git a/Service/Storage/PhpRedis.php b/Service/Storage/PhpRedis.php index 60919f2..d5a9d14 100644 --- a/Service/Storage/PhpRedis.php +++ b/Service/Storage/PhpRedis.php @@ -29,6 +29,7 @@ public function getRateInfo($key) $rateLimitInfo->setLimit($info['limit']); $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); + $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); return $rateLimitInfo; } @@ -52,6 +53,7 @@ public function createRate($key, $limit, $period) $this->client->hset($key, 'limit', $limit); $this->client->hset($key, 'calls', 1); $this->client->hset($key, 'reset', $reset); + $this->client->hset($key, 'blocked', 0); $this->client->expire($key, $period); $rateLimitInfo = new RateLimitInfo(); @@ -75,8 +77,10 @@ public function resetRate($key) public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) { $resetTimestamp = time() + $periodBlock; - $this->client->hset($rateLimitInfo->getKey(), 'blocked', 1); + $this->client->hset($rateLimitInfo->getKey(), 'limit', $rateLimitInfo->getLimit()); + $this->client->hset($rateLimitInfo->getKey(), 'calls', $rateLimitInfo->getCalls()); $this->client->hset($rateLimitInfo->getKey(), 'reset', $resetTimestamp); + $this->client->hset($rateLimitInfo->getKey(), 'blocked', 1); $this->client->expire($rateLimitInfo->getKey(), $periodBlock); $rateLimitInfo->setBlocked(true); diff --git a/Service/Storage/Redis.php b/Service/Storage/Redis.php index eb8e6b4..5d32737 100644 --- a/Service/Storage/Redis.php +++ b/Service/Storage/Redis.php @@ -28,6 +28,7 @@ public function getRateInfo($key) $rateLimitInfo->setLimit($info['limit']); $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); + $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); return $rateLimitInfo; } @@ -51,12 +52,14 @@ public function createRate($key, $limit, $period) $this->client->hset($key, 'limit', $limit); $this->client->hset($key, 'calls', 1); $this->client->hset($key, 'reset', $reset); + $this->client->hset($key, 'blocked', 0); $this->client->expire($key, $period); $rateLimitInfo = new RateLimitInfo(); $rateLimitInfo->setLimit($limit); $rateLimitInfo->setCalls(1); $rateLimitInfo->setResetTimestamp($reset); + $rateLimitInfo->setBlocked(false); return $rateLimitInfo; } @@ -74,8 +77,10 @@ public function resetRate($key) public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) { $resetTimestamp = time() + $periodBlock; - $this->client->hset($rateLimitInfo->getKey(), 'blocked', 1); + $this->client->hset($rateLimitInfo->getKey(), 'limit', $rateLimitInfo->getLimit()); + $this->client->hset($rateLimitInfo->getKey(), 'calls', $rateLimitInfo->getCalls()); $this->client->hset($rateLimitInfo->getKey(), 'reset', $resetTimestamp); + $this->client->hset($rateLimitInfo->getKey(), 'blocked', 1); $this->client->expire($rateLimitInfo->getKey(), $periodBlock); $rateLimitInfo->setBlocked(true); diff --git a/Tests/EventListener/MockStorage.php b/Tests/EventListener/MockStorage.php index 96b73f9..64d57a2 100644 --- a/Tests/EventListener/MockStorage.php +++ b/Tests/EventListener/MockStorage.php @@ -23,6 +23,8 @@ public function getRateInfo($key) $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); $rateLimitInfo->setLimit($info['limit']); + $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); + return $rateLimitInfo; } diff --git a/Tests/Service/Storage/DoctrineCacheTest.php b/Tests/Service/Storage/DoctrineCacheTest.php index 0551e1c..945c07d 100644 --- a/Tests/Service/Storage/DoctrineCacheTest.php +++ b/Tests/Service/Storage/DoctrineCacheTest.php @@ -23,7 +23,7 @@ public function testgetRateInfo() $client->expects($this->once()) ->method('fetch') ->with('foo') - ->will($this->returnValue(array('limit' => 100, 'calls' => 50, 'reset' => 1234))); + ->will($this->returnValue(array('limit' => 100, 'calls' => 50, 'reset' => 1234, 'blocked' => 1))); $storage = new DoctrineCache($client); $rli = $storage->getRateInfo('foo'); @@ -31,6 +31,7 @@ public function testgetRateInfo() $this->assertEquals(100, $rli->getLimit()); $this->assertEquals(50, $rli->getCalls()); $this->assertEquals(1234, $rli->getResetTimestamp()); + $this->assertTrue($rli->isBlocked()); } public function testcreateRate() diff --git a/Tests/Service/Storage/MemcacheTest.php b/Tests/Service/Storage/MemcacheTest.php index f2685d9..6fcc2a5 100644 --- a/Tests/Service/Storage/MemcacheTest.php +++ b/Tests/Service/Storage/MemcacheTest.php @@ -23,7 +23,7 @@ public function testgetRateInfo() $client->expects($this->once()) ->method('get') ->with('foo') - ->will($this->returnValue(array('limit' => 100, 'calls' => 50, 'reset' => 1234))); + ->will($this->returnValue(array('limit' => 100, 'calls' => 50, 'reset' => 1234, 'blocked' => 1))); $storage = new Memcache($client); $rli = $storage->getRateInfo('foo'); @@ -31,6 +31,7 @@ public function testgetRateInfo() $this->assertEquals(100, $rli->getLimit()); $this->assertEquals(50, $rli->getCalls()); $this->assertEquals(1234, $rli->getResetTimestamp()); + $this->assertTrue($rli->isBlocked()); } public function testcreateRate() diff --git a/Tests/Service/Storage/PhpRedisTest.php b/Tests/Service/Storage/PhpRedisTest.php index b7eec73..5f18cac 100644 --- a/Tests/Service/Storage/PhpRedisTest.php +++ b/Tests/Service/Storage/PhpRedisTest.php @@ -24,7 +24,7 @@ public function testgetRateInfo() $client->expects($this->once()) ->method('hgetall') ->with('foo') - ->will($this->returnValue(array('limit' => 100, 'calls' => 50, 'reset' => 1234))); + ->will($this->returnValue(array('limit' => 100, 'calls' => 50, 'reset' => 1234, 'blocked' => 1))); $storage = new PhpRedis($client); $rli = $storage->getRateInfo('foo'); @@ -32,6 +32,7 @@ public function testgetRateInfo() $this->assertEquals(100, $rli->getLimit()); $this->assertEquals(50, $rli->getCalls()); $this->assertEquals(1234, $rli->getResetTimestamp()); + $this->assertTrue($rli->isBlocked()); } public function testcreateRate() @@ -42,12 +43,13 @@ public function testcreateRate() $client->expects($this->once()) ->method('expire') ->with('foo', 123); - $client->expects($this->exactly(3)) + $client->expects($this->exactly(4)) ->method('hset') ->withConsecutive( array('foo', 'limit', 100), array('foo', 'calls', 1), - array('foo', 'reset') + array('foo', 'reset'), + array('foo', 'blocked', 0) ); $storage = new PhpRedis($client); @@ -111,11 +113,13 @@ public function testSetBlock() $client = $this->getMockBuilder('\Redis') ->setMethods(array('hset', 'expire')) ->getMock(); - $client->expects(self::exactly(2)) + $client->expects(self::exactly(4)) ->method('hset') ->withConsecutive( - array('foo', 'blocked', 1), - array('foo', 'reset', time() + 100) + array('foo', 'limit', 2), + array('foo', 'calls', 1), + array('foo', 'reset', time() + 100), + array('foo', 'blocked', 1) ); $client->expects(self::once()) ->method('expire') @@ -124,6 +128,8 @@ public function testSetBlock() $rateLimitInfo = new RateLimitInfo(); $rateLimitInfo->setKey('foo'); $rateLimitInfo->setResetTimestamp(10); + $rateLimitInfo->setLimit(2); + $rateLimitInfo->setCalls(1); $storage = new PhpRedis($client); self::assertTrue($storage->setBlock($rateLimitInfo, 100)); diff --git a/Tests/Service/Storage/RedisTest.php b/Tests/Service/Storage/RedisTest.php index c7b8b1f..aaa1d01 100644 --- a/Tests/Service/Storage/RedisTest.php +++ b/Tests/Service/Storage/RedisTest.php @@ -23,7 +23,7 @@ public function testgetRateInfo() $client->expects($this->once()) ->method('hgetall') ->with('foo') - ->will($this->returnValue(array('limit' => 100, 'calls' => 50, 'reset' => 1234))); + ->will($this->returnValue(array('limit' => 100, 'calls' => 50, 'reset' => 1234, 'blocked' => 1))); $storage = new Redis($client); $rli = $storage->getRateInfo('foo'); @@ -31,6 +31,7 @@ public function testgetRateInfo() $this->assertEquals(100, $rli->getLimit()); $this->assertEquals(50, $rli->getCalls()); $this->assertEquals(1234, $rli->getResetTimestamp()); + $this->assertTrue($rli->isBlocked()); } public function testcreateRate() @@ -41,12 +42,13 @@ public function testcreateRate() $client->expects($this->once()) ->method('expire') ->with('foo', 123); - $client->expects($this->exactly(3)) + $client->expects($this->exactly(4)) ->method('hset') ->withConsecutive( array('foo', 'limit', 100), array('foo', 'calls', 1), - array('foo', 'reset') + array('foo', 'reset'), + array('foo', 'blocked', 0) ); $storage = new Redis($client); @@ -110,11 +112,13 @@ public function testSetBlock() $client = $this->getMockBuilder('Predis\\Client') ->setMethods(array('hset', 'expire')) ->getMock(); - $client->expects(self::exactly(2)) + $client->expects(self::exactly(4)) ->method('hset') ->withConsecutive( - array('foo', 'blocked', 1), - array('foo', 'reset', time() + 100) + array('foo', 'limit', 2), + array('foo', 'calls', 1), + array('foo', 'reset', time() + 100), + array('foo', 'blocked', 1) ); $client->expects(self::once()) ->method('expire') @@ -123,6 +127,8 @@ public function testSetBlock() $rateLimitInfo = new RateLimitInfo(); $rateLimitInfo->setKey('foo'); $rateLimitInfo->setResetTimestamp(10); + $rateLimitInfo->setLimit(2); + $rateLimitInfo->setCalls(1); $storage = new Redis($client); self::assertTrue($storage->setBlock($rateLimitInfo, 100)); From 6d8a4d96d2f73c90dcb8bfc037582e3c1ca57f8b Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 28 Jun 2018 08:24:38 +0300 Subject: [PATCH 11/24] Add an ability of setting custom period of time of blocking a call We added that ability, because sometimes we want to use another period of time for blocking a specific call, but we want to save a period of time when somebody can do requests of URLs without restrictions. --- EventListener/RateLimitAnnotationListener.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/EventListener/RateLimitAnnotationListener.php b/EventListener/RateLimitAnnotationListener.php index 80a9b6e..0875ae7 100644 --- a/EventListener/RateLimitAnnotationListener.php +++ b/EventListener/RateLimitAnnotationListener.php @@ -93,7 +93,7 @@ public function onKernelController(FilterControllerEvent $event) $request->attributes->set('rate_limit_info', $rateLimitInfo); // Reset the rate limits - if(time() >= $rateLimitInfo->getResetTimestamp()) { + if(!$rateLimitInfo->isBlocked() && time() >= $rateLimitInfo->getResetTimestamp()) { $this->rateLimitService->resetRate($key); $rateLimitInfo = $this->rateLimitService->createRate($key, $rateLimit->getLimit(), $rateLimit->getPeriod()); if (! $rateLimitInfo) { @@ -104,8 +104,14 @@ public function onKernelController(FilterControllerEvent $event) } // When we exceeded our limit, return a custom error response - if ($rateLimitInfo->getCalls() > $rateLimitInfo->getLimit()) { + if (!$rateLimitInfo->isBlocked() && $rateLimitInfo->getCalls() > $rateLimitInfo->getLimit()) { + $this->rateLimitService->setBlock( + $rateLimitInfo, + $rateLimit->getBlockPeriod() > 0 ? $rateLimit->getBlockPeriod() : $rateLimit->getPeriod() + ); + } + if ($rateLimitInfo->isBlocked()) { // Throw an exception if configured. if ($this->getParameter('rate_response_exception')) { $class = $this->getParameter('rate_response_exception'); From a00ca57035f40a38c818c57ca65d99f3bb2ca588 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 28 Jun 2018 08:35:09 +0300 Subject: [PATCH 12/24] Add a new event is dispatched after a block happened We need this event for making different things when a block happens. --- Events/RateLimitEvents.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Events/RateLimitEvents.php b/Events/RateLimitEvents.php index f3ea169..653f988 100644 --- a/Events/RateLimitEvents.php +++ b/Events/RateLimitEvents.php @@ -5,4 +5,9 @@ final class RateLimitEvents { const GENERATE_KEY = 'ratelimit.generate.key'; + + /** + * This event is dispatched after a block happened + */ + const AFTER_BLOCK = 'ratelimit.block.after'; } From 4ba74d29dc0cb8a2a1cb50eca00932a4b42483e2 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 28 Jun 2018 08:36:27 +0300 Subject: [PATCH 13/24] Add comment for generate key event --- Events/RateLimitEvents.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Events/RateLimitEvents.php b/Events/RateLimitEvents.php index 653f988..a9753ff 100644 --- a/Events/RateLimitEvents.php +++ b/Events/RateLimitEvents.php @@ -4,7 +4,10 @@ final class RateLimitEvents { - const GENERATE_KEY = 'ratelimit.generate.key'; + /** + * This event is dispatched when generating a key is doing + */ + const GENERATE_KEY = 'ratelimit.generate.key'; /** * This event is dispatched after a block happened From 52b054959cf4b7ca9b6ca887f1166d31d5ea885d Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 28 Jun 2018 08:51:42 +0300 Subject: [PATCH 14/24] Add firing block event when block happens We should dispatch that event, because some developers want to do additional things when block happens. For example, to register block event into system log. --- EventListener/RateLimitAnnotationListener.php | 3 ++ Events/BlockEvent.php | 49 +++++++++++++++++++ Tests/Events/BlockEventTest.php | 21 ++++++++ 3 files changed, 73 insertions(+) create mode 100644 Events/BlockEvent.php create mode 100644 Tests/Events/BlockEventTest.php diff --git a/EventListener/RateLimitAnnotationListener.php b/EventListener/RateLimitAnnotationListener.php index 0875ae7..5167c42 100644 --- a/EventListener/RateLimitAnnotationListener.php +++ b/EventListener/RateLimitAnnotationListener.php @@ -3,6 +3,7 @@ namespace Noxlogic\RateLimitBundle\EventListener; use Noxlogic\RateLimitBundle\Annotation\RateLimit; +use Noxlogic\RateLimitBundle\Events\BlockEvent; use Noxlogic\RateLimitBundle\Events\GenerateKeyEvent; use Noxlogic\RateLimitBundle\Events\RateLimitEvents; use Noxlogic\RateLimitBundle\Service\RateLimitService; @@ -39,6 +40,7 @@ public function __construct( RateLimitService $rateLimitService, PathLimitProcessor $pathLimitProcessor ) { + //todo:use an event dispatcher passed into onKernelController $this->eventDispatcher = $eventDispatcher; $this->rateLimitService = $rateLimitService; $this->pathLimitProcessor = $pathLimitProcessor; @@ -109,6 +111,7 @@ public function onKernelController(FilterControllerEvent $event) $rateLimitInfo, $rateLimit->getBlockPeriod() > 0 ? $rateLimit->getBlockPeriod() : $rateLimit->getPeriod() ); + $this->eventDispatcher->dispatch(RateLimitEvents::AFTER_BLOCK, new BlockEvent($rateLimitInfo, $request)); } if ($rateLimitInfo->isBlocked()) { diff --git a/Events/BlockEvent.php b/Events/BlockEvent.php new file mode 100644 index 0000000..56e13ee --- /dev/null +++ b/Events/BlockEvent.php @@ -0,0 +1,49 @@ +rateLimitInfo = $rateLimitInfo; + $this->request = $request; + } + + + /** + * @return RateLimitInfo + */ + public function getRateLimitInfo() + { + return $this->rateLimitInfo; + } + + /** + * @return Request + */ + public function getRequest() + { + return $this->request; + } +} diff --git a/Tests/Events/BlockEventTest.php b/Tests/Events/BlockEventTest.php new file mode 100644 index 0000000..19e0c3b --- /dev/null +++ b/Tests/Events/BlockEventTest.php @@ -0,0 +1,21 @@ +createMock(RateLimitInfo::class); + $request = $this->createMock(Request::class); + $event = new BlockEvent($rateLimitInfo, $request); + + self::assertSame($rateLimitInfo, $event->getRateLimitInfo()); + self::assertSame($request, $event->getRequest()); + } +} From f5892689abe741d988f1854f778c411a8330df4b Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 28 Jun 2018 09:45:21 +0300 Subject: [PATCH 15/24] Add dispatching an event before sending a response We added dispatching the event before sending a response. It needs when we want to send another more reach message in the response. Also it allows us to make other types of responses. --- EventListener/RateLimitAnnotationListener.php | 18 ++++- Events/GetResponseEvent.php | 79 +++++++++++++++++++ Events/RateLimitEvents.php | 5 ++ .../RateLimitAnnotationListenerTest.php | 9 ++- Tests/Events/GetResponseEventTest.php | 63 +++++++++++++++ 5 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 Events/GetResponseEvent.php create mode 100644 Tests/Events/GetResponseEventTest.php diff --git a/EventListener/RateLimitAnnotationListener.php b/EventListener/RateLimitAnnotationListener.php index 5167c42..90fb32d 100644 --- a/EventListener/RateLimitAnnotationListener.php +++ b/EventListener/RateLimitAnnotationListener.php @@ -5,6 +5,7 @@ use Noxlogic\RateLimitBundle\Annotation\RateLimit; use Noxlogic\RateLimitBundle\Events\BlockEvent; use Noxlogic\RateLimitBundle\Events\GenerateKeyEvent; +use Noxlogic\RateLimitBundle\Events\GetResponseEvent; use Noxlogic\RateLimitBundle\Events\RateLimitEvents; use Noxlogic\RateLimitBundle\Service\RateLimitService; use Noxlogic\RateLimitBundle\Util\PathLimitProcessor; @@ -121,11 +122,20 @@ public function onKernelController(FilterControllerEvent $event) throw new $class($this->getParameter('rate_response_message'), $this->getParameter('rate_response_code')); } - $message = $this->getParameter('rate_response_message'); - $code = $this->getParameter('rate_response_code'); - $event->setController(function () use ($message, $code) { + $response = new Response( + $this->getParameter('rate_response_message'), + $this->getParameter('rate_response_code') + ); + + $eventResponse = new GetResponseEvent($request, $rateLimitInfo); + $this->eventDispatcher->dispatch(RateLimitEvents::RESPONSE_SENDING_BEFORE, $eventResponse); + if ($eventResponse->hasResponse()) { + $response = $eventResponse->getResponse(); + } + + $event->setController(function () use ($response) { // @codeCoverageIgnoreStart - return new Response($message, $code); + return $response; // @codeCoverageIgnoreEnd }); $event->stopPropagation(); diff --git a/Events/GetResponseEvent.php b/Events/GetResponseEvent.php new file mode 100644 index 0000000..848a9c4 --- /dev/null +++ b/Events/GetResponseEvent.php @@ -0,0 +1,79 @@ +request = $request; + $this->rateLimitInfo = $rateLimitInfo; + } + + + /** + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * @param Response $response + */ + public function setResponse(Response $response) + { + $this->response = $response; + } + + /** + * @return bool + */ + public function hasResponse() + { + return null !== $this->response; + } + + /** + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * @return RateLimitInfo + */ + public function getRateLimitInfo() + { + return $this->rateLimitInfo; + } +} diff --git a/Events/RateLimitEvents.php b/Events/RateLimitEvents.php index a9753ff..40e72f8 100644 --- a/Events/RateLimitEvents.php +++ b/Events/RateLimitEvents.php @@ -13,4 +13,9 @@ final class RateLimitEvents * This event is dispatched after a block happened */ const AFTER_BLOCK = 'ratelimit.block.after'; + + /** + * This event is dispatched before response is sent + */ + const RESPONSE_SENDING_BEFORE = 'ratelimit.response.sending.before'; } diff --git a/Tests/EventListener/RateLimitAnnotationListenerTest.php b/Tests/EventListener/RateLimitAnnotationListenerTest.php index e0a4d5b..4c55384 100644 --- a/Tests/EventListener/RateLimitAnnotationListenerTest.php +++ b/Tests/EventListener/RateLimitAnnotationListenerTest.php @@ -154,6 +154,9 @@ public function testRateLimit() new RateLimit(array('limit' => 5, 'period' => 5)), )); + $listener->setParameter('rate_response_code', 200); + $listener->setParameter('rate_response_message', 'Test message'); + $listener->onKernelController($event); $this->assertInternalType('array', $event->getController()); $listener->onKernelController($event); @@ -181,6 +184,9 @@ public function testRateLimitThrottling() new RateLimit(array('limit' => 5, 'period' => 3)), )); + $listener->setParameter('rate_response_code', 200); + $listener->setParameter('rate_response_message', 'Test message'); + // Throttled $storage = $this->getMockStorage(); $storage->createMockRate(':Noxlogic\RateLimitBundle\EventListener\Tests\MockController:mockAction', 5, 10, 6); @@ -323,7 +329,8 @@ protected function createListener($expects) $mockDispatcher = $this->getMockBuilder('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface')->getMock(); $mockDispatcher ->expects($expects) - ->method('dispatch'); + ->method('dispatch') + ; $rateLimitService = new RateLimitService(); $rateLimitService->setStorage($this->getMockStorage()); diff --git a/Tests/Events/GetResponseEventTest.php b/Tests/Events/GetResponseEventTest.php new file mode 100644 index 0000000..f5a9666 --- /dev/null +++ b/Tests/Events/GetResponseEventTest.php @@ -0,0 +1,63 @@ +createMock(Request::class); + $rateLimitInfo = $this->createMock(RateLimitInfo::class); + + $event = new GetResponseEvent($request, $rateLimitInfo); + + self::assertSame($request, $event->getRequest()); + self::assertSame($rateLimitInfo, $event->getRateLimitInfo()); + + return $event; + } + + /** + * @depends testConstruct + * + * @param GetResponseEvent $event + */ + public function testHasResponseReturnFalse(GetResponseEvent $event) + { + self::assertFalse($event->hasResponse()); + } + + /** + * @depends testConstruct + * + * @param GetResponseEvent $event + * @return GetResponseEvent + */ + public function testSetResponse(GetResponseEvent $event) + { + $response = $this->createMock(Response::class); + $event->setResponse($response); + self::assertSame($response, $event->getResponse()); + + return $event; + } + + /** + * @depends testSetResponse + * + * @param GetResponseEvent $event + */ + public function testHasResponseReturnTrue(GetResponseEvent $event) + { + self::assertTrue($event->hasResponse()); + } +} From 8896661985d73950a6e80f6203014103b7ebe4ee Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 28 Jun 2018 12:18:57 +0300 Subject: [PATCH 16/24] Add information about new feature block_period This feature allows to block the URL or call for custom period of time which can be not equal period or might be omit at all. --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b7ccb66..5eecae2 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Installation takes just few easy steps: If you're not yet familiar with Composer see http://getcomposer.org. Add the NoxlogicRateLimitBundle in your composer.json: -```js +```json { "require": { "noxlogic/ratelimit-bundle": "1.x" @@ -157,8 +157,10 @@ noxlogic_rate_limit: - * limit: ~ # Required period: ~ # Required + block_period: ~ # Optional # - { path: /api, limit: 1000, period: 3600 } + # - { path: /auth, limit: 1000, period: 3600, block_period: 7200 } # - { path: /dashboard, limit: 100, period: 3600, methods: ['GET', 'POST']} # Should the FOS OAuthServerBundle listener be enabled @@ -188,6 +190,27 @@ public function someApiAction() } ``` +### Simple rate limit with custom block period + +To enable this feature, you should write `blockPeriod` into the annotation `RateLimit`. If `blockPeriod` is not set up +its value will be equaled `period` parameter. + +```php + Date: Thu, 28 Jun 2018 12:23:06 +0300 Subject: [PATCH 17/24] Rename constant of an event ratelimit.block.after --- EventListener/RateLimitAnnotationListener.php | 2 +- Events/RateLimitEvents.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EventListener/RateLimitAnnotationListener.php b/EventListener/RateLimitAnnotationListener.php index 90fb32d..aeba8d6 100644 --- a/EventListener/RateLimitAnnotationListener.php +++ b/EventListener/RateLimitAnnotationListener.php @@ -112,7 +112,7 @@ public function onKernelController(FilterControllerEvent $event) $rateLimitInfo, $rateLimit->getBlockPeriod() > 0 ? $rateLimit->getBlockPeriod() : $rateLimit->getPeriod() ); - $this->eventDispatcher->dispatch(RateLimitEvents::AFTER_BLOCK, new BlockEvent($rateLimitInfo, $request)); + $this->eventDispatcher->dispatch(RateLimitEvents::BLOCK_AFTER, new BlockEvent($rateLimitInfo, $request)); } if ($rateLimitInfo->isBlocked()) { diff --git a/Events/RateLimitEvents.php b/Events/RateLimitEvents.php index 40e72f8..8a55c53 100644 --- a/Events/RateLimitEvents.php +++ b/Events/RateLimitEvents.php @@ -12,7 +12,7 @@ final class RateLimitEvents /** * This event is dispatched after a block happened */ - const AFTER_BLOCK = 'ratelimit.block.after'; + const BLOCK_AFTER = 'ratelimit.block.after'; /** * This event is dispatched before response is sent From 65dbbf59711eca86aeab103d4d67bb7726efa653 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 28 Jun 2018 12:59:14 +0300 Subject: [PATCH 18/24] Add documentation and examples about events --- README.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5eecae2..f586927 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This bundle is partially inspired by a GitHub gist from Ruud Kamphuis: https://g * Simple usage through annotations * Customize rates per controller, action and even per HTTP method + * Customize the period of a lock * Multiple storage backends: Redis, Memcached and Doctrine cache ## Installation @@ -259,6 +260,17 @@ class DefaultController extends Controller } ``` +## Events + +This bundle has several events. They are placed in +[Noxlogic\RateLimitBundle\Events\RateLimitEvents](Events/RateLimitEvents.php) + + * `ratelimit.generate.key` + * `ratelimit.block.after` you can do additional manipulations when a block happens, for example to register a block + event in journal + * `ratelimit.response.sending.before` you can use this event for making your own response when somebody call a blocked + URL + ## Create a custom key generator If you need to create a custom key generator, you need to register a listener to listen to the `ratelimit.generate.key` event: @@ -292,13 +304,93 @@ class RateLimitGenerateKeyListener Make sure to generate a key based on what is rate limited in your controllers. +## Creating custom response with `ratelimit.response.sending.before` + +```yaml +services: + mybundle.listener.rate_limit_custom_response: + class: MyBundle\Listener\RateLimitResponseListener + tags: + - { name: kernel.event_listener, event: 'ratelimit.response.sending.before', method: 'onResponse' } +``` + +```php +getRequest(); + $rateLimitInfo = $event->getRateLimitInfo(); + + $event->setResponse(new JsonResponse( + array( + "error" => sprintf("Access for URL %s deny", $request->getRequestUri()), + "block_period" => $rateLimitInfo->getResetTimestamp() - time() + ), + Response::HTTP_TOO_MANY_REQUESTS + )); + } +} +``` + +## Logging a block into a journal + +You could do a lot of things with an event `ratelimit.block.after`. This event is dispatched just before a lock is set. +For example below you could register every blocked URL in your security journal. + +```yaml +services: + mybundle.listener.rate_limit_log_block: + class: MyBundle\Listener\RateLimitLogBlockListener + arguments: ["@my.service.journal"] + tags: + - { name: kernel.event_listener, event: 'ratelimit.block.after', method: 'onLog' } +``` + +```php +journal = $journal; + } + + public function onLog(BlockEvent $event) + { + $message = sprintf( + "Somebody tries to make brute force on URL %s with login %s. They will be lock until %s", + $event->getRequest()->getUri(), + $event->getRequest()->request->get('login'), + date(\DateTime::ISO8601, $event->getRateLimitInfo()->getResetTimestamp()) + ); + $this->journal->log($message); + } +} +``` ## Throwing exceptions Instead of returning a Response object when a rate limit has exceeded, it's also possible to throw an exception. This allows you to easily handle the rate limit on another level, for instance by capturing the ``kernel.exception`` event. - ## Running tests If you want to run the tests use: From 067dac305ecc71188aca2cd1efc49584d7e13dd5 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 28 Jun 2018 20:44:05 +0300 Subject: [PATCH 19/24] Fix missing of setting key value into RateLimitInfo --- Service/Storage/DoctrineCache.php | 1 + Service/Storage/Memcache.php | 1 + Service/Storage/PhpRedis.php | 1 + Service/Storage/Redis.php | 1 + 4 files changed, 4 insertions(+) diff --git a/Service/Storage/DoctrineCache.php b/Service/Storage/DoctrineCache.php index 5fe7bad..5dacd36 100644 --- a/Service/Storage/DoctrineCache.php +++ b/Service/Storage/DoctrineCache.php @@ -21,6 +21,7 @@ public function getRateInfo($key) { $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); + $rateLimitInfo->setKey($key); return $rateLimitInfo; } diff --git a/Service/Storage/Memcache.php b/Service/Storage/Memcache.php index 7bd1920..160d425 100644 --- a/Service/Storage/Memcache.php +++ b/Service/Storage/Memcache.php @@ -24,6 +24,7 @@ public function getRateInfo($key) $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); + $rateLimitInfo->setKey($key); return $rateLimitInfo; } diff --git a/Service/Storage/PhpRedis.php b/Service/Storage/PhpRedis.php index d5a9d14..0af8495 100644 --- a/Service/Storage/PhpRedis.php +++ b/Service/Storage/PhpRedis.php @@ -30,6 +30,7 @@ public function getRateInfo($key) $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); + $rateLimitInfo->setKey($key); return $rateLimitInfo; } diff --git a/Service/Storage/Redis.php b/Service/Storage/Redis.php index 5d32737..7549e14 100644 --- a/Service/Storage/Redis.php +++ b/Service/Storage/Redis.php @@ -29,6 +29,7 @@ public function getRateInfo($key) $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); + $rateLimitInfo->setKey($key); return $rateLimitInfo; } From bc6e1bbf0eb3796cd2f43a6e3250648d5f1f0744 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 3 Jul 2018 13:15:44 +0300 Subject: [PATCH 20/24] Add block method into PsrCache storage --- Service/Storage/PsrCache.php | 45 ++++++++++++++++++++++---- Tests/Service/Storage/PsrCacheTest.php | 41 +++++++++++++++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/Service/Storage/PsrCache.php b/Service/Storage/PsrCache.php index 0d2b8a7..9c83d04 100644 --- a/Service/Storage/PsrCache.php +++ b/Service/Storage/PsrCache.php @@ -25,7 +25,7 @@ public function getRateInfo($key) return false; } - return $this->createRateInfo($item->get()); + return $this->createRateInfo($key, $item->get()); } public function limitRate($key) @@ -43,15 +43,16 @@ public function limitRate($key) $this->client->save($item); - return $this->createRateInfo($info); + return $this->createRateInfo($key, $info); } public function createRate($key, $limit, $period) { $info = [ - 'limit' => $limit, - 'calls' => 1, - 'reset' => time() + $period, + 'limit' => $limit, + 'calls' => 1, + 'reset' => time() + $period, + 'blocked' => 0 ]; $item = $this->client->getItem($key); $item->set($info); @@ -59,7 +60,7 @@ public function createRate($key, $limit, $period) $this->client->save($item); - return $this->createRateInfo($info); + return $this->createRateInfo($key, $info); } public function resetRate($key) @@ -69,13 +70,43 @@ public function resetRate($key) return true; } - private function createRateInfo(array $info) + private function createRateInfo($key, array $info) { $rateLimitInfo = new RateLimitInfo(); $rateLimitInfo->setLimit($info['limit']); $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); + $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); + $rateLimitInfo->setKey($key); return $rateLimitInfo; } + + /** + * @inheritDoc + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) + { + $resetTimestamp = time() + $periodBlock; + $info = [ + 'limit' => $rateLimitInfo->getLimit(), + 'calls' => 1, + 'reset' => $resetTimestamp, + 'blocked' => 1 + ]; + + $item = $this->client->getItem($rateLimitInfo->getKey()); + $item->set($info); + $item->expiresAfter($periodBlock); + + if (!$this->client->save($item)) { + return false; + } + + $rateLimitInfo->setBlocked(1); + $rateLimitInfo->setResetTimestamp($resetTimestamp); + + return true; + } + } diff --git a/Tests/Service/Storage/PsrCacheTest.php b/Tests/Service/Storage/PsrCacheTest.php index 6948ffb..eaa49f4 100644 --- a/Tests/Service/Storage/PsrCacheTest.php +++ b/Tests/Service/Storage/PsrCacheTest.php @@ -2,6 +2,7 @@ namespace Noxlogic\RateLimitBundle\Tests\Service\Storage; +use Noxlogic\RateLimitBundle\Service\RateLimitInfo; use Noxlogic\RateLimitBundle\Service\Storage\PsrCache; use Noxlogic\RateLimitBundle\Tests\TestCase; @@ -121,4 +122,44 @@ public function testResetRate() $storage = new PsrCache($client); $this->assertTrue($storage->resetRate('foo')); } + + public function testSetBlock() + { + $client = $this->getMockBuilder('Psr\Cache\CacheItemPoolInterface')->getMock(); + $item = $this->createMock('Psr\Cache\CacheItemInterface'); + + $client->expects($this->once()) + ->method('getItem') + ->with('foo') + ->willReturn($item); + + $rateLimitInfo = new RateLimitInfo(); + $rateLimitInfo->setKey('foo'); + $rateLimitInfo->setCalls(1); + $rateLimitInfo->setLimit(2); + $rateLimitInfo->setResetTimestamp(time()); + + $periodBlock = 100; + $resetTimestamp = time() + $periodBlock; + $item->expects(self::once()) + ->method('set') + ->with([ + 'limit' => $rateLimitInfo->getLimit(), + 'calls' => $rateLimitInfo->getCalls(), + 'reset' => $resetTimestamp, + 'blocked' => 1 + ]); + $item->expects(self::once()) + ->method('expiresAfter') + ->with($periodBlock); + $client->expects(self::once()) + ->method('save') + ->with($item) + ->willReturn(true); + + $storage = new PsrCache($client); + self::assertTrue($storage->setBlock($rateLimitInfo, $periodBlock), 'Result of setting the block must equal true'); + self::assertTrue($rateLimitInfo->isBlocked(), 'After setting the block RateLimitInfo must contain blocked=true'); + self::assertEquals($resetTimestamp, $rateLimitInfo->getResetTimestamp()); + } } From 02b460d5afe7ff4a716461407ba4aeb5661075df Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 3 Jul 2018 13:27:47 +0300 Subject: [PATCH 21/24] Add block method into SimpleCache storage --- Service/Storage/SimpleCache.php | 39 +++++++++++++++++++---- Tests/Service/Storage/SimpleCacheTest.php | 33 +++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/Service/Storage/SimpleCache.php b/Service/Storage/SimpleCache.php index 7821633..37d4d16 100644 --- a/Service/Storage/SimpleCache.php +++ b/Service/Storage/SimpleCache.php @@ -24,7 +24,7 @@ public function getRateInfo($key) return false; } - return $this->createRateInfo($info); + return $this->createRateInfo($key, $info); } public function limitRate($key) @@ -39,19 +39,20 @@ public function limitRate($key) $this->client->set($key, $info, $ttl); - return $this->createRateInfo($info); + return $this->createRateInfo($key, $info); } public function createRate($key, $limit, $period) { $info = [ - 'limit' => $limit, - 'calls' => 1, - 'reset' => time() + $period, + 'limit' => $limit, + 'calls' => 1, + 'reset' => time() + $period, + 'blocked' => 0 ]; $this->client->set($key, $info, $period); - return $this->createRateInfo($info); + return $this->createRateInfo($key, $info); } public function resetRate($key) @@ -61,13 +62,37 @@ public function resetRate($key) return true; } - private function createRateInfo(array $info) + private function createRateInfo($key, array $info) { $rateLimitInfo = new RateLimitInfo(); $rateLimitInfo->setLimit($info['limit']); $rateLimitInfo->setCalls($info['calls']); $rateLimitInfo->setResetTimestamp($info['reset']); + $rateLimitInfo->setBlocked(isset($info['blocked']) && $info['blocked']); + $rateLimitInfo->setKey($key); return $rateLimitInfo; } + + /** + * @inheritDoc + */ + public function setBlock(RateLimitInfo $rateLimitInfo, $periodBlock) + { + $resetTimestamp = time() + $periodBlock; + $info = [ + 'limit' => $rateLimitInfo->getLimit(), + 'calls' => $rateLimitInfo->getCalls(), + 'reset' => $resetTimestamp, + 'blocked' => 1 + ]; + if (!$this->client->set($rateLimitInfo->getKey(), $info, $periodBlock)) { + return false; + } + + $rateLimitInfo->setBlocked(true); + $rateLimitInfo->setResetTimestamp($resetTimestamp); + + return true; + } } diff --git a/Tests/Service/Storage/SimpleCacheTest.php b/Tests/Service/Storage/SimpleCacheTest.php index 1e2ae9d..9c62809 100644 --- a/Tests/Service/Storage/SimpleCacheTest.php +++ b/Tests/Service/Storage/SimpleCacheTest.php @@ -2,6 +2,7 @@ namespace Noxlogic\RateLimitBundle\Tests\Service\Storage; +use Noxlogic\RateLimitBundle\Service\RateLimitInfo; use Noxlogic\RateLimitBundle\Service\Storage\SimpleCache; use Noxlogic\RateLimitBundle\Tests\TestCase; @@ -80,4 +81,36 @@ public function testResetRate() $storage = new SimpleCache($client); $this->assertTrue($storage->resetRate('foo')); } + + public function testSetBlock() + { + $client = $this->getMockBuilder('Psr\SimpleCache\CacheInterface')->getMock(); + + $rateLimitInfo = new RateLimitInfo(); + $rateLimitInfo->setKey('foo'); + $rateLimitInfo->setCalls(1); + $rateLimitInfo->setLimit(2); + $rateLimitInfo->setResetTimestamp(time()); + + $periodBlock = 100; + $resetTimestamp = time() + $periodBlock; + $client->expects(self::once()) + ->method('set') + ->with( + 'foo', + [ + 'limit' => $rateLimitInfo->getLimit(), + 'calls' => $rateLimitInfo->getCalls(), + 'reset' => $resetTimestamp, + 'blocked' => 1 + ], + $periodBlock + ) + ->willReturn(true); + + $storage = new SimpleCache($client); + self::assertTrue($storage->setBlock($rateLimitInfo, $periodBlock), 'Result of setting the block must equal true'); + self::assertTrue($rateLimitInfo->isBlocked(), 'After setting the block RateLimitInfo must contain blocked=true'); + self::assertEquals($resetTimestamp, $rateLimitInfo->getResetTimestamp()); + } } From 59a7c8511a54b7a973b40ddc8e4d95a793872141 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 3 Jul 2018 15:02:18 +0300 Subject: [PATCH 22/24] Fix new tests --- Tests/Events/BlockEventTest.php | 8 +++----- Tests/Events/GetResponseEventTest.php | 11 ++++------- Tests/Events/RateLimitEventsTest.php | 4 ++-- Tests/Service/RateLimitServiceTest.php | 3 +-- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Tests/Events/BlockEventTest.php b/Tests/Events/BlockEventTest.php index 19e0c3b..3792e55 100644 --- a/Tests/Events/BlockEventTest.php +++ b/Tests/Events/BlockEventTest.php @@ -3,16 +3,14 @@ namespace Tests\Events; use Noxlogic\RateLimitBundle\Events\BlockEvent; -use Noxlogic\RateLimitBundle\Service\RateLimitInfo; -use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpFoundation\Request; +use Noxlogic\RateLimitBundle\Tests\TestCase; class BlockEventTest extends TestCase { public function testConstruct() { - $rateLimitInfo = $this->createMock(RateLimitInfo::class); - $request = $this->createMock(Request::class); + $rateLimitInfo = $this->createMock('Noxlogic\RateLimitBundle\Service\RateLimitInfo'); + $request = $this->createMock('Symfony\Component\HttpFoundation\Request'); $event = new BlockEvent($rateLimitInfo, $request); self::assertSame($rateLimitInfo, $event->getRateLimitInfo()); diff --git a/Tests/Events/GetResponseEventTest.php b/Tests/Events/GetResponseEventTest.php index f5a9666..e4c6242 100644 --- a/Tests/Events/GetResponseEventTest.php +++ b/Tests/Events/GetResponseEventTest.php @@ -3,10 +3,7 @@ namespace Noxlogic\RateLimitBundle\Tests\Events; use Noxlogic\RateLimitBundle\Events\GetResponseEvent; -use Noxlogic\RateLimitBundle\Service\RateLimitInfo; -use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; +use Noxlogic\RateLimitBundle\Tests\TestCase; class GetResponseEventTest extends TestCase { @@ -15,8 +12,8 @@ class GetResponseEventTest extends TestCase */ public function testConstruct() { - $request = $this->createMock(Request::class); - $rateLimitInfo = $this->createMock(RateLimitInfo::class); + $request = $this->createMock('Symfony\Component\HttpFoundation\Request'); + $rateLimitInfo = $this->createMock('Noxlogic\RateLimitBundle\Service\RateLimitInfo'); $event = new GetResponseEvent($request, $rateLimitInfo); @@ -44,7 +41,7 @@ public function testHasResponseReturnFalse(GetResponseEvent $event) */ public function testSetResponse(GetResponseEvent $event) { - $response = $this->createMock(Response::class); + $response = $this->createMock('Symfony\Component\HttpFoundation\Response'); $event->setResponse($response); self::assertSame($response, $event->getResponse()); diff --git a/Tests/Events/RateLimitEventsTest.php b/Tests/Events/RateLimitEventsTest.php index 37688fb..19e25b3 100644 --- a/Tests/Events/RateLimitEventsTest.php +++ b/Tests/Events/RateLimitEventsTest.php @@ -2,15 +2,15 @@ namespace Noxlogic\RateLimitBundle\Tests\Annotation; -use Noxlogic\RateLimitBundle\Events\GenerateKeyEvent; use Noxlogic\RateLimitBundle\Events\RateLimitEvents; use Noxlogic\RateLimitBundle\Tests\TestCase; -use Symfony\Component\HttpFoundation\Request; class RateLimitEventsTest extends TestCase { public function testConstants() { $this->assertEquals('ratelimit.generate.key', RateLimitEvents::GENERATE_KEY); + $this->assertEquals('ratelimit.block.after', RateLimitEvents::BLOCK_AFTER); + $this->assertEquals('ratelimit.response.sending.before', RateLimitEvents::RESPONSE_SENDING_BEFORE); } } diff --git a/Tests/Service/RateLimitServiceTest.php b/Tests/Service/RateLimitServiceTest.php index ca57a71..4e14639 100644 --- a/Tests/Service/RateLimitServiceTest.php +++ b/Tests/Service/RateLimitServiceTest.php @@ -74,8 +74,7 @@ public function testResetRate() public function testSetBlock() { $mockStorage = $this->getMockBuilder('Noxlogic\\RateLimitBundle\\Service\\Storage\\StorageInterface')->getMock(); - - $rateLimitInfo = $this->createMock(RateLimitInfo::class); + $rateLimitInfo = $this->createMock('Noxlogic\RateLimitBundle\Service\RateLimitInfo'); $mockStorage ->expects(self::once()) From 630b1d15ea2a8f851349de7df8c90964b52d2fca Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 3 Jul 2018 15:26:28 +0300 Subject: [PATCH 23/24] Fix tests --- Tests/Events/BlockEventTest.php | 4 ++-- Tests/Events/GetResponseEventTest.php | 6 +++--- Tests/Service/Storage/PsrCacheTest.php | 2 +- Tests/Service/Storage/RedisTest.php | 8 +++++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Tests/Events/BlockEventTest.php b/Tests/Events/BlockEventTest.php index 3792e55..725600a 100644 --- a/Tests/Events/BlockEventTest.php +++ b/Tests/Events/BlockEventTest.php @@ -9,8 +9,8 @@ class BlockEventTest extends TestCase { public function testConstruct() { - $rateLimitInfo = $this->createMock('Noxlogic\RateLimitBundle\Service\RateLimitInfo'); - $request = $this->createMock('Symfony\Component\HttpFoundation\Request'); + $rateLimitInfo = $this->getMockBuilder('Noxlogic\RateLimitBundle\Service\RateLimitInfo')->getMock(); + $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock(); $event = new BlockEvent($rateLimitInfo, $request); self::assertSame($rateLimitInfo, $event->getRateLimitInfo()); diff --git a/Tests/Events/GetResponseEventTest.php b/Tests/Events/GetResponseEventTest.php index e4c6242..1108da7 100644 --- a/Tests/Events/GetResponseEventTest.php +++ b/Tests/Events/GetResponseEventTest.php @@ -12,8 +12,8 @@ class GetResponseEventTest extends TestCase */ public function testConstruct() { - $request = $this->createMock('Symfony\Component\HttpFoundation\Request'); - $rateLimitInfo = $this->createMock('Noxlogic\RateLimitBundle\Service\RateLimitInfo'); + $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock(); + $rateLimitInfo = $this->getMockBuilder('Noxlogic\RateLimitBundle\Service\RateLimitInfo')->getMock(); $event = new GetResponseEvent($request, $rateLimitInfo); @@ -41,7 +41,7 @@ public function testHasResponseReturnFalse(GetResponseEvent $event) */ public function testSetResponse(GetResponseEvent $event) { - $response = $this->createMock('Symfony\Component\HttpFoundation\Response'); + $response = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response')->getMock(); $event->setResponse($response); self::assertSame($response, $event->getResponse()); diff --git a/Tests/Service/Storage/PsrCacheTest.php b/Tests/Service/Storage/PsrCacheTest.php index eaa49f4..bf54737 100644 --- a/Tests/Service/Storage/PsrCacheTest.php +++ b/Tests/Service/Storage/PsrCacheTest.php @@ -126,7 +126,7 @@ public function testResetRate() public function testSetBlock() { $client = $this->getMockBuilder('Psr\Cache\CacheItemPoolInterface')->getMock(); - $item = $this->createMock('Psr\Cache\CacheItemInterface'); + $item = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock(); $client->expects($this->once()) ->method('getItem') diff --git a/Tests/Service/Storage/RedisTest.php b/Tests/Service/Storage/RedisTest.php index 724ad48..efcc046 100644 --- a/Tests/Service/Storage/RedisTest.php +++ b/Tests/Service/Storage/RedisTest.php @@ -110,7 +110,13 @@ public function testSetBlock() ->withConsecutive( array('foo', 'limit', 2), array('foo', 'calls', 1), - array('foo', 'reset', time() + 100), + array( + 'foo', + 'reset', + self::callback(function ($time) { + return $time > 10; + }) + ), array('foo', 'blocked', 1) ); $client->expects(self::once()) From 4b7300777cd4966cd22bbd48f563158c3a34cb6b Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 3 Jul 2018 15:46:32 +0300 Subject: [PATCH 24/24] Fix the last test --- Tests/Service/RateLimitServiceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Service/RateLimitServiceTest.php b/Tests/Service/RateLimitServiceTest.php index 4e14639..f125095 100644 --- a/Tests/Service/RateLimitServiceTest.php +++ b/Tests/Service/RateLimitServiceTest.php @@ -74,7 +74,7 @@ public function testResetRate() public function testSetBlock() { $mockStorage = $this->getMockBuilder('Noxlogic\\RateLimitBundle\\Service\\Storage\\StorageInterface')->getMock(); - $rateLimitInfo = $this->createMock('Noxlogic\RateLimitBundle\Service\RateLimitInfo'); + $rateLimitInfo = $this->getMockBuilder('Noxlogic\RateLimitBundle\Service\RateLimitInfo')->getMock(); $mockStorage ->expects(self::once())