Skip to content

Commit 1d70d4d

Browse files
authored
fix(next): version view did not properly handle field-level permissions (#13336)
Field-level permissions were not handled correctly at all. If you had a field set with access control, this would mean that nested fields would incorrectly be omitted from the version view. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210932060696919
1 parent 1b31c74 commit 1d70d4d

File tree

4 files changed

+83
-29
lines changed

4 files changed

+83
-29
lines changed

packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ import {
1717
type SanitizedFieldPermissions,
1818
type VersionField,
1919
} from 'payload'
20-
import { fieldIsID, fieldShouldBeLocalized, getUniqueListBy, tabHasName } from 'payload/shared'
20+
import {
21+
fieldIsID,
22+
fieldShouldBeLocalized,
23+
getFieldPermissions,
24+
getUniqueListBy,
25+
tabHasName,
26+
} from 'payload/shared'
2127

2228
import { diffComponents } from './fields/index.js'
2329
import { getFieldPathsModified } from './utilities/getFieldPathsModified.js'
@@ -223,21 +229,16 @@ const buildVersionField = ({
223229
BuildVersionFieldsArgs,
224230
'fields' | 'parentIndexPath' | 'versionFromSiblingData' | 'versionToSiblingData'
225231
>): BaseVersionField | null => {
226-
const fieldName: null | string = 'name' in field ? field.name : null
227-
228-
const hasPermission =
229-
fieldPermissions === true ||
230-
!fieldName ||
231-
fieldPermissions?.[fieldName] === true ||
232-
fieldPermissions?.[fieldName]?.read
233-
234-
const subFieldPermissions =
235-
fieldPermissions === true ||
236-
!fieldName ||
237-
fieldPermissions?.[fieldName] === true ||
238-
fieldPermissions?.[fieldName]?.fields
232+
const { permissions, read: hasReadPermission } = getFieldPermissions({
233+
field,
234+
operation: 'read',
235+
parentName: parentPath?.includes('.')
236+
? parentPath.split('.')[parentPath.split('.').length - 1]
237+
: parentPath,
238+
permissions: fieldPermissions,
239+
})
239240

240-
if (!hasPermission) {
241+
if (!hasReadPermission) {
241242
return null
242243
}
243244

@@ -292,13 +293,29 @@ const buildVersionField = ({
292293
parentPath,
293294
parentSchemaPath,
294295
})
296+
297+
let tabPermissions: typeof fieldPermissions = undefined
298+
299+
if (typeof permissions === 'boolean') {
300+
tabPermissions = permissions
301+
} else if (permissions && typeof permissions === 'object') {
302+
if ('name' in tab) {
303+
tabPermissions =
304+
typeof permissions.fields?.[tab.name] === 'object'
305+
? permissions.fields?.[tab.name].fields
306+
: permissions.fields?.[tab.name]
307+
} else {
308+
tabPermissions = permissions.fields
309+
}
310+
}
311+
295312
const tabVersion = {
296313
name: 'name' in tab ? tab.name : null,
297314
fields: buildVersionFields({
298315
clientSchemaMap,
299316
customDiffComponents,
300317
entitySlug,
301-
fieldPermissions,
318+
fieldPermissions: tabPermissions,
302319
fields: tab.fields,
303320
i18n,
304321
modifiedOnly,
@@ -324,6 +341,13 @@ const buildVersionField = ({
324341
}
325342
} // At this point, we are dealing with a `row`, `collapsible`, etc
326343
else if ('fields' in field) {
344+
let subfieldPermissions: typeof fieldPermissions = undefined
345+
346+
if (typeof permissions === 'boolean') {
347+
subfieldPermissions = permissions
348+
} else if (permissions && typeof permissions === 'object') {
349+
subfieldPermissions = permissions.fields
350+
}
327351
if (field.type === 'array' && (valueTo || valueFrom)) {
328352
const maxLength = Math.max(
329353
Array.isArray(valueTo) ? valueTo.length : 0,
@@ -339,7 +363,7 @@ const buildVersionField = ({
339363
clientSchemaMap,
340364
customDiffComponents,
341365
entitySlug,
342-
fieldPermissions,
366+
fieldPermissions: subfieldPermissions,
343367
fields: field.fields,
344368
i18n,
345369
modifiedOnly,
@@ -363,7 +387,7 @@ const buildVersionField = ({
363387
clientSchemaMap,
364388
customDiffComponents,
365389
entitySlug,
366-
fieldPermissions,
390+
fieldPermissions: subfieldPermissions,
367391
fields: field.fields,
368392
i18n,
369393
modifiedOnly,
@@ -421,11 +445,24 @@ const buildVersionField = ({
421445
}
422446
}
423447

448+
let blockPermissions: typeof fieldPermissions = undefined
449+
450+
if (permissions === true) {
451+
blockPermissions = true
452+
} else {
453+
const permissionsBlockSpecific = permissions?.blocks?.[blockSlugToMatch]
454+
if (permissionsBlockSpecific === true) {
455+
blockPermissions = true
456+
} else {
457+
blockPermissions = permissionsBlockSpecific?.fields
458+
}
459+
}
460+
424461
baseVersionField.rows[i] = buildVersionFields({
425462
clientSchemaMap,
426463
customDiffComponents,
427464
entitySlug,
428-
fieldPermissions,
465+
fieldPermissions: blockPermissions,
429466
fields,
430467
i18n,
431468
modifiedOnly,
@@ -459,7 +496,7 @@ const buildVersionField = ({
459496
*/
460497
diffMethod: 'diffWordsWithSpace',
461498
field: clientField,
462-
fieldPermissions: subFieldPermissions,
499+
fieldPermissions: typeof permissions === 'object' ? permissions.fields : permissions,
463500
parentIsLocalized,
464501

465502
nestingLevel: nestingLevel ? nestingLevel : undefined,

test/versions/e2e.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,18 @@ describe('Versions', () => {
17761776
String(uploadDocs?.docs?.[1]?.filename),
17771777
)
17781778
})
1779+
1780+
test('does not render diff for fields with read access control false', async () => {
1781+
await navigateToDiffVersionView()
1782+
1783+
const hiddenField1 = page.locator(
1784+
'[data-field-path="blocks.2.textInUnnamedTab2InBlockAccessFalse"]',
1785+
)
1786+
await expect(hiddenField1).toBeHidden()
1787+
1788+
const hiddenField2 = page.locator('[data-field-path="textCannotRead"]')
1789+
await expect(hiddenField2).toBeHidden()
1790+
})
17791791
})
17801792

17811793
describe('Scheduled publish', () => {

test/versions/payload-types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ export interface Diff {
366366
textInNamedTab1InBlock?: string | null;
367367
};
368368
textInUnnamedTab2InBlock?: string | null;
369+
textInUnnamedTab2InBlockAccessFalse?: string | null;
369370
id?: string | null;
370371
blockName?: string | null;
371372
blockType: 'TabsBlock';
@@ -468,6 +469,7 @@ export interface Diff {
468469
};
469470
textInUnnamedTab2?: string | null;
470471
text?: string | null;
472+
textCannotRead?: string | null;
471473
textArea?: string | null;
472474
upload?: (string | null) | Media;
473475
uploadHasMany?: (string | Media)[] | null;
@@ -958,6 +960,7 @@ export interface DiffSelect<T extends boolean = true> {
958960
textInNamedTab1InBlock?: T;
959961
};
960962
textInUnnamedTab2InBlock?: T;
963+
textInUnnamedTab2InBlockAccessFalse?: T;
961964
id?: T;
962965
blockName?: T;
963966
};
@@ -992,6 +995,7 @@ export interface DiffSelect<T extends boolean = true> {
992995
};
993996
textInUnnamedTab2?: T;
994997
text?: T;
998+
textCannotRead?: T;
995999
textArea?: T;
9961000
upload?: T;
9971001
uploadHasMany?: T;

test/versions/seed.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
235235
textInNamedTab1InBlock: 'textInNamedTab1InBlock',
236236
},
237237
textInUnnamedTab2InBlock: 'textInUnnamedTab2InBlock',
238+
textInUnnamedTab2InBlockAccessFalse: 'textInUnnamedTab2InBlockAccessFalse',
238239
},
239240
],
240241
checkbox: true,
@@ -274,6 +275,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
274275
textInCollapsible: 'textInCollapsible',
275276
textInRow: 'textInRow',
276277
textInUnnamedTab2: 'textInUnnamedTab2',
278+
textCannotRead: 'textCannotRead',
277279
relationshipPolymorphic: {
278280
relationTo: 'text',
279281
value: doc1ID,
@@ -308,8 +310,8 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
308310
await _payload.db.updateOne({
309311
collection: diffCollectionSlug,
310312
id: diffDoc.id,
313+
returning: false,
311314
data: {
312-
...diffDoc,
313315
point: pointGeoJSON,
314316
createdAt: new Date(new Date(diffDoc.createdAt).getTime() - 2 * 60 * 10000).toISOString(),
315317
updatedAt: new Date(new Date(diffDoc.updatedAt).getTime() - 2 * 60 * 10000).toISOString(),
@@ -326,18 +328,15 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
326328
let i = 0
327329
for (const version of versions.docs) {
328330
i += 1
331+
const date = new Date(new Date(version.createdAt).getTime() - 2 * 60 * 10000 * i).toISOString()
329332
await _payload.db.updateVersion({
330333
id: version.id,
331334
collection: diffCollectionSlug,
335+
returning: false,
332336
versionData: {
333-
...version.version,
334-
createdAt: new Date(
335-
new Date(version.createdAt).getTime() - 2 * 60 * 10000 * i,
336-
).toISOString(),
337-
updatedAt: new Date(
338-
new Date(version.updatedAt).getTime() - 2 * 60 * 10000 * i,
339-
).toISOString(),
340-
},
337+
createdAt: date,
338+
updatedAt: date,
339+
} as any,
341340
})
342341
}
343342

@@ -373,6 +372,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
373372
textInNamedTab1InBlock: 'textInNamedTab1InBlock2',
374373
},
375374
textInUnnamedTab2InBlock: 'textInUnnamedTab2InBlock2',
375+
textInUnnamedTab2InBlockAccessFalse: 'textInUnnamedTab2InBlockAccessFalse2',
376376
},
377377
],
378378
checkbox: false,
@@ -435,6 +435,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
435435
textArea: 'textArea2',
436436
textInCollapsible: 'textInCollapsible2',
437437
textInRow: 'textInRow2',
438+
textCannotRead: 'textCannotRead2',
438439
textInUnnamedTab2: 'textInUnnamedTab22',
439440
upload: uploadedImage2,
440441
uploadHasMany: [uploadedImage, uploadedImage2],

0 commit comments

Comments
 (0)