fix: skip validation of where query paths from access result (#9349)

### What?

Previously, `payload.findByID` with `overrideAccess: false` and this
collection config
```ts
{
  slug: 'fields-and-top-access',
  access: {
    read: () => ({
      secret: {
        equals: '12345',
      },
    }),
  },
  fields: [
    {
      type: 'text',
      name: 'secret',
      access: { read: () => false },
    },
  ],
},
```

Led to the `The following path cannot be queried: secret` error because
`where` input to `validateQueryPaths` also includes the result from
access control, which shouldn't be.

This works when using `payload.find`.

The same applies to find with drafts / joins `where`. We need to
validate only user `where` input, not access control that we defined in
our config.

Also, this exact logic seems be used in `find` without drafts - we don't
use `fullWhere` here but `where`, that's why this error isn't being
thrown with `find` but only `findByID`.

d9c6288cb2/packages/payload/src/collections/operations/find.ts (L134)

d9c6288cb2/packages/payload/src/collections/operations/find.ts (L166-L171)

Fixes https://github.com/payloadcms/payload/issues/9210
This commit is contained in:
Sasha
2024-11-26 19:02:45 +02:00
committed by GitHub
parent 44c0cdb75f
commit a9f511d540
8 changed files with 155 additions and 19 deletions

View File

@@ -509,6 +509,29 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: 'fields-and-top-access',
access: {
readVersions: () => ({
'version.secret': {
equals: 'will-success-access-read',
},
}),
read: () => ({
secret: {
equals: 'will-success-access-read',
},
}),
},
versions: { drafts: true },
fields: [
{
type: 'text',
name: 'secret',
access: { read: () => false },
},
],
},
Disabled,
RichText,
Regression1,

View File

@@ -1,3 +1,4 @@
import type { NextRESTClient } from 'helpers/NextRESTClient.js'
import type {
CollectionSlug,
DataFromCollectionSlug,
@@ -28,6 +29,7 @@ import {
} from './shared.js'
let payload: Payload
let restClient: NextRESTClient
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
describe('Access Control', () => {
@@ -35,7 +37,7 @@ describe('Access Control', () => {
let restricted: FullyRestricted
beforeAll(async () => {
;({ payload } = await initPayloadInt(dirname))
;({ payload, restClient } = await initPayloadInt(dirname))
})
beforeEach(async () => {
@@ -512,6 +514,7 @@ describe('Access Control', () => {
hidden: false,
},
})
const { docs } = await payload.findVersions({
collection: restrictedVersionsSlug,
overrideAccess: false,
@@ -522,6 +525,85 @@ describe('Access Control', () => {
expect(docs).toHaveLength(1)
})
it('should ignore false access on query constraint added by top collection level access control', async () => {
await payload.create({
collection: 'fields-and-top-access',
data: { secret: 'will-fail-access-read' },
})
const { id: hitID } = await payload.create({
collection: 'fields-and-top-access',
data: { secret: 'will-success-access-read' },
})
await payload.create({
collection: 'fields-and-top-access',
data: { secret: 'will-fail-access-read' },
})
// assert find, only will-success should be in the result
const resFind = await payload.find({
overrideAccess: false,
collection: 'fields-and-top-access',
})
expect(resFind.docs[0].id).toBe(hitID)
expect(resFind.docs).toHaveLength(1)
// assert find draft: true
const resFindDraft = await payload.find({
draft: true,
overrideAccess: false,
collection: 'fields-and-top-access',
})
expect(resFindDraft.docs).toHaveLength(1)
expect(resFind.docs[0].id).toBe(hitID)
// assert findByID
const res = await payload.findByID({
id: hitID,
collection: 'fields-and-top-access',
overrideAccess: false,
})
expect(res).toBeTruthy()
})
it('should ignore false access in versions on query constraint added by top collection level access control', async () => {
// clean up
await payload.delete({ collection: 'fields-and-top-access', where: {} })
await payload.create({
collection: 'fields-and-top-access',
data: { secret: 'will-fail-access-read' },
})
const { id: hitID } = await payload.create({
collection: 'fields-and-top-access',
data: { secret: 'will-success-access-read' },
})
await payload.create({
collection: 'fields-and-top-access',
data: { secret: 'will-fail-access-read' },
})
// Assert findVersions only will-success should be in the result
const resFind = await payload.findVersions({
overrideAccess: false,
collection: 'fields-and-top-access',
})
expect(resFind.docs).toHaveLength(1)
const version = resFind.docs[0]
expect(version.parent).toBe(hitID)
// Assert findVersionByID
const res = await payload.findVersionByID({
id: version.id,
collection: 'fields-and-top-access',
overrideAccess: false,
})
expect(res).toBeTruthy()
})
})
})

View File

@@ -28,6 +28,7 @@ export interface Config {
'hidden-fields': HiddenField;
'hidden-access': HiddenAccess;
'hidden-access-count': HiddenAccessCount;
'fields-and-top-access': FieldsAndTopAccess;
disabled: Disabled;
'rich-text': RichText;
regression1: Regression1;
@@ -54,6 +55,7 @@ export interface Config {
'hidden-fields': HiddenFieldsSelect<false> | HiddenFieldsSelect<true>;
'hidden-access': HiddenAccessSelect<false> | HiddenAccessSelect<true>;
'hidden-access-count': HiddenAccessCountSelect<false> | HiddenAccessCountSelect<true>;
'fields-and-top-access': FieldsAndTopAccessSelect<false> | FieldsAndTopAccessSelect<true>;
disabled: DisabledSelect<false> | DisabledSelect<true>;
'rich-text': RichTextSelect<false> | RichTextSelect<true>;
regression1: Regression1Select<false> | Regression1Select<true>;
@@ -334,6 +336,16 @@ export interface HiddenAccessCount {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "fields-and-top-access".
*/
export interface FieldsAndTopAccess {
id: string;
secret?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "disabled".
@@ -670,6 +682,10 @@ export interface PayloadLockedDocument {
relationTo: 'hidden-access-count';
value: string | HiddenAccessCount;
} | null)
| ({
relationTo: 'fields-and-top-access';
value: string | FieldsAndTopAccess;
} | null)
| ({
relationTo: 'disabled';
value: string | Disabled;
@@ -930,6 +946,15 @@ export interface HiddenAccessCountSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "fields-and-top-access_select".
*/
export interface FieldsAndTopAccessSelect<T extends boolean = true> {
secret?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "disabled_select".