mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-03 22:32:40 +08:00
Merge pull request #14123 from cebe/access-rule-params
Alternative implementation for passing parameters to AccessRule roles
This commit is contained in:
@ -100,6 +100,9 @@ The comparison is case-sensitive. If this option is empty or not set, it means t
|
||||
Using other role names will trigger the invocation of [[yii\web\User::can()]], which requires enabling RBAC
|
||||
(to be described in the next subsection). If this option is empty or not set, it means this rule applies to all roles.
|
||||
|
||||
* [[yii\filters\AccessRule::roleParams|roleParams]]: specifies the parameters that will be passed to [[yii\web\User::can()]].
|
||||
See the section below describing RBAC rules to see how it can be used. If this option is empty or not set, then no parameters will be passed.
|
||||
|
||||
* [[yii\filters\AccessRule::ips|ips]]: specifies which [[yii\web\Request::userIP|client IP addresses]] this rule matches.
|
||||
An IP address can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix.
|
||||
For example, '192.168.*' matches all IP addresses in the segment '192.168.'. If this option is empty or not set,
|
||||
@ -356,6 +359,7 @@ created previously author cannot edit his own post. Let's fix it. First we need
|
||||
namespace app\rbac;
|
||||
|
||||
use yii\rbac\Rule;
|
||||
use app\models\Post;
|
||||
|
||||
/**
|
||||
* Checks if authorID matches user passed via params
|
||||
@ -487,6 +491,35 @@ public function behaviors()
|
||||
If all the CRUD operations are managed together then it's a good idea to use a single permission, like `managePost`, and
|
||||
check it in [[yii\web\Controller::beforeAction()]].
|
||||
|
||||
In the above example, no parameters are passed with the roles specified for accessing an action, but in case of the
|
||||
`updatePost` permission, we need to pass a `post` parameter for it to work properly.
|
||||
You can pass parameters to [[yii\web\User::can()]] by specifying [[yii\filters\AccessRule::roleParams|roleParams]] on
|
||||
the access rule:
|
||||
|
||||
```php
|
||||
[
|
||||
'allow' => true,
|
||||
'actions' => ['update'],
|
||||
'roles' => ['updatePost'],
|
||||
'roleParams' => function() {
|
||||
return ['post' => Post::findOne(Yii::$app->request->get('id'))];
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
In the above example, [[yii\filters\AccessRule::roleParams|roleParams]] is a Closure that will be evaluated when
|
||||
the access rule is checked, so the model will only be loaded when needed.
|
||||
If the creation of role parameters is a simple operation, you may just specify an array, like so:
|
||||
|
||||
```php
|
||||
[
|
||||
'allow' => true,
|
||||
'actions' => ['update'],
|
||||
'roles' => ['updatePost'],
|
||||
'roleParams' => ['postId' => Yii::$app->request->get('id')];
|
||||
],
|
||||
```
|
||||
|
||||
### Using Default Roles <span id="using-default-roles"></span>
|
||||
|
||||
A default role is a role that is *implicitly* assigned to *all* users. The call to [[yii\rbac\ManagerInterface::assign()]]
|
||||
|
||||
@ -59,6 +59,7 @@ Yii Framework 2 Change Log
|
||||
- Bug #14074: Fixed default value of `yii\console\controllers\FixtureController::$globalFixtures` to contain valid class name (lynicidn)
|
||||
- Bug #14094: Fixed bug when single `yii\web\UrlManager::createUrl()` call my result multiple calls of `yii\web\UrlRule::createUrl()` for the same rule (rossoneri)
|
||||
- Bug #14133: Fixed bug when calculating timings with mixed nested profile begin and end in `yii\log\Logger::calculateTimings()` (bizley)
|
||||
- Enh #8426: `yii\filters\AccessRule` now allows passing parameters to the role checking function (fsateler, cebe, Faryshta)
|
||||
- Enh #8641: Enhanced `yii\console\Request::resolve()` to prevent passing parameters, that begin from digits (silverfire)
|
||||
- Enh #11288: Added support for caching of `yii\web\UrlRule::createUrl()` results in `yii\web\UrlManager` for rules with defaults (rob006)
|
||||
- Enh #12528: Added option to disable query logging and profiling in DB command (cebe)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
namespace yii\filters;
|
||||
|
||||
use Closure;
|
||||
use yii\base\Component;
|
||||
use yii\base\Action;
|
||||
use yii\base\InvalidConfigException;
|
||||
@ -55,8 +56,43 @@ 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()]]. For example for passing an
|
||||
* ID from the current request, you may use the following:
|
||||
*
|
||||
* ```php
|
||||
* ['postId' => Yii::$app->request->get('id')]
|
||||
* ```
|
||||
*
|
||||
* You may also specify a closure that returns an array. This can be used to
|
||||
* evaluate the array values only if they are needed, for example when a model needs to be
|
||||
* loaded like in the following code:
|
||||
*
|
||||
* ```php
|
||||
* 'rules' => [
|
||||
* [
|
||||
* 'allow' => true,
|
||||
* 'actions' => ['update'],
|
||||
* 'roles' => ['updatePost'],
|
||||
* 'roleParams' => function($rule) {
|
||||
* return ['post' => Post::findOne(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.
|
||||
@ -161,8 +197,13 @@ class AccessRule extends Component
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
namespace yiiunit\framework\filters;
|
||||
|
||||
use Closure;
|
||||
use Yii;
|
||||
use yii\base\Action;
|
||||
use yii\filters\AccessRule;
|
||||
@ -10,6 +11,7 @@ use yii\web\Request;
|
||||
use yii\web\User;
|
||||
use yiiunit\framework\filters\stubs\MockAuthManager;
|
||||
use yiiunit\framework\filters\stubs\UserIdentity;
|
||||
use yiiunit\framework\rbac\AuthorRule;
|
||||
|
||||
/**
|
||||
* @group filters
|
||||
@ -81,10 +83,20 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
$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
|
||||
@ -142,14 +154,34 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
*/
|
||||
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],
|
||||
];
|
||||
}
|
||||
|
||||
@ -160,17 +192,19 @@ class AccessRuleTest extends \yiiunit\TestCase
|
||||
* @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;
|
||||
|
||||
Reference in New Issue
Block a user