fix: #2592, allows usage of hidden fields within access query constraints (#2599)

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This commit is contained in:
James Mikrut
2023-05-01 17:15:14 -04:00
committed by GitHub
parent 870838e756
commit a0bb13a412
18 changed files with 262 additions and 325 deletions

View File

@@ -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,
});

View File

@@ -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,
});

View File

@@ -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,
});
// /////////////////////////////////////

View File

@@ -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,
});

View File

@@ -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,
});

View File

@@ -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,
});

View File

@@ -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,
});

View File

@@ -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,
});

View File

@@ -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,
});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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();

View File

@@ -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,
});

View File

@@ -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,

View File

@@ -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,
},
],
},
],

View File

@@ -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> {