Skip to content

Commit c333eef

Browse files
authored
feat: Add Yii authorization integration with AuthManager and Behaviors methods (#18)
* feat: add Yii authorization integration with AuthManager and Behaviors methods * docs: update README with new Yii Authorization features and usage examples
1 parent c8cb31b commit c333eef

9 files changed

+638
-81
lines changed

README.md

+58
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,64 @@ Determines whether a user has a permission.
188188
$permission->hasPermissionForUser('eve', 'articles', 'read'); // true or false
189189
```
190190

191+
### Using Yii Authorization
192+
193+
It allows you to integrate Yii's authorization with the Casbin permission management system.
194+
195+
**(1) AccessChecker**
196+
197+
Add the accessChecker configuration in your application's `config/web.php` file:
198+
199+
```php
200+
$config = [
201+
'components' => [
202+
'user' => [
203+
...
204+
'accessChecker' => 'yii\permission\components\PermissionChecker',
205+
]
206+
];
207+
```
208+
209+
Once configured, you can use the `can()` method to check if a user has permission to perform certain actions:
210+
211+
```php
212+
$user->can('acrticles,read');
213+
```
214+
215+
**(2) Behaviors**
216+
217+
The `PermissionControl` behavior allows you to enforce permission checks at the controller level. Add the PermissionControl behavior to your controller's behaviors() method:
218+
219+
```php
220+
public function behaviors()
221+
{
222+
return [
223+
'permission' => [
224+
'class' => \yii\permission\components\PermissionControl::class,
225+
'user' => $user, // optional, defaults to \Yii::$app->user
226+
'only' => ['read-articles', 'write-articles'],
227+
'policy' => [
228+
[
229+
'allow' => true,
230+
'actions' => ['read-articles'],
231+
'enforce' => ['articles', 'read']
232+
],
233+
[
234+
'allow' => true,
235+
'actions' => ['write-articles'],
236+
'enforce' => ['articles', 'write']
237+
]
238+
],
239+
'denyCallback' => function ($policy, $action) {
240+
// custom action when access is denied
241+
} // optional, defaults to throwing an exception
242+
]
243+
];
244+
}
245+
```
246+
247+
**Note:** Additionally,You can also configure a `denyCallback` for each `policy`, which will be invoked when the user does not meet the required permission. This callback takes precedence. The configuration is similar to Yii's official [AccessControl](https://www.yiiframework.com/doc/guide/2.0/zh-cn/security-authorization#access-control-filter).
248+
191249
See [Casbin API](https://casbin.org/docs/en/management-api) for more APIs.
192250

193251
## Define your own model.conf

src/components/PermissionChecker.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace yii\permission\components;
4+
5+
use Yii;
6+
use yii\rbac\CheckAccessInterface;
7+
8+
class PermissionChecker implements CheckAccessInterface
9+
{
10+
/**
11+
* Checks if the user has access to a certain policy.
12+
*
13+
* @param int $userId The ID of the user to check.
14+
* @param string $policy The policy to check access for.
15+
* @param array $guards Optional guards to check, not supported yet.
16+
*
17+
* @return bool Whether the user has access to the policy.
18+
*/
19+
public function checkAccess($userId, $policy, $guards = [])
20+
{
21+
$params = explode(',', $policy);
22+
return Yii::$app->permission->enforce($userId, ...$params);
23+
}
24+
}

src/components/PermissionControl.php

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
namespace yii\permission\components;
4+
5+
use Yii;
6+
use yii\base\ActionFilter;
7+
use yii\di\Instance;
8+
use yii\web\ForbiddenHttpException;
9+
use yii\web\User;
10+
11+
class PermissionControl extends ActionFilter
12+
{
13+
/**
14+
* @var User|array|string|false the user object.
15+
*/
16+
public $user = 'user';
17+
18+
/**
19+
* @var callable|null a callback that will be called if the access should be denied
20+
*/
21+
public $denyCallback;
22+
23+
/**
24+
* @var array the default configuration of the policy
25+
*/
26+
public $policyConfig = ['class' => 'yii\permission\components\PermissionPolicy'];
27+
28+
/**
29+
* @var array the policies.
30+
*/
31+
public $policy = [];
32+
33+
/**
34+
* Initializes the PermissionControl component.
35+
*
36+
* @return void
37+
*/
38+
public function init()
39+
{
40+
parent::init();
41+
if ($this->user !== false) {
42+
$this->user = Instance::ensure($this->user, User::class);
43+
}
44+
foreach ($this->policy as $i => $policy) {
45+
if (is_array($policy)) {
46+
$this->policy[$i] = Yii::createObject(array_merge($this->policyConfig, $policy));
47+
}
48+
}
49+
}
50+
51+
/**
52+
* Checks if the current user has permission to perform the given action.
53+
*
54+
* @param Action $action the action to be performed
55+
* @throws ForbiddenHttpException if the user does not have permission
56+
* @return bool true if the user has permission, false otherwise
57+
*/
58+
public function beforeAction($action)
59+
{
60+
$user = $this->user;
61+
foreach ($this->policy as $policy) {
62+
if ($allow = $policy->allows($action, $user)) {
63+
return true;
64+
} elseif ($allow === false) {
65+
if (isset($policy->denyCallback)) {
66+
call_user_func($policy->denyCallback, $policy, $action);
67+
} elseif ($this->denyCallback !== null) {
68+
call_user_func($this->denyCallback, $policy, $action);
69+
} else {
70+
$this->denyAccess($user);
71+
}
72+
73+
return false;
74+
}
75+
}
76+
77+
if ($this->denyCallback !== null) {
78+
call_user_func($this->denyCallback, null, $action);
79+
} else {
80+
$this->denyAccess($user);
81+
}
82+
return false;
83+
}
84+
/**
85+
* Denies the access of the user.
86+
* The default implementation will redirect the user to the login page if he is a guest;
87+
* if the user is already logged, a 403 HTTP exception will be thrown.
88+
*
89+
* @param User|false $user the current user or boolean `false` in case of detached User component
90+
* @throws ForbiddenHttpException if the user is already logged in or in case of detached User component.
91+
*/
92+
protected function denyAccess($user)
93+
{
94+
if ($user !== false && $user->getIsGuest()) {
95+
$user->loginRequired();
96+
} else {
97+
throw new ForbiddenHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
98+
}
99+
}
100+
}

src/components/PermissionPolicy.php

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace yii\permission\components;
4+
5+
use Yii;
6+
use yii\base\Component;
7+
use yii\web\User;
8+
9+
class PermissionPolicy extends Component
10+
{
11+
/**
12+
* @var bool whether this is an 'allow' rule or 'deny' rule.
13+
*/
14+
public $allow = false;
15+
16+
/**
17+
* @var array|null list of the controller IDs that this rule applies to.
18+
*/
19+
public $actions = [];
20+
21+
/**
22+
* @var array|null list of params that passed to Casbin.
23+
*/
24+
public $enforce = [];
25+
26+
/**
27+
* @var callable|null a callback that will be called if the access should be denied
28+
*/
29+
public $denyCallback;
30+
31+
/**
32+
* Checks whether the given action is allowed for the specified user.
33+
*
34+
* @param string $action the action to be checked
35+
* @param User $user the user to be checked
36+
*
37+
* @return bool|null true if the action is allowed, false if not, null if the rule does not apply
38+
*/
39+
public function allows($action, $user)
40+
{
41+
if (
42+
$this->matchAction($action)
43+
&& $this->matchEnforce($user, $this->enforce)
44+
) {
45+
return $this->allow ? true : false;
46+
}
47+
48+
return null;
49+
}
50+
51+
/**
52+
* Checks if the rule applies to the specified action.
53+
*
54+
* @param Action $action the action
55+
* @return bool whether the rule applies to the action
56+
*/
57+
protected function matchAction($action)
58+
{
59+
return empty($this->actions) || in_array($action->id, $this->actions, true);
60+
}
61+
62+
/**
63+
* Checks if the rule applies to the specified user.
64+
*
65+
* @param User $user
66+
* @param array $params
67+
*
68+
* @return bool
69+
*/
70+
protected function matchEnforce($user, $params)
71+
{
72+
return Yii::$app->permission->enforce($user->getId(), ...$params);
73+
}
74+
}

tests/AdapterTest.php

-81
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@
22

33
namespace yii\permission\tests;
44

5-
use PHPUnit\Framework\TestCase;
6-
use yii\web\Application;
75
use Yii;
8-
use yii\permission\models\CasbinRule;
96
use Casbin\Persist\Adapters\Filter;
107
use Casbin\Exceptions\InvalidFilterTypeException;
118
use yii\db\ActiveQueryInterface;
129

1310
class AdapterTest extends TestCase
1411
{
15-
protected $app;
16-
1712
public function testEnforce()
1813
{
1914
$this->assertTrue(Yii::$app->permission->enforce('alice', 'data1', 'read'));
@@ -331,80 +326,4 @@ public function testLoadFilteredPolicy()
331326
['alice', 'data1', 'read'],
332327
], Yii::$app->permission->getPolicy());
333328
}
334-
335-
public function createApplication()
336-
{
337-
$config = require __DIR__ . '/../vendor/yiisoft/yii2-app-basic/config/web.php';
338-
$config['components']['permission'] = require __DIR__ . '/../config/permission.php';
339-
340-
$config['components']['db']['dsn'] = 'mysql:host=' . $this->env('DB_HOST', '127.0.0.1') . ';port=' . $this->env('DB_PORT', '3306') . ';dbname=' . $this->env('DB_DATABASE', 'casbin');
341-
$config['components']['db']['username'] = $this->env('DB_USERNAME', 'root');
342-
$config['components']['db']['password'] = $this->env('DB_PASSWORD', '');
343-
344-
return new Application($config);
345-
}
346-
347-
/**
348-
* init table.
349-
*/
350-
protected function initTable()
351-
{
352-
$db = CasbinRule::getDb();
353-
$tableName = CasbinRule::tableName();
354-
$table = $db->getTableSchema($tableName);
355-
if ($table) {
356-
$db->createCommand()->dropTable($tableName)->execute();
357-
}
358-
359-
Yii::$app->permission->init();
360-
361-
Yii::$app->db->createCommand()->batchInsert(
362-
$tableName,
363-
['ptype', 'v0', 'v1', 'v2'],
364-
[
365-
['p', 'alice', 'data1', 'read'],
366-
['p', 'bob', 'data2', 'write'],
367-
['p', 'data2_admin', 'data2', 'read'],
368-
['p', 'data2_admin', 'data2', 'write'],
369-
['g', 'alice', 'data2_admin', null],
370-
]
371-
)->execute();
372-
}
373-
374-
/**
375-
* Refresh the application instance.
376-
*/
377-
protected function refreshApplication()
378-
{
379-
$this->app = $this->createApplication();
380-
}
381-
382-
/**
383-
* This method is called before each test.
384-
*/
385-
protected function setUp(): void/* The :void return type declaration that should be here would cause a BC issue */
386-
{
387-
if (!$this->app) {
388-
$this->refreshApplication();
389-
}
390-
391-
$this->initTable();
392-
}
393-
394-
/**
395-
* This method is called after each test.
396-
*/
397-
protected function tearDown(): void/* The :void return type declaration that should be here would cause a BC issue */
398-
{
399-
}
400-
401-
protected function env($key, $default = null)
402-
{
403-
$value = getenv($key);
404-
if (is_null($default)) {
405-
return $value;
406-
}
407-
408-
return false === $value ? $default : $value;
409-
}
410329
}

0 commit comments

Comments
 (0)