From 6e15522140d2e23bd5ce997532806c9550f5bae0 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 6 Sep 2016 20:10:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D1=80=D0=B5=D0=B9=D1=82-=D0=BB=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=D1=80=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=BE=D0=B2=20=D0=BA=20hasJoined=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BD=D0=B5=D0=B7=D0=B0=D1=80=D0=B5=D0=B3=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/User/Component.php | 2 +- .../session/controllers/SessionController.php | 5 ++ api/modules/session/filters/RateLimiter.php | 77 ++++++++++++++++++ .../session/filters/RateLimiterTest.php | 78 +++++++++++++++++++ 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 api/modules/session/filters/RateLimiter.php create mode 100644 tests/codeception/api/unit/modules/session/filters/RateLimiterTest.php diff --git a/api/components/User/Component.php b/api/components/User/Component.php index bf22613..7407a55 100644 --- a/api/components/User/Component.php +++ b/api/components/User/Component.php @@ -22,7 +22,7 @@ use yii\web\User as YiiUserComponent; * @property AccountSession|null $activeSession * @property AccountIdentity|null $identity * - * @method AccountIdentity|null getIdentity() + * @method AccountIdentity|null getIdentity($autoRenew = true) */ class Component extends YiiUserComponent { diff --git a/api/modules/session/controllers/SessionController.php b/api/modules/session/controllers/SessionController.php index 583b5a0..8e0835b 100644 --- a/api/modules/session/controllers/SessionController.php +++ b/api/modules/session/controllers/SessionController.php @@ -4,6 +4,7 @@ namespace api\modules\session\controllers; use api\controllers\ApiController; use api\modules\session\exceptions\ForbiddenOperationException; use api\modules\session\exceptions\SessionServerException; +use api\modules\session\filters\RateLimiter; use api\modules\session\models\HasJoinedForm; use api\modules\session\models\JoinForm; use api\modules\session\models\protocols\LegacyJoin; @@ -18,6 +19,10 @@ class SessionController extends ApiController { public function behaviors() { $behaviors = parent::behaviors(); unset($behaviors['authenticator']); + $behaviors['rateLimiting'] = [ + 'class' => RateLimiter::class, + 'only' => ['has-joined', 'has-joined-legacy'], + ]; return $behaviors; } diff --git a/api/modules/session/filters/RateLimiter.php b/api/modules/session/filters/RateLimiter.php new file mode 100644 index 0000000..8e2f239 --- /dev/null +++ b/api/modules/session/filters/RateLimiter.php @@ -0,0 +1,77 @@ +getServer($request); + if ($server !== null) { + return; + } + + $ip = $request->getUserIP(); + $key = $this->buildKey($ip); + + $redis = $this->getRedis(); + $countRequests = intval($redis->executeCommand('INCR', [$key])); + if ($countRequests === 1) { + $redis->executeCommand('EXPIRE', [$key, $this->limitTime]); + } + + if ($countRequests > $this->limit) { + throw new TooManyRequestsHttpException($this->errorMessage); + } + } + + /** + * @return \yii\redis\Connection + */ + public function getRedis() { + return Yii::$app->redis; + } + + /** + * @param Request $request + * @return OauthClient|null + */ + protected function getServer(Request $request) { + $serverId = $request->get('server_id'); + if ($serverId === null) { + $this->server = false; + return null; + } + + if ($this->server === null) { + /** @var OauthClient $server */ + $this->server = OauthClient::findOne($serverId); + // TODO: убедится, что это сервер + if ($this->server === null) { + $this->server = false; + } + } + + if ($this->server === false) { + return null; + } + + return $this->server; + } + + protected function buildKey($ip) : string { + return 'sessionserver:ratelimit:' . $ip; + } + +} diff --git a/tests/codeception/api/unit/modules/session/filters/RateLimiterTest.php b/tests/codeception/api/unit/modules/session/filters/RateLimiterTest.php new file mode 100644 index 0000000..10fa675 --- /dev/null +++ b/tests/codeception/api/unit/modules/session/filters/RateLimiterTest.php @@ -0,0 +1,78 @@ +getMockBuilder(Connection::class) + ->setMethods(['executeCommand']) + ->getMock(); + + $redis->expects($this->never()) + ->method('executeCommand'); + + Yii::$app->set('redis', $redis); + + /** @var RateLimiter|\PHPUnit_Framework_MockObject_MockObject $filter */ + $filter = $this->getMockBuilder(RateLimiter::class) + ->setMethods(['getServer']) + ->getMock(); + + $filter->expects($this->any()) + ->method('getServer') + ->will($this->returnValue(new OauthClient())); + + $filter->checkRateLimit(null, new Request(), null, null); + } + + /** + * @expectedException \yii\web\TooManyRequestsHttpException + */ + public function testCheckRateLimiter() { + /** @var Connection|\PHPUnit_Framework_MockObject_MockObject $redis */ + $redis = $this->getMockBuilder(Connection::class) + ->setMethods(['executeCommand']) + ->getMock(); + + $redis->expects($this->exactly(5)) + ->method('executeCommand') + ->will($this->onConsecutiveCalls('1', '1', '2', '3', '4')); + + Yii::$app->set('redis', $redis); + + /** @var Request|\PHPUnit_Framework_MockObject_MockObject $request */ + $request = $this->getMockBuilder(Request::class) + ->setMethods(['getUserIP']) + ->getMock(); + + $request->expects($this->any()) + ->method('getUserIp') + ->will($this->returnValue(Internet::localIpv4())); + + /** @var RateLimiter|\PHPUnit_Framework_MockObject_MockObject $filter */ + $filter = $this->getMockBuilder(RateLimiter::class) + ->setConstructorArgs([[ + 'limit' => 3, + ]]) + ->setMethods(['getServer']) + ->getMock(); + + $filter->expects($this->any()) + ->method('getServer') + ->will($this->returnValue(null)); + + for ($i = 0; $i < 5; $i++) { + $filter->checkRateLimit(null, $request, null, null); + } + } + +}