From 55be22f565df3726935d523d51e8fd87a1861247 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sun, 9 Jun 2024 18:54:30 +0800 Subject: [PATCH 1/2] fix: additional fixes and tests related to cross-model field comparison --- .../src/plugins/enhancer/policy/utils.ts | 6 +- .../cross-model-field-comparison.test.ts | 183 +++++++++++++++++- 2 files changed, 183 insertions(+), 6 deletions(-) diff --git a/packages/schema/src/plugins/enhancer/policy/utils.ts b/packages/schema/src/plugins/enhancer/policy/utils.ts index f6f8bd801..1085a6e88 100644 --- a/packages/schema/src/plugins/enhancer/policy/utils.ts +++ b/packages/schema/src/plugins/enhancer/policy/utils.ts @@ -483,7 +483,9 @@ function hasCrossModelComparison(expr: Expression) { } function getSourceModelOfFieldAccess(expr: Expression) { - if (isDataModel(expr.$resolvedType?.decl)) { + // 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)) { return expr.$resolvedType?.decl; } @@ -497,7 +499,7 @@ function getSourceModelOfFieldAccess(expr: Expression) { return getContainerOfType(expr, isDataModel); } - // direct field reference + // direct field reference, return the model if (isDataModelFieldReference(expr)) { return (expr.target.ref as DataModelField).$container; } diff --git a/tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts b/tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts index 1ebfaeba6..9baa4c1ff 100644 --- a/tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts +++ b/tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts @@ -769,8 +769,8 @@ describe('Cross-model field comparison', () => { await expect(db.user.update({ where: { id: 1 }, data: { age: 25 } })).toResolveTruthy(); }); - it('with auth', async () => { - const { prisma, enhance } = await loadSchema( + it('with auth case 1', async () => { + const { enhance } = await loadSchema( ` model User { id Int @id @default(autoincrement()) @@ -803,8 +803,7 @@ describe('Cross-model field comparison', () => { level Int @@allow('all', true) } - `, - { preserveTsFiles: true } + ` ); await expect(enhance().post.create({ data: { title: 'P1' } })).toBeRejectedByPolicy(); @@ -820,4 +819,180 @@ describe('Cross-model field comparison', () => { }) ).toResolveTruthy(); }); + + it('with auth case 2', async () => { + const { prisma, enhance } = await loadSchema( + ` + model User { + id Int @id @default(autoincrement()) + teamMembership TeamMembership[] + @@allow('all', true) + } + + model Team { + id Int @id @default(autoincrement()) + permissions Permission[] + assets Asset[] + @@allow('all', true) + } + + model Asset { + id Int @id @default(autoincrement()) + name String + team Team @relation(fields: [teamId], references: [id]) + teamId Int + @@allow('all', auth().teamMembership?[role.permissions?[name == 'ManageTeam' && teamId == this.teamId]]) + @@allow('read', true) + } + + model TeamMembership { + id Int @id @default(autoincrement()) + role TeamRole? + user User @relation(fields: [userId], references: [id]) + userId Int + @@allow('all', true) + } + + model TeamRole { + id Int @id @default(autoincrement()) + permissions Permission[] + membership TeamMembership @relation(fields: [membershipId], references: [id]) + membershipId Int @unique + @@allow('all', true) + } + + model Permission { + id Int @id @default(autoincrement()) + name String + team Team @relation(fields: [teamId], references: [id]) + teamId Int + role TeamRole @relation(fields: [roleId], references: [id]) + roleId Int + @@allow('all', true) + } + `, + { preserveTsFiles: true, logPrismaQuery: true } + ); + + const team1 = await prisma.team.create({ data: {} }); + const team2 = await prisma.team.create({ data: {} }); + + const user = await prisma.user.create({ + data: { + teamMembership: { + create: { + role: { + create: { + permissions: { create: [{ name: 'ManageTeam', team: { connect: { id: team1.id } } }] }, + }, + }, + }, + }, + }, + }); + + const asset = await prisma.asset.create({ + data: { name: 'Asset1', team: { connect: { id: team1.id } } }, + }); + + const dbTeam1 = enhance({ + id: user.id, + teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team1.id }] } }], + }); + expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy(); + + const dbTeam2 = enhance({ + id: user.id, + teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team2.id }] } }], + }); + expect(dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toBeRejectedByPolicy(); + }); + + it('with auth case 3', async () => { + const { prisma, enhance } = await loadSchema( + ` + model User { + id Int @id @default(autoincrement()) + teamMembership TeamMembership[] + @@allow('all', true) + } + + model Team { + id Int @id @default(autoincrement()) + permissions Permission[] + assets Asset[] + @@allow('all', true) + } + + model Asset { + id Int @id @default(autoincrement()) + name String + team Team @relation(fields: [teamId], references: [id]) + teamId Int + @@allow('all', auth().teamMembership?[role.permissions?[name == 'ManageTeam' && team == this.team]]) + @@allow('read', true) + } + + model TeamMembership { + id Int @id @default(autoincrement()) + role TeamRole? + user User @relation(fields: [userId], references: [id]) + userId Int + @@allow('all', true) + } + + model TeamRole { + id Int @id @default(autoincrement()) + permissions Permission[] + membership TeamMembership @relation(fields: [membershipId], references: [id]) + membershipId Int @unique + @@allow('all', true) + } + + model Permission { + id Int @id @default(autoincrement()) + name String + team Team @relation(fields: [teamId], references: [id]) + teamId Int + role TeamRole @relation(fields: [roleId], references: [id]) + roleId Int + @@allow('all', true) + } + `, + { preserveTsFiles: true, logPrismaQuery: true } + ); + + const team1 = await prisma.team.create({ data: {} }); + const team2 = await prisma.team.create({ data: {} }); + + const user = await prisma.user.create({ + data: { + teamMembership: { + create: { + role: { + create: { + permissions: { create: [{ name: 'ManageTeam', team: { connect: { id: team1.id } } }] }, + }, + }, + }, + }, + }, + }); + + const asset = await prisma.asset.create({ + data: { name: 'Asset1', team: { connect: { id: team1.id } } }, + }); + + const dbTeam1 = enhance({ + id: user.id, + teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team1.id }] } }], + }); + expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy(); + + const dbTeam2 = enhance({ + id: user.id, + teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team2.id }] } }], + }); + expect(dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toBeRejectedByPolicy(); + }); }); From d0c0e7627f4f55ad74e52ddcfc7ec4cb1de1f5c2 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sun, 9 Jun 2024 19:43:47 +0800 Subject: [PATCH 2/2] fix test --- .../cross-model-field-comparison.test.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts b/tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts index 9baa4c1ff..75d694f12 100644 --- a/tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts +++ b/tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts @@ -870,8 +870,7 @@ describe('Cross-model field comparison', () => { roleId Int @@allow('all', true) } - `, - { preserveTsFiles: true, logPrismaQuery: true } + ` ); const team1 = await prisma.team.create({ data: {} }); @@ -899,13 +898,15 @@ describe('Cross-model field comparison', () => { id: user.id, teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team1.id }] } }], }); - expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy(); + await expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy(); const dbTeam2 = enhance({ id: user.id, teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team2.id }] } }], }); - expect(dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toBeRejectedByPolicy(); + await expect( + dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } }) + ).toBeRejectedByPolicy(); }); it('with auth case 3', async () => { @@ -958,8 +959,7 @@ describe('Cross-model field comparison', () => { roleId Int @@allow('all', true) } - `, - { preserveTsFiles: true, logPrismaQuery: true } + ` ); const team1 = await prisma.team.create({ data: {} }); @@ -985,14 +985,16 @@ describe('Cross-model field comparison', () => { const dbTeam1 = enhance({ id: user.id, - teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team1.id }] } }], + teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', team: { id: team1.id } }] } }], }); - expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy(); + await expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy(); const dbTeam2 = enhance({ id: user.id, teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team2.id }] } }], }); - expect(dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toBeRejectedByPolicy(); + await expect( + dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } }) + ).toBeRejectedByPolicy(); }); });