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 {
|
||||
and: [
|
||||
...(typeof constraintAccess === 'object' ? [constraintAccess] : []),
|
||||
...(typeof constraintAccess === 'object'
|
||||
? [constraintAccess]
|
||||
: constraintAccess === false
|
||||
? [
|
||||
{
|
||||
id: {
|
||||
equals: null,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
[`access.${operation}.constraint`]: {
|
||||
equals: constraint.value,
|
||||
|
||||
@@ -78,7 +78,7 @@ export const getConstraints = (config: Config): Field => ({
|
||||
},
|
||||
...(config?.queryPresets?.constraints?.[operation]?.reduce(
|
||||
(acc: Field[], option: QueryPresetConstraint) => {
|
||||
option.fields.forEach((field, index) => {
|
||||
option.fields?.forEach((field, index) => {
|
||||
acc.push({ ...field })
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export type QueryPreset = {
|
||||
|
||||
export type QueryPresetConstraint = {
|
||||
access: Access<QueryPreset>
|
||||
fields: Field[]
|
||||
fields?: Field[]
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ export default buildConfigWithDefaults({
|
||||
// },
|
||||
access: {
|
||||
read: ({ req: { user } }) =>
|
||||
user ? !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||
user ? user && !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||
update: ({ req: { user } }) =>
|
||||
user ? !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||
user ? user && !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||
},
|
||||
constraints: {
|
||||
read: [
|
||||
@@ -40,6 +40,11 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Noone',
|
||||
value: 'noone',
|
||||
access: () => false,
|
||||
},
|
||||
],
|
||||
update: [
|
||||
{
|
||||
|
||||
@@ -503,6 +503,42 @@ describe('Query Presets', () => {
|
||||
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 () => {
|
||||
|
||||
@@ -86,7 +86,7 @@ export interface Config {
|
||||
'payload-query-presets': PayloadQueryPresetsSelect<false> | PayloadQueryPresetsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {};
|
||||
globalsSelect: {};
|
||||
@@ -122,7 +122,7 @@ export interface UserAuthOperations {
|
||||
* via the `definition` "pages".
|
||||
*/
|
||||
export interface Page {
|
||||
id: number;
|
||||
id: string;
|
||||
text?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -133,7 +133,7 @@ export interface Page {
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
id: string;
|
||||
name?: string | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
updatedAt: string;
|
||||
@@ -152,7 +152,7 @@ export interface User {
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: number;
|
||||
id: string;
|
||||
text?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -163,24 +163,24 @@ export interface Post {
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: number;
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
value: string | Page;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
value: string | Post;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -190,10 +190,10 @@ export interface PayloadLockedDocument {
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: number;
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
@@ -213,7 +213,7 @@ export interface PayloadPreference {
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: number;
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
@@ -224,23 +224,23 @@ export interface PayloadMigration {
|
||||
* via the `definition` "payload-query-presets".
|
||||
*/
|
||||
export interface PayloadQueryPreset {
|
||||
id: number;
|
||||
id: string;
|
||||
title: string;
|
||||
isShared?: boolean | null;
|
||||
access?: {
|
||||
read?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
|
||||
users?: (number | User)[] | null;
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles' | 'noone') | null;
|
||||
users?: (string | User)[] | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
};
|
||||
update?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
|
||||
users?: (number | User)[] | null;
|
||||
users?: (string | User)[] | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
};
|
||||
delete?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers') | null;
|
||||
users?: (number | User)[] | null;
|
||||
users?: (string | User)[] | null;
|
||||
};
|
||||
};
|
||||
where?:
|
||||
|
||||
@@ -167,6 +167,21 @@ export const seed = async (_payload: Payload) => {
|
||||
overrideAccess: false,
|
||||
data: seedData.onlyMe,
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
relatedCollection: 'pages',
|
||||
title: 'Noone',
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'noone',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
false,
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": ["./test/form-state/config.ts"],
|
||||
"@payload-config": ["./test/query-presets/config.ts"],
|
||||
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
|
||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user