Skip to content

Commit

Permalink
feat: Implement UpdatableAdapter on adapter
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkalberer committed Mar 26, 2024
1 parent ae174c1 commit cc727c9
Show file tree
Hide file tree
Showing 5 changed files with 746 additions and 43 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"prepublish": "yarn run lint && yarn build",
"build": "rimraf lib && tsc",
"coverage": "jest --coverage --runInBand",
"lint": "eslint src/**/*.{ts,tsx,js,jsx}",
"fix": "eslint src/**/*.{ts,tsx,js,jsx} --fix",
"lint": "eslint \"src/**/*.{ts,js,jsx}\"",
"fix": "eslint \"src/**/*.{ts,js,jsx}\" --fix",
"test": "jest --runInBand",
"release": "npx -p semantic-release -p @semantic-release/git -p @semantic-release/changelog semantic-release",
"prepare": "npm run build"
Expand All @@ -28,6 +28,7 @@
"mysql2": "^2.1.0",
"pg": "^8.4.2",
"rimraf": "^2.6.2",
"sqlite3": "^5.1.7",
"ts-jest": "28.0.7",
"typescript": "^5.2.2"
},
Expand Down
31 changes: 29 additions & 2 deletions src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { Helper, Model, FilteredAdapter } from 'casbin';
import { Helper, Model, FilteredAdapter, UpdatableAdapter } from 'casbin';
import { CasbinRule } from './casbinRule';
import {
DataSource,
Expand All @@ -37,7 +37,9 @@ export interface TypeORMAdapterConfig {
/**
* TypeORMAdapter represents the TypeORM filtered adapter for policy storage.
*/
export default class TypeORMAdapter implements FilteredAdapter {
export default class TypeORMAdapter
implements FilteredAdapter, UpdatableAdapter
{
private adapterConfig?: TypeORMAdapterConfig;
private option: DataSourceOptions;
private typeorm: DataSource;
Expand Down Expand Up @@ -257,6 +259,31 @@ export default class TypeORMAdapter implements FilteredAdapter {
}
}

async updatePolicy(
sec: string,
ptype: string,
oldRule: string[],
newRule: string[],
): Promise<void> {
const { v0, v1, v2, v3, v4, v5, v6 } = this.savePolicyLine(ptype, oldRule);
const newLine = this.savePolicyLine(ptype, newRule);

const foundLine = await this.getRepository().findOneOrFail({
where: {
ptype,
v0,
v1,
v2,
v3,
v4,
v5,
v6,
},
});

await this.getRepository().save(Object.assign(foundLine, newLine));
}

/**
* removePolicy removes a policy rule from the storage.
*/
Expand Down
32 changes: 30 additions & 2 deletions test/adapter-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {Enforcer, setDefaultFileSystem} from 'casbin';
import { Enforcer, setDefaultFileSystem } from 'casbin';
import {
CreateDateColumn,
DataSource,
Entity,
EntityNotFoundError,
UpdateDateColumn,
} from 'typeorm';
import TypeORMAdapter, { CasbinRule } from '../src/index';
Expand Down Expand Up @@ -64,9 +65,36 @@ test(
// The current policy means the policy in the Node-Casbin enforcer (aka in memory).
await a.savePolicy(e.getModel());

const rules = await datasource.getRepository(CustomCasbinRule).find();
const repository = datasource.getRepository(CustomCasbinRule);
const rules = await repository.find();
expect(rules[0].createdDate).not.toBeFalsy();
expect(rules[0].updatedDate).not.toBeFalsy();

// Verify update method works
const initialPolicy = ['bob', 'data3', 'write'];
const updatedPolicy = ['bob', 'data3', 'read'];
const getCurrentPolicyLinesFromDB = async () => {
const policyRow = await repository.findOneByOrFail({
v0: initialPolicy[0],
v1: initialPolicy[1],
});
return [policyRow.v0, policyRow.v1, policyRow.v2];
};

await a.addPolicy('', 'p', initialPolicy);
expect(await getCurrentPolicyLinesFromDB()).toMatchObject(initialPolicy);

await a.updatePolicy('', 'p', initialPolicy, updatedPolicy);
expect(await getCurrentPolicyLinesFromDB()).toMatchObject(updatedPolicy);

// We expect that we won't find a read policy anymore
await expect(
repository.findOneByOrFail({
v0: initialPolicy[0],
v1: initialPolicy[1],
v2: initialPolicy[2],
}),
).rejects.toThrow(EntityNotFoundError);
} finally {
a.close();
}
Expand Down
40 changes: 26 additions & 14 deletions test/config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { DataSourceOptions } from 'typeorm';

export const connectionConfig: DataSourceOptions = {
type: 'mysql',
host: 'localhost',
port: parseInt(process.env.MYSQL_PORT || '', 10) || 3306,
username: process.env.MYSQL_USER || 'root',
password:
process.env.MYSQL_PASSWORD !== undefined
? process.env.MYSQL_PASSWORD === ''
? undefined
: process.env.MYSQL_PASSWORD
: 'password',
database: process.env.MYSQL_DB || 'casbin',
dropSchema: true,
};
const SHOULD_USE_MYSQL =
process.env.MYSQL_USER != null ||
process.env.MYSQL_PORT != null ||
process.env.MYSQL_PASSWORD != null ||
process.env.MYSQL_DB != null;

export const connectionConfig: DataSourceOptions = SHOULD_USE_MYSQL
? {
type: 'mysql',
host: 'localhost',
port: parseInt(process.env.MYSQL_PORT || '', 10) || 3306,
username: process.env.MYSQL_USER || 'root',
password:
process.env.MYSQL_PASSWORD !== undefined
? process.env.MYSQL_PASSWORD === ''
? undefined
: process.env.MYSQL_PASSWORD
: 'password',
database: process.env.MYSQL_DB || 'casbin',
dropSchema: true,
}
: {
type: 'sqlite',
database: ':memory:',
dropSchema: true,
};
Loading

0 comments on commit cc727c9

Please sign in to comment.