From f8b3d6881771475acf93f057d016f3f78dcc96eb Mon Sep 17 00:00:00 2001 From: Bizley Date: Wed, 3 May 2017 00:18:16 +0200 Subject: [PATCH] Fixes #4793: `yii\filters\AccessControl` now can be used without `user` component --- framework/CHANGELOG.md | 2 +- framework/UPGRADE.md | 3 +++ framework/filters/AccessControl.php | 13 ++++++++----- framework/filters/AccessRule.php | 13 +++++++++---- tests/framework/filters/AccessRuleTest.php | 26 ++++++++++++++++++++++---- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 768ae7b..0e6c97f 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -86,9 +86,9 @@ Yii Framework 2 Change Log - Enh #13981: `yii\caching\Cache::getOrSet()` now supports both `Closure` and `callable` (silverfire) - Enh #13994: Refactored `yii\filters\RateLimiter`. Added tests (vladis84) - Enh #14059: Removed unused AR instantiating for calling of static methods (ElisDN) +- Enh #4793: `yii\filters\AccessControl` now can be used without `user` component (bizley) - Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire) - Bug #14072: Fixed a bug where `\yii\db\Command::createTable()`, `addForeignKey()`, `dropForeignKey()`, `addCommentOnColumn()`, and `dropCommentFromColumn()` weren't refreshing the table cache on `yii\db\Schema` (brandonkelly) - - Bug #10305: Oracle SQL queries with `IN` condition and more than 1000 parameters are working now (silverfire) 2.0.11.2 February 08, 2017 diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 4a67062..2bbba8d 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -65,6 +65,9 @@ Upgrade from Yii 2.0.11 * The signature of `yii\cache\Cache::getOrSet()` has been adjusted to also accept a callable and not only `Closure`. If you extend this method, make sure to adjust your code. +* `yii\filters\AccessControl` now can be used without `user` component. + In this case `yii\filters\AccessControl::denyAccess()` throws `yii\web\ForbiddenHttpException` and using `AccessRule` + matching a role throws `yii\base\InvalidConfigException`. Upgrade from Yii 2.0.10 ----------------------- diff --git a/framework/filters/AccessControl.php b/framework/filters/AccessControl.php index 7b39adc..c228d66 100644 --- a/framework/filters/AccessControl.php +++ b/framework/filters/AccessControl.php @@ -57,8 +57,9 @@ use yii\web\ForbiddenHttpException; class AccessControl extends ActionFilter { /** - * @var User|array|string the user object representing the authentication status or the ID of the user application component. + * @var User|array|string|false the user object representing the authentication status or the ID of the user application component. * Starting from version 2.0.2, this can also be a configuration array for creating the object. + * Starting from version 2.0.12, you can set it to `false` to explicitly switch this component support off for the filter. */ public $user = 'user'; /** @@ -95,7 +96,9 @@ class AccessControl extends ActionFilter public function init() { parent::init(); - $this->user = Instance::ensure($this->user, User::className()); + if ($this->user !== false) { + $this->user = Instance::ensure($this->user, User::className()); + } foreach ($this->rules as $i => $rule) { if (is_array($rule)) { $this->rules[$i] = Yii::createObject(array_merge($this->ruleConfig, $rule)); @@ -140,12 +143,12 @@ class AccessControl extends ActionFilter * Denies the access of the user. * The default implementation will redirect the user to the login page if he is a guest; * if the user is already logged, a 403 HTTP exception will be thrown. - * @param User $user the current user - * @throws ForbiddenHttpException if the user is already logged in. + * @param User|false $user the current user or boolean `false` in case of detached User component + * @throws ForbiddenHttpException if the user is already logged in or in case of detached User component. */ protected function denyAccess($user) { - if ($user->getIsGuest()) { + if ($user !== false && $user->getIsGuest()) { $user->loginRequired(); } else { throw new ForbiddenHttpException(Yii::t('yii', 'You are not allowed to perform this action.')); diff --git a/framework/filters/AccessRule.php b/framework/filters/AccessRule.php index 0d859e9..b5a09fd 100644 --- a/framework/filters/AccessRule.php +++ b/framework/filters/AccessRule.php @@ -9,6 +9,7 @@ namespace yii\filters; use yii\base\Component; use yii\base\Action; +use yii\base\InvalidConfigException; use yii\web\User; use yii\web\Request; use yii\base\Controller; @@ -44,8 +45,8 @@ class AccessRule extends Component */ public $controllers; /** - * @var array list of roles that this rule applies to. Two special roles are recognized, and - * they are checked via [[User::isGuest]]: + * @var array list of roles that this rule applies to (requires properly configured User component). + * Two special roles are recognized, and they are checked via [[User::isGuest]]: * * - `?`: matches a guest user (not authenticated yet) * - `@`: matches an authenticated user @@ -101,9 +102,9 @@ class AccessRule extends Component /** * Checks whether the Web user is allowed to perform the specified action. * @param Action $action the action to be performed - * @param User $user the user object + * @param User|false $user the user object or `false` in case of detached User component * @param Request $request - * @return bool|null true if the user is allowed, false if the user is denied, null if the rule does not apply to the user + * @return bool|null `true` if the user is allowed, `false` if the user is denied, `null` if the rule does not apply to the user */ public function allows($action, $user, $request) { @@ -141,12 +142,16 @@ class AccessRule extends Component /** * @param User $user the user object * @return bool whether the rule applies to the role + * @throws InvalidConfigException if User component is detached */ protected function matchRole($user) { if (empty($this->roles)) { return true; } + if ($user === false) { + throw new InvalidConfigException('The user application component must be available to specify roles in AccessRule.'); + } foreach ($this->roles as $role) { if ($role === '?') { if ($user->getIsGuest()) { diff --git a/tests/framework/filters/AccessRuleTest.php b/tests/framework/filters/AccessRuleTest.php index 8bcb21d..45a46a1 100644 --- a/tests/framework/filters/AccessRuleTest.php +++ b/tests/framework/filters/AccessRuleTest.php @@ -104,7 +104,7 @@ class AccessRuleTest extends \yiiunit\TestCase public function testMatchAction() { $action = $this->mockAction(); - $user = $this->mockUser(); + $user = false; $request = $this->mockRequest(); $rule = new AccessRule([ @@ -180,10 +180,28 @@ class AccessRuleTest extends \yiiunit\TestCase $this->assertEquals($expected, $rule->allows($action, $user, $request)); } + /** + * Test that matching role is not possible without User component + * + * @see https://github.com/yiisoft/yii2/issues/4793 + */ + public function testMatchRoleWithoutUser() { + $action = $this->mockAction(); + $request = $this->mockRequest(); + + $rule = new AccessRule([ + 'allow' => true, + 'roles' => ['@'], + ]); + + $this->expectException('yii\base\InvalidConfigException'); + $rule->allows($action, false, $request); + } + public function testMatchVerb() { $action = $this->mockAction(); - $user = $this->mockUser(); + $user = false; $rule = new AccessRule([ 'allow' => true, @@ -214,7 +232,7 @@ class AccessRuleTest extends \yiiunit\TestCase public function testMatchIP() { $action = $this->mockAction(); - $user = $this->mockUser(); + $user = false; $request = $this->mockRequest(); $rule = new AccessRule(); @@ -304,7 +322,7 @@ class AccessRuleTest extends \yiiunit\TestCase public function testMatchIPWildcard() { $action = $this->mockAction(); - $user = $this->mockUser(); + $user = false; $request = $this->mockRequest(); $rule = new AccessRule();