Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This commit is contained in:
@@ -7,7 +7,6 @@ import { APIError } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { BeforeOperationHook, Collection } from '../config/types';
|
||||
import { Where } from '../../types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
import { deleteCollectionVersions } from '../../versions/deleteCollectionVersions';
|
||||
import { deleteAssociatedFiles } from '../../uploads/deleteAssociatedFiles';
|
||||
@@ -55,7 +54,6 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
req,
|
||||
req: {
|
||||
t,
|
||||
locale,
|
||||
payload,
|
||||
payload: {
|
||||
config,
|
||||
@@ -74,36 +72,15 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
// Access
|
||||
// /////////////////////////////////////
|
||||
|
||||
let queryToBuild: Where = {
|
||||
and: [],
|
||||
};
|
||||
|
||||
if (where) {
|
||||
queryToBuild = {
|
||||
and: [],
|
||||
...where,
|
||||
};
|
||||
|
||||
if (Array.isArray(where.AND)) {
|
||||
queryToBuild.and = [
|
||||
...queryToBuild.and,
|
||||
...where.AND,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
let accessResult: AccessResult;
|
||||
|
||||
if (!overrideAccess) {
|
||||
accessResult = await executeAccess({ req }, collectionConfig.access.delete);
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
queryToBuild.and.push(accessResult);
|
||||
}
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
where: queryToBuild,
|
||||
where,
|
||||
access: accessResult,
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
@@ -79,23 +79,14 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
|
||||
// Retrieve document
|
||||
// /////////////////////////////////////
|
||||
|
||||
const queryToBuild: Where = {
|
||||
and: [
|
||||
{
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResults)) {
|
||||
queryToBuild.and.push(accessResults);
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
req,
|
||||
where: queryToBuild,
|
||||
where: {
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
access: accessResults,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import executeAccess from '../../auth/executeAccess';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { Collection, TypeWithID } from '../config/types';
|
||||
import { PaginatedDocs } from '../../mongoose/types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import flattenWhereConstraints from '../../utilities/flattenWhereConstraints';
|
||||
import { buildSortParam } from '../../mongoose/buildSortParam';
|
||||
import { AccessResult } from '../../config/types';
|
||||
@@ -72,27 +71,10 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
||||
// Access
|
||||
// /////////////////////////////////////
|
||||
|
||||
let queryToBuild: Where = {
|
||||
and: [],
|
||||
};
|
||||
|
||||
let useEstimatedCount = false;
|
||||
|
||||
if (where) {
|
||||
queryToBuild = {
|
||||
and: [],
|
||||
...where,
|
||||
};
|
||||
|
||||
if (Array.isArray(where.AND)) {
|
||||
queryToBuild.and = [
|
||||
...queryToBuild.and,
|
||||
...where.AND,
|
||||
];
|
||||
}
|
||||
|
||||
const constraints = flattenWhereConstraints(queryToBuild);
|
||||
|
||||
const constraints = flattenWhereConstraints(where);
|
||||
useEstimatedCount = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'));
|
||||
}
|
||||
|
||||
@@ -116,16 +98,13 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
||||
limit,
|
||||
};
|
||||
}
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
queryToBuild.and.push(accessResult);
|
||||
}
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
req,
|
||||
where: queryToBuild,
|
||||
where,
|
||||
overrideAccess,
|
||||
access: accessResult,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -5,8 +5,6 @@ import { Collection, TypeWithID } from '../config/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { NotFound } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { Where } from '../../types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
|
||||
@@ -68,22 +66,13 @@ async function findByID<T extends TypeWithID>(
|
||||
// If errors are disabled, and access returns false, return null
|
||||
if (accessResult === false) return null;
|
||||
|
||||
const queryToBuild: Where = {
|
||||
and: [
|
||||
{
|
||||
_id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
queryToBuild.and.push(accessResult);
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
where: queryToBuild,
|
||||
where: {
|
||||
_id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
access: accessResult,
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
@@ -5,8 +5,6 @@ import { Collection, CollectionModel } from '../config/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { APIError, Forbidden, NotFound } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { Where } from '../../types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import { TypeWithVersion } from '../../versions/types';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
|
||||
@@ -56,22 +54,13 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
|
||||
|
||||
const hasWhereAccess = typeof accessResults === 'object';
|
||||
|
||||
const queryToBuild: Where = {
|
||||
and: [
|
||||
{
|
||||
_id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResults)) {
|
||||
queryToBuild.and.push(accessResults);
|
||||
}
|
||||
|
||||
const query = await VersionsModel.buildQuery({
|
||||
where: queryToBuild,
|
||||
where: {
|
||||
_id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
access: accessResults,
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import { PayloadRequest } from '../../express/types';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { Collection, CollectionModel } from '../config/types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import flattenWhereConstraints from '../../utilities/flattenWhereConstraints';
|
||||
import { buildSortParam } from '../../mongoose/buildSortParam';
|
||||
import { PaginatedDocs } from '../../mongoose/types';
|
||||
@@ -49,45 +48,23 @@ async function findVersions<T extends TypeWithVersion<T>>(
|
||||
// Access
|
||||
// /////////////////////////////////////
|
||||
|
||||
let queryToBuild: Where = {};
|
||||
let useEstimatedCount = false;
|
||||
|
||||
if (where) {
|
||||
let and = [];
|
||||
|
||||
if (Array.isArray(where.and)) and = where.and;
|
||||
if (Array.isArray(where.AND)) and = where.AND;
|
||||
|
||||
queryToBuild = {
|
||||
...where,
|
||||
and: [
|
||||
...and,
|
||||
],
|
||||
};
|
||||
|
||||
const constraints = flattenWhereConstraints(queryToBuild);
|
||||
const constraints = flattenWhereConstraints(where);
|
||||
|
||||
useEstimatedCount = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'));
|
||||
}
|
||||
|
||||
if (!overrideAccess) {
|
||||
const accessResults = await executeAccess({ req }, collectionConfig.access.readVersions);
|
||||
let accessResults;
|
||||
|
||||
if (hasWhereAccessResult(accessResults)) {
|
||||
if (!where) {
|
||||
queryToBuild = {
|
||||
and: [
|
||||
accessResults,
|
||||
],
|
||||
};
|
||||
} else {
|
||||
queryToBuild.and.push(accessResults);
|
||||
}
|
||||
}
|
||||
if (!overrideAccess) {
|
||||
accessResults = await executeAccess({ req }, collectionConfig.access.readVersions);
|
||||
}
|
||||
|
||||
const query = await VersionsModel.buildQuery({
|
||||
where: queryToBuild,
|
||||
where,
|
||||
access: accessResults,
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Collection, TypeWithID } from '../config/types';
|
||||
import { APIError, Forbidden, NotFound } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import { Where } from '../../types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { afterChange } from '../../fields/hooks/afterChange';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
@@ -34,7 +33,6 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
|
||||
depth,
|
||||
req: {
|
||||
t,
|
||||
locale,
|
||||
payload,
|
||||
},
|
||||
req,
|
||||
@@ -73,22 +71,13 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
|
||||
// Retrieve document
|
||||
// /////////////////////////////////////
|
||||
|
||||
const queryToBuild: Where = {
|
||||
and: [
|
||||
{
|
||||
id: {
|
||||
equals: parentDocID,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResults)) {
|
||||
queryToBuild.and.push(accessResults);
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
where: queryToBuild,
|
||||
where: {
|
||||
id: {
|
||||
equals: parentDocID,
|
||||
},
|
||||
},
|
||||
access: accessResults,
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { APIError, ValidationError } from '../../errors';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import { saveVersion } from '../../versions/saveVersion';
|
||||
import { uploadFiles } from '../../uploads/uploadFiles';
|
||||
import { beforeChange } from '../../fields/hooks/beforeChange';
|
||||
@@ -84,36 +83,15 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// Access
|
||||
// /////////////////////////////////////
|
||||
|
||||
let queryToBuild: Where = {
|
||||
and: [],
|
||||
};
|
||||
|
||||
if (where) {
|
||||
queryToBuild = {
|
||||
and: [],
|
||||
...where,
|
||||
};
|
||||
|
||||
if (Array.isArray(where.AND)) {
|
||||
queryToBuild.and = [
|
||||
...queryToBuild.and,
|
||||
...where.AND,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
let accessResult: AccessResult;
|
||||
|
||||
if (!overrideAccess) {
|
||||
accessResult = await executeAccess({ req }, collectionConfig.access.update);
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
queryToBuild.and.push(accessResult);
|
||||
}
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
where: queryToBuild,
|
||||
where,
|
||||
access: accessResult,
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import httpStatus from 'http-status';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { DeepPartial } from 'ts-essentials';
|
||||
import { Where, Document } from '../../types';
|
||||
import { Document } from '../../types';
|
||||
import { Collection } from '../config/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
@@ -96,22 +96,13 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// Retrieve document
|
||||
// /////////////////////////////////////
|
||||
|
||||
const queryToBuild: Where = {
|
||||
and: [
|
||||
{
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResults)) {
|
||||
queryToBuild.and.push(accessResults);
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
where: queryToBuild,
|
||||
where: {
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
access: accessResults,
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { hasWhereAccessResult } from '../../auth';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { Where } from '../../types';
|
||||
import { AccessResult } from '../../config/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable';
|
||||
@@ -22,7 +20,6 @@ type Args = {
|
||||
async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T> {
|
||||
const {
|
||||
globalConfig,
|
||||
locale,
|
||||
req,
|
||||
req: {
|
||||
payload,
|
||||
@@ -40,28 +37,19 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
|
||||
// Retrieve and execute access
|
||||
// /////////////////////////////////////
|
||||
|
||||
const queryToBuild: Where = {
|
||||
and: [
|
||||
{
|
||||
globalType: {
|
||||
equals: slug,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let accessResult: AccessResult;
|
||||
|
||||
if (!overrideAccess) {
|
||||
accessResult = await executeAccess({ req }, globalConfig.access.read);
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
queryToBuild.and.push(accessResult);
|
||||
}
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
where: queryToBuild,
|
||||
where: {
|
||||
globalType: {
|
||||
equals: slug,
|
||||
},
|
||||
},
|
||||
access: accessResult,
|
||||
req,
|
||||
overrideAccess,
|
||||
globalSlug: slug,
|
||||
|
||||
@@ -3,8 +3,6 @@ import { PayloadRequest } from '../../express/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { Forbidden, NotFound } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { Where } from '../../types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import { TypeWithVersion } from '../../versions/types';
|
||||
import { SanitizedGlobalConfig } from '../config/types';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
@@ -49,22 +47,13 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
|
||||
|
||||
const hasWhereAccess = typeof accessResults === 'object';
|
||||
|
||||
const queryToBuild: Where = {
|
||||
and: [
|
||||
{
|
||||
_id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResults)) {
|
||||
queryToBuild.and.push(accessResults);
|
||||
}
|
||||
|
||||
const query = await VersionsModel.buildQuery({
|
||||
where: queryToBuild,
|
||||
where: {
|
||||
_id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
access: accessResults,
|
||||
req,
|
||||
overrideAccess,
|
||||
globalSlug: globalConfig.slug,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { PayloadRequest } from '../../express/types';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { PaginatedDocs } from '../../mongoose/types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import flattenWhereConstraints from '../../utilities/flattenWhereConstraints';
|
||||
import { buildSortParam } from '../../mongoose/buildSortParam';
|
||||
import { SanitizedGlobalConfig } from '../config/types';
|
||||
@@ -47,45 +46,18 @@ async function findVersions<T extends TypeWithVersion<T>>(
|
||||
// Access
|
||||
// /////////////////////////////////////
|
||||
|
||||
let queryToBuild: Where = {};
|
||||
let useEstimatedCount = false;
|
||||
|
||||
if (where) {
|
||||
let and = [];
|
||||
|
||||
if (Array.isArray(where.and)) and = where.and;
|
||||
if (Array.isArray(where.AND)) and = where.AND;
|
||||
|
||||
queryToBuild = {
|
||||
...where,
|
||||
and: [
|
||||
...and,
|
||||
],
|
||||
};
|
||||
|
||||
const constraints = flattenWhereConstraints(queryToBuild);
|
||||
|
||||
const constraints = flattenWhereConstraints(where);
|
||||
useEstimatedCount = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'));
|
||||
}
|
||||
|
||||
if (!overrideAccess) {
|
||||
const accessResults = await executeAccess({ req }, globalConfig.access.readVersions);
|
||||
|
||||
if (hasWhereAccessResult(accessResults)) {
|
||||
if (!where) {
|
||||
queryToBuild = {
|
||||
and: [
|
||||
accessResults,
|
||||
],
|
||||
};
|
||||
} else {
|
||||
queryToBuild.and.push(accessResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
const accessResults = !overrideAccess ? await executeAccess({ req }, globalConfig.access.readVersions) : true;
|
||||
|
||||
const query = await VersionsModel.buildQuery({
|
||||
where: queryToBuild,
|
||||
where,
|
||||
access: accessResults,
|
||||
req,
|
||||
overrideAccess,
|
||||
globalSlug: globalConfig.slug,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { DeepPartial } from 'ts-essentials';
|
||||
import { Where } from '../../types';
|
||||
import { SanitizedGlobalConfig } from '../config/types';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { hasWhereAccessResult } from '../../auth';
|
||||
import { beforeChange } from '../../fields/hooks/beforeChange';
|
||||
import { beforeValidate } from '../../fields/hooks/beforeValidate';
|
||||
import { afterChange } from '../../fields/hooks/afterChange';
|
||||
@@ -61,22 +59,13 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
// Retrieve document
|
||||
// /////////////////////////////////////
|
||||
|
||||
const queryToBuild: Where = {
|
||||
and: [
|
||||
{
|
||||
globalType: {
|
||||
equals: slug,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResults)) {
|
||||
queryToBuild.and.push(accessResults);
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
where: queryToBuild,
|
||||
where: {
|
||||
globalType: {
|
||||
equals: slug,
|
||||
},
|
||||
},
|
||||
access: accessResults,
|
||||
req,
|
||||
overrideAccess,
|
||||
globalSlug: slug,
|
||||
|
||||
@@ -44,6 +44,7 @@ type ParamParserArgs = {
|
||||
versionsFields?: Field[]
|
||||
model: any
|
||||
where: Where
|
||||
access?: Where | boolean
|
||||
overrideAccess?: boolean
|
||||
}
|
||||
|
||||
@@ -56,6 +57,8 @@ export class ParamParser {
|
||||
|
||||
req: PayloadRequest;
|
||||
|
||||
access?: Where | boolean;
|
||||
|
||||
where: Where;
|
||||
|
||||
model: any;
|
||||
@@ -82,6 +85,7 @@ export class ParamParser {
|
||||
versionsFields,
|
||||
model,
|
||||
where,
|
||||
access,
|
||||
overrideAccess,
|
||||
}: ParamParserArgs) {
|
||||
this.req = req;
|
||||
@@ -90,6 +94,7 @@ export class ParamParser {
|
||||
this.parse = this.parse.bind(this);
|
||||
this.model = model;
|
||||
this.where = where;
|
||||
this.access = access;
|
||||
this.overrideAccess = overrideAccess;
|
||||
this.localizationConfig = req.payload.config.localization;
|
||||
this.policies = {
|
||||
@@ -113,65 +118,77 @@ export class ParamParser {
|
||||
// Entry point to the ParamParser class
|
||||
|
||||
async parse(): Promise<Record<string, unknown>> {
|
||||
if (typeof this.where === 'object') {
|
||||
const query = await this.parsePathOrRelation(this.where);
|
||||
return query;
|
||||
const query = await this.parsePathOrRelation(this.where, this.overrideAccess);
|
||||
|
||||
const result = {
|
||||
$and: [],
|
||||
};
|
||||
|
||||
if (query) result.$and.push(query);
|
||||
|
||||
if (typeof this.access === 'object') {
|
||||
const accessQuery = await this.parsePathOrRelation(this.access, true);
|
||||
if (accessQuery) result.$and.push(accessQuery);
|
||||
}
|
||||
|
||||
return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
async parsePathOrRelation(object: Where): Promise<Record<string, unknown>> {
|
||||
async parsePathOrRelation(object: Where, overrideAccess: boolean): Promise<Record<string, unknown>> {
|
||||
let result = {} as FilterQuery<any>;
|
||||
// We need to determine if the whereKey is an AND, OR, or a schema path
|
||||
for (const relationOrPath of Object.keys(object)) {
|
||||
if (relationOrPath.toLowerCase() === 'and') {
|
||||
const andConditions = object[relationOrPath];
|
||||
const builtAndConditions = await this.buildAndOrConditions(andConditions);
|
||||
if (builtAndConditions.length > 0) result.$and = builtAndConditions;
|
||||
} else if (relationOrPath.toLowerCase() === 'or' && Array.isArray(object[relationOrPath])) {
|
||||
const orConditions = object[relationOrPath];
|
||||
const builtOrConditions = await this.buildAndOrConditions(orConditions);
|
||||
if (builtOrConditions.length > 0) result.$or = builtOrConditions;
|
||||
} else {
|
||||
// It's a path - and there can be multiple comparisons on a single path.
|
||||
// For example - title like 'test' and title not equal to 'tester'
|
||||
// So we need to loop on keys again here to handle each operator independently
|
||||
const pathOperators = object[relationOrPath];
|
||||
if (typeof pathOperators === 'object') {
|
||||
for (const operator of Object.keys(pathOperators)) {
|
||||
if (validOperators.includes(operator)) {
|
||||
const searchParam = await this.buildSearchParam({
|
||||
fields: this.fields,
|
||||
incomingPath: relationOrPath,
|
||||
val: pathOperators[operator],
|
||||
operator,
|
||||
});
|
||||
|
||||
if (searchParam?.value && searchParam?.path) {
|
||||
result = {
|
||||
...result,
|
||||
[searchParam.path]: searchParam.value,
|
||||
};
|
||||
} else if (typeof searchParam?.value === 'object') {
|
||||
result = deepmerge(result, searchParam.value, { arrayMerge: combineMerge });
|
||||
if (typeof object === 'object') {
|
||||
// We need to determine if the whereKey is an AND, OR, or a schema path
|
||||
for (const relationOrPath of Object.keys(object)) {
|
||||
const condition = object[relationOrPath];
|
||||
if (relationOrPath.toLowerCase() === 'and' && Array.isArray(condition)) {
|
||||
const builtAndConditions = await this.buildAndOrConditions(condition, overrideAccess);
|
||||
if (builtAndConditions.length > 0) result.$and = builtAndConditions;
|
||||
} else if (relationOrPath.toLowerCase() === 'or' && Array.isArray(condition)) {
|
||||
const builtOrConditions = await this.buildAndOrConditions(condition, overrideAccess);
|
||||
if (builtOrConditions.length > 0) result.$or = builtOrConditions;
|
||||
} else {
|
||||
// It's a path - and there can be multiple comparisons on a single path.
|
||||
// For example - title like 'test' and title not equal to 'tester'
|
||||
// So we need to loop on keys again here to handle each operator independently
|
||||
const pathOperators = object[relationOrPath];
|
||||
if (typeof pathOperators === 'object') {
|
||||
for (const operator of Object.keys(pathOperators)) {
|
||||
if (validOperators.includes(operator)) {
|
||||
const searchParam = await this.buildSearchParam({
|
||||
fields: this.fields,
|
||||
incomingPath: relationOrPath,
|
||||
val: pathOperators[operator],
|
||||
operator,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
if (searchParam?.value && searchParam?.path) {
|
||||
result = {
|
||||
...result,
|
||||
[searchParam.path]: searchParam.value,
|
||||
};
|
||||
} else if (typeof searchParam?.value === 'object') {
|
||||
result = deepmerge(result, searchParam.value, { arrayMerge: combineMerge });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async buildAndOrConditions(conditions) {
|
||||
async buildAndOrConditions(conditions: Where[], overrideAccess: boolean): Promise<Record<string, unknown>[]> {
|
||||
const completedConditions = [];
|
||||
// Loop over all AND / OR operations and add them to the AND / OR query param
|
||||
// Operations should come through as an array
|
||||
for (const condition of conditions) {
|
||||
// If the operation is properly formatted as an object
|
||||
if (typeof condition === 'object') {
|
||||
const result = await this.parsePathOrRelation(condition);
|
||||
const result = await this.parsePathOrRelation(condition, overrideAccess);
|
||||
if (Object.keys(result).length > 0) {
|
||||
completedConditions.push(result);
|
||||
}
|
||||
@@ -186,11 +203,13 @@ export class ParamParser {
|
||||
incomingPath,
|
||||
val,
|
||||
operator,
|
||||
overrideAccess,
|
||||
}: {
|
||||
fields: Field[],
|
||||
incomingPath: string,
|
||||
val: unknown,
|
||||
operator: string
|
||||
overrideAccess: boolean
|
||||
}): Promise<SearchParam> {
|
||||
// Replace GraphQL nested field double underscore formatting
|
||||
let sanitizedPath = incomingPath.replace(/__/gi, '.');
|
||||
@@ -228,6 +247,7 @@ export class ParamParser {
|
||||
globalSlug: this.globalSlug,
|
||||
fields,
|
||||
incomingPath: sanitizedPath,
|
||||
overrideAccess,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -268,7 +288,7 @@ export class ParamParser {
|
||||
},
|
||||
},
|
||||
req: this.req,
|
||||
overrideAccess: this.overrideAccess,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
const result = await SubModel.find(subQuery, subQueryOptions);
|
||||
@@ -334,11 +354,13 @@ export class ParamParser {
|
||||
globalSlug,
|
||||
fields,
|
||||
incomingPath,
|
||||
overrideAccess,
|
||||
}: {
|
||||
collectionSlug?: string
|
||||
globalSlug?: string
|
||||
fields: Field[]
|
||||
incomingPath: string
|
||||
overrideAccess: boolean
|
||||
}): Promise<PathToQuery[]> {
|
||||
const pathSegments = incomingPath.split('.');
|
||||
|
||||
@@ -353,7 +375,7 @@ export class ParamParser {
|
||||
},
|
||||
];
|
||||
|
||||
if (!this.overrideAccess) {
|
||||
if (!overrideAccess) {
|
||||
if (collectionSlug) {
|
||||
const collection = { ...this.req.payload.collections[collectionSlug].config };
|
||||
collection.fields = fields;
|
||||
@@ -431,7 +453,7 @@ export class ParamParser {
|
||||
}
|
||||
|
||||
if (matchedField) {
|
||||
if (!this.overrideAccess) {
|
||||
if (!overrideAccess) {
|
||||
const fieldAccess = lastIncompletePath.fieldPolicies[matchedField.name].read.permission;
|
||||
|
||||
if (!fieldAccess || ('hidden' in matchedField && matchedField.hidden)) {
|
||||
@@ -492,6 +514,7 @@ export class ParamParser {
|
||||
collectionSlug: relatedCollection.slug,
|
||||
fields: relatedCollection.fields,
|
||||
incomingPath: nestedPathToQuery,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
paths = [
|
||||
@@ -512,7 +535,7 @@ export class ParamParser {
|
||||
lastIncompletePath.fields = flattenFields(lastIncompletePath.field.fields, false);
|
||||
}
|
||||
|
||||
if (!this.overrideAccess && 'fields' in lastIncompletePath.fieldPolicies[lastIncompletePath.field.name]) {
|
||||
if (!overrideAccess && 'fields' in lastIncompletePath.fieldPolicies[lastIncompletePath.field.name]) {
|
||||
lastIncompletePath.fieldPolicies = lastIncompletePath.fieldPolicies[lastIncompletePath.field.name].fields;
|
||||
}
|
||||
|
||||
@@ -542,6 +565,7 @@ export type BuildQueryArgs = {
|
||||
req: PayloadRequest
|
||||
where: Where
|
||||
overrideAccess: boolean
|
||||
access?: Where | boolean
|
||||
globalSlug?: string
|
||||
}
|
||||
|
||||
@@ -553,7 +577,7 @@ const getBuildQueryPlugin = ({
|
||||
}: GetBuildQueryPluginArgs = {}) => {
|
||||
return function buildQueryPlugin(schema) {
|
||||
const modifiedSchema = schema;
|
||||
async function buildQuery({ req, where, overrideAccess = false, globalSlug }: BuildQueryArgs): Promise<Record<string, unknown>> {
|
||||
async function buildQuery({ req, where, overrideAccess = false, access, globalSlug }: BuildQueryArgs): Promise<Record<string, unknown>> {
|
||||
const paramParser = new ParamParser({
|
||||
req,
|
||||
collectionSlug,
|
||||
@@ -561,6 +585,7 @@ const getBuildQueryPlugin = ({
|
||||
versionsFields,
|
||||
model: this,
|
||||
where,
|
||||
access,
|
||||
overrideAccess,
|
||||
});
|
||||
const result = await paramParser.parse();
|
||||
|
||||
@@ -37,20 +37,15 @@ export const queryDrafts = async <T extends TypeWithID>({
|
||||
|
||||
const where = appendVersionToQueryKey(incomingWhere || {});
|
||||
|
||||
const versionQueryToBuild: Where = {
|
||||
...where,
|
||||
and: [
|
||||
...where?.and || [],
|
||||
],
|
||||
};
|
||||
let versionAccessResult;
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
const versionAccessResult = appendVersionToQueryKey(accessResult);
|
||||
versionQueryToBuild.and.push(versionAccessResult);
|
||||
versionAccessResult = appendVersionToQueryKey(accessResult);
|
||||
}
|
||||
|
||||
const versionQuery = await VersionModel.buildQuery({
|
||||
where: versionQueryToBuild,
|
||||
where,
|
||||
access: versionAccessResult,
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
@@ -54,13 +54,15 @@ const replaceWithDraftIfAvailable = async <T extends TypeWithID>({
|
||||
});
|
||||
}
|
||||
|
||||
let versionAccessResult;
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
const versionAccessResult = appendVersionToQueryKey(accessResult);
|
||||
queryToBuild.and.push(versionAccessResult);
|
||||
versionAccessResult = appendVersionToQueryKey(accessResult);
|
||||
}
|
||||
|
||||
const query = await VersionModel.buildQuery({
|
||||
where: queryToBuild,
|
||||
access: versionAccessResult,
|
||||
req,
|
||||
overrideAccess,
|
||||
globalSlug: entityType === 'global' ? entity.slug : undefined,
|
||||
|
||||
@@ -15,6 +15,8 @@ export const relyOnRequestHeadersSlug = 'rely-on-request-headers';
|
||||
export const docLevelAccessSlug = 'doc-level-access';
|
||||
export const hiddenFieldsSlug = 'hidden-fields';
|
||||
|
||||
export const hiddenAccessSlug = 'hidden-access';
|
||||
|
||||
const openAccess = {
|
||||
create: () => true,
|
||||
read: () => true,
|
||||
@@ -187,9 +189,31 @@ export default buildConfig({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
type: 'checkbox',
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
access: {
|
||||
readVersions: () => false,
|
||||
read: ({ req: { user } }) => {
|
||||
if (user) return true;
|
||||
|
||||
return {
|
||||
hidden: {
|
||||
not_equals: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
readVersions: ({ req: { user } }) => {
|
||||
if (user) return true;
|
||||
|
||||
return {
|
||||
'version.hidden': {
|
||||
not_equals: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -320,6 +344,37 @@ export default buildConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
type: 'checkbox',
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: hiddenAccessSlug,
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
if (user) return true;
|
||||
|
||||
return {
|
||||
hidden: {
|
||||
not_equals: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
type: 'checkbox',
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -3,8 +3,17 @@ import payload from '../../src';
|
||||
import { Forbidden } from '../../src/errors';
|
||||
import type { PayloadRequest } from '../../src/types';
|
||||
import { initPayloadTest } from '../helpers/configHelpers';
|
||||
import { hiddenFieldsSlug, relyOnRequestHeadersSlug, requestHeaders, restrictedSlug, siblingDataSlug, slug } from './config';
|
||||
import type { Restricted, Post, RelyOnRequestHeader } from './payload-types';
|
||||
import {
|
||||
hiddenAccessSlug,
|
||||
hiddenFieldsSlug,
|
||||
relyOnRequestHeadersSlug,
|
||||
requestHeaders,
|
||||
restrictedSlug,
|
||||
restrictedVersionsSlug,
|
||||
siblingDataSlug,
|
||||
slug,
|
||||
} from './config';
|
||||
import type { Post, RelyOnRequestHeader, Restricted } from './payload-types';
|
||||
import { firstArrayText, secondArrayText } from './shared';
|
||||
|
||||
describe('Access Control', () => {
|
||||
@@ -359,6 +368,59 @@ describe('Access Control', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Querying', () => {
|
||||
it('should respect query constraint using hidden field', async () => {
|
||||
await payload.create({
|
||||
collection: hiddenAccessSlug,
|
||||
data: {
|
||||
title: 'hello',
|
||||
},
|
||||
});
|
||||
|
||||
await payload.create({
|
||||
collection: hiddenAccessSlug,
|
||||
data: {
|
||||
title: 'hello',
|
||||
hidden: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { docs } = await payload.find({
|
||||
collection: hiddenAccessSlug,
|
||||
overrideAccess: false,
|
||||
});
|
||||
|
||||
expect(docs).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should respect query constraint using hidden field on versions', async () => {
|
||||
await payload.create({
|
||||
collection: restrictedVersionsSlug,
|
||||
data: {
|
||||
name: 'match',
|
||||
hidden: true,
|
||||
},
|
||||
});
|
||||
|
||||
await payload.create({
|
||||
collection: restrictedVersionsSlug,
|
||||
data: {
|
||||
name: 'match',
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
const { docs } = await payload.findVersions({
|
||||
where: {
|
||||
'version.name': { equals: 'match' },
|
||||
},
|
||||
collection: restrictedVersionsSlug,
|
||||
overrideAccess: false,
|
||||
});
|
||||
|
||||
expect(docs).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function createDoc<Collection>(data: Partial<Collection>, overrideSlug = slug, options?: Partial<Collection>): Promise<Collection> {
|
||||
|
||||
Reference in New Issue
Block a user