From 4b60b5375c2ae233d4c1452a2b62974a50bfcf4b Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:34:13 +0800 Subject: [PATCH] fix: policy compilation error for deeply nested post-update rules --- .../routers/generated/routers/Post.router.ts | 23 ------- .../routers/generated/routers/User.router.ts | 23 ------- .../enhancer/policy/expression-writer.ts | 10 ++- .../with-policy/post-update.test.ts | 65 +++++++++++++++++++ tests/regression/tests/issue-1381.test.ts | 61 +++++++++++++++++ 5 files changed, 135 insertions(+), 47 deletions(-) create mode 100644 tests/regression/tests/issue-1381.test.ts diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/Post.router.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/Post.router.ts index 15408f3ef..fbc73cf06 100644 --- a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/Post.router.ts +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/Post.router.ts @@ -61,29 +61,6 @@ export interface ClientType; }; - createMany: { - useMutation: ( - opts?: UseTRPCMutationOptions< - Prisma.PostCreateManyArgs, - TRPCClientErrorLike, - Prisma.BatchPayload, - Context - >, - ) => Omit< - UseTRPCMutationResult< - Prisma.BatchPayload, - TRPCClientErrorLike, - Prisma.SelectSubset, - Context - >, - 'mutateAsync' - > & { - mutateAsync: ( - variables: T, - opts?: UseTRPCMutationOptions, Prisma.BatchPayload, Context>, - ) => Promise; - }; - }; create: { useMutation: (opts?: UseTRPCMutationOptions< diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/User.router.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/User.router.ts index cb9c8614b..c4bdb89de 100644 --- a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/User.router.ts +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/User.router.ts @@ -61,29 +61,6 @@ export interface ClientType; }; - createMany: { - useMutation: ( - opts?: UseTRPCMutationOptions< - Prisma.UserCreateManyArgs, - TRPCClientErrorLike, - Prisma.BatchPayload, - Context - >, - ) => Omit< - UseTRPCMutationResult< - Prisma.BatchPayload, - TRPCClientErrorLike, - Prisma.SelectSubset, - Context - >, - 'mutateAsync' - > & { - mutateAsync: ( - variables: T, - opts?: UseTRPCMutationOptions, Prisma.BatchPayload, Context>, - ) => Promise; - }; - }; create: { useMutation: (opts?: UseTRPCMutationOptions< diff --git a/packages/schema/src/plugins/enhancer/policy/expression-writer.ts b/packages/schema/src/plugins/enhancer/policy/expression-writer.ts index 3ce681dad..f301e5b9d 100644 --- a/packages/schema/src/plugins/enhancer/policy/expression-writer.ts +++ b/packages/schema/src/plugins/enhancer/policy/expression-writer.ts @@ -463,7 +463,15 @@ export class ExpressionWriter { } private isFutureMemberAccess(expr: Expression): expr is MemberAccessExpr { - return isMemberAccessExpr(expr) && isFutureExpr(expr.operand); + if (!isMemberAccessExpr(expr)) { + return false; + } + + if (isFutureExpr(expr.operand)) { + return true; + } + + return this.isFutureMemberAccess(expr.operand); } private requireIdFields(dataModel: DataModel) { diff --git a/tests/integration/tests/enhancements/with-policy/post-update.test.ts b/tests/integration/tests/enhancements/with-policy/post-update.test.ts index e2d7e0156..b101356cd 100644 --- a/tests/integration/tests/enhancements/with-policy/post-update.test.ts +++ b/tests/integration/tests/enhancements/with-policy/post-update.test.ts @@ -237,6 +237,71 @@ describe('With Policy: post update', () => { ).toResolveTruthy(); }); + it('collection predicate deep-nested post-update', async () => { + const { prisma, enhance } = await loadSchema( + ` + model M1 { + id String @id @default(uuid()) + value Int + m2 M2? + @@allow('read', true) + @@allow('update', value > 0 && future().m2.m3?[value > 0]) + } + + model M2 { + id String @id @default(uuid()) + m1 M1 @relation(fields: [m1Id], references:[id]) + m1Id String @unique + m3 M3[] + @@allow('all', true) + } + + model M3 { + id String @id @default(uuid()) + value Int + m2 M2 @relation(fields: [m2Id], references:[id]) + m2Id String + + @@allow('all', true) + } + ` + ); + + const db = enhance(); + + await prisma.m1.create({ + data: { + id: '1', + value: 1, + m2: { + create: { id: '1', m3: { create: [{ id: '1', value: 0 }] } }, + }, + }, + }); + + await expect( + db.m1.update({ + where: { id: '1' }, + data: { value: 2 }, + }) + ).toBeRejectedByPolicy(); + + await prisma.m3.create({ + data: { + id: '2', + m2: { connect: { id: '1' } }, + value: 1, + }, + }); + + await expect( + db.m1.update({ + where: { id: '1' }, + data: { value: 2 }, + }) + ).toResolveTruthy(); + }); + it('nested to-many', async () => { const { enhance } = await loadSchema( ` diff --git a/tests/regression/tests/issue-1381.test.ts b/tests/regression/tests/issue-1381.test.ts new file mode 100644 index 000000000..14bf8f4b9 --- /dev/null +++ b/tests/regression/tests/issue-1381.test.ts @@ -0,0 +1,61 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 1381', () => { + it('regression', async () => { + await loadSchema( + ` + enum MemberRole { + owner + admin + } + + enum SpaceType { + contractor + public + private + } + + model User { + id String @id @default(cuid()) + name String? + email String? @unique @lower + password String? @password @omit + memberships Membership[] + } + + model Membership { + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + spaceId String + space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade) + role MemberRole @deny("update", auth() == user) + @@id([userId, spaceId]) + } + + model Space { + id String @id @default(cuid()) + name String + type SpaceType @default(private) + memberships Membership[] + options Option[] + } + + model Option { + id String @id @default(cuid()) + name String? + spaceId String? + space Space? @relation(fields: [spaceId], references: [id], onDelete: SetNull) + + @@allow("update", + future().space.type in [contractor, public] && + future().space.memberships?[space.type in [contractor, public] && auth() == user && role in [owner, admin]] + ) + } + `, + { + provider: 'postgresql', + pushDb: false, + } + ); + }); +});