Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing parameters to can() via AccessRule #8426

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion docs/guide/security-authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ empty or not set, it means the rule applies to all controllers.
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::queryParams|queryParams]]: specifies the query 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,
Expand Down Expand Up @@ -347,6 +350,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
Expand All @@ -363,7 +367,14 @@ class AuthorRule extends Rule
*/
public function execute($user, $item, $params)
{
return isset($params['post']) ? $params['post']->createdBy == $user : false;
if (isset($params['post'])) {
$post = $params['post'];
if (is_integer($post)) {
$post = Post::findOne($post);
}
return $post->createdBy == $user;
}
return false;
}
}
```
Expand Down Expand Up @@ -433,6 +444,37 @@ In case of Jane it is a bit simpler since she is an admin:

![Access check](images/rbac-access-check-3.png "Access check")

You can also use rules from the [[yii\filters\AccessControl|AccessControl]] filter. For that you specify the
[[yii\filters\AccessRule::queryParams|queryParams]] that you need to pass to the rule:


```php
use yii\filters\AccessControl;

class PostsController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'actions' => ['update'],
'roles' => ['updatePost'],
'queryParams' => [
// The id in the url will be passed as 'post'
'id' => 'post',
],
'allow' => true,
],
],
],
];
}
}
```


### Using Default Roles <span id="using-default-roles"></span>

Expand Down
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Yii Framework 2 Change Log
- Enh #8574: Added `yii\console\controllers\MessageController` support .pot file creation (pgaultier)
- Chg #6354: `ErrorHandler::logException()` will now log the whole exception object instead of only its string representation (cebe)
- Chg #8556: Extracted `yii\web\User::getAuthManager()` method (samdark)
- Enh #8426: `yii\filters\AccessRule` now allows passing GET parameters to the role checking function (fsateler)


2.0.4 May 10, 2015
Expand Down
60 changes: 59 additions & 1 deletion framework/filters/AccessRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use yii\web\User;
use yii\web\Request;
use yii\base\Controller;
use yii\helpers\ArrayHelper;
use Yii;

/**
* This class represents an access rule defined by the [[AccessControl]] action filter
Expand Down Expand Up @@ -90,6 +92,62 @@ class AccessRule extends Component
*/
public $denyCallback;

/**
* @var array a map for passing parameters to the [[User::can()]] function
Copy link
Contributor

@Faryshta Faryshta May 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about allowing for callbacks? this would ease the implementation a lot.

'params' => function ($action, $accessRule) {
     return ['autor_id' => Yii::$app->user->id];
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see #14123 for an alternative implementation using closures.

*
* The map indicates query parameters that will be passed when determining
* role access.
*
* If both a key and a value are passed, then the name of the parameter will
* be changed. A value of `['a' => 'b']` will cause [[User::can()]] to be
* invoked with the content of the request query parameter 'a' as key 'b'
* in the params argument.
* If only a value is passed then it is equivalent to passing the same value
* as key.
*
* The special parameters '{action}' and '{controller}' are bound to the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think {route} would be better since i can have api/v1/user/index and api/v2/user/index with different permissions.

* action and controller ids
*/
public $queryParams = [];

/**
* @var array the parameters to be passed to [[User::can()]].
*
* Do not use directly, use [[getParamsForCan()]] to access it
*/
private $_paramsForCan = null;

/**
* Gets the parameters to be passed to [[User::can()]].
*
* It will parse [[AccessRule::queryParams]] and create the array of
* parameters, if it has not been created already.
*
* @return array
*/
private function getParamsForCan() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need PHPdoc for private stuff as well.

if ($this->_paramsForCan === null) {
$newParams = [];
$queryParams = ArrayHelper::getValue(Yii::$app->request, 'queryParams', []);
foreach($this->queryParams as $key => $value) {
if (!is_string($key)) {
$key = $value;
}
if ($key === '{action}') {
$newParams[$value] = Yii::$app->controller->action->id;
}
elseif ($key === '{controller}') {
$newParams[$value] = Yii::$app->controller->id;
}
if (ArrayHelper::keyExists($key, $queryParams)) {
$newParams[$value] = ArrayHelper::getValue($queryParams, $key);
}
}
$this->_paramsForCan = $newParams;
}
return $this->_paramsForCan;
}


/**
* Checks whether the Web user is allowed to perform the specified action.
Expand Down Expand Up @@ -149,7 +207,7 @@ protected function matchRole($user)
if (!$user->getIsGuest()) {
return true;
}
} elseif ($user->can($role)) {
} elseif ($user->can($role, $this->getParamsForCan())) {
return true;
}
}
Expand Down