Skip to content

Commit 0890017

Browse files
committed
feat(store): add concept of policy statement groups (#71)
* refactor: make ParsedPolicyStatement its own thing * refactor: use arrayify in parser * feat: add addGroup and deleteGroup * fix(store): use correct filter for exact reindexing * chore(store): add tests for add/deleteGroup * refactor: add -> set since we are replacing statements by sid * chore: add more test coverage
1 parent bfe2586 commit 0890017

15 files changed

+546
-633
lines changed

lib/__fixtures__/contexts.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export const allowedContext = {
2+
subject: {
3+
id: 1234,
4+
},
5+
doc: {
6+
createdBy: 1234,
7+
},
8+
};
9+
10+
export const deniedContext = {
11+
subject: {
12+
groups: ["group5"],
13+
},
14+
};
15+
16+
export enum Actions {
17+
create = "create",
18+
read = "read",
19+
createDocument = "documents:createDocument",
20+
readDocument = "documents:readDocument",
21+
signDocuments = "sign:documents",
22+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as statements from "./statements";
2+
import { parsePolicyStatement } from "../parsed-policy-statement";
3+
4+
export const GlobAllStatement = parsePolicyStatement(
5+
statements.GlobAllStatement
6+
);
7+
export const GlobEndStatement = parsePolicyStatement(
8+
statements.GlobEndStatement
9+
);
10+
export const GlobStartStatement = parsePolicyStatement(
11+
statements.GlobStartStatement
12+
);
13+
14+
export const BasicAllowStatement = parsePolicyStatement(
15+
statements.BasicAllowStatement
16+
);
17+
export const MultipleActionsStatement = parsePolicyStatement(
18+
statements.MultipleActionsStatement
19+
);
20+
export const ContextualAllowStatement = parsePolicyStatement(
21+
statements.ContextualAllowStatement
22+
);
23+
export const ContextualDenyStatement = parsePolicyStatement(
24+
statements.ContextualDenyStatement
25+
);
26+
export const ContextualGlobStatement = parsePolicyStatement(
27+
statements.ContextualGlobStatement
28+
);

lib/__fixtures__/statements.ts

Lines changed: 58 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,72 @@
1-
import { parsePolicyStatement } from "../parsed-policy-statement";
1+
import { PolicyStatement } from "../types";
2+
import { Actions } from "./contexts";
23

3-
export const GlobAllStatement = parsePolicyStatement(
4-
{
5-
action: "*",
6-
effect: "allow",
7-
constraint: true,
8-
},
9-
"GlobAllStatement"
10-
);
11-
12-
export const GlobStartStatement = parsePolicyStatement(
13-
{
14-
action: "*:documents",
15-
effect: "allow",
16-
constraint: true,
17-
},
18-
"GlobStartStatement"
19-
);
4+
export const GlobAllStatement: PolicyStatement = {
5+
sid: "GlobAllStatement",
6+
action: "*",
7+
effect: "allow",
8+
constraint: true,
9+
};
2010

21-
export const GlobEndStatement = parsePolicyStatement(
22-
{
23-
action: "documents:*",
24-
effect: "allow",
25-
constraint: true,
26-
},
27-
"GlobEndStatement"
28-
);
11+
export const GlobStartStatement: PolicyStatement = {
12+
sid: "GlobStartStatement",
13+
action: "*:documents",
14+
effect: "allow",
15+
constraint: true,
16+
};
2917

30-
export const BasicAllowStatement = parsePolicyStatement(
31-
{
32-
action: "create",
33-
effect: "allow",
34-
constraint: true,
35-
},
36-
"BasicAllowStatement"
37-
);
18+
export const GlobEndStatement: PolicyStatement = {
19+
sid: "GlobEndStatement",
20+
action: "documents:*",
21+
effect: "allow",
22+
constraint: true,
23+
};
3824

39-
export const MultipleActionsStatement = parsePolicyStatement(
40-
{
41-
action: ["create", "read"],
42-
effect: "allow",
43-
constraint: true,
44-
},
45-
"MultipleActionsStatement"
46-
);
25+
export const BasicAllowStatement: PolicyStatement = {
26+
sid: "BasicAllowStatement",
27+
action: Actions.create,
28+
effect: "allow",
29+
constraint: true,
30+
};
4731

48-
export const allowedContext = {
49-
subject: {
50-
id: 1234,
51-
},
52-
doc: {
53-
createdBy: 1234,
54-
},
32+
export const MultipleActionsStatement: PolicyStatement = {
33+
sid: "MultipleActionsStatement",
34+
action: [Actions.create, Actions.read],
35+
effect: "allow",
36+
constraint: true,
5537
};
5638

57-
export const ContextualAllowStatement = parsePolicyStatement(
58-
{
59-
action: "create",
60-
effect: "allow",
61-
constraint: {
62-
"===": [{ var: "subject.id" }, { var: "doc.createdBy" }],
63-
},
39+
export const ContextualAllowStatement: PolicyStatement = {
40+
sid: "ContextualAllowStatement",
41+
action: Actions.create,
42+
effect: "allow",
43+
constraint: {
44+
"===": [{ var: "subject.id" }, { var: "doc.createdBy" }],
6445
},
65-
"ContextualAllowStatement"
66-
);
46+
};
6747

68-
export const deniedContext = {
69-
subject: {
70-
groups: ["group5"],
48+
export const ContextualDenyStatement: PolicyStatement = {
49+
action: Actions.create,
50+
effect: "deny",
51+
sid: "ContextualDenyStatement",
52+
constraint: {
53+
some: [{ var: "subject.groups" }, { in: [{ var: "" }, ["group5"]] }],
7154
},
7255
};
7356

74-
export const ContextualDenyStatement = parsePolicyStatement(
75-
{
76-
action: "create",
77-
effect: "deny",
78-
constraint: {
79-
some: [{ var: "subject.groups" }, { in: [{ var: "" }, ["group5"]] }],
80-
},
57+
export const ContextualGlobStatement: PolicyStatement = {
58+
sid: "ContextualGlobStatement",
59+
action: "*",
60+
effect: "allow",
61+
constraint: {
62+
"===": [{ var: "subject.id" }, { var: "doc.createdBy" }],
8163
},
82-
"ContextualDenyStatement"
83-
);
64+
};
8465

85-
export const InvalidConstraintStatement = parsePolicyStatement(
86-
{
87-
action: "create",
88-
effect: "allow",
89-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
90-
constraint: { foo: "bar" } as any,
91-
},
92-
"InvalidConstraintStatement"
93-
);
66+
export const InvalidConstraintStatement: PolicyStatement = {
67+
sid: "InvalidConstraintStatement",
68+
action: Actions.create,
69+
effect: "allow",
70+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
71+
constraint: { foo: "bar" } as any,
72+
};

lib/parsed-policy-statement.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { RulesLogic } from "json-logic-js";
22
import { randomUUID } from "node:crypto";
33
import { PolicyStatement } from "./types";
4+
import { arrayify } from "./utils/arr";
45
import { traverseRulesLogic } from "./utils/logic";
56
import {
67
isStringLiteral,
@@ -14,13 +15,18 @@ export type ActionsByType = {
1415
globAll: boolean;
1516
};
1617

17-
export const SYM_SID = Symbol("PolicyStatementID");
18+
export type ParsedPolicyStatement = {
19+
/** a globally-unique statement identifier */
20+
sid: string;
1821

19-
export type ParsedPolicyStatement = PolicyStatement & {
22+
/** an optional grouping identifier */
23+
gid?: string;
24+
25+
effect: PolicyStatement["effect"];
26+
constraint: PolicyStatement["constraint"];
2027
/** paths referenced in the logic with { var: 'my.path' } */
2128
contextPaths: string[];
2229
actionsByType: ActionsByType;
23-
[SYM_SID]: string;
2430
};
2531

2632
const LIST_OPS = ["map", "reduce", "filter", "all", "none", "some"];
@@ -40,9 +46,7 @@ function extractVarPaths(logic: RulesLogic): string[] {
4046
}
4147

4248
// syntactic sugar means var could be one of string, [string], or [string, string]
43-
const args = Array.isArray(innerLogic.var)
44-
? innerLogic.var
45-
: [innerLogic.var];
49+
const args = arrayify(innerLogic.var);
4650
if (typeof args[0] !== "string") {
4751
throw new Error(
4852
`var: only path strings are permitted (at ${path.join(".")})`
@@ -80,17 +84,25 @@ export function parsePolicyActions(actions: string[]): ActionsByType {
8084
return res;
8185
}
8286

87+
interface ParseOptions {
88+
sid?: string;
89+
}
90+
8391
export function parsePolicyStatement(
8492
statement: PolicyStatement,
85-
sid?: string
93+
opts: ParseOptions = {}
8694
): ParsedPolicyStatement {
87-
const { action, constraint } = statement;
88-
const actions = Array.isArray(action) ? action : [action];
95+
const { action, constraint, effect } = statement;
96+
const actions = arrayify(action);
97+
98+
const sid = opts.sid ?? statement.sid ?? randomUUID();
8999

90100
return {
91-
...statement,
101+
sid,
102+
103+
effect,
104+
constraint,
92105
contextPaths: extractVarPaths(constraint),
93106
actionsByType: parsePolicyActions(actions),
94-
[SYM_SID]: sid ?? randomUUID(),
95107
};
96108
}

0 commit comments

Comments
 (0)