Skip to content

Commit

Permalink
fix: issues with cross-model comparison identification and injection (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Jun 14, 2024
1 parent 92f187f commit 665f9b3
Show file tree
Hide file tree
Showing 19 changed files with 118 additions and 95 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-monorepo",
"version": "2.2.1",
"version": "2.2.2",
"description": "",
"scripts": {
"build": "pnpm -r build",
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = "dev.zenstack"
version = "2.2.1"
version = "2.2.2"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jetbrains",
"version": "2.2.1",
"version": "2.2.2",
"displayName": "ZenStack JetBrains IDE Plugin",
"description": "ZenStack JetBrains IDE plugin",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/language/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/language",
"version": "2.2.1",
"version": "2.2.2",
"displayName": "ZenStack modeling language compiler",
"description": "ZenStack modeling language compiler",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/misc/redwood/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/redwood",
"displayName": "ZenStack RedwoodJS Integration",
"version": "2.2.1",
"version": "2.2.2",
"description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/openapi/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/openapi",
"displayName": "ZenStack Plugin and Runtime for OpenAPI",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin and runtime supporting OpenAPI",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/swr/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/swr",
"displayName": "ZenStack plugin for generating SWR hooks",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin for generating SWR hooks",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/tanstack-query/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/tanstack-query",
"displayName": "ZenStack plugin for generating tanstack-query hooks",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin for generating tanstack-query hooks",
"main": "index.js",
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/trpc/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/trpc",
"displayName": "ZenStack plugin for tRPC",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin for tRPC",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/runtime",
"displayName": "ZenStack Runtime Library",
"version": "2.2.1",
"version": "2.2.2",
"description": "Runtime of ZenStack for both client-side and server-side environments.",
"repository": {
"type": "git",
Expand Down
15 changes: 11 additions & 4 deletions packages/runtime/src/enhancements/policy/policy-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1074,10 +1074,17 @@ export class PolicyUtil extends QueryUtils {
// can then cause infinite recursion when we visit relation later

// recurse into relation fields
for (const [k, v] of Object.entries<any>(args.select ?? args.include ?? {})) {
const field = resolveField(this.modelMeta, model, k);
if (field?.isDataModel && v && typeof v === 'object') {
this.injectReadCheckSelect(field.type, v);
const visitTarget = args.select ?? args.include;
if (visitTarget) {
for (const key of Object.keys(visitTarget)) {
const field = resolveField(this.modelMeta, model, key);
if (field?.isDataModel && visitTarget[key]) {
if (typeof visitTarget[key] !== 'object') {
// v is "true", ensure it's an object
visitTarget[key] = {};
}
this.injectReadCheckSelect(field.type, visitTarget[key]);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack Language Tools",
"description": "Build scalable web apps with minimum code by defining authorization and validation rules inside the data schema that closer to the database",
"version": "2.2.1",
"version": "2.2.2",
"author": {
"name": "ZenStack Team"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,15 @@ export class PolicyGenerator {
? '!(' +
denies
.map((deny) => {
return transformer.transform(deny);
return transformer.transform(deny, false);
})
.join(' || ') +
')'
: undefined;

const allowStmt = allows
.map((allow) => {
return transformer.transform(allow);
return transformer.transform(allow, false);
})
.join(' || ');

Expand Down Expand Up @@ -607,79 +607,6 @@ export class PolicyGenerator {
writer.writeLine(',');
}

private generateFieldReadCheckerFunction(
sourceFile: SourceFile,
field: DataModelField,
allows: Expression[],
denies: Expression[]
) {
const statements: (string | WriterFunction)[] = [];

generateNormalizedAuthRef(field.$container as DataModel, allows, denies, statements);

// compile rules down to typescript expressions
statements.push((writer) => {
const transformer = new TypeScriptExpressionTransformer({
context: ExpressionContext.AccessPolicy,
fieldReferenceContext: 'input',
});

const denyStmt =
denies.length > 0
? '!(' +
denies
.map((deny) => {
return transformer.transform(deny);
})
.join(' || ') +
')'
: undefined;

const allowStmt =
allows.length > 0
? '(' +
allows
.map((allow) => {
return transformer.transform(allow);
})
.join(' || ') +
')'
: undefined;

let expr: string | undefined;

if (denyStmt && allowStmt) {
expr = `${denyStmt} && ${allowStmt}`;
} else if (denyStmt) {
expr = denyStmt;
} else if (allowStmt) {
expr = allowStmt;
} else {
throw new Error('should not happen');
}

writer.write('return ' + expr);
});

const func = sourceFile.addFunction({
name: `${field.$container.name}$${field.name}_read`,
returnType: 'boolean',
parameters: [
{
name: 'input',
type: 'any',
},
{
name: 'context',
type: 'QueryContext',
},
],
statements,
});

return func;
}

// #endregion

//#region Auth selector
Expand Down
9 changes: 7 additions & 2 deletions packages/schema/src/plugins/enhancer/policy/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,12 @@ export function generateEntityCheckerFunction(
});

denies.forEach((rule) => {
const compiled = transformer.transform(rule);
const compiled = transformer.transform(rule, false);
statements.push(`if (${compiled}) { return false; }`);
});

allows.forEach((rule) => {
const compiled = transformer.transform(rule);
const compiled = transformer.transform(rule, false);
statements.push(`if (${compiled}) { return true; }`);
});

Expand Down Expand Up @@ -483,6 +483,11 @@ function hasCrossModelComparison(expr: Expression) {
}

function getSourceModelOfFieldAccess(expr: Expression) {
// `auth()` access doesn't involve db field look up so doesn't count as cross-model comparison
if (isAuthInvocation(expr)) {
return undefined;
}

// an expression that resolves to a data model and is part of a member access, return the model
// e.g.: profile.age => Profile
if (isDataModel(expr.$resolvedType?.decl) && isMemberAccessExpr(expr.$container)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/sdk",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin development SDK",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/server",
"version": "2.2.1",
"version": "2.2.2",
"displayName": "ZenStack Server-side Adapters",
"description": "ZenStack server-side adapters",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/testtools/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/testtools",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack Test Tools",
"main": "index.js",
"private": true,
Expand Down
57 changes: 57 additions & 0 deletions tests/regression/tests/issue-1506.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { loadSchema } from '@zenstackhq/testtools';
describe('issue 1506', () => {
it('regression', async () => {
const { prisma, enhance } = await loadSchema(
`
model A {
id Int @id @default(autoincrement())
value Int
b B @relation(fields: [bId], references: [id])
bId Int @unique
@@allow('read', true)
}
model B {
id Int @id @default(autoincrement())
value Int
a A?
c C @relation(fields: [cId], references: [id])
cId Int @unique
@@allow('read', value > c.value)
}
model C {
id Int @id @default(autoincrement())
value Int
b B?
@@allow('read', true)
}
`,
{ preserveTsFiles: true, logPrismaQuery: true }
);

await prisma.a.create({
data: {
value: 3,
b: {
create: {
value: 2,
c: {
create: {
value: 1,
},
},
},
},
},
});

const db = enhance();
const read = await db.a.findMany({ include: { b: true } });
expect(read).toHaveLength(1);
expect(read[0].b).toBeTruthy();
});
});
27 changes: 27 additions & 0 deletions tests/regression/tests/issue-1507.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { loadSchema } from '@zenstackhq/testtools';
describe('issue 1507', () => {
it('regression', async () => {
const { prisma, enhance } = await loadSchema(
`
model User {
id Int @id @default(autoincrement())
age Int
}
model Profile {
id Int @id @default(autoincrement())
age Int
@@allow('read', auth().age == age)
}
`,
{ preserveTsFiles: true, logPrismaQuery: true }
);

await prisma.profile.create({ data: { age: 18 } });
await prisma.profile.create({ data: { age: 20 } });
const db = enhance({ id: 1, age: 18 });
await expect(db.profile.findMany()).resolves.toHaveLength(1);
await expect(db.profile.count()).resolves.toBe(1);
});
});

0 comments on commit 665f9b3

Please sign in to comment.