diff --git a/src/Adapter.php b/src/Adapter.php index 8a4b840..f6cbcc5 100755 --- a/src/Adapter.php +++ b/src/Adapter.php @@ -6,19 +6,27 @@ use Casbin\Model\Model; use Casbin\Persist\Adapter as AdapterContract; use Casbin\Persist\BatchAdapter as BatchAdapterContract; +use Casbin\Persist\FilteredAdapter as FilteredAdapterContract; use Casbin\Persist\AdapterHelper; +use Casbin\Persist\Adapters\Filter; +use Casbin\Exceptions\InvalidFilterTypeException; /** * DatabaseAdapter. * * @author techlee@qq.com */ -class Adapter implements AdapterContract, BatchAdapterContract +class Adapter implements AdapterContract, BatchAdapterContract, FilteredAdapterContract { use AdapterHelper; protected $casbinRule; + /** + * @var bool + */ + private $filtered = false; + public function __construct(CasbinRule $casbinRule) { $this->casbinRule = $casbinRule; @@ -181,4 +189,60 @@ public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex $this->casbinRule->deleteAll($where); } + + /** + * Loads only policy rules that match the filter. + * + * @param Model $model + * @param mixed $filter + */ + public function loadFilteredPolicy(Model $model, $filter): void + { + $entity = clone $this->casbinRule; + $entity = $entity->find(); + + if (is_string($filter)) { + $entity->where($filter); + } elseif ($filter instanceof Filter) { + foreach ($filter->p as $k => $v) { + $where[$v] = $filter->g[$k]; + $entity->where([$v => $filter->g[$k]]); + } + } elseif ($filter instanceof \Closure) { + $filter($entity); + } else { + throw new InvalidFilterTypeException('invalid filter type'); + } + + $rows = $entity->all(); + foreach ($rows as $row) { + unset($row->id); + $row = $row->toArray(); + $line = implode(', ', array_filter($row, function ($val) { + return '' != $val && !is_null($val); + })); + $this->loadPolicyLine(trim($line), $model); + } + $this->setFiltered(true); + } + + /** + * Returns true if the loaded policy has been filtered. + * + * @return bool + */ + public function isFiltered(): bool + { + return $this->filtered; + } + + /** + * Sets filtered parameter. + * + * @param bool $filtered + */ + public function setFiltered(bool $filtered): void + { + $this->filtered = $filtered; + } } diff --git a/tests/AdapterTest.php b/tests/AdapterTest.php index fe79999..21b196a 100755 --- a/tests/AdapterTest.php +++ b/tests/AdapterTest.php @@ -6,6 +6,9 @@ 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 { @@ -103,6 +106,48 @@ public function testRemoveFilteredPolicy() $this->assertFalse(Yii::$app->permission->enforce('alice', 'data2', 'write')); } + public function testLoadFilteredPolicy() + { + Yii::$app->permission->clearPolicy(); + $adapter = Yii::$app->permission->getAdapter(); + $adapter->setFiltered(true); + $this->assertEquals([], Yii::$app->permission->getPolicy()); + + // invalid filter type + try { + $filter = ['alice', 'data1', 'read']; + Yii::$app->permission->loadFilteredPolicy($filter); + $exception = InvalidFilterTypeException::class; + $this->fail("Expected exception $exception not thrown"); + } catch (InvalidFilterTypeException $exception) { + $this->assertEquals("invalid filter type", $exception->getMessage()); + } + + // string + $filter = "v0 = 'bob'"; + Yii::$app->permission->loadFilteredPolicy($filter); + $this->assertEquals([ + ['bob', 'data2', 'write'] + ], Yii::$app->permission->getPolicy()); + + // Filter + $filter = new Filter(['v2'], ['read']); + Yii::$app->permission->loadFilteredPolicy($filter); + $this->assertEquals([ + ['alice', 'data1', 'read'], + ['data2_admin', 'data2', 'read'], + ], Yii::$app->permission->getPolicy()); + + // Closure + Yii::$app->permission->loadFilteredPolicy(function (ActiveQueryInterface &$entity) { + $entity->where(['v1' => 'data1']); + }); + + $this->assertEquals([ + ['alice', 'data1', 'read'], + ], Yii::$app->permission->getPolicy()); + } + public function createApplication() { $config = require __DIR__ . '/../vendor/yiisoft/yii2-app-basic/config/web.php';