Skip to content

Commit 5616c05

Browse files
authored
fix: make sure auxiliary fields in nested entities are stripped (#387)
1 parent 954884a commit 5616c05

File tree

2 files changed

+89
-39
lines changed

2 files changed

+89
-39
lines changed

packages/runtime/src/enhancements/policy/policy-utils.ts

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ export class PolicyUtil {
247247

248248
const result: any[] = await this.db[model].findMany(args);
249249

250-
await Promise.all(result.map((item) => this.postProcessForRead(item, model, args, 'read')));
250+
await this.postProcessForRead(result, model, args, 'read');
251251

252252
return result;
253253
}
@@ -320,54 +320,47 @@ export class PolicyUtil {
320320
* (which can't be trimmed at query time) and removes fields that should be
321321
* omitted.
322322
*/
323-
async postProcessForRead(entityData: any, model: string, args: any, operation: PolicyOperationKind) {
324-
if (typeof entityData !== 'object' || !entityData) {
325-
return;
326-
}
327-
328-
const ids = this.getEntityIds(model, entityData);
329-
if (Object.keys(ids).length === 0) {
330-
return;
331-
}
332-
333-
// strip auxiliary fields
334-
for (const auxField of AUXILIARY_FIELDS) {
335-
if (auxField in entityData) {
336-
delete entityData[auxField];
337-
}
338-
}
339-
340-
const injectTarget = args.select ?? args.include;
341-
if (!injectTarget) {
342-
return;
343-
}
344-
345-
// to-one relation data cannot be trimmed by injected guards, we have to
346-
// post-check them
347-
348-
for (const field of getModelFields(injectTarget)) {
349-
if (!entityData?.[field]) {
323+
async postProcessForRead(data: any, model: string, args: any, operation: PolicyOperationKind) {
324+
for (const entityData of enumerate(data)) {
325+
if (typeof entityData !== 'object' || !entityData) {
350326
continue;
351327
}
352328

353-
const fieldInfo = resolveField(this.modelMeta, model, field);
354-
if (!fieldInfo || !fieldInfo.isDataModel || fieldInfo.isArray) {
355-
continue;
329+
// strip auxiliary fields
330+
for (const auxField of AUXILIARY_FIELDS) {
331+
if (auxField in entityData) {
332+
delete entityData[auxField];
333+
}
356334
}
357335

358-
const ids = this.getEntityIds(fieldInfo.type, entityData[field]);
359-
360-
if (Object.keys(ids).length === 0) {
336+
const injectTarget = args.select ?? args.include;
337+
if (!injectTarget) {
361338
continue;
362339
}
363340

364-
// DEBUG
365-
// this.logger.info(`Validating read of to-one relation: ${fieldInfo.type}#${formatObject(ids)}`);
341+
// recurse into nested entities
342+
for (const field of Object.keys(injectTarget)) {
343+
const fieldData = entityData[field];
344+
if (typeof fieldData !== 'object' || !fieldData) {
345+
continue;
346+
}
366347

367-
await this.checkPolicyForFilter(fieldInfo.type, ids, operation, this.db);
348+
const fieldInfo = resolveField(this.modelMeta, model, field);
349+
if (fieldInfo && fieldInfo.isDataModel && !fieldInfo.isArray) {
350+
// to-one relation data cannot be trimmed by injected guards, we have to
351+
// post-check them
352+
const ids = this.getEntityIds(fieldInfo.type, fieldData);
368353

369-
// recurse
370-
await this.postProcessForRead(entityData[field], fieldInfo.type, injectTarget[field], operation);
354+
if (Object.keys(ids).length !== 0) {
355+
// DEBUG
356+
// this.logger.info(`Validating read of to-one relation: ${fieldInfo.type}#${formatObject(ids)}`);
357+
await this.checkPolicyForFilter(fieldInfo.type, ids, operation, this.db);
358+
}
359+
}
360+
361+
// recurse
362+
await this.postProcessForRead(fieldData, fieldInfo.type, injectTarget[field], operation);
363+
}
371364
}
372365
}
373366

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { loadSchema } from '@zenstackhq/testtools';
2+
import path from 'path';
3+
4+
describe('GitHub issues regression', () => {
5+
let origDir: string;
6+
7+
beforeAll(async () => {
8+
origDir = path.resolve('.');
9+
});
10+
11+
afterEach(() => {
12+
process.chdir(origDir);
13+
});
14+
15+
it('issue 386', async () => {
16+
const { withPolicy } = await loadSchema(
17+
`
18+
model User {
19+
id String @id @unique @default(uuid())
20+
posts Post[]
21+
22+
@@allow('all', true)
23+
}
24+
25+
model Post {
26+
id String @id @default(uuid())
27+
title String
28+
published Boolean @default(false)
29+
author User @relation(fields: [authorId], references: [id])
30+
authorId String
31+
32+
@@allow('all', contains(title, 'Post'))
33+
}
34+
`
35+
);
36+
37+
const db = withPolicy();
38+
const created = await db.user.create({
39+
data: {
40+
posts: {
41+
create: {
42+
title: 'Post 1',
43+
},
44+
},
45+
},
46+
include: {
47+
posts: true,
48+
},
49+
});
50+
expect(created.posts[0].zenstack_guard).toBeUndefined();
51+
expect(created.posts[0].zenstack_transaction).toBeUndefined();
52+
53+
const queried = await db.user.findFirst({ include: { posts: true } });
54+
expect(queried.posts[0].zenstack_guard).toBeUndefined();
55+
expect(queried.posts[0].zenstack_transaction).toBeUndefined();
56+
});
57+
});

0 commit comments

Comments
 (0)