Skip to content

Commit 514ab2a

Browse files
authored
feat: allow customizing CasbinRuleTable by providing optional argument (#30)
1 parent 34b1f02 commit 514ab2a

File tree

6 files changed

+98
-27
lines changed

6 files changed

+98
-27
lines changed

.github/workflows/ci.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,17 @@ jobs:
3232
- run: npm test -t pg-adapter
3333
mysql_and_mysql2:
3434
runs-on: ubuntu-latest
35-
3635
services:
3736
mysql:
38-
image: bitnami/mysql
37+
image: bitnami/mysql:5.7
3938
ports:
4039
- 3306:3306
4140
env:
41+
ALLOW_EMPTY_PASSWORD: yes
4242
MYSQL_DATABASE: casbin
43-
MYSQL_ROOT_PASSWORD: password
44-
MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password
43+
MYSQL_USER: casbin
44+
MYSQL_PASSWORD: password
4545
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
46-
4746
steps:
4847
- uses: actions/checkout@v2
4948
- run: npm i
@@ -59,15 +58,22 @@ jobs:
5958
- run: npm test -t sqlite3-adapter
6059

6160
mssql:
62-
runs-on: ubuntu-latest
61+
runs-on: ubuntu-20.04
6362
steps:
63+
- name: Install SQLCMD Tools
64+
run: |
65+
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
66+
curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
67+
sudo apt-get update
68+
sudo ACCEPT_EULA=Y apt-get install -y mssql-tools unixodbc-dev
69+
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> $GITHUB_ENV
6470
- name: run sqlserver
6571
env:
6672
SA_PASSWORD: Passw0rd
6773
run: |
68-
docker run --name sqlserver -it -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD='$SA_PASSWORD'' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest
74+
docker run --name sqlserver -it -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD='$SA_PASSWORD'' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest
6975
echo 'awaiting sqlserver bootup' && sleep 15
70-
sqlcmd -S 127.0.0.1 -U sa -P $SA_PASSWORD -Q 'CREATE DATABASE casbin'
76+
sqlcmd -C -S 127.0.0.1 -U sa -P $SA_PASSWORD -No -Q 'CREATE DATABASE casbin'
7177
- uses: actions/checkout@v2
7278
- run: npm i
7379
- run: npm test -t mssql-adapter

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,66 @@ async function myFunction() {
5959
await e.savePolicy();
6060
```
6161
62+
## Custom Table Name Example
63+
64+
By default, each adapter creates its own table in the database. While this might seem convenient, it poses a significant drawback when multiple adapters need to share the same database: policies cannot be shared across adapters if they are stored in separate tables.
65+
66+
### Why Customization is Important
67+
68+
**Compliance with Naming Conventions:**
69+
Many production databases follow strict naming conventions. By customizing the table name, you can ensure that your adapter’s tables comply with these conventions, which is essential for production environments.
70+
71+
**Shared Policies:**
72+
When the same set of policies should be accessible by different adapters or services, having them in separate tables prevents a unified view. Customizing the table name allows you to consolidate policies into a single table that can be shared across adapters.
73+
74+
**Avoiding Duplication:**
75+
If each adapter creates its own table by default, it can lead to unnecessary duplication of data. This not only wastes storage space but can also lead to inconsistent policy enforcement across different parts of your application.
76+
77+
**Simplified Management:**
78+
With a single, customized table for all policies, managing, updating, and querying policy data becomes much easier. This is especially beneficial in environments where policies need to be audited or maintained centrally.
79+
80+
### How to Customize the Table Name
81+
82+
Most adapters offer a configuration option to set the table name explicitly. For example:
83+
84+
```ts
85+
import { newEnforcer } from 'casbin';
86+
import { ConnectionPool } from 'mssql';
87+
import { BasicAdapter } from 'casbin-basic-adapter';
88+
89+
const tableName = 'dbo.policies'// instead of the default 'casbin_rule'
90+
91+
async function myFunction() {
92+
const a = await BasicAdapter.newAdapter('mssql',
93+
new ConnectionPool({
94+
server: 'localhost',
95+
port: 1433,
96+
user: 'usr',
97+
password: 'pwd',
98+
database: 'casbin',
99+
options: {
100+
encrypt: true,
101+
trustServerCertificate: true,
102+
},
103+
}),
104+
tableName
105+
);
106+
107+
const e = await newEnforcer('examples/rbac_model.conf', a);
108+
109+
// Check the permission.
110+
e.enforce('alice', 'data1', 'read');
111+
112+
// Modify the policy.
113+
// await e.addPolicy(...);
114+
// await e.removePolicy(...);
115+
116+
// Save the policy back to DB.
117+
await e.savePolicy();
118+
```
119+
120+
By setting the `tableName` option, you ensure that all adapters point to the same table, enabling seamless policy sharing and compliance with any required naming conventions.
121+
62122
## Getting Help
63123
64124
- [Node-Casbin](https://github.com/casbin/node-casbin)

src/adapter.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,18 @@ export type Instance = {
3535
mssql: mssql.ConnectionPool;
3636
};
3737

38-
const CasbinRuleTable = 'casbin_rule';
39-
4038
export class BasicAdapter<T extends keyof Instance> implements Adapter {
4139
private knex: Knex.Knex;
4240
private config: Config;
4341
private drive: T;
4442
private client: Instance[T];
43+
private tableName: string;
4544

46-
private constructor(drive: T, client: Instance[T]) {
45+
private constructor(
46+
drive: T,
47+
client: Instance[T],
48+
tableName: string = 'casbin_rule',
49+
) {
4750
this.config = {
4851
client: drive,
4952
useNullAsDefault: drive === 'sqlite3',
@@ -52,13 +55,15 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
5255
this.knex = Knex.knex(this.config);
5356
this.drive = drive;
5457
this.client = client;
58+
this.tableName = tableName;
5559
}
5660

5761
static async newAdapter<T extends keyof Instance>(
5862
drive: T,
5963
client: Instance[T],
64+
tableName: string = 'casbin_rule',
6065
): Promise<BasicAdapter<T>> {
61-
const a = new BasicAdapter(drive, client);
66+
const a = new BasicAdapter(drive, client, tableName);
6267
await a.connect();
6368
await a.createTable();
6469

@@ -67,7 +72,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
6772

6873
async loadPolicy(model: Model): Promise<void> {
6974
const result = await this.query(
70-
this.knex.select().from(CasbinRuleTable).toQuery(),
75+
this.knex.select().from(this.tableName).toQuery(),
7176
);
7277

7378
for (const line of result) {
@@ -76,7 +81,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
7681
}
7782

7883
async savePolicy(model: Model): Promise<boolean> {
79-
await this.query(this.knex.del().from(CasbinRuleTable).toQuery());
84+
await this.query(this.knex.del().from(this.tableName).toQuery());
8085

8186
let astMap = model.model.get('p')!;
8287
const processes: Array<Promise<CasbinRule[]>> = [];
@@ -85,7 +90,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
8590
for (const rule of ast.policy) {
8691
const line = this.savePolicyLine(ptype, rule);
8792
const p = this.query(
88-
this.knex.insert(line).into(CasbinRuleTable).toQuery(),
93+
this.knex.insert(line).into(this.tableName).toQuery(),
8994
);
9095
processes.push(p);
9196
}
@@ -96,7 +101,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
96101
for (const rule of ast.policy) {
97102
const line = this.savePolicyLine(ptype, rule);
98103
const p = this.query(
99-
this.knex.insert(line).into(CasbinRuleTable).toQuery(),
104+
this.knex.insert(line).into(this.tableName).toQuery(),
100105
);
101106
processes.push(p);
102107
}
@@ -109,7 +114,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
109114

110115
async addPolicy(sec: string, ptype: string, rule: string[]): Promise<void> {
111116
const line = this.savePolicyLine(ptype, rule);
112-
await this.query(this.knex.insert(line).into(CasbinRuleTable).toQuery());
117+
await this.query(this.knex.insert(line).into(this.tableName).toQuery());
113118
}
114119

115120
async addPolicies(
@@ -121,7 +126,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
121126
for (const rule of rules) {
122127
const line = this.savePolicyLine(ptype, rule);
123128
const p = this.query(
124-
this.knex.insert(line).into(CasbinRuleTable).toQuery(),
129+
this.knex.insert(line).into(this.tableName).toQuery(),
125130
);
126131
processes.push(p);
127132
}
@@ -136,7 +141,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
136141
): Promise<void> {
137142
const line = this.savePolicyLine(ptype, rule);
138143
await this.query(
139-
this.knex.del().where(line).from(CasbinRuleTable).toQuery(),
144+
this.knex.del().where(line).from(this.tableName).toQuery(),
140145
);
141146
}
142147

@@ -149,7 +154,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
149154
for (const rule of rules) {
150155
const line = this.savePolicyLine(ptype, rule);
151156
const p = this.query(
152-
this.knex.del().where(line).from(CasbinRuleTable).toQuery(),
157+
this.knex.del().where(line).from(this.tableName).toQuery(),
153158
);
154159
processes.push(p);
155160
}
@@ -186,7 +191,7 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
186191
}
187192

188193
await this.query(
189-
this.knex.del().where(line).from(CasbinRuleTable).toQuery(),
194+
this.knex.del().where(line).from(this.tableName).toQuery(),
190195
);
191196
}
192197

@@ -264,13 +269,13 @@ export class BasicAdapter<T extends keyof Instance> implements Adapter {
264269

265270
private async createTable(): Promise<void> {
266271
const tableExists = await this.query(
267-
this.knex.schema.hasTable(CasbinRuleTable).toString(),
272+
this.knex.schema.hasTable(this.tableName).toString(),
268273
);
269274

270275
if (tableExists.length > 0) return;
271276

272277
const createTableSQL = this.knex.schema
273-
.createTable(CasbinRuleTable, (table) => {
278+
.createTable(this.tableName, (table) => {
274279
table.increments();
275280
table.string('ptype').notNullable();
276281
for (const i of ['v0', 'v1', 'v2', 'v3', 'v4', 'v5']) {

test/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: '3.8'
22
services:
33
mysql:
4-
image: mysql:5
4+
image: bitnami/mysql:5.7
55
environment:
66
MYSQL_ROOT_PASSWORD: password
77
MYSQL_DATABASE: casbin
@@ -20,7 +20,7 @@ services:
2020
expose:
2121
- '5432'
2222
mssql:
23-
image: mcr.microsoft.com/azure-sql-edge
23+
image: mcr.microsoft.com/mssql/server:2022-latest
2424
environment:
2525
SA_PASSWORD: Passw0rd
2626
ACCEPT_EULA: Y

test/mysql-adapter.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ test(
2020
Runner(
2121
'mysql',
2222
createConnection({
23-
user: 'root',
23+
user: 'casbin',
2424
password: 'password',
2525
database: 'casbin',
2626
}),

test/mysql2-adapter.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ test(
2020
Runner(
2121
'mysql2',
2222
createConnection({
23-
user: 'root',
23+
user: 'casbin',
2424
password: 'password',
2525
database: 'casbin',
2626
}),

0 commit comments

Comments
 (0)