From 21d38f980cc57555974b4c2a785ac3596ecd6da9 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 14 Jun 2024 21:41:40 +0800 Subject: [PATCH] fix: field-level access is incorrectly rejected when there're no allow rules Fixes #1501 --- .../src/plugins/enhancer/policy/utils.ts | 19 ++++++++- .../with-policy/field-level-policy.test.ts | 41 +++++++++++++------ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/packages/schema/src/plugins/enhancer/policy/utils.ts b/packages/schema/src/plugins/enhancer/policy/utils.ts index 31e37e00d..3cc43223e 100644 --- a/packages/schema/src/plugins/enhancer/policy/utils.ts +++ b/packages/schema/src/plugins/enhancer/policy/utils.ts @@ -403,8 +403,23 @@ export function generateEntityCheckerFunction( statements.push(`if (${compiled}) { return true; }`); }); - // default: deny unless for 'postUpdate' - statements.push(kind === 'postUpdate' ? 'return true;' : 'return false;'); + if (kind === 'postUpdate') { + // 'postUpdate' rule defaults to allow + statements.push('return true;'); + } else { + if (forField) { + // if there's no allow rule, for field-level rules, by default we allow + if (allows.length === 0) { + statements.push('return true;'); + } else { + // if there's any allow rule, we deny unless any allow rule evaluates to true + statements.push(`return false;`); + } + } else { + // for other cases, defaults to deny + statements.push(`return false;`); + } + } const func = sourceFile.addFunction({ name: `$check_${model.name}${forField ? '$' + forField.name : ''}${fieldOverride ? '$override' : ''}_${kind}`, diff --git a/tests/integration/tests/enhancements/with-policy/field-level-policy.test.ts b/tests/integration/tests/enhancements/with-policy/field-level-policy.test.ts index 0297116a0..de103f4a5 100644 --- a/tests/integration/tests/enhancements/with-policy/field-level-policy.test.ts +++ b/tests/integration/tests/enhancements/with-policy/field-level-policy.test.ts @@ -27,6 +27,7 @@ describe('Policy: field-level policy', () => { id Int @id @default(autoincrement()) x Int y Int @allow('read', x > 0) + z Int @deny('read', x <= 0) owner User @relation(fields: [ownerId], references: [id]) ownerId Int @@ -40,71 +41,82 @@ describe('Policy: field-level policy', () => { const db = enhance(); let r; - // y is unreadable + // y and x are unreadable r = await db.model.create({ - data: { id: 1, x: 0, y: 0, ownerId: 1 }, + data: { id: 1, x: 0, y: 0, z: 0, ownerId: 1 }, }); expect(r.x).toEqual(0); expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); r = await db.model.findUnique({ where: { id: 1 } }); expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); r = await db.user.findUnique({ where: { id: 1 }, select: { models: true } }); expect(r.models[0].y).toBeUndefined(); + expect(r.models[0].z).toBeUndefined(); r = await db.user.findUnique({ where: { id: 1 }, select: { models: { select: { y: true } } } }); expect(r.models[0].y).toBeUndefined(); + expect(r.models[0].z).toBeUndefined(); r = await db.user.findUnique({ where: { id: 1 } }).models(); expect(r[0].y).toBeUndefined(); + expect(r[0].z).toBeUndefined(); r = await db.user.findUnique({ where: { id: 1 } }).models({ select: { y: true } }); expect(r[0].y).toBeUndefined(); + expect(r[0].z).toBeUndefined(); r = await db.model.findUnique({ select: { x: true }, where: { id: 1 } }); expect(r.x).toEqual(0); expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); r = await db.model.findUnique({ select: { y: true }, where: { id: 1 } }); expect(r.x).toBeUndefined(); expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); r = await db.model.findUnique({ select: { x: false, y: true }, where: { id: 1 } }); expect(r.x).toBeUndefined(); expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); r = await db.model.findUnique({ select: { x: true, y: true }, where: { id: 1 } }); expect(r.x).toEqual(0); expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); r = await db.model.findUnique({ include: { owner: true }, where: { id: 1 } }); expect(r.x).toEqual(0); expect(r.owner).toBeTruthy(); expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); // y is readable r = await db.model.create({ - data: { id: 2, x: 1, y: 0, ownerId: 1 }, + data: { id: 2, x: 1, y: 0, z: 0, ownerId: 1 }, }); - expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0, z: 0 })); r = await db.model.findUnique({ where: { id: 2 } }); - expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0, z: 0 })); r = await db.user.findUnique({ where: { id: 1 }, select: { models: { where: { id: 2 } } } }); - expect(r.models[0]).toEqual(expect.objectContaining({ x: 1, y: 0 })); + expect(r.models[0]).toEqual(expect.objectContaining({ x: 1, y: 0, z: 0 })); r = await db.user.findUnique({ where: { id: 1 }, - select: { models: { where: { id: 2 }, select: { y: true } } }, + select: { models: { where: { id: 2 }, select: { y: true, z: true } } }, }); - expect(r.models[0]).toEqual(expect.objectContaining({ y: 0 })); + expect(r.models[0]).toEqual(expect.objectContaining({ y: 0, z: 0 })); r = await db.user.findUnique({ where: { id: 1 } }).models({ where: { id: 2 } }); - expect(r[0]).toEqual(expect.objectContaining({ x: 1, y: 0 })); + expect(r[0]).toEqual(expect.objectContaining({ x: 1, y: 0, z: 0 })); r = await db.user.findUnique({ where: { id: 1 } }).models({ where: { id: 2 }, select: { y: true } }); expect(r[0]).toEqual(expect.objectContaining({ y: 0 })); @@ -112,20 +124,23 @@ describe('Policy: field-level policy', () => { r = await db.model.findUnique({ select: { x: true }, where: { id: 2 } }); expect(r.x).toEqual(1); expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); r = await db.model.findUnique({ select: { y: true }, where: { id: 2 } }); expect(r.x).toBeUndefined(); expect(r.y).toEqual(0); + expect(r.z).toBeUndefined(); - r = await db.model.findUnique({ select: { x: false, y: true }, where: { id: 2 } }); + r = await db.model.findUnique({ select: { x: false, y: true, z: true }, where: { id: 2 } }); expect(r.x).toBeUndefined(); expect(r.y).toEqual(0); + expect(r.z).toEqual(0); - r = await db.model.findUnique({ select: { x: true, y: true }, where: { id: 2 } }); - expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + r = await db.model.findUnique({ select: { x: true, y: true, z: true }, where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0, z: 0 })); r = await db.model.findUnique({ include: { owner: true }, where: { id: 2 } }); - expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0, z: 0 })); expect(r.owner).toBeTruthy(); });