Skip to content

Commit a3a74bb

Browse files
committed
fix currentModel and currentOperation inside of collection predicates
1 parent 80f364a commit a3a74bb

3 files changed

Lines changed: 57 additions & 0 deletions

File tree

packages/plugins/policy/src/expression-evaluator.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { invariant } from '@zenstackhq/common-helpers';
2+
import type { CRUD_EXT } from '@zenstackhq/orm';
23
import {
34
ExpressionUtils,
45
type ArrayExpression,
@@ -18,6 +19,8 @@ type ExpressionEvaluatorContext = {
1819
thisValue?: any;
1920
// scope for resolving references to collection predicate bindings
2021
bindingScope?: Record<string, any>;
22+
operation: CRUD_EXT;
23+
thisType: string;
2124
};
2225

2326
/**
@@ -44,6 +47,10 @@ export class ExpressionEvaluator {
4447
private evaluateCall(expr: CallExpression, context: ExpressionEvaluatorContext): any {
4548
if (expr.function === 'auth') {
4649
return context.auth;
50+
} else if (expr.function === 'currentModel') {
51+
return context.thisType;
52+
} else if (expr.function === 'currentOperation') {
53+
return context.operation;
4754
} else {
4855
throw new Error(`Unsupported call expression function: ${expr.function}`);
4956
}

packages/plugins/policy/src/expression-transformer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
337337
thisValue: context.contextValue,
338338
auth: this.auth,
339339
bindingScope: this.getEvaluationBindingScope(context.bindingScope),
340+
operation: context.operation,
341+
thisType: context.thisType,
340342
});
341343

342344
// get LHS's type
@@ -436,6 +438,8 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
436438
auth: this.auth,
437439
thisValue: context.contextValue,
438440
bindingScope: this.getEvaluationBindingScope(context.bindingScope),
441+
operation: context.operation,
442+
thisType: context.thisType,
439443
});
440444
return this.transformValue(value, 'Boolean');
441445
} else {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { createPolicyTestClient } from '@zenstackhq/testtools';
2+
import { describe, expect, it } from 'vitest';
3+
4+
describe('Regression for issue #2536', () => {
5+
it('supports currentModel and currentOperation in nested expressions', async () => {
6+
const db = await createPolicyTestClient(
7+
`
8+
model User {
9+
id String @id
10+
groups Group[] @relation("UserGroups")
11+
}
12+
13+
model Group {
14+
id String @id
15+
modelName String
16+
modelOperation String
17+
users User[] @relation("UserGroups")
18+
}
19+
20+
model Foo {
21+
id String @id @default(cuid())
22+
@@allow('all', auth().groups?[modelName == currentModel() && modelOperation == currentOperation()])
23+
}
24+
`,
25+
);
26+
27+
const readGroup = { modelName: 'Foo', modelOperation: 'read' };
28+
29+
await expect(db.$setAuth({ id: 'user1', groups: [readGroup] }).foo.create({ data: {} })).toBeRejectedByPolicy();
30+
await expect(
31+
db
32+
.$setAuth({ id: 'user1', groups: [{ modelName: 'FooBar', modelOperation: 'create' }, readGroup] })
33+
.foo.create({ data: {} }),
34+
).toBeRejectedByPolicy();
35+
await expect(
36+
db
37+
.$setAuth({ id: 'user1', groups: [{ modelName: 'Foo', modelOperation: 'read' }, readGroup] })
38+
.foo.create({ data: {} }),
39+
).toBeRejectedByPolicy();
40+
await expect(
41+
db
42+
.$setAuth({ id: 'user1', groups: [{ modelName: 'Foo', modelOperation: 'create' }, readGroup] })
43+
.foo.create({ data: {} }),
44+
).toResolveTruthy();
45+
});
46+
});

0 commit comments

Comments
 (0)