Skip to content

Commit

Permalink
feat: add Yii authorization integration with AuthManager and Behavior…
Browse files Browse the repository at this point in the history
…s methods
  • Loading branch information
Dobmod committed Aug 19, 2024
1 parent c8cb31b commit b99acac
Show file tree
Hide file tree
Showing 8 changed files with 563 additions and 81 deletions.
27 changes: 27 additions & 0 deletions src/components/PermissionChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace yii\permission\components;

use Yii;
use yii\rbac\CheckAccessInterface;

class PermissionChecker implements CheckAccessInterface
{
/**
* Checks if the user has access to a certain policy.
*
* @param int $userId The ID of the user to check.
* @param string $policy The policy to check access for.
* @param array $guards Optional guards to check, not supported yet.
*
* @return bool Whether the user has access to the policy.
*/
public function checkAccess($userId, $policy, $guards = [])
{
$params = explode(',', $policy);
if (empty($guards)) {
return Yii::$app->permission->enforce($userId, ...$params);
}
return false;
}
}
100 changes: 100 additions & 0 deletions src/components/PermissionControl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

namespace yii\permission\components;

use Yii;
use yii\base\ActionFilter;
use yii\di\Instance;
use yii\web\ForbiddenHttpException;
use yii\web\User;

class PermissionControl extends ActionFilter
{
/**
* @var User|array|string|false the user object.
*/
public $user = 'user';

/**
* @var callable|null a callback that will be called if the access should be denied
*/
public $denyCallback;

/**
* @var array the default configuration of the policy
*/
public $policyConfig = ['class' => 'yii\permission\components\PermissionPolicy'];

/**
* @var array the policies.
*/
public $policy = [];

/**
* Initializes the PermissionControl component.
*
* @return void
*/
public function init()
{
parent::init();
if ($this->user !== false) {
$this->user = Instance::ensure($this->user, User::class);
}
foreach ($this->policy as $i => $policy) {
if (is_array($policy)) {
$this->policy[$i] = Yii::createObject(array_merge($this->policyConfig, $policy));
}
}
}

/**
* Checks if the current user has permission to perform the given action.
*
* @param Action $action the action to be performed
* @throws ForbiddenHttpException if the user does not have permission
* @return bool true if the user has permission, false otherwise
*/
public function beforeAction($action)
{
$user = $this->user;
foreach ($this->policy as $policy) {
if ($allow = $policy->allows($action, $user)) {
return true;
} elseif ($allow === false) {
if (isset($policy->denyCallback)) {
call_user_func($policy->denyCallback, $policy, $action);
} elseif ($this->denyCallback !== null) {
call_user_func($this->denyCallback, $policy, $action);
} else {
$this->denyAccess($user);
}

return false;
}
}

if ($this->denyCallback !== null) {
call_user_func($this->denyCallback, null, $action);
} else {
$this->denyAccess($user);
}
return false;
}
/**
* 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|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 !== false && $user->getIsGuest()) {
$user->loginRequired();
} else {
throw new ForbiddenHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
}
}
}
74 changes: 74 additions & 0 deletions src/components/PermissionPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace yii\permission\components;

use Yii;
use yii\base\Component;
use yii\web\User;

class PermissionPolicy extends Component
{
/**
* @var bool whether this is an 'allow' rule or 'deny' rule.
*/
public $allow = false;

/**
* @var array|null list of the controller IDs that this rule applies to.
*/
public $actions = [];

/**
* @var array|null list of params that passed to Casbin.
*/
public $enforce = [];

/**
* @var callable|null a callback that will be called if the access should be denied
*/
public $denyCallback;

/**
* Checks whether the given action is allowed for the specified user.
*
* @param string $action the action to be checked
* @param User $user the user to be checked
*
* @return bool|null true if the action is allowed, false if not, null if the rule does not apply
*/
public function allows($action, $user)
{
if (
$this->matchAction($action)
&& $this->matchEnforce($user, $this->enforce)
) {
return $this->allow ? true : false;
}

return null;
}

/**
* Checks if the rule applies to the specified action.
*
* @param Action $action the action
* @return bool whether the rule applies to the action
*/
protected function matchAction($action)
{
return empty($this->actions) || in_array($action->id, $this->actions, true);
}

/**
* Checks if the rule applies to the specified user.
*
* @param User $user
* @param array $params
*
* @return bool
*/
protected function matchEnforce($user, $params)
{
return Yii::$app->permission->enforce($user->getId(), ...$params);
}
}
81 changes: 0 additions & 81 deletions tests/AdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@

namespace yii\permission\tests;

use PHPUnit\Framework\TestCase;
use yii\web\Application;
use Yii;
use yii\permission\models\CasbinRule;
use Casbin\Persist\Adapters\Filter;
use Casbin\Exceptions\InvalidFilterTypeException;
use yii\db\ActiveQueryInterface;

class AdapterTest extends TestCase
{
protected $app;

public function testEnforce()
{
$this->assertTrue(Yii::$app->permission->enforce('alice', 'data1', 'read'));
Expand Down Expand Up @@ -331,80 +326,4 @@ public function testLoadFilteredPolicy()
['alice', 'data1', 'read'],
], Yii::$app->permission->getPolicy());
}

public function createApplication()
{
$config = require __DIR__ . '/../vendor/yiisoft/yii2-app-basic/config/web.php';
$config['components']['permission'] = require __DIR__ . '/../config/permission.php';

$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');
$config['components']['db']['username'] = $this->env('DB_USERNAME', 'root');
$config['components']['db']['password'] = $this->env('DB_PASSWORD', '');

return new Application($config);
}

/**
* init table.
*/
protected function initTable()
{
$db = CasbinRule::getDb();
$tableName = CasbinRule::tableName();
$table = $db->getTableSchema($tableName);
if ($table) {
$db->createCommand()->dropTable($tableName)->execute();
}

Yii::$app->permission->init();

Yii::$app->db->createCommand()->batchInsert(
$tableName,
['ptype', 'v0', 'v1', 'v2'],
[
['p', 'alice', 'data1', 'read'],
['p', 'bob', 'data2', 'write'],
['p', 'data2_admin', 'data2', 'read'],
['p', 'data2_admin', 'data2', 'write'],
['g', 'alice', 'data2_admin', null],
]
)->execute();
}

/**
* Refresh the application instance.
*/
protected function refreshApplication()
{
$this->app = $this->createApplication();
}

/**
* This method is called before each test.
*/
protected function setUp(): void/* The :void return type declaration that should be here would cause a BC issue */
{
if (!$this->app) {
$this->refreshApplication();
}

$this->initTable();
}

/**
* This method is called after each test.
*/
protected function tearDown(): void/* The :void return type declaration that should be here would cause a BC issue */
{
}

protected function env($key, $default = null)
{
$value = getenv($key);
if (is_null($default)) {
return $value;
}

return false === $value ? $default : $value;
}
}
Loading

0 comments on commit b99acac

Please sign in to comment.