Fix #18236: Allow yii\filters\RateLimiter to accept a closure function for the $user property in order to assign values on runtime

This commit is contained in:
Basil
2020-08-19 12:57:45 +02:00
committed by GitHub
parent 6bfd21cead
commit 175a201004
3 changed files with 31 additions and 2 deletions

View File

@ -4,6 +4,7 @@ Yii Framework 2 Change Log
2.0.38 under development 2.0.38 under development
------------------------ ------------------------
- Enh #18236: Allow `yii\filters\RateLimiter` to accept a closure function for the `$user` property in order to assign values on runtime (nadar)
- Bug #18233: Add PHP 8 support (samdark) - Bug #18233: Add PHP 8 support (samdark)
- Bug #18229: Add flag for recognize SyBase databases on uses pdo_dblib (darkdef) - Bug #18229: Add flag for recognize SyBase databases on uses pdo_dblib (darkdef)

View File

@ -7,6 +7,7 @@
namespace yii\filters; namespace yii\filters;
use Closure;
use Yii; use Yii;
use yii\base\ActionFilter; use yii\base\ActionFilter;
use yii\web\Request; use yii\web\Request;
@ -48,8 +49,14 @@ class RateLimiter extends ActionFilter
*/ */
public $errorMessage = 'Rate limit exceeded.'; public $errorMessage = 'Rate limit exceeded.';
/** /**
* @var RateLimitInterface the user object that implements the RateLimitInterface. * @var RateLimitInterface|Closure the user object that implements the RateLimitInterface. If not set, it will take the value of `Yii::$app->user->getIdentity(false)`.
* If not set, it will take the value of `Yii::$app->user->getIdentity(false)`. * {@since 2.0.38} It's possible to provide a closure function in order to assign the user identity on runtime. Using a closure to assign the user identity is recommend
* when you are **not** using the standard `Yii::$app->user` component. See the example below:
* ```php
* 'user' => function() {
* return Yii::$app->apiUser->identity;
* }
* ```
*/ */
public $user; public $user;
/** /**
@ -84,6 +91,10 @@ class RateLimiter extends ActionFilter
$this->user = Yii::$app->getUser()->getIdentity(false); $this->user = Yii::$app->getUser()->getIdentity(false);
} }
if ($this->user instanceof Closure) {
$this->user = call_user_func($this->user, $action);
}
if ($this->user instanceof RateLimitInterface) { if ($this->user instanceof RateLimitInterface) {
Yii::debug('Check rate limit', __METHOD__); Yii::debug('Check rate limit', __METHOD__);
$this->checkRateLimit($this->user, $this->request, $this->response, $action); $this->checkRateLimit($this->user, $this->request, $this->response, $action);

View File

@ -158,4 +158,21 @@ class RateLimiterTest extends TestCase
$rateLimiter->addRateLimitHeaders($response, 1, 0, 0); $rateLimiter->addRateLimitHeaders($response, 1, 0, 0);
$this->assertCount(3, $response->getHeaders()); $this->assertCount(3, $response->getHeaders());
} }
/**
* @see https://github.com/yiisoft/yii2/issues/18236
*/
public function testUserWithClosureFunction()
{
$rateLimiter = new RateLimiter();
$rateLimiter->user = function($action) {
return new User(['identityClass' => RateLimit::className()]);
};
$rateLimiter->beforeAction('test');
// testing the evaluation of user closure, which in this case returns not the expect object and therefore
// the log message "does not implement RateLimitInterface" is expected.
$this->assertInstanceOf(User::className(), $rateLimiter->user);
$this->assertContains('Rate limit skipped: "user" does not implement RateLimitInterface.', Yii::getLogger()->messages);
}
} }