Skip to content

Commit dfa4a4b

Browse files
authored
Merge pull request #52 from audioeye/remove_deprecated_apis
fix: Remove deprecated APIs and fix adapter when filtering policy
2 parents 91339c2 + 65bd719 commit dfa4a4b

File tree

8 files changed

+2146
-2603
lines changed

8 files changed

+2146
-2603
lines changed

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,65 @@ async function myFunction() {
9191
});
9292

9393

94+
const e = await newEnforcer('examples/rbac_model.conf', a);
95+
96+
// Load the filtered policy from DB.
97+
await e.loadFilteredPolicy({
98+
'ptype': 'p',
99+
'v0': 'alice'
100+
});
101+
102+
// Check the permission.
103+
await e.enforce('alice', 'data1', 'read');
104+
105+
// Modify the policy.
106+
// await e.addPolicy(...);
107+
// await e.removePolicy(...);
108+
109+
// Save the policy back to DB.
110+
await e.savePolicy();
111+
}
112+
```
113+
114+
## Custom Entity Example
115+
Use a custom entity that matches the CasbinRule or MongoCasbinRule in order to add additional fields or metadata to the entity.
116+
117+
```typescript
118+
import { newEnforcer } from 'casbin';
119+
import {
120+
CreateDateColumn,
121+
UpdateDateColumn,
122+
} from 'typeorm';
123+
import TypeORMAdapter from 'typeorm-adapter';
124+
125+
@Entity('custom_rule')
126+
class CustomCasbinRule extends CasbinRule {
127+
@CreateDateColumn()
128+
createdDate: Date;
129+
130+
@UpdateDateColumn()
131+
updatedDate: Date;
132+
}
133+
134+
async function myFunction() {
135+
// Initialize a TypeORM adapter and use it in a Node-Casbin enforcer:
136+
// The adapter can not automatically create database.
137+
// But the adapter will automatically and use the table named "casbin_rule".
138+
// I think ORM should not automatically create databases.
139+
const a = await TypeORMAdapter.newAdapter(
140+
{
141+
type: 'mysql',
142+
host: 'localhost',
143+
port: 3306,
144+
username: 'root',
145+
password: '',
146+
database: 'casbin',
147+
},
148+
{
149+
customCasbinRuleEntity: CustomCasbinRule,
150+
},
151+
);
152+
94153
const e = await newEnforcer('examples/rbac_model.conf', a);
95154

96155
// Load the filtered policy from DB.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@
2020
"@types/node": "^10.11.7",
2121
"coveralls": "^3.0.2",
2222
"husky": "^1.1.2",
23-
"jest": "^23.6.0",
23+
"jest": "^28.1.3",
2424
"lint-staged": "^7.3.0",
2525
"mysql2": "^2.1.0",
2626
"pg": "^8.4.2",
2727
"rimraf": "^2.6.2",
28-
"ts-jest": "22.4.6",
28+
"ts-jest": "28.0.7",
2929
"tslint": "^5.11.0",
3030
"typescript": "^4.7.3"
3131
},
3232
"dependencies": {
3333
"casbin": "^5.11.5",
34+
"reflect-metadata": "^0.1.13",
3435
"typeorm": "^0.3.6"
3536
},
3637
"files": [

src/adapter.ts

Lines changed: 74 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,45 @@
1515
import { Helper, Model, FilteredAdapter } from 'casbin';
1616
import { CasbinRule } from './casbinRule';
1717
import {
18-
Connection,
19-
ConnectionOptions,
20-
createConnection,
21-
getRepository,
18+
DataSource,
19+
DataSourceOptions,
20+
FindOptionsWhere,
21+
Repository,
2222
} from 'typeorm';
2323
import { CasbinMongoRule } from './casbinMongoRule';
2424

2525
type GenericCasbinRule = CasbinRule | CasbinMongoRule;
2626
type CasbinRuleConstructor = new (...args: any[]) => GenericCasbinRule;
2727

2828
interface ExistentConnection {
29-
connection: Connection;
29+
connection: DataSource;
30+
}
31+
export type TypeORMAdapterOptions = ExistentConnection | DataSourceOptions;
32+
33+
export interface TypeORMAdapterConfig {
34+
customCasbinRuleEntity?: CasbinRuleConstructor;
3035
}
31-
export type TypeORMAdapterOptions = ExistentConnection | ConnectionOptions;
3236

3337
/**
3438
* TypeORMAdapter represents the TypeORM filtered adapter for policy storage.
3539
*/
3640
export default class TypeORMAdapter implements FilteredAdapter {
37-
private option: ConnectionOptions;
38-
private typeorm: Connection;
41+
private adapterConfig?: TypeORMAdapterConfig;
42+
private option: DataSourceOptions;
43+
private typeorm: DataSource;
3944
private filtered = false;
4045

41-
private constructor(option: TypeORMAdapterOptions) {
46+
private constructor(
47+
option: TypeORMAdapterOptions,
48+
adapterConfig?: TypeORMAdapterConfig,
49+
) {
50+
this.adapterConfig = adapterConfig;
51+
4252
if ((option as ExistentConnection).connection) {
4353
this.typeorm = (option as ExistentConnection).connection;
4454
this.option = this.typeorm.options;
4555
} else {
46-
this.option = option as ConnectionOptions;
56+
this.option = option as DataSourceOptions;
4757
}
4858
}
4959

@@ -54,47 +64,55 @@ export default class TypeORMAdapter implements FilteredAdapter {
5464
/**
5565
* newAdapter is the constructor.
5666
* @param option typeorm connection option
67+
* @param adapterConfig additional configuration options for the adapter
5768
*/
58-
public static async newAdapter(option: TypeORMAdapterOptions) {
69+
public static async newAdapter(
70+
option: TypeORMAdapterOptions,
71+
adapterConfig?: TypeORMAdapterConfig,
72+
) {
5973
let a: TypeORMAdapter;
6074

6175
const defaults = {
6276
synchronize: true,
6377
name: 'node-casbin-official',
6478
};
6579
if ((option as ExistentConnection).connection) {
66-
a = new TypeORMAdapter(option);
80+
a = new TypeORMAdapter(option, adapterConfig);
6781
} else {
68-
const options = option as ConnectionOptions;
69-
const entities = { entities: [this.getCasbinRuleType(options.type)] };
82+
const options = option as DataSourceOptions;
83+
const entities = {
84+
entities: [
85+
TypeORMAdapter.getCasbinRuleType(options.type, adapterConfig),
86+
],
87+
};
7088
const configuration = Object.assign(defaults, options);
71-
a = new TypeORMAdapter(Object.assign(configuration, entities));
89+
a = new TypeORMAdapter(
90+
Object.assign(configuration, entities),
91+
adapterConfig,
92+
);
7293
}
7394
await a.open();
7495
return a;
7596
}
7697

7798
private async open() {
7899
if (!this.typeorm) {
79-
this.typeorm = await createConnection(this.option);
100+
this.typeorm = new DataSource(this.option);
80101
}
81102

82-
if (!this.typeorm.isConnected) {
83-
await this.typeorm.connect();
103+
if (!this.typeorm.isInitialized) {
104+
await this.typeorm.initialize();
84105
}
85106
}
86107

87108
public async close() {
88-
if (this.typeorm.isConnected) {
89-
await this.typeorm.close();
109+
if (this.typeorm.isInitialized) {
110+
await this.typeorm.destroy();
90111
}
91112
}
92113

93114
private async clearTable() {
94-
await getRepository(
95-
this.getCasbinRuleConstructor(),
96-
this.option.name,
97-
).clear();
115+
await this.getRepository().clear();
98116
}
99117

100118
private loadPolicyLine(line: GenericCasbinRule, model: Model) {
@@ -112,22 +130,19 @@ export default class TypeORMAdapter implements FilteredAdapter {
112130
* loadPolicy loads all policy rules from the storage.
113131
*/
114132
public async loadPolicy(model: Model) {
115-
const lines = await getRepository(
116-
this.getCasbinRuleConstructor(),
117-
this.option.name,
118-
).find();
133+
const lines = await this.getRepository().find();
119134

120135
for (const line of lines) {
121136
this.loadPolicyLine(line, model);
122137
}
123138
}
124139

125140
// Loading policies based on filter condition
126-
public async loadFilteredPolicy(model: Model, filter: object) {
127-
const filteredLines = await getRepository(
128-
this.getCasbinRuleConstructor(),
129-
this.option.name,
130-
).find(filter);
141+
public async loadFilteredPolicy(
142+
model: Model,
143+
filter: FindOptionsWhere<GenericCasbinRule>,
144+
) {
145+
const filteredLines = await this.getRepository().find({ where: filter });
131146
for (const line of filteredLines) {
132147
this.loadPolicyLine(line, model);
133148
}
@@ -211,9 +226,7 @@ export default class TypeORMAdapter implements FilteredAdapter {
211226
*/
212227
public async addPolicy(sec: string, ptype: string, rule: string[]) {
213228
const line = this.savePolicyLine(ptype, rule);
214-
await getRepository(this.getCasbinRuleConstructor(), this.option.name).save(
215-
line,
216-
);
229+
await this.getRepository().save(line);
217230
}
218231

219232
/**
@@ -247,10 +260,7 @@ export default class TypeORMAdapter implements FilteredAdapter {
247260
*/
248261
public async removePolicy(sec: string, ptype: string, rule: string[]) {
249262
const line = this.savePolicyLine(ptype, rule);
250-
await getRepository(
251-
this.getCasbinRuleConstructor(),
252-
this.option.name,
253-
).delete({
263+
await this.getRepository().delete({
254264
...line,
255265
});
256266
}
@@ -260,7 +270,10 @@ export default class TypeORMAdapter implements FilteredAdapter {
260270
*/
261271
public async removePolicies(sec: string, ptype: string, rules: string[][]) {
262272
const queryRunner = this.typeorm.createQueryRunner();
263-
const type = TypeORMAdapter.getCasbinRuleType(this.option.type);
273+
const type = TypeORMAdapter.getCasbinRuleType(
274+
this.option.type,
275+
this.adapterConfig,
276+
);
264277

265278
await queryRunner.connect();
266279
await queryRunner.startTransaction();
@@ -313,27 +326,39 @@ export default class TypeORMAdapter implements FilteredAdapter {
313326
if (fieldIndex <= 6 && 6 < fieldIndex + fieldValues.length) {
314327
line.v6 = fieldValues[6 - fieldIndex];
315328
}
316-
await getRepository(
317-
this.getCasbinRuleConstructor(),
318-
this.option.name,
319-
).delete({
329+
330+
await this.getRepository().delete({
320331
...line,
321332
});
322333
}
323334

324335
private getCasbinRuleConstructor(): CasbinRuleConstructor {
325-
return TypeORMAdapter.getCasbinRuleType(this.option.type);
336+
return TypeORMAdapter.getCasbinRuleType(
337+
this.option.type,
338+
this.adapterConfig,
339+
);
326340
}
327341

328342
/**
329-
* Returns either a {@link CasbinRule} or a {@link CasbinMongoRule}, depending on the type. This switch is required as the normal
330-
* {@link CasbinRule} does not work when using MongoDB as a backend (due to a missing ObjectID field).
343+
* Returns either a {@link CasbinRule} or a {@link CasbinMongoRule}, depending on the type. If passed a custom entity through the adapter config it will use that entity type.
344+
* This switch is required as the normal {@link CasbinRule} does not work when using MongoDB as a backend (due to a missing ObjectID field).
331345
* @param type
332346
*/
333-
private static getCasbinRuleType(type: string): CasbinRuleConstructor {
347+
private static getCasbinRuleType(
348+
type: string,
349+
adapterConfig?: TypeORMAdapterConfig,
350+
): CasbinRuleConstructor {
351+
if (adapterConfig?.customCasbinRuleEntity) {
352+
return adapterConfig.customCasbinRuleEntity;
353+
}
354+
334355
if (type === 'mongodb') {
335356
return CasbinMongoRule;
336357
}
337358
return CasbinRule;
338359
}
360+
361+
private getRepository(): Repository<GenericCasbinRule> {
362+
return this.typeorm.getRepository(this.getCasbinRuleConstructor());
363+
}
339364
}

test/adapter-config.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2018 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 { Enforcer } from 'casbin';
16+
import {
17+
CreateDateColumn,
18+
DataSource,
19+
Entity,
20+
UpdateDateColumn,
21+
} from 'typeorm';
22+
import TypeORMAdapter, { CasbinRule } from '../src/index';
23+
import { connectionConfig } from './config';
24+
25+
@Entity('custom_rule')
26+
class CustomCasbinRule extends CasbinRule {
27+
@CreateDateColumn()
28+
public createdDate: Date;
29+
30+
@UpdateDateColumn()
31+
public updatedDate: Date;
32+
}
33+
34+
test(
35+
'TestAdapter',
36+
async () => {
37+
const datasource = new DataSource({
38+
...connectionConfig,
39+
entities: [CustomCasbinRule],
40+
synchronize: true,
41+
});
42+
43+
const a = await TypeORMAdapter.newAdapter(
44+
{ connection: datasource },
45+
{
46+
customCasbinRuleEntity: CustomCasbinRule,
47+
},
48+
);
49+
try {
50+
// Because the DB is empty at first,
51+
// so we need to load the policy from the file adapter (.CSV) first.
52+
const e = new Enforcer();
53+
54+
await e.initWithFile(
55+
'examples/rbac_model.conf',
56+
'examples/rbac_policy.csv',
57+
);
58+
59+
// This is a trick to save the current policy to the DB.
60+
// We can't call e.savePolicy() because the adapter in the enforcer is still the file adapter.
61+
// The current policy means the policy in the Node-Casbin enforcer (aka in memory).
62+
await a.savePolicy(e.getModel());
63+
64+
const rules = await datasource.getRepository(CustomCasbinRule).find();
65+
expect(rules[0].createdDate).not.toBeFalsy();
66+
expect(rules[0].updatedDate).not.toBeFalsy();
67+
} finally {
68+
a.close();
69+
}
70+
},
71+
60 * 1000,
72+
);

0 commit comments

Comments
 (0)