Skip to content

Commit 1b5fed5

Browse files
authored
Merge pull request #15 from basakest/updatable-adapter
feat: Support Casbin UpdatableAdapter interface
2 parents 6d4976b + 163412f commit 1b5fed5

File tree

3 files changed

+311
-8
lines changed

3 files changed

+311
-8
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
- uses: actions/checkout@v2
7777
- uses: actions/setup-node@v1
7878
with:
79-
node-version: '12'
79+
node-version: '14.17'
8080

8181
- name: Run semantic-release
8282
env:

src/Adapter.php

+126-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Casbin\Persist\Adapter as AdapterContract;
88
use Casbin\Persist\BatchAdapter as BatchAdapterContract;
99
use Casbin\Persist\FilteredAdapter as FilteredAdapterContract;
10+
use Casbin\Persist\UpdatableAdapter as UpdatableAdapterContract;
1011
use Casbin\Persist\AdapterHelper;
1112
use Casbin\Persist\Adapters\Filter;
1213
use Casbin\Exceptions\InvalidFilterTypeException;
@@ -16,7 +17,7 @@
1617
*
1718
1819
*/
19-
class Adapter implements AdapterContract, BatchAdapterContract, FilteredAdapterContract
20+
class Adapter implements AdapterContract, BatchAdapterContract, FilteredAdapterContract, UpdatableAdapterContract
2021
{
2122
use AdapterHelper;
2223

@@ -162,19 +163,19 @@ public function removePolicies(string $sec, string $ptype, array $rules): void
162163
$transaction->commit();
163164
} catch (\Exception $e) {
164165
$transaction->rollBack();
166+
throw $e;
165167
}
166168
}
167169

168170
/**
169-
* RemoveFilteredPolicy removes policy rules that match the filter from the storage.
170-
* This is part of the Auto-Save feature.
171-
*
172171
* @param string $sec
173172
* @param string $ptype
174-
* @param int $fieldIndex
175-
* @param string ...$fieldValues
173+
* @param int $fieldIndex
174+
* @param string|null ...$fieldValues
175+
* @return array
176+
* @throws Throwable
176177
*/
177-
public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void
178+
public function _removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, ?string ...$fieldValues): array
178179
{
179180
$where = [];
180181
$where['ptype'] = $ptype;
@@ -187,7 +188,31 @@ public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex
187188
}
188189
}
189190

191+
$removedRules = $this->casbinRule->find()->where($where)->all();
190192
$this->casbinRule->deleteAll($where);
193+
194+
array_walk($removedRules, function (&$removedRule) {
195+
unset($removedRule->id);
196+
unset($removedRule->ptype);
197+
$removedRule = $removedRule->toArray();
198+
$removedRule = $this->filterRule($removedRule);
199+
});
200+
201+
return $removedRules;
202+
}
203+
204+
/**
205+
* RemoveFilteredPolicy removes policy rules that match the filter from the storage.
206+
* This is part of the Auto-Save feature.
207+
*
208+
* @param string $sec
209+
* @param string $ptype
210+
* @param int $fieldIndex
211+
* @param string ...$fieldValues
212+
*/
213+
public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void
214+
{
215+
$this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues);
191216
}
192217

193218
/**
@@ -226,6 +251,100 @@ public function loadFilteredPolicy(Model $model, $filter): void
226251
$this->setFiltered(true);
227252
}
228253

254+
/**
255+
* Updates a policy rule from storage.
256+
* This is part of the Auto-Save feature.
257+
*
258+
* @param string $sec
259+
* @param string $ptype
260+
* @param string[] $oldRule
261+
* @param string[] $newPolicy
262+
*/
263+
public function updatePolicy(string $sec, string $ptype, array $oldRule, array $newPolicy): void
264+
{
265+
$entity = clone $this->casbinRule;
266+
267+
$condition['ptype'] = $ptype;
268+
foreach ($oldRule as $k => $v) {
269+
$condition['v' . $k] = $v;
270+
}
271+
$item = $entity->findOne($condition);
272+
foreach ($newPolicy as $k => $v) {
273+
$key = 'v' . $k;
274+
$item->$key = $v;
275+
}
276+
$item->update();
277+
}
278+
279+
/**
280+
* UpdatePolicies updates some policy rules to storage, like db, redis.
281+
*
282+
* @param string $sec
283+
* @param string $ptype
284+
* @param string[][] $oldRules
285+
* @param string[][] $newRules
286+
* @return void
287+
*/
288+
public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): void
289+
{
290+
$transaction = $this->casbinRule->getDb()->beginTransaction();
291+
try {
292+
foreach ($oldRules as $i => $oldRule) {
293+
$this->updatePolicy($sec, $ptype, $oldRule, $newRules[$i]);
294+
}
295+
$transaction->commit();
296+
} catch (\Exception $e) {
297+
$transaction->rollBack();
298+
throw $e;
299+
}
300+
}
301+
302+
/**
303+
* UpdateFilteredPolicies deletes old rules and adds new rules.
304+
*
305+
* @param string $sec
306+
* @param string $ptype
307+
* @param array $newPolicies
308+
* @param integer $fieldIndex
309+
* @param string ...$fieldValues
310+
* @return array
311+
*/
312+
public function updateFilteredPolicies(string $sec, string $ptype, array $newRules, int $fieldIndex, ?string ...$fieldValues): array
313+
{
314+
$oldRules = [];
315+
$transaction = $this->casbinRule->getDb()->beginTransaction();
316+
try {
317+
$oldRules = $this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues);
318+
$this->addPolicies($sec, $ptype, $newRules);
319+
$transaction->commit();
320+
} catch (\Exception $e) {
321+
$transaction->rollBack();
322+
throw $e;
323+
}
324+
325+
return $oldRules;
326+
}
327+
328+
/**
329+
* Filter the rule.
330+
*
331+
* @param array $rule
332+
* @return array
333+
*/
334+
public function filterRule(array $rule): array
335+
{
336+
$rule = array_values($rule);
337+
338+
$i = count($rule) - 1;
339+
for (; $i >= 0; $i--) {
340+
if ($rule[$i] != "" && !is_null($rule[$i])) {
341+
break;
342+
}
343+
}
344+
345+
return array_slice($rule, 0, $i + 1);
346+
}
347+
229348
/**
230349
* Returns true if the loaded policy has been filtered.
231350
*

tests/AdapterTest.php

+184
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,190 @@ public function testRemoveFilteredPolicy()
106106
$this->assertFalse(Yii::$app->permission->enforce('alice', 'data2', 'write'));
107107
}
108108

109+
public function testUpdatePolicy()
110+
{
111+
$this->assertEquals([
112+
['alice', 'data1', 'read'],
113+
['bob', 'data2', 'write'],
114+
['data2_admin', 'data2', 'read'],
115+
['data2_admin', 'data2', 'write'],
116+
], Yii::$app->permission->getPolicy());
117+
118+
Yii::$app->permission->updatePolicy(
119+
['alice', 'data1', 'read'],
120+
['alice', 'data1', 'write']
121+
);
122+
123+
Yii::$app->permission->updatePolicy(
124+
['bob', 'data2', 'write'],
125+
['bob', 'data2', 'read']
126+
);
127+
128+
$this->assertEquals([
129+
['alice', 'data1', 'write'],
130+
['bob', 'data2', 'read'],
131+
['data2_admin', 'data2', 'read'],
132+
['data2_admin', 'data2', 'write'],
133+
], Yii::$app->permission->getPolicy());
134+
}
135+
136+
public function testUpdatePolicies()
137+
{
138+
$this->assertEquals([
139+
['alice', 'data1', 'read'],
140+
['bob', 'data2', 'write'],
141+
['data2_admin', 'data2', 'read'],
142+
['data2_admin', 'data2', 'write'],
143+
], Yii::$app->permission->getPolicy());
144+
145+
$oldPolicies = [
146+
['alice', 'data1', 'read'],
147+
['bob', 'data2', 'write']
148+
];
149+
$newPolicies = [
150+
['alice', 'data1', 'write'],
151+
['bob', 'data2', 'read']
152+
];
153+
154+
Yii::$app->permission->updatePolicies($oldPolicies, $newPolicies);
155+
156+
$this->assertEquals([
157+
['alice', 'data1', 'write'],
158+
['bob', 'data2', 'read'],
159+
['data2_admin', 'data2', 'read'],
160+
['data2_admin', 'data2', 'write'],
161+
], Yii::$app->permission->getPolicy());
162+
}
163+
164+
public function arrayEqualsWithoutOrder(array $expected, array $actual)
165+
{
166+
if (method_exists($this, 'assertEqualsCanonicalizing')) {
167+
$this->assertEqualsCanonicalizing($expected, $actual);
168+
} else {
169+
array_multisort($expected);
170+
array_multisort($actual);
171+
$this->assertEquals($expected, $actual);
172+
}
173+
}
174+
175+
public function testUpdateFilteredPolicies()
176+
{
177+
$this->assertEquals([
178+
['alice', 'data1', 'read'],
179+
['bob', 'data2', 'write'],
180+
['data2_admin', 'data2', 'read'],
181+
['data2_admin', 'data2', 'write'],
182+
], Yii::$app->permission->getPolicy());
183+
184+
Yii::$app->permission->updateFilteredPolicies([["alice", "data1", "write"]], 0, "alice", "data1", "read");
185+
Yii::$app->permission->updateFilteredPolicies([["bob", "data2", "read"]], 0, "bob", "data2", "write");
186+
187+
$policies = [
188+
['data2_admin', 'data2', 'read'],
189+
['data2_admin', 'data2', 'write'],
190+
['alice', 'data1', 'write'],
191+
['bob', 'data2', 'read'],
192+
];
193+
$this->arrayEqualsWithoutOrder($policies, Yii::$app->permission->getPolicy());
194+
195+
// test use updateFilteredPolicies to update all policies of a user
196+
$this->initTable();
197+
$this->refreshApplication();
198+
199+
$policies = [
200+
['alice', 'data2', 'write'],
201+
['bob', 'data1', 'read']
202+
];
203+
204+
Yii::$app->permission->addPolicies($policies);
205+
$this->arrayEqualsWithoutOrder([
206+
['alice', 'data1', 'read'],
207+
['bob', 'data2', 'write'],
208+
['data2_admin', 'data2', 'read'],
209+
['data2_admin', 'data2', 'write'],
210+
['alice', 'data2', 'write'],
211+
['bob', 'data1', 'read']
212+
], Yii::$app->permission->getPolicy());
213+
214+
Yii::$app->permission->updateFilteredPolicies([['alice', 'data1', 'write'], ['alice', 'data2', 'read']], 0, 'alice');
215+
Yii::$app->permission->updateFilteredPolicies([['bob', 'data1', 'write'], ["bob", "data2", "read"]], 0, 'bob');
216+
217+
$policies = [
218+
['alice', 'data1', 'write'],
219+
['alice', 'data2', 'read'],
220+
['bob', 'data1', 'write'],
221+
['bob', 'data2', 'read'],
222+
['data2_admin', 'data2', 'read'],
223+
['data2_admin', 'data2', 'write']
224+
];
225+
226+
$this->arrayEqualsWithoutOrder($policies, Yii::$app->permission->getPolicy());
227+
228+
// test if $fieldValues contains empty string
229+
$this->initTable();
230+
$this->refreshApplication();
231+
232+
$policies = [
233+
['alice', 'data2', 'write'],
234+
['bob', 'data1', 'read']
235+
];
236+
Yii::$app->permission->addPolicies($policies);
237+
238+
$this->assertEquals([
239+
['alice', 'data1', 'read'],
240+
['bob', 'data2', 'write'],
241+
['data2_admin', 'data2', 'read'],
242+
['data2_admin', 'data2', 'write'],
243+
['alice', 'data2', 'write'],
244+
['bob', 'data1', 'read']
245+
], Yii::$app->permission->getPolicy());
246+
247+
Yii::$app->permission->updateFilteredPolicies([['alice', 'data1', 'write'], ['alice', 'data2', 'read']], 0, 'alice', '', '');
248+
Yii::$app->permission->updateFilteredPolicies([['bob', 'data1', 'write'], ["bob", "data2", "read"]], 0, 'bob', '', '');
249+
250+
$policies = [
251+
['alice', 'data1', 'write'],
252+
['alice', 'data2', 'read'],
253+
['bob', 'data1', 'write'],
254+
['bob', 'data2', 'read'],
255+
['data2_admin', 'data2', 'read'],
256+
['data2_admin', 'data2', 'write']
257+
];
258+
259+
$this->arrayEqualsWithoutOrder($policies, Yii::$app->permission->getPolicy());
260+
261+
// test if $fieldIndex is not zero
262+
$this->initTable();
263+
$this->refreshApplication();
264+
265+
$policies = [
266+
['alice', 'data2', 'write'],
267+
['bob', 'data1', 'read']
268+
];
269+
Yii::$app->permission->addPolicies($policies);
270+
271+
$this->assertEquals([
272+
['alice', 'data1', 'read'],
273+
['bob', 'data2', 'write'],
274+
['data2_admin', 'data2', 'read'],
275+
['data2_admin', 'data2', 'write'],
276+
['alice', 'data2', 'write'],
277+
['bob', 'data1', 'read']
278+
], Yii::$app->permission->getPolicy());
279+
280+
Yii::$app->permission->updateFilteredPolicies([['alice', 'data1', 'edit'], ['bob', 'data1', 'edit']], 2, 'read');
281+
Yii::$app->permission->updateFilteredPolicies([['alice', 'data2', 'read'], ["bob", "data2", "read"]], 2, 'write');
282+
283+
$policies = [
284+
['alice', 'data1', 'edit'],
285+
['alice', 'data2', 'read'],
286+
['bob', 'data1', 'edit'],
287+
['bob', 'data2', 'read'],
288+
];
289+
290+
$this->arrayEqualsWithoutOrder($policies, Yii::$app->permission->getPolicy());
291+
}
292+
109293
public function testLoadFilteredPolicy()
110294
{
111295
Yii::$app->permission->clearPolicy();

0 commit comments

Comments
 (0)