Skip to content

Commit

Permalink
Merge pull request #15 from basakest/updatable-adapter
Browse files Browse the repository at this point in the history
feat: Support Casbin UpdatableAdapter interface
  • Loading branch information
leeqvip authored Sep 26, 2021
2 parents 6d4976b + 163412f commit 1b5fed5
Show file tree
Hide file tree
Showing 3 changed files with 311 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '12'
node-version: '14.17'

- name: Run semantic-release
env:
Expand Down
133 changes: 126 additions & 7 deletions src/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Casbin\Persist\Adapter as AdapterContract;
use Casbin\Persist\BatchAdapter as BatchAdapterContract;
use Casbin\Persist\FilteredAdapter as FilteredAdapterContract;
use Casbin\Persist\UpdatableAdapter as UpdatableAdapterContract;
use Casbin\Persist\AdapterHelper;
use Casbin\Persist\Adapters\Filter;
use Casbin\Exceptions\InvalidFilterTypeException;
Expand All @@ -16,7 +17,7 @@
*
* @author [email protected]
*/
class Adapter implements AdapterContract, BatchAdapterContract, FilteredAdapterContract
class Adapter implements AdapterContract, BatchAdapterContract, FilteredAdapterContract, UpdatableAdapterContract
{
use AdapterHelper;

Expand Down Expand Up @@ -162,19 +163,19 @@ public function removePolicies(string $sec, string $ptype, array $rules): void
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
}

/**
* RemoveFilteredPolicy removes policy rules that match the filter from the storage.
* This is part of the Auto-Save feature.
*
* @param string $sec
* @param string $ptype
* @param int $fieldIndex
* @param string ...$fieldValues
* @param int $fieldIndex
* @param string|null ...$fieldValues
* @return array
* @throws Throwable
*/
public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void
public function _removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, ?string ...$fieldValues): array
{
$where = [];
$where['ptype'] = $ptype;
Expand All @@ -187,7 +188,31 @@ public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex
}
}

$removedRules = $this->casbinRule->find()->where($where)->all();
$this->casbinRule->deleteAll($where);

array_walk($removedRules, function (&$removedRule) {
unset($removedRule->id);
unset($removedRule->ptype);
$removedRule = $removedRule->toArray();
$removedRule = $this->filterRule($removedRule);
});

return $removedRules;
}

/**
* RemoveFilteredPolicy removes policy rules that match the filter from the storage.
* This is part of the Auto-Save feature.
*
* @param string $sec
* @param string $ptype
* @param int $fieldIndex
* @param string ...$fieldValues
*/
public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void
{
$this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues);
}

/**
Expand Down Expand Up @@ -226,6 +251,100 @@ public function loadFilteredPolicy(Model $model, $filter): void
$this->setFiltered(true);
}

/**
* Updates a policy rule from storage.
* This is part of the Auto-Save feature.
*
* @param string $sec
* @param string $ptype
* @param string[] $oldRule
* @param string[] $newPolicy
*/
public function updatePolicy(string $sec, string $ptype, array $oldRule, array $newPolicy): void
{
$entity = clone $this->casbinRule;

$condition['ptype'] = $ptype;
foreach ($oldRule as $k => $v) {
$condition['v' . $k] = $v;
}
$item = $entity->findOne($condition);
foreach ($newPolicy as $k => $v) {
$key = 'v' . $k;
$item->$key = $v;
}
$item->update();
}

/**
* UpdatePolicies updates some policy rules to storage, like db, redis.
*
* @param string $sec
* @param string $ptype
* @param string[][] $oldRules
* @param string[][] $newRules
* @return void
*/
public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): void
{
$transaction = $this->casbinRule->getDb()->beginTransaction();
try {
foreach ($oldRules as $i => $oldRule) {
$this->updatePolicy($sec, $ptype, $oldRule, $newRules[$i]);
}
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
}

/**
* UpdateFilteredPolicies deletes old rules and adds new rules.
*
* @param string $sec
* @param string $ptype
* @param array $newPolicies
* @param integer $fieldIndex
* @param string ...$fieldValues
* @return array
*/
public function updateFilteredPolicies(string $sec, string $ptype, array $newRules, int $fieldIndex, ?string ...$fieldValues): array
{
$oldRules = [];
$transaction = $this->casbinRule->getDb()->beginTransaction();
try {
$oldRules = $this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues);
$this->addPolicies($sec, $ptype, $newRules);
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}

return $oldRules;
}

/**
* Filter the rule.
*
* @param array $rule
* @return array
*/
public function filterRule(array $rule): array
{
$rule = array_values($rule);

$i = count($rule) - 1;
for (; $i >= 0; $i--) {
if ($rule[$i] != "" && !is_null($rule[$i])) {
break;
}
}

return array_slice($rule, 0, $i + 1);
}

/**
* Returns true if the loaded policy has been filtered.
*
Expand Down
184 changes: 184 additions & 0 deletions tests/AdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,190 @@ public function testRemoveFilteredPolicy()
$this->assertFalse(Yii::$app->permission->enforce('alice', 'data2', 'write'));
}

public function testUpdatePolicy()
{
$this->assertEquals([
['alice', 'data1', 'read'],
['bob', 'data2', 'write'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
], Yii::$app->permission->getPolicy());

Yii::$app->permission->updatePolicy(
['alice', 'data1', 'read'],
['alice', 'data1', 'write']
);

Yii::$app->permission->updatePolicy(
['bob', 'data2', 'write'],
['bob', 'data2', 'read']
);

$this->assertEquals([
['alice', 'data1', 'write'],
['bob', 'data2', 'read'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
], Yii::$app->permission->getPolicy());
}

public function testUpdatePolicies()
{
$this->assertEquals([
['alice', 'data1', 'read'],
['bob', 'data2', 'write'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
], Yii::$app->permission->getPolicy());

$oldPolicies = [
['alice', 'data1', 'read'],
['bob', 'data2', 'write']
];
$newPolicies = [
['alice', 'data1', 'write'],
['bob', 'data2', 'read']
];

Yii::$app->permission->updatePolicies($oldPolicies, $newPolicies);

$this->assertEquals([
['alice', 'data1', 'write'],
['bob', 'data2', 'read'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
], Yii::$app->permission->getPolicy());
}

public function arrayEqualsWithoutOrder(array $expected, array $actual)
{
if (method_exists($this, 'assertEqualsCanonicalizing')) {
$this->assertEqualsCanonicalizing($expected, $actual);
} else {
array_multisort($expected);
array_multisort($actual);
$this->assertEquals($expected, $actual);
}
}

public function testUpdateFilteredPolicies()
{
$this->assertEquals([
['alice', 'data1', 'read'],
['bob', 'data2', 'write'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
], Yii::$app->permission->getPolicy());

Yii::$app->permission->updateFilteredPolicies([["alice", "data1", "write"]], 0, "alice", "data1", "read");
Yii::$app->permission->updateFilteredPolicies([["bob", "data2", "read"]], 0, "bob", "data2", "write");

$policies = [
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
['alice', 'data1', 'write'],
['bob', 'data2', 'read'],
];
$this->arrayEqualsWithoutOrder($policies, Yii::$app->permission->getPolicy());

// test use updateFilteredPolicies to update all policies of a user
$this->initTable();
$this->refreshApplication();

$policies = [
['alice', 'data2', 'write'],
['bob', 'data1', 'read']
];

Yii::$app->permission->addPolicies($policies);
$this->arrayEqualsWithoutOrder([
['alice', 'data1', 'read'],
['bob', 'data2', 'write'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
['alice', 'data2', 'write'],
['bob', 'data1', 'read']
], Yii::$app->permission->getPolicy());

Yii::$app->permission->updateFilteredPolicies([['alice', 'data1', 'write'], ['alice', 'data2', 'read']], 0, 'alice');
Yii::$app->permission->updateFilteredPolicies([['bob', 'data1', 'write'], ["bob", "data2", "read"]], 0, 'bob');

$policies = [
['alice', 'data1', 'write'],
['alice', 'data2', 'read'],
['bob', 'data1', 'write'],
['bob', 'data2', 'read'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write']
];

$this->arrayEqualsWithoutOrder($policies, Yii::$app->permission->getPolicy());

// test if $fieldValues contains empty string
$this->initTable();
$this->refreshApplication();

$policies = [
['alice', 'data2', 'write'],
['bob', 'data1', 'read']
];
Yii::$app->permission->addPolicies($policies);

$this->assertEquals([
['alice', 'data1', 'read'],
['bob', 'data2', 'write'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
['alice', 'data2', 'write'],
['bob', 'data1', 'read']
], Yii::$app->permission->getPolicy());

Yii::$app->permission->updateFilteredPolicies([['alice', 'data1', 'write'], ['alice', 'data2', 'read']], 0, 'alice', '', '');
Yii::$app->permission->updateFilteredPolicies([['bob', 'data1', 'write'], ["bob", "data2", "read"]], 0, 'bob', '', '');

$policies = [
['alice', 'data1', 'write'],
['alice', 'data2', 'read'],
['bob', 'data1', 'write'],
['bob', 'data2', 'read'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write']
];

$this->arrayEqualsWithoutOrder($policies, Yii::$app->permission->getPolicy());

// test if $fieldIndex is not zero
$this->initTable();
$this->refreshApplication();

$policies = [
['alice', 'data2', 'write'],
['bob', 'data1', 'read']
];
Yii::$app->permission->addPolicies($policies);

$this->assertEquals([
['alice', 'data1', 'read'],
['bob', 'data2', 'write'],
['data2_admin', 'data2', 'read'],
['data2_admin', 'data2', 'write'],
['alice', 'data2', 'write'],
['bob', 'data1', 'read']
], Yii::$app->permission->getPolicy());

Yii::$app->permission->updateFilteredPolicies([['alice', 'data1', 'edit'], ['bob', 'data1', 'edit']], 2, 'read');
Yii::$app->permission->updateFilteredPolicies([['alice', 'data2', 'read'], ["bob", "data2", "read"]], 2, 'write');

$policies = [
['alice', 'data1', 'edit'],
['alice', 'data2', 'read'],
['bob', 'data1', 'edit'],
['bob', 'data2', 'read'],
];

$this->arrayEqualsWithoutOrder($policies, Yii::$app->permission->getPolicy());
}

public function testLoadFilteredPolicy()
{
Yii::$app->permission->clearPolicy();
Expand Down

0 comments on commit 1b5fed5

Please sign in to comment.