Skip to content

Commit e0a116b

Browse files
authored
feat: Implement UpdatableAdapter on adapter (#73)
This updates the adapter so that we can use updatePolicy from the enforcer. I also set up local testing to use sqlite so we can run the tests without a mysql instance.
1 parent ae174c1 commit e0a116b

File tree

5 files changed

+746
-43
lines changed

5 files changed

+746
-43
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"prepublish": "yarn run lint && yarn build",
1010
"build": "rimraf lib && tsc",
1111
"coverage": "jest --coverage --runInBand",
12-
"lint": "eslint src/**/*.{ts,tsx,js,jsx}",
13-
"fix": "eslint src/**/*.{ts,tsx,js,jsx} --fix",
12+
"lint": "eslint \"src/**/*.{ts,js,jsx}\"",
13+
"fix": "eslint \"src/**/*.{ts,js,jsx}\" --fix",
1414
"test": "jest --runInBand",
1515
"release": "npx -p semantic-release -p @semantic-release/git -p @semantic-release/changelog semantic-release",
1616
"prepare": "npm run build"
@@ -28,6 +28,7 @@
2828
"mysql2": "^2.1.0",
2929
"pg": "^8.4.2",
3030
"rimraf": "^2.6.2",
31+
"sqlite3": "^5.1.7",
3132
"ts-jest": "28.0.7",
3233
"typescript": "^5.2.2"
3334
},

src/adapter.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { Helper, Model, FilteredAdapter } from 'casbin';
15+
import { Helper, Model, FilteredAdapter, UpdatableAdapter } from 'casbin';
1616
import { CasbinRule } from './casbinRule';
1717
import {
1818
DataSource,
@@ -37,7 +37,9 @@ export interface TypeORMAdapterConfig {
3737
/**
3838
* TypeORMAdapter represents the TypeORM filtered adapter for policy storage.
3939
*/
40-
export default class TypeORMAdapter implements FilteredAdapter {
40+
export default class TypeORMAdapter
41+
implements FilteredAdapter, UpdatableAdapter
42+
{
4143
private adapterConfig?: TypeORMAdapterConfig;
4244
private option: DataSourceOptions;
4345
private typeorm: DataSource;
@@ -257,6 +259,31 @@ export default class TypeORMAdapter implements FilteredAdapter {
257259
}
258260
}
259261

262+
async updatePolicy(
263+
sec: string,
264+
ptype: string,
265+
oldRule: string[],
266+
newRule: string[],
267+
): Promise<void> {
268+
const { v0, v1, v2, v3, v4, v5, v6 } = this.savePolicyLine(ptype, oldRule);
269+
const newLine = this.savePolicyLine(ptype, newRule);
270+
271+
const foundLine = await this.getRepository().findOneOrFail({
272+
where: {
273+
ptype,
274+
v0,
275+
v1,
276+
v2,
277+
v3,
278+
v4,
279+
v5,
280+
v6,
281+
},
282+
});
283+
284+
await this.getRepository().save(Object.assign(foundLine, newLine));
285+
}
286+
260287
/**
261288
* removePolicy removes a policy rule from the storage.
262289
*/

test/adapter-config.test.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import {Enforcer, setDefaultFileSystem} from 'casbin';
15+
import { Enforcer, setDefaultFileSystem } from 'casbin';
1616
import {
1717
CreateDateColumn,
1818
DataSource,
1919
Entity,
20+
EntityNotFoundError,
2021
UpdateDateColumn,
2122
} from 'typeorm';
2223
import TypeORMAdapter, { CasbinRule } from '../src/index';
@@ -64,9 +65,36 @@ test(
6465
// The current policy means the policy in the Node-Casbin enforcer (aka in memory).
6566
await a.savePolicy(e.getModel());
6667

67-
const rules = await datasource.getRepository(CustomCasbinRule).find();
68+
const repository = datasource.getRepository(CustomCasbinRule);
69+
const rules = await repository.find();
6870
expect(rules[0].createdDate).not.toBeFalsy();
6971
expect(rules[0].updatedDate).not.toBeFalsy();
72+
73+
// Verify update method works
74+
const initialPolicy = ['bob', 'data3', 'write'];
75+
const updatedPolicy = ['bob', 'data3', 'read'];
76+
const getCurrentPolicyLinesFromDB = async () => {
77+
const policyRow = await repository.findOneByOrFail({
78+
v0: initialPolicy[0],
79+
v1: initialPolicy[1],
80+
});
81+
return [policyRow.v0, policyRow.v1, policyRow.v2];
82+
};
83+
84+
await a.addPolicy('', 'p', initialPolicy);
85+
expect(await getCurrentPolicyLinesFromDB()).toMatchObject(initialPolicy);
86+
87+
await a.updatePolicy('', 'p', initialPolicy, updatedPolicy);
88+
expect(await getCurrentPolicyLinesFromDB()).toMatchObject(updatedPolicy);
89+
90+
// We expect that we won't find a read policy anymore
91+
await expect(
92+
repository.findOneByOrFail({
93+
v0: initialPolicy[0],
94+
v1: initialPolicy[1],
95+
v2: initialPolicy[2],
96+
}),
97+
).rejects.toThrow(EntityNotFoundError);
7098
} finally {
7199
a.close();
72100
}

test/config.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
import { DataSourceOptions } from 'typeorm';
22

3-
export const connectionConfig: DataSourceOptions = {
4-
type: 'mysql',
5-
host: 'localhost',
6-
port: parseInt(process.env.MYSQL_PORT || '', 10) || 3306,
7-
username: process.env.MYSQL_USER || 'root',
8-
password:
9-
process.env.MYSQL_PASSWORD !== undefined
10-
? process.env.MYSQL_PASSWORD === ''
11-
? undefined
12-
: process.env.MYSQL_PASSWORD
13-
: 'password',
14-
database: process.env.MYSQL_DB || 'casbin',
15-
dropSchema: true,
16-
};
3+
const SHOULD_USE_MYSQL =
4+
process.env.MYSQL_USER != null ||
5+
process.env.MYSQL_PORT != null ||
6+
process.env.MYSQL_PASSWORD != null ||
7+
process.env.MYSQL_DB != null;
8+
9+
export const connectionConfig: DataSourceOptions = SHOULD_USE_MYSQL
10+
? {
11+
type: 'mysql',
12+
host: 'localhost',
13+
port: parseInt(process.env.MYSQL_PORT || '', 10) || 3306,
14+
username: process.env.MYSQL_USER || 'root',
15+
password:
16+
process.env.MYSQL_PASSWORD !== undefined
17+
? process.env.MYSQL_PASSWORD === ''
18+
? undefined
19+
: process.env.MYSQL_PASSWORD
20+
: 'password',
21+
database: process.env.MYSQL_DB || 'casbin',
22+
dropSchema: true,
23+
}
24+
: {
25+
type: 'sqlite',
26+
database: ':memory:',
27+
dropSchema: true,
28+
};

0 commit comments

Comments
 (0)