Skip to content

Commit

Permalink
Alternative implementation for passing parameters to AccessRule roles
Browse files Browse the repository at this point in the history
fixes yiisoft#8426: `yii\filters\AccessRule` now allows passing GET or other parameters to the role checking function
replaces yiisoft#8426
  • Loading branch information
fsateler authored and cebe committed May 8, 2017
1 parent ff92c5c commit ba11fdb
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 13 deletions.
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Yii Framework 2 Change Log
- Bug #14033: Fixed `yii\filters\AccessRule::matchIp()` erroring in case IP is not defined under HHVM (Kolyunya)
- Bug #14052: Fixed processing parse errors on PHP 7 since these are instances of `\ParseError` (samdark)
- Bug #14074: Fixed default value of `yii\console\controllers\FixtureController::$globalFixtures` to contain valid class name (lynicidn)
- Enh #8426: `yii\filters\AccessRule` now allows passing parameters to the role checking function (fsateler, cebe)
- Enh #8641: Enhanced `yii\console\Request::resolve()` to prevent passing parameters, that begin from digits (silverfire)
- Enh #13144: Refactored `yii\db\Query::queryScalar()` (Alex-Code)
- Enh #13179: Added `yii\data\Sort::parseSortParam` allowing to customize sort param in descendant class (leandrogehlen)
Expand Down
39 changes: 37 additions & 2 deletions framework/filters/AccessRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace yii\filters;

use Closure;
use yii\base\Component;
use yii\base\Action;
use yii\base\InvalidConfigException;
Expand Down Expand Up @@ -55,8 +56,37 @@ class AccessRule extends Component
* In this case, [[User::can()]] will be called to check access.
*
* If this property is not set or empty, it means this rule applies to all roles.
* @see $roleParams
*/
public $roles;
/**
* @var array|Closure parameters to pass to the [[User::can()]] function for evaluating
* user permissions in [[$roles]].
*
* If this is an array, it will be passed directly to [[User::can()]].
* You may also specify a closure that returns an array. This can be used to
* evaluate the array values only if they are needed.
* This can be used for example like this:
*
* ```php
* 'rules' => [
* [
* 'allow' => true,
* 'actions' => ['update'],
* 'roles' => ['updatePost'],
* 'roleParams' => function($rule) {
* return ['postId' => Yii::$app->request->get('id')];
* },
* ],
* ],
* ```
*
* A reference to the [[AccessRule]] instance will be passed to the closure as the first parameter.
*
* @see $roles
* @since 2.0.12
*/
public $roleParams = [];
/**
* @var array list of user IP addresses that this rule applies to. An IP address
* can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix.
Expand Down Expand Up @@ -161,8 +191,13 @@ protected function matchRole($user)
if (!$user->getIsGuest()) {
return true;
}
} elseif ($user->can($role)) {
return true;
} else {
if (!isset($roleParams)) {
$roleParams = $this->roleParams instanceof Closure ? call_user_func($this->roleParams, $this) : $this->roleParams;
}
if ($user->can($role, $roleParams)) {
return true;
}
}
}

Expand Down
56 changes: 45 additions & 11 deletions tests/framework/filters/AccessRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace yiiunit\framework\filters;

use Closure;
use Yii;
use yii\base\Action;
use yii\filters\AccessRule;
Expand All @@ -10,6 +11,7 @@
use yii\web\User;
use yiiunit\framework\filters\stubs\MockAuthManager;
use yiiunit\framework\filters\stubs\UserIdentity;
use yiiunit\framework\rbac\AuthorRule;

/**
* @group filters
Expand Down Expand Up @@ -81,10 +83,20 @@ protected function mockAuthManager()
$updatePost->description = 'Update post';
$auth->add($updatePost);

// add "updateOwnPost" permission
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update post';
$updateRule = new AuthorRule();
$auth->add($updateRule);
$updateOwnPost->ruleName = $updateRule->name;
$auth->add($updateOwnPost);
$auth->addChild($updateOwnPost, $updatePost);

// add "author" role and give this role the "createPost" permission
$author = $auth->createRole('author');
$auth->add($author);
$auth->addChild($author, $createPost);
$auth->addChild($author, $updateOwnPost);

// add "admin" role and give this role the "updatePost" permission
// as well as the permissions of the "author" role
Expand Down Expand Up @@ -142,14 +154,34 @@ public function testMatchAction()
*/
public function matchRoleProvider() {
return [
['create', true, 'user1', true],
['create', true, 'user2', true],
['create', true, 'user3', null],
['create', true, 'unknown', null],
['create', false, 'user1', false],
['create', false, 'user2', false],
['create', false, 'user3', null],
['create', false, 'unknown', null],
['create', true, 'user1', [], true],
['create', true, 'user2', [], true],
['create', true, 'user3', [], null],
['create', true, 'unknown', [], null],
['create', false, 'user1', [], false],
['create', false, 'user2', [], false],
['create', false, 'user3', [], null],
['create', false, 'unknown', [], null],

// user2 is author, can only edit own posts
['update', true, 'user2', ['authorID' => 'user2'], true],
['update', true, 'user2', ['authorID' => 'user1'], null],
// user1 is admin, can update all posts
['update', true, 'user1', ['authorID' => 'user1'], true],
['update', true, 'user1', ['authorID' => 'user2'], true],
// unknown user can not edit anything
['update', true, 'unknown', ['authorID' => 'user1'], null],
['update', true, 'unknown', ['authorID' => 'user2'], null],

// user2 is author, can only edit own posts
['update', true, 'user2', function() { return ['authorID' => 'user2']; }, true],
['update', true, 'user2', function() { return ['authorID' => 'user1']; }, null],
// user1 is admin, can update all posts
['update', true, 'user1', function() { return ['authorID' => 'user1']; }, true],
['update', true, 'user1', function() { return ['authorID' => 'user2']; }, true],
// unknown user can not edit anything
['update', true, 'unknown', function() { return ['authorID' => 'user1']; }, null],
['update', true, 'unknown', function() { return ['authorID' => 'user2']; }, null],
];
}

Expand All @@ -160,17 +192,19 @@ public function matchRoleProvider() {
* @param string $actionid the action id
* @param boolean $allow whether the rule should allow access
* @param string $userid the userid to check
* @param array|Closure $roleParams params for $roleParams
* @param boolean $expected the expected result or null
*/
public function testMatchRole($actionid, $allow, $userid, $expected) {
public function testMatchRole($actionid, $allow, $userid, $roleParams, $expected) {
$action = $this->mockAction();
$auth = $this->mockAuthManager();
$request = $this->mockRequest();

$rule = new AccessRule([
'allow' => $allow,
'roles' => ['createPost'],
'actions' => ['create'],
'roles' => [$actionid === 'create' ? 'createPost' : 'updatePost'],
'actions' => [$actionid],
'roleParams' => $roleParams,
]);

$action->id = $actionid;
Expand Down

0 comments on commit ba11fdb

Please sign in to comment.