Skip to content

Commit c67291d

Browse files
authored
fix(ui): invalid permissions passed to group and named tab sub-fields (#9366)
Fixes #9363 This fixes the following issues that caused fields to be either hidden, or incorrectly set to readOnly in certain configurations: - In some cases, permissions were sanitized incorrectly. This PR rewrites the sanitizePermissions function and adds new unit tests - after a document save, the client was receiving unsanitized permissions. Moving the sanitization logic to the endpoint fixes this - Various incorrect handling of permissions in our form state endpoints / RenderFields
1 parent 5db7e1e commit c67291d

File tree

23 files changed

+2052
-285
lines changed

23 files changed

+2052
-285
lines changed

packages/graphql/src/resolvers/collections/docAccess.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { Collection, CollectionPermission, GlobalPermission, PayloadRequest } from 'payload'
1+
import type {
2+
Collection,
3+
PayloadRequest,
4+
SanitizedCollectionPermission,
5+
SanitizedGlobalPermission,
6+
} from 'payload'
27

38
import { docAccessOperation, isolateObjectProperty } from 'payload'
49

@@ -12,7 +17,7 @@ export type Resolver = (
1217
context: {
1318
req: PayloadRequest
1419
},
15-
) => Promise<CollectionPermission | GlobalPermission>
20+
) => Promise<SanitizedCollectionPermission | SanitizedGlobalPermission>
1621

1722
export function docAccessResolver(collection: Collection): Resolver {
1823
async function resolver(_, args, context: Context) {

packages/graphql/src/resolvers/globals/docAccess.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type {
2-
CollectionPermission,
3-
GlobalPermission,
42
PayloadRequest,
3+
SanitizedCollectionPermission,
54
SanitizedGlobalConfig,
5+
SanitizedGlobalPermission,
66
} from 'payload'
77

88
import { docAccessOperationGlobal, isolateObjectProperty } from 'payload'
@@ -14,7 +14,7 @@ export type Resolver = (
1414
context: {
1515
req: PayloadRequest
1616
},
17-
) => Promise<CollectionPermission | GlobalPermission>
17+
) => Promise<SanitizedCollectionPermission | SanitizedGlobalPermission>
1818

1919
export function docAccessResolver(global: SanitizedGlobalConfig): Resolver {
2020
async function resolver(_, context: Context) {

packages/next/src/views/Document/getDocumentPermissions.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type {
22
Data,
3-
DocumentPermissions,
43
PayloadRequest,
54
SanitizedCollectionConfig,
65
SanitizedDocumentPermissions,
@@ -11,7 +10,7 @@ import {
1110
hasSavePermission as getHasSavePermission,
1211
isEditing as getIsEditing,
1312
} from '@payloadcms/ui/shared'
14-
import { docAccessOperation, docAccessOperationGlobal, sanitizePermissions } from 'payload'
13+
import { docAccessOperation, docAccessOperationGlobal } from 'payload'
1514

1615
export const getDocumentPermissions = async (args: {
1716
collectionConfig?: SanitizedCollectionConfig
@@ -26,7 +25,7 @@ export const getDocumentPermissions = async (args: {
2625
}> => {
2726
const { id, collectionConfig, data = {}, globalConfig, req } = args
2827

29-
let docPermissions: DocumentPermissions
28+
let docPermissions: SanitizedDocumentPermissions
3029
let hasPublishPermission = false
3130

3231
if (collectionConfig) {
@@ -58,7 +57,7 @@ export const getDocumentPermissions = async (args: {
5857
_status: 'published',
5958
},
6059
},
61-
}).then(({ update }) => update?.permission)
60+
}).then((permissions) => permissions.update)
6261
}
6362
} catch (error) {
6463
req.payload.logger.error(error)
@@ -85,20 +84,16 @@ export const getDocumentPermissions = async (args: {
8584
_status: 'published',
8685
},
8786
},
88-
}).then(({ update }) => update?.permission)
87+
}).then((permissions) => permissions.update)
8988
}
9089
} catch (error) {
9190
req.payload.logger.error(error)
9291
}
9392
}
9493

95-
// TODO: do this in a better way. Only doing this bc this is how the fn was written (mutates the original object)
96-
const sanitizedDocPermissions = { ...docPermissions } as any as SanitizedDocumentPermissions
97-
sanitizePermissions(sanitizedDocPermissions)
98-
9994
const hasSavePermission = getHasSavePermission({
10095
collectionSlug: collectionConfig?.slug,
101-
docPermissions: sanitizedDocPermissions,
96+
docPermissions,
10297
globalSlug: globalConfig?.slug,
10398
isEditing: getIsEditing({
10499
id,
@@ -108,7 +103,7 @@ export const getDocumentPermissions = async (args: {
108103
})
109104

110105
return {
111-
docPermissions: sanitizedDocPermissions,
106+
docPermissions,
112107
hasPublishPermission,
113108
hasSavePermission,
114109
}

packages/payload/src/auth/types.ts

Lines changed: 47 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,61 +11,61 @@ export type Permission = {
1111
where?: Where
1212
}
1313

14-
export type FieldPermissions = {
15-
blocks?: {
16-
[blockSlug: string]: {
17-
create: {
18-
permission: boolean
19-
}
20-
fields: {
21-
[fieldName: string]: FieldPermissions
22-
}
23-
read: {
24-
permission: boolean
25-
}
26-
update: {
27-
permission: boolean
28-
}
14+
export type FieldsPermissions = {
15+
[fieldName: string]: FieldPermissions
16+
}
17+
18+
export type BlockPermissions = {
19+
create: Permission
20+
fields: FieldsPermissions
21+
read: Permission
22+
update: Permission
23+
}
24+
25+
export type SanitizedBlockPermissions =
26+
| {
27+
fields: SanitizedFieldsPermissions
2928
}
30-
}
31-
create: {
32-
permission: boolean
33-
}
34-
fields?: {
35-
[fieldName: string]: FieldPermissions
36-
}
37-
read: {
38-
permission: boolean
39-
}
40-
update: {
41-
permission: boolean
42-
}
29+
| true
30+
31+
export type BlocksPermissions = {
32+
[blockSlug: string]: BlockPermissions
33+
}
34+
35+
export type SanitizedBlocksPermissions =
36+
| {
37+
[blockSlug: string]: SanitizedBlockPermissions
38+
}
39+
| true
40+
41+
export type FieldPermissions = {
42+
blocks?: BlocksPermissions
43+
create: Permission
44+
fields?: FieldsPermissions
45+
read: Permission
46+
update: Permission
4347
}
4448

4549
export type SanitizedFieldPermissions =
4650
| {
47-
blocks?: {
48-
[blockSlug: string]: {
49-
fields: {
50-
[fieldName: string]: SanitizedFieldPermissions
51-
}
52-
}
53-
}
51+
blocks?: SanitizedBlocksPermissions
5452
create: true
55-
fields?: {
56-
[fieldName: string]: SanitizedFieldPermissions
57-
}
53+
fields?: SanitizedFieldsPermissions
5854
read: true
5955
update: true
6056
}
6157
| true
6258

59+
export type SanitizedFieldsPermissions =
60+
| {
61+
[fieldName: string]: SanitizedFieldPermissions
62+
}
63+
| true
64+
6365
export type CollectionPermission = {
6466
create: Permission
6567
delete: Permission
66-
fields: {
67-
[fieldName: string]: FieldPermissions
68-
}
68+
fields: FieldsPermissions
6969
read: Permission
7070
readVersions?: Permission
7171
update: Permission
@@ -74,31 +74,21 @@ export type CollectionPermission = {
7474
export type SanitizedCollectionPermission = {
7575
create?: true
7676
delete?: true
77-
fields:
78-
| {
79-
[fieldName: string]: SanitizedFieldPermissions
80-
}
81-
| true
77+
fields: SanitizedFieldsPermissions
8278
read?: true
8379
readVersions?: true
8480
update?: true
8581
}
8682

8783
export type GlobalPermission = {
88-
fields: {
89-
[fieldName: string]: FieldPermissions
90-
}
84+
fields: FieldsPermissions
9185
read: Permission
9286
readVersions?: Permission
9387
update: Permission
9488
}
9589

9690
export type SanitizedGlobalPermission = {
97-
fields:
98-
| {
99-
[fieldName: string]: SanitizedFieldPermissions
100-
}
101-
| true
91+
fields: SanitizedFieldsPermissions
10292
read?: true
10393
readVersions?: true
10494
update?: true
@@ -110,7 +100,7 @@ export type SanitizedDocumentPermissions = SanitizedCollectionPermission | Sanit
110100

111101
export type Permissions = {
112102
canAccessAdmin: boolean
113-
collections: {
103+
collections?: {
114104
[collectionSlug: CollectionSlug]: CollectionPermission
115105
}
116106
globals?: {
@@ -121,26 +111,10 @@ export type Permissions = {
121111
export type SanitizedPermissions = {
122112
canAccessAdmin?: boolean
123113
collections?: {
124-
[collectionSlug: string]: {
125-
create?: true
126-
delete?: true
127-
fields: {
128-
[fieldName: string]: SanitizedFieldPermissions
129-
}
130-
read?: true
131-
readVersions?: true
132-
update?: true
133-
}
114+
[collectionSlug: string]: SanitizedCollectionPermission
134115
}
135116
globals?: {
136-
[globalSlug: string]: {
137-
fields: {
138-
[fieldName: string]: SanitizedFieldPermissions
139-
}
140-
read?: true
141-
readVersions?: true
142-
update?: true
143-
}
117+
[globalSlug: string]: SanitizedGlobalPermission
144118
}
145119
}
146120

packages/payload/src/collections/operations/docAccess.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type { CollectionPermission } from '../../auth/index.js'
1+
import type { CollectionPermission, SanitizedCollectionPermission } from '../../auth/index.js'
22
import type { AllOperations, PayloadRequest } from '../../types/index.js'
33
import type { Collection } from '../config/types.js'
44

55
import { getEntityPolicies } from '../../utilities/getEntityPolicies.js'
66
import { killTransaction } from '../../utilities/killTransaction.js'
7+
import { sanitizePermissions } from '../../utilities/sanitizePermissions.js'
78

89
const allOperations: AllOperations[] = ['create', 'read', 'update', 'delete']
910

@@ -13,7 +14,7 @@ type Arguments = {
1314
req: PayloadRequest
1415
}
1516

16-
export async function docAccessOperation(args: Arguments): Promise<CollectionPermission> {
17+
export async function docAccessOperation(args: Arguments): Promise<SanitizedCollectionPermission> {
1718
const {
1819
id,
1920
collection: { config },
@@ -43,7 +44,14 @@ export async function docAccessOperation(args: Arguments): Promise<CollectionPer
4344
req,
4445
})
4546

46-
return result
47+
const sanitizedPermissions = sanitizePermissions({
48+
canAccessAdmin: true,
49+
collections: {
50+
[config.slug]: result,
51+
},
52+
})
53+
54+
return sanitizedPermissions.collections[config.slug]
4755
} catch (e: unknown) {
4856
await killTransaction(req)
4957
throw e

packages/payload/src/globals/operations/docAccess.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
import type { GlobalPermission } from '../../auth/index.js'
1+
import type { SanitizedGlobalPermission } from '../../auth/index.js'
22
import type { AllOperations, PayloadRequest } from '../../types/index.js'
33
import type { SanitizedGlobalConfig } from '../config/types.js'
44

55
import { commitTransaction } from '../../utilities/commitTransaction.js'
66
import { getEntityPolicies } from '../../utilities/getEntityPolicies.js'
77
import { initTransaction } from '../../utilities/initTransaction.js'
88
import { killTransaction } from '../../utilities/killTransaction.js'
9+
import { sanitizePermissions } from '../../utilities/sanitizePermissions.js'
910

1011
type Arguments = {
1112
globalConfig: SanitizedGlobalConfig
1213
req: PayloadRequest
1314
}
1415

15-
export const docAccessOperation = async (args: Arguments): Promise<GlobalPermission> => {
16+
export const docAccessOperation = async (args: Arguments): Promise<SanitizedGlobalPermission> => {
1617
const { globalConfig, req } = args
1718

1819
const globalOperations: AllOperations[] = ['read', 'update']
@@ -32,7 +33,14 @@ export const docAccessOperation = async (args: Arguments): Promise<GlobalPermiss
3233
if (shouldCommit) {
3334
await commitTransaction(req)
3435
}
35-
return result
36+
const sanitizedPermissions = sanitizePermissions({
37+
canAccessAdmin: true,
38+
globals: {
39+
[globalConfig.slug]: result,
40+
},
41+
})
42+
43+
return sanitizedPermissions.globals[globalConfig.slug]
3644
} catch (e: unknown) {
3745
await killTransaction(req)
3846
throw e

packages/payload/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1341,7 +1341,6 @@ export { isValidID } from './utilities/isValidID.js'
13411341
export { killTransaction } from './utilities/killTransaction.js'
13421342
export { mapAsync } from './utilities/mapAsync.js'
13431343
export { sanitizeFallbackLocale } from './utilities/sanitizeFallbackLocale.js'
1344-
export { recursivelySanitizePermissions as sanitizePermissions } from './utilities/sanitizePermissions.js'
13451344
export { traverseFields } from './utilities/traverseFields.js'
13461345
export type { TraverseFieldsCallback } from './utilities/traverseFields.js'
13471346
export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'

0 commit comments

Comments
 (0)