UNPKG

payload

Version:

Node, React, Headless CMS and Application Framework built on Next.js

291 lines (290 loc) • 12.6 kB
// @ts-strict-ignore import { combineQueries } from '../database/combineQueries.js'; import { tabHasName } from '../fields/config/types.js'; /** * Build up permissions object for an entity (collection or global) */ export async function getEntityPolicies(args) { const { id, type, blockPolicies, entity, operations, req } = args; const { data, locale, payload, user } = req; const isLoggedIn = !!user; const policies = { fields: {} }; let docBeingAccessed; async function getEntityDoc({ operation, where } = {}) { if (!entity.slug) { return undefined; } if (type === 'global') { return payload.findGlobal({ slug: entity.slug, depth: 0, fallbackLocale: null, locale, overrideAccess: true, req }); } if (type === 'collection' && id) { if (typeof where === 'object') { const options = { collection: entity.slug, depth: 0, fallbackLocale: null, limit: 1, locale, overrideAccess: true, req }; if (operation === 'readVersions') { const paginatedRes = await payload.findVersions({ ...options, where: combineQueries(where, { parent: { equals: id } }) }); return paginatedRes?.docs?.[0] || undefined; } const paginatedRes = await payload.find({ ...options, pagination: false, where: combineQueries(where, { id: { equals: id } }) }); return paginatedRes?.docs?.[0] || undefined; } return payload.findByID({ id, collection: entity.slug, depth: 0, fallbackLocale: null, locale, overrideAccess: true, req }); } } const createAccessPromise = async ({ access, accessLevel, disableWhere = false, operation, policiesObj: mutablePolicies })=>{ if (accessLevel === 'field' && docBeingAccessed === undefined) { // assign docBeingAccessed first as the promise to avoid multiple calls to getEntityDoc docBeingAccessed = getEntityDoc().then((doc)=>{ docBeingAccessed = doc; }); } // awaiting the promise to ensure docBeingAccessed is assigned before it is used await docBeingAccessed; // https://payloadcms.slack.com/archives/C048Z9C2BEX/p1702054928343769 const accessResult = await access({ id, data, doc: docBeingAccessed, req }); // Where query was returned from access function => check if document is returned when querying with where if (typeof accessResult === 'object' && !disableWhere) { mutablePolicies[operation] = { permission: id || type === 'global' ? !!await getEntityDoc({ operation, where: accessResult }) : true, where: accessResult }; } else if (mutablePolicies[operation]?.permission !== false) { mutablePolicies[operation] = { permission: !!accessResult }; } }; for (const operation of operations){ if (typeof entity.access[operation] === 'function') { await createAccessPromise({ access: entity.access[operation], accessLevel: 'entity', operation, policiesObj: policies }); } else { policies[operation] = { permission: isLoggedIn }; } await executeFieldPolicies({ blockPolicies, createAccessPromise, entityPermission: policies[operation].permission, fields: entity.fields, operation, payload, policiesObj: policies }); } return policies; } /** * Build up permissions object and run access functions for each field of an entity */ const executeFieldPolicies = async ({ blockPolicies, createAccessPromise, entityPermission, fields, operation, payload, policiesObj })=>{ const mutablePolicies = policiesObj.fields; // Fields don't have all operations of a collection if (operation === 'delete' || operation === 'readVersions' || operation === 'unlock') { return; } await Promise.all(fields.map(async (field)=>{ if ('name' in field && field.name) { if (!mutablePolicies[field.name]) { mutablePolicies[field.name] = {}; } if ('access' in field && field.access && typeof field.access[operation] === 'function') { await createAccessPromise({ access: field.access[operation], accessLevel: 'field', disableWhere: true, operation, policiesObj: mutablePolicies[field.name] }); } else { mutablePolicies[field.name][operation] = { permission: policiesObj[operation]?.permission }; } if ('fields' in field && field.fields) { if (!mutablePolicies[field.name].fields) { mutablePolicies[field.name].fields = {}; } await executeFieldPolicies({ blockPolicies, createAccessPromise, entityPermission, fields: field.fields, operation, payload, policiesObj: mutablePolicies[field.name] }); } if ('blocks' in field && field.blocks?.length || 'blockReferences' in field && field.blockReferences?.length) { if (!mutablePolicies[field.name]?.blocks) { mutablePolicies[field.name].blocks = {}; } await Promise.all((field.blockReferences ?? field.blocks).map(async (_block)=>{ const block = typeof _block === 'string' ? payload.blocks[_block] : _block; if (typeof _block === 'string') { if (blockPolicies[_block]) { if (typeof blockPolicies[_block].then === 'function') { // Earlier access to this block is still pending, so await it instead of re-running executeFieldPolicies mutablePolicies[field.name].blocks[block.slug] = await blockPolicies[_block]; } else { // It's already a resolved policy object mutablePolicies[field.name].blocks[block.slug] = blockPolicies[_block]; } return; } else { // We have not seen this block slug yet. Immediately create a promise // so that any parallel calls will just await this same promise // instead of re-running executeFieldPolicies. blockPolicies[_block] = (async ()=>{ // If the block doesn’t exist yet in our mutablePolicies, initialize it if (!mutablePolicies[field.name].blocks?.[block.slug]) { mutablePolicies[field.name].blocks[block.slug] = { fields: {}, [operation]: { permission: entityPermission } }; } else if (!mutablePolicies[field.name].blocks[block.slug][operation]) { mutablePolicies[field.name].blocks[block.slug][operation] = { permission: entityPermission }; } await executeFieldPolicies({ blockPolicies, createAccessPromise, entityPermission, fields: block.fields, operation, payload, policiesObj: mutablePolicies[field.name].blocks[block.slug] }); return mutablePolicies[field.name].blocks[block.slug]; })(); mutablePolicies[field.name].blocks[block.slug] = await blockPolicies[_block]; blockPolicies[_block] = mutablePolicies[field.name].blocks[block.slug]; return; } } if (!mutablePolicies[field.name].blocks?.[block.slug]) { mutablePolicies[field.name].blocks[block.slug] = { fields: {}, [operation]: { permission: entityPermission } }; } else if (!mutablePolicies[field.name].blocks[block.slug][operation]) { mutablePolicies[field.name].blocks[block.slug][operation] = { permission: entityPermission }; } await executeFieldPolicies({ blockPolicies, createAccessPromise, entityPermission, fields: block.fields, operation, payload, policiesObj: mutablePolicies[field.name].blocks[block.slug] }); })); } } else if ('fields' in field && field.fields) { await executeFieldPolicies({ blockPolicies, createAccessPromise, entityPermission, fields: field.fields, operation, payload, policiesObj }); } else if (field.type === 'tabs') { await Promise.all(field.tabs.map(async (tab)=>{ if (tabHasName(tab)) { if (!mutablePolicies[tab.name]) { mutablePolicies[tab.name] = { fields: {}, [operation]: { permission: entityPermission } }; } else if (!mutablePolicies[tab.name][operation]) { mutablePolicies[tab.name][operation] = { permission: entityPermission }; } await executeFieldPolicies({ blockPolicies, createAccessPromise, entityPermission, fields: tab.fields, operation, payload, policiesObj: mutablePolicies[tab.name] }); } else { await executeFieldPolicies({ blockPolicies, createAccessPromise, entityPermission, fields: tab.fields, operation, payload, policiesObj }); } })); } })); }; //# sourceMappingURL=getEntityPolicies.js.map