From 7747d1d3d16a1b2f3c07a084c35a72b5c3898d18 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Fri, 5 May 2023 13:11:59 +0200 Subject: [PATCH] initial commit --- .../core/utils/lib/convert-query-params.js | 55 +++++++++++++++++-- .../core/utils/lib/sanitize/sanitizers.js | 1 + 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/packages/core/utils/lib/convert-query-params.js b/packages/core/utils/lib/convert-query-params.js index efc279535e..747e6fee5e 100644 --- a/packages/core/utils/lib/convert-query-params.js +++ b/packages/core/utils/lib/convert-query-params.js @@ -18,6 +18,7 @@ const { cloneDeep, get, mergeAll, + isString, } = require('lodash/fp'); const _ = require('lodash'); const parseType = require('./parse-type'); @@ -46,7 +47,7 @@ class InvalidSortError extends Error { } const validateOrder = (order) => { - if (!['asc', 'desc'].includes(order.toLocaleLowerCase())) { + if (!isString(order) || !['asc', 'desc'].includes(order.toLocaleLowerCase())) { throw new InvalidOrderError(); } }; @@ -80,6 +81,13 @@ const convertSortQueryParams = (sortQuery) => { }; const convertSingleSortQueryParam = (sortQuery) => { + if (!sortQuery) { + return {}; + } + if (!isString(sortQuery)) { + throw new Error('Invalid sort query'); + } + // split field and order param with default order to ascending const [field, order = 'asc'] = sortQuery.split(':'); @@ -89,6 +97,8 @@ const convertSingleSortQueryParam = (sortQuery) => { validateOrder(order); + // TODO: field should be a valid path on an object model + return _.set({}, field, order); }; @@ -368,6 +378,7 @@ const convertNestedPopulate = (subPopulate, schema) => { return query; }; + // TODO: ensure field is valid in content types (will probably have to check strapi.contentTypes since it can be a string.path) const convertFieldsQueryParams = (fields, depth = 0) => { if (depth === 0 && fields === '*') { return undefined; @@ -387,6 +398,33 @@ const convertFieldsQueryParams = (fields, depth = 0) => { throw new Error('Invalid fields parameter. Expected a string or an array of strings'); }; +// TODO: this is temporary as a POC, get operators from @strapi/database (will likely require refactoring) +const isOperator = (key) => { + return key.startsWith('$'); +}; + +const isValidSchemaAttribute = (key, schema) => { + if (key.trim().toLowerCase() === 'id') { + return true; + } + + if (!schema) { + return false; + } + + // case insensitive check if the attribute exists in the schema + const findAttribute = key.toLowerCase(); + if ( + Object.keys(schema.attributes) + .map((attributeName) => attributeName.toLowerCase()) + .includes(findAttribute) + ) { + return true; + } + + return false; +}; + const convertFiltersQueryParams = (filters, schema) => { // Filters need to be either an array or an object // Here we're only checking for 'object' type since typeof [] => object and typeof {} => object @@ -401,10 +439,6 @@ const convertFiltersQueryParams = (filters, schema) => { }; const convertAndSanitizeFilters = (filters, schema) => { - if (!isPlainObject(filters)) { - return filters; - } - if (Array.isArray(filters)) { return ( filters @@ -415,14 +449,23 @@ const convertAndSanitizeFilters = (filters, schema) => { ); } + // This must come after check for Array or else arrays are not filtered + if (!isPlainObject(filters)) { + return filters; + } + const removeOperator = (operator) => delete filters[operator]; // Here, `key` can either be an operator or an attribute name for (const [key, value] of Object.entries(filters)) { const attribute = get(key, schema?.attributes); + const validKey = isOperator(key) || isValidSchemaAttribute(key, schema); + if (!validKey) { + removeOperator(key); + } // Handle attributes - if (attribute) { + else if (attribute) { // Relations if (attribute.type === 'relation') { filters[key] = convertAndSanitizeFilters(value, strapi.getModel(attribute.target)); diff --git a/packages/core/utils/lib/sanitize/sanitizers.js b/packages/core/utils/lib/sanitize/sanitizers.js index 67ab23f380..b776062f6c 100644 --- a/packages/core/utils/lib/sanitize/sanitizers.js +++ b/packages/core/utils/lib/sanitize/sanitizers.js @@ -35,6 +35,7 @@ const defaultSanitizeOutput = async (schema, entity) => { ); }; +// TODO: remove fields that don't exist on the schema const defaultSanitizeFilters = curry((schema, filters) => { return pipeAsync( // Remove dynamic zones from filters