fix: respects boolean query preset constraints (#12124)
Returning a boolean value from a constraint-level access control
function does nothing. For example:
```ts
{
label: 'Noone',
value: 'noone',
access: () => false,
},
```
This is because we were only handling query objects, disregarding any
boolean values. The fix is to check if the query is a boolean, and if
so, format a query object to return.
This commit is contained in:
@@ -71,7 +71,17 @@ export const getAccess = (config: Config): Record<Operation, Access> =>
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
and: [
|
and: [
|
||||||
...(typeof constraintAccess === 'object' ? [constraintAccess] : []),
|
...(typeof constraintAccess === 'object'
|
||||||
|
? [constraintAccess]
|
||||||
|
: constraintAccess === false
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
equals: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
[`access.${operation}.constraint`]: {
|
[`access.${operation}.constraint`]: {
|
||||||
equals: constraint.value,
|
equals: constraint.value,
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const getConstraints = (config: Config): Field => ({
|
|||||||
},
|
},
|
||||||
...(config?.queryPresets?.constraints?.[operation]?.reduce(
|
...(config?.queryPresets?.constraints?.[operation]?.reduce(
|
||||||
(acc: Field[], option: QueryPresetConstraint) => {
|
(acc: Field[], option: QueryPresetConstraint) => {
|
||||||
option.fields.forEach((field, index) => {
|
option.fields?.forEach((field, index) => {
|
||||||
acc.push({ ...field })
|
acc.push({ ...field })
|
||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export type QueryPreset = {
|
|||||||
|
|
||||||
export type QueryPresetConstraint = {
|
export type QueryPresetConstraint = {
|
||||||
access: Access<QueryPreset>
|
access: Access<QueryPreset>
|
||||||
fields: Field[]
|
fields?: Field[]
|
||||||
label: string
|
label: string
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ export default buildConfigWithDefaults({
|
|||||||
// },
|
// },
|
||||||
access: {
|
access: {
|
||||||
read: ({ req: { user } }) =>
|
read: ({ req: { user } }) =>
|
||||||
user ? !user?.roles?.some((role) => role === 'anonymous') : false,
|
user ? user && !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||||
update: ({ req: { user } }) =>
|
update: ({ req: { user } }) =>
|
||||||
user ? !user?.roles?.some((role) => role === 'anonymous') : false,
|
user ? user && !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||||
},
|
},
|
||||||
constraints: {
|
constraints: {
|
||||||
read: [
|
read: [
|
||||||
@@ -40,6 +40,11 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Noone',
|
||||||
|
value: 'noone',
|
||||||
|
access: () => false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
update: [
|
update: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -503,6 +503,42 @@ describe('Query Presets', () => {
|
|||||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should respect boolean access control results', async () => {
|
||||||
|
// create a preset with the read constraint set to "noone"
|
||||||
|
const presetForNoone = await payload.create({
|
||||||
|
collection: queryPresetsCollectionSlug,
|
||||||
|
user,
|
||||||
|
data: {
|
||||||
|
relatedCollection: 'pages',
|
||||||
|
title: 'Noone',
|
||||||
|
where: {
|
||||||
|
text: {
|
||||||
|
equals: 'example page',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
read: {
|
||||||
|
constraint: 'noone',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const foundPresetWithUser1 = await payload.findByID({
|
||||||
|
collection: queryPresetsCollectionSlug,
|
||||||
|
depth: 0,
|
||||||
|
user,
|
||||||
|
overrideAccess: false,
|
||||||
|
id: presetForNoone.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(foundPresetWithUser1).toBeFalsy()
|
||||||
|
} catch (error: unknown) {
|
||||||
|
expect((error as Error).message).toBe('Not Found')
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('should disable query presets when "enabledQueryPresets" is not true on the collection', async () => {
|
it.skip('should disable query presets when "enabledQueryPresets" is not true on the collection', async () => {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export interface Config {
|
|||||||
'payload-query-presets': PayloadQueryPresetsSelect<false> | PayloadQueryPresetsSelect<true>;
|
'payload-query-presets': PayloadQueryPresetsSelect<false> | PayloadQueryPresetsSelect<true>;
|
||||||
};
|
};
|
||||||
db: {
|
db: {
|
||||||
defaultIDType: number;
|
defaultIDType: string;
|
||||||
};
|
};
|
||||||
globals: {};
|
globals: {};
|
||||||
globalsSelect: {};
|
globalsSelect: {};
|
||||||
@@ -122,7 +122,7 @@ export interface UserAuthOperations {
|
|||||||
* via the `definition` "pages".
|
* via the `definition` "pages".
|
||||||
*/
|
*/
|
||||||
export interface Page {
|
export interface Page {
|
||||||
id: number;
|
id: string;
|
||||||
text?: string | null;
|
text?: string | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -133,7 +133,7 @@ export interface Page {
|
|||||||
* via the `definition` "users".
|
* via the `definition` "users".
|
||||||
*/
|
*/
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: string;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -152,7 +152,7 @@ export interface User {
|
|||||||
* via the `definition` "posts".
|
* via the `definition` "posts".
|
||||||
*/
|
*/
|
||||||
export interface Post {
|
export interface Post {
|
||||||
id: number;
|
id: string;
|
||||||
text?: string | null;
|
text?: string | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -163,24 +163,24 @@ export interface Post {
|
|||||||
* via the `definition` "payload-locked-documents".
|
* via the `definition` "payload-locked-documents".
|
||||||
*/
|
*/
|
||||||
export interface PayloadLockedDocument {
|
export interface PayloadLockedDocument {
|
||||||
id: number;
|
id: string;
|
||||||
document?:
|
document?:
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'pages';
|
relationTo: 'pages';
|
||||||
value: number | Page;
|
value: string | Page;
|
||||||
} | null)
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'users';
|
relationTo: 'users';
|
||||||
value: number | User;
|
value: string | User;
|
||||||
} | null)
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'posts';
|
relationTo: 'posts';
|
||||||
value: number | Post;
|
value: string | Post;
|
||||||
} | null);
|
} | null);
|
||||||
globalSlug?: string | null;
|
globalSlug?: string | null;
|
||||||
user: {
|
user: {
|
||||||
relationTo: 'users';
|
relationTo: 'users';
|
||||||
value: number | User;
|
value: string | User;
|
||||||
};
|
};
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -190,10 +190,10 @@ export interface PayloadLockedDocument {
|
|||||||
* via the `definition` "payload-preferences".
|
* via the `definition` "payload-preferences".
|
||||||
*/
|
*/
|
||||||
export interface PayloadPreference {
|
export interface PayloadPreference {
|
||||||
id: number;
|
id: string;
|
||||||
user: {
|
user: {
|
||||||
relationTo: 'users';
|
relationTo: 'users';
|
||||||
value: number | User;
|
value: string | User;
|
||||||
};
|
};
|
||||||
key?: string | null;
|
key?: string | null;
|
||||||
value?:
|
value?:
|
||||||
@@ -213,7 +213,7 @@ export interface PayloadPreference {
|
|||||||
* via the `definition` "payload-migrations".
|
* via the `definition` "payload-migrations".
|
||||||
*/
|
*/
|
||||||
export interface PayloadMigration {
|
export interface PayloadMigration {
|
||||||
id: number;
|
id: string;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
batch?: number | null;
|
batch?: number | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -224,23 +224,23 @@ export interface PayloadMigration {
|
|||||||
* via the `definition` "payload-query-presets".
|
* via the `definition` "payload-query-presets".
|
||||||
*/
|
*/
|
||||||
export interface PayloadQueryPreset {
|
export interface PayloadQueryPreset {
|
||||||
id: number;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
isShared?: boolean | null;
|
isShared?: boolean | null;
|
||||||
access?: {
|
access?: {
|
||||||
read?: {
|
read?: {
|
||||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
|
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles' | 'noone') | null;
|
||||||
users?: (number | User)[] | null;
|
users?: (string | User)[] | null;
|
||||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||||
};
|
};
|
||||||
update?: {
|
update?: {
|
||||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
|
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
|
||||||
users?: (number | User)[] | null;
|
users?: (string | User)[] | null;
|
||||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||||
};
|
};
|
||||||
delete?: {
|
delete?: {
|
||||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers') | null;
|
constraint?: ('everyone' | 'onlyMe' | 'specificUsers') | null;
|
||||||
users?: (number | User)[] | null;
|
users?: (string | User)[] | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
where?:
|
where?:
|
||||||
|
|||||||
@@ -167,6 +167,21 @@ export const seed = async (_payload: Payload) => {
|
|||||||
overrideAccess: false,
|
overrideAccess: false,
|
||||||
data: seedData.onlyMe,
|
data: seedData.onlyMe,
|
||||||
}),
|
}),
|
||||||
|
() =>
|
||||||
|
_payload.create({
|
||||||
|
collection: 'payload-query-presets',
|
||||||
|
user: devUser,
|
||||||
|
overrideAccess: false,
|
||||||
|
data: {
|
||||||
|
relatedCollection: 'pages',
|
||||||
|
title: 'Noone',
|
||||||
|
access: {
|
||||||
|
read: {
|
||||||
|
constraint: 'noone',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@payload-config": ["./test/form-state/config.ts"],
|
"@payload-config": ["./test/query-presets/config.ts"],
|
||||||
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
|
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
|
||||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||||
|
|||||||
Reference in New Issue
Block a user