Skip to content

Commit 7efaa56

Browse files
committed
fix(resolver): return false if a deny statement is missing context
1 parent fb5bf3b commit 7efaa56

File tree

2 files changed

+29
-7
lines changed

2 files changed

+29
-7
lines changed

lib/policy-resolver.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ describe("PolicyParser", () => {
110110
expect(policies.can(Actions.create, allowedContext)).toEqual(true);
111111
});
112112

113-
it("returns false if the call is made without required context", () => {
113+
it("returns false if an allow statement is missing a required context", () => {
114+
expect(policies.can(Actions.create)).toEqual(false);
115+
});
116+
117+
it("returns false if a deny statement is missing a required context", () => {
118+
const policies = PolicyResolver.fromStatements([ContextualDenyStatement]);
119+
114120
expect(policies.can(Actions.create)).toEqual(false);
115121
});
116122
});

lib/policy-resolver.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ type CompiledFns<TContext> = {
2828
explain(ctx: TContext): ParsedPolicyStatement[];
2929
};
3030

31+
type ApplyOptions = {
32+
onContextMissing: boolean;
33+
};
34+
3135
function hasAllPaths(statement: ParsedPolicyStatement, ctx: unknown) {
3236
if (statement.contextPaths.length === 0) {
3337
return true;
@@ -140,26 +144,38 @@ export class PolicyResolver {
140144

141145
const explain = (ctx: TContext): ParsedPolicyStatement[] => {
142146
return [
143-
...deny.filter((statement) => this.#apply(statement, ctx)),
144-
...allow.filter((statement) => this.#apply(statement, ctx)),
147+
...deny.filter((statement) =>
148+
this.#apply(statement, ctx, { onContextMissing: true })
149+
),
150+
...allow.filter((statement) =>
151+
this.#apply(statement, ctx, { onContextMissing: false })
152+
),
145153
];
146154
};
147155

148156
const can = (ctx: TContext): boolean => {
149-
const isDenied = deny.some((statement) => this.#apply(statement, ctx));
157+
const isDenied = deny.some((statement) =>
158+
this.#apply(statement, ctx, { onContextMissing: true })
159+
);
150160
if (isDenied) {
151161
return false;
152162
}
153163

154-
return allow.some((statement) => this.#apply(statement, ctx));
164+
return allow.some((statement) =>
165+
this.#apply(statement, ctx, { onContextMissing: false })
166+
);
155167
};
156168

157169
return { can, explain };
158170
}
159171

160-
#apply<TContext>(statement: ParsedPolicyStatement, ctx: TContext): boolean {
172+
#apply<TContext>(
173+
statement: ParsedPolicyStatement,
174+
ctx: TContext,
175+
opts: ApplyOptions
176+
): boolean {
161177
if (!hasAllPaths(statement, ctx)) {
162-
return false;
178+
return opts.onContextMissing;
163179
}
164180

165181
return this.#parser.apply(statement.constraint, ctx) === true;

0 commit comments

Comments
 (0)