diff --git a/package.json b/package.json index 10c0342..4dd5994 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@casbin/expression-eval": "^5.2.0", "await-lock": "^2.0.1", "buffer": "^6.0.3", - "csv-parse": "^5.3.5", + "csv-parse": "^5.5.6", "minimatch": "^7.4.2" }, "files": [ diff --git a/src/persist/fileAdapter.ts b/src/persist/fileAdapter.ts index e6b7f8c..b4926a3 100644 --- a/src/persist/fileAdapter.ts +++ b/src/persist/fileAdapter.ts @@ -34,11 +34,11 @@ export class FileAdapter implements Adapter { private async loadPolicyFile(model: Model, handler: (line: string, model: Model) => void): Promise { const bodyBuf = await (this.fs ? this.fs : mustGetDefaultFileSystem()).readFileSync(this.filePath); const lines = bodyBuf.toString().split('\n'); - lines.forEach((n: string, index: number) => { - if (!n) { + lines.forEach((line: string) => { + if (!line || line.trim().startsWith('#')) { return; } - handler(n, model); + handler(line, model); }); } diff --git a/src/persist/helper.ts b/src/persist/helper.ts index 141b886..2ddbb88 100644 --- a/src/persist/helper.ts +++ b/src/persist/helper.ts @@ -1,23 +1,83 @@ import { Model } from '../model'; import { parse } from 'csv-parse/sync'; -export class Helper { - public static loadPolicyLine(line: string, model: Model): void { +export interface IPolicyParser { + parse(line: string): string[][] | null; +} + +export class BasicCsvParser implements IPolicyParser { + parse(line: string): string[][] | null { if (!line || line.trimStart().charAt(0) === '#') { - return; + return null; } - const tokens = parse(line, { + return parse(line, { delimiter: ',', skip_empty_lines: true, trim: true, + relax_quotes: true, }); + } +} + +export class BracketAwareCsvParser implements IPolicyParser { + private readonly baseParser: IPolicyParser; + + constructor(baseParser: IPolicyParser = new BasicCsvParser()) { + this.baseParser = baseParser; + } + + parse(line: string): string[][] | null { + const rawTokens = this.baseParser.parse(line); + if (!rawTokens || !rawTokens[0]) { + return null; + } + + const tokens = rawTokens[0]; + const processedTokens: string[] = []; + let currentToken = ''; + let bracketCount = 0; + + for (const token of tokens) { + for (const char of token) { + if (char === '(') bracketCount++; + else if (char === ')') bracketCount--; + } + + currentToken += (currentToken ? ',' : '') + token; + + if (bracketCount === 0) { + processedTokens.push(currentToken); + currentToken = ''; + } + } + + if (bracketCount !== 0) { + throw new Error(`Unmatched brackets in policy line: ${line}`); + } + + return processedTokens.length > 0 ? [processedTokens] : null; + } +} + +export class PolicyLoader { + private readonly parser: IPolicyParser; + constructor(parser: IPolicyParser = new BracketAwareCsvParser()) { + this.parser = parser; + } + + loadPolicyLine(line: string, model: Model): void { + const tokens = this.parser.parse(line); if (!tokens || !tokens[0]) { return; } - const key = tokens[0][0]; + let key = tokens[0][0].trim(); + if (key.startsWith('"') && key.endsWith('"')) { + key = key.slice(1, -1); + } + const sec = key.substring(0, 1); const item = model.model.get(sec); if (!item) { @@ -28,6 +88,22 @@ export class Helper { if (!policy) { return; } - policy.policy.push(tokens[0].slice(1)); + + const values = tokens[0].slice(1).map((v) => { + if (v.startsWith('"') && v.endsWith('"')) { + v = v.slice(1, -1); + } + return v.replace(/""/g, '"').trim(); + }); + + policy.policy.push(values); + } +} + +export class Helper { + private static readonly policyLoader = new PolicyLoader(); + + public static loadPolicyLine(line: string, model: Model): void { + Helper.policyLoader.loadPolicyLine(line, model); } } diff --git a/test/persist/helper.test.ts b/test/persist/helper.test.ts index aa3217c..cfacaa1 100644 --- a/test/persist/helper.test.ts +++ b/test/persist/helper.test.ts @@ -45,7 +45,7 @@ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act ['admin', '/', 'POST'], ['admin', '/', 'PUT'], ['admin', '/', 'DELETE'], - [' admin', '/ ', 'PATCH'], + ['admin', '/', 'PATCH'], ]; testdata.forEach((n) => { diff --git a/yarn.lock b/yarn.lock index 0c6a1c8..1646a31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2282,10 +2282,10 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csv-parse@^5.3.5: - version "5.3.5" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.3.5.tgz#9924bbba9f7056122f06b7af18edc1a7f022ce99" - integrity sha512-8O5KTIRtwmtD3+EVfW6BCgbwZqJbhTYsQZry12F1TP5RUp0sD9tp1UnCWic3n0mLOhzeocYaCZNYxOGSg3dmmQ== +csv-parse@^5.5.6: + version "5.5.6" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.5.6.tgz#0d726d58a60416361358eec291a9f93abe0b6b1a" + integrity sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A== cz-conventional-changelog@3.2.0: version "3.2.0"