Skip to content

Commit a3218f1

Browse files
authored
feat: add updatePolicy() (#234)
* feat: add updatePolicy() Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * feat: add unittest for updatePolicy() Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * feat: add updateForUpdatePolicy() Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * feat: add updatePolicy() Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * refactor: rename interface Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * docs: fix comment Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * fix: fix adapter error Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * feat: add updatableFileAdapter Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * fix: remove incorrect claim and fix comment Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * fix: fix comment Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * perf: refactor updatePolicy Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * fix: fix index Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * style: prevent conflict Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * fix: remove UpdatableFileAdapter Signed-off-by: Zxilly <zhouxinyu1001@gmail.com> * fix: fix unittest() In fact,this makes unittest cannot pass.The fix will refer to issue #235 Signed-off-by: Zxilly <zhouxinyu1001@gmail.com>
1 parent da608ae commit a3218f1

File tree

7 files changed

+131
-3
lines changed

7 files changed

+131
-3
lines changed

src/coreEnforcer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { compile, compileAsync } from 'expression-eval';
1616

1717
import { DefaultEffector, Effect, Effector } from './effect';
1818
import { FunctionMap, Model, newModel, PolicyOp } from './model';
19-
import { Adapter, FilteredAdapter, Watcher, BatchAdapter } from './persist';
19+
import { Adapter, FilteredAdapter, Watcher, BatchAdapter, UpdatableAdapter } from './persist';
2020
import { DefaultRoleManager, RoleManager } from './rbac';
2121
import { escapeAssertion, generateGFunction, getEvalValue, hasEval, replaceEval, generatorRunSync, generatorRunAsync } from './util';
2222
import { getLogger, logPrint } from './log';
@@ -33,7 +33,7 @@ export class CoreEnforcer {
3333
protected eft: Effector = new DefaultEffector();
3434
private matcherMap: Map<string, Matcher> = new Map();
3535

36-
protected adapter: FilteredAdapter | Adapter | BatchAdapter;
36+
protected adapter: UpdatableAdapter | FilteredAdapter | Adapter | BatchAdapter;
3737
protected watcher: Watcher | null = null;
3838
protected rm: RoleManager = new DefaultRoleManager(10);
3939

src/internalEnforcer.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
// limitations under the License.
1414

1515
import { CoreEnforcer } from './coreEnforcer';
16-
import { BatchAdapter } from './persist/batchAdapter';
16+
import { BatchAdapter } from './persist';
17+
import { UpdatableAdapter } from './persist';
1718
import { PolicyOp } from './model';
1819

1920
/**
@@ -85,6 +86,43 @@ export class InternalEnforcer extends CoreEnforcer {
8586
return ok;
8687
}
8788

89+
/**
90+
* updatePolicyInternal updates a rule from the current policy.
91+
*/
92+
public async updatePolicyInternal(sec: string, ptype: string, oldRule: string[], newRule: string[]): Promise<boolean> {
93+
if (!this.model.hasPolicy(sec, ptype, oldRule)) {
94+
return false;
95+
}
96+
97+
if (this.autoSave) {
98+
if ('updatePolicy' in this.adapter) {
99+
try {
100+
await this.adapter.updatePolicy(sec, ptype, oldRule, newRule);
101+
} catch (e) {
102+
if (e.message !== 'not implemented') {
103+
throw e;
104+
}
105+
}
106+
} else {
107+
throw new Error('cannot to update policy, the adapter does not implement the UpdatableAdapter');
108+
}
109+
}
110+
111+
if (this.watcher && this.autoNotifyWatcher) {
112+
// In fact I think it should wait for the respond, but they implement add_policy() like this
113+
// error intentionally ignored
114+
this.watcher.update();
115+
}
116+
117+
const ok = this.model.updatePolicy(sec, ptype, oldRule, newRule);
118+
if (sec === 'g' && ok) {
119+
await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, [oldRule]);
120+
await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, [newRule]);
121+
}
122+
123+
return ok;
124+
}
125+
88126
/**
89127
* removePolicyInternal removes a rule from the current policy.
90128
*/

src/managementEnforcer.ts

+27
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,33 @@ export class ManagementEnforcer extends InternalEnforcer {
276276
return this.addPoliciesInternal('p', ptype, rules);
277277
}
278278

279+
/**
280+
* updatePolicy updates an authorization rule from the current policy.
281+
* If the rule not exists, the function returns false.
282+
* Otherwise the function returns true by changing it to the new rule.
283+
*
284+
* @return succeeds or not.
285+
* @param oldRule the policy will be remove
286+
* @param newRule the policy will be added
287+
*/
288+
public async updatePolicy(oldRule: string[], newRule: string[]): Promise<boolean> {
289+
return this.updateNamedPolicy('p', oldRule, newRule);
290+
}
291+
292+
/**
293+
* updateNamedPolicy updates an authorization rule from the current named policy.
294+
* If the rule not exists, the function returns false.
295+
* Otherwise the function returns true by changing it to the new rule.
296+
*
297+
* @param ptype the policy type, can be "p", "p2", "p3", ..
298+
* @param oldRule the policy rule will be remove
299+
* @param newRule the policy rule will be added
300+
* @return succeeds or not.
301+
*/
302+
public async updateNamedPolicy(ptype: string, oldRule: string[], newRule: string[]): Promise<boolean> {
303+
return this.updatePolicyInternal('p', ptype, oldRule, newRule);
304+
}
305+
279306
/**
280307
* removePolicy removes an authorization rule from the current policy.
281308
*

src/model/model.ts

+17
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,23 @@ export class Model {
245245
return [true, rules];
246246
}
247247

248+
// updatePolicy updates a policy from the model
249+
public updatePolicy(sec: string, ptype: string, oldRule: string[], newRule: string[]): boolean {
250+
if (this.hasPolicy(sec, ptype, oldRule)) {
251+
const ast = this.model.get(sec)?.get(ptype);
252+
if (!ast) {
253+
return false;
254+
}
255+
// const index = ast.policy.indexOf(oldRule);
256+
const index = ast.policy.findIndex((r) => util.arrayEquals(r, oldRule));
257+
if (index !== -1) {
258+
ast.policy[index] = newRule;
259+
return true;
260+
}
261+
}
262+
return false;
263+
}
264+
248265
// removePolicy removes a policy rule from the model.
249266
public removePolicy(sec: string, key: string, rule: string[]): boolean {
250267
if (this.hasPolicy(sec, key, rule)) {

src/persist/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export * from './watcher';
66
export * from './filteredAdapter';
77
export * from './defaultFilteredAdapter';
88
export * from './batchAdapter';
9+
export * from './batchFileAdapter';
10+
export * from './updatableAdapter';

src/persist/updatableAdapter.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2021 The Casbin Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Adapter } from './adapter';
16+
17+
// UpdatableAdapter is the interface for Casbin adapters with update policy functions.
18+
export interface UpdatableAdapter extends Adapter {
19+
// UpdatePolicy updates a policy rule from storage.
20+
// This is part of the Auto-Save feature.
21+
updatePolicy(sec: string, ptype: string, oldRule: string[], newRule: string[]): Promise<void>;
22+
}

test/managementAPI.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,28 @@ test('addNamedPolicies', async () => {
185185
}
186186
});
187187

188+
test('updatePolicy', async () => {
189+
const a = new FileAdapter('examples/rbac_policy.csv');
190+
e.setAdapter(a);
191+
const p = ['alice', 'data1', 'read'];
192+
const q = ['alice', 'data2', 'read'];
193+
const updated = await e.updatePolicy(p, q);
194+
expect(updated).toBe(true);
195+
expect(await e.hasPolicy(...p)).toBe(false);
196+
expect(await e.hasPolicy(...q)).toBe(true);
197+
});
198+
199+
test('updateNamedPolicy', async () => {
200+
const a = new FileAdapter('examples/rbac_policy.csv');
201+
e.setAdapter(a);
202+
const p = ['alice', 'data1', 'read'];
203+
const q = ['alice', 'data2', 'read'];
204+
const updated = await e.updateNamedPolicy('p', p, q);
205+
expect(updated).toBe(true);
206+
expect(await e.hasPolicy(...p)).toBe(false);
207+
expect(await e.hasPolicy(...q)).toBe(true);
208+
});
209+
188210
test('removePolicy', async () => {
189211
const p = ['alice', 'data1', 'read'];
190212
const removed = await e.removePolicy(...p);

0 commit comments

Comments
 (0)