error: hasFilterKeyError()

in x-pack/platform/plugins/shared/fleet/server/routes/utils/filter_utils.ts [87:236]


          error: hasFilterKeyError(key, types, indexMapping, skipNormalization),
          isSavedObjectAttr: isSavedObjectAttr(key, indexMapping),
          key,
          type: getType(key),
        },
      ];
    }
    return kueryNode;
  }, []);
};

const getType = (key: string | undefined | null) => {
  if (key != null && key.includes('.')) {
    return key.split('.')[0];
  } else if (allowedTerms.some((term) => term === key)) {
    return 'searchTerm';
  } else {
    return null;
  }
};

/**
 * Is this filter key referring to a a top-level SavedObject attribute such as
 * `updated_at` or `references`.
 *
 * @param key
 * @param indexMapping
 */
export const isSavedObjectAttr = (key: string | null | undefined, indexMapping: IndexMapping) => {
  const keySplit = key != null ? key.split('.') : [];
  if (keySplit.length === 1 && fieldDefined(indexMapping, keySplit[0])) {
    return true;
  } else if (keySplit.length === 2 && keySplit[1] === 'id') {
    return true;
  } else if (keySplit.length === 2 && fieldDefined(indexMapping, keySplit[1])) {
    return true;
  } else {
    return false;
  }
};

export const hasFilterKeyError = (
  key: string | null | undefined,
  types: string[],
  indexMapping: IndexMapping,
  skipNormalization?: boolean
): string | null => {
  if (key === null) {
    return null;
  }
  if (!key) {
    return `Invalid key`;
  }
  if (!key.includes('.')) {
    if (allowedTerms.some((term) => term === key) || fieldDefined(indexMapping, key)) {
      return null;
    }
    return `This type '${key}' is not allowed`;
  } else if (key.includes('.')) {
    const keySplit = key.split('.');
    const firstField = keySplit[0];
    const hasIndexWrap = types.includes(firstField);

    if (keySplit.length <= 1 && !fieldDefined(indexMapping, firstField) && !hasIndexWrap) {
      return `This type '${firstField}' is not allowed`;
    }
    // In some cases we don't want to check about the `attributes` presence
    // In that case pass the `skipNormalization` parameter
    if (
      (!skipNormalization && keySplit.length === 2 && fieldDefined(indexMapping, key)) ||
      (!skipNormalization && keySplit.length > 2 && keySplit[1] !== 'attributes')
    ) {
      return `This key '${key}' does NOT match the filter proposition SavedObjectType.attributes.key`;
    }
    // Check that the key exists in the mappings
    let searchKey = '';
    if (keySplit.length === 2) {
      searchKey = hasIndexWrap ? keySplit[1] : key;
    } else if (keySplit.length > 2) {
      searchKey =
        skipNormalization || keySplit[1] !== 'attributes'
          ? `${firstField}.${keySplit.slice(1, keySplit.length).join('.')}`
          : `${firstField}.${keySplit.slice(2, keySplit.length).join('.')}`;
    }
    if (!fieldDefined(indexMapping, searchKey)) {
      return `This key '${key}' does NOT exist in ${types.join()} saved object index patterns`;
    }
  }
  return null;
};

const getMappingKey = (key?: string) =>
  !!key ? 'properties.' + key.split('.').join('.properties.') : '';

export const fieldDefined = (indexMappings: IndexMapping, key: string): boolean => {
  const keySplit = key.split('.');
  const shortenedKey = `${keySplit[1]}.${keySplit.slice(2, keySplit.length).join('.')}`;
  const mappingKey = getMappingKey(key);

  if (
    !!get(indexMappings, mappingKey) ||
    !!get(indexMappings, getMappingKey(shortenedKey)) ||
    mappingKey === 'properties.id'
  ) {
    return true;
  }

  // If the `mappingKey` does not match a valid path, before returning false,
  // we want to check and see if the intended path was for a multi-field
  // such as `x.attributes.field.text` where `field` is mapped to both text
  // and keyword
  const propertiesAttribute = 'properties';
  const indexOfLastProperties = mappingKey.lastIndexOf(propertiesAttribute);
  const fieldMapping = mappingKey.substr(0, indexOfLastProperties);
  const fieldType = mappingKey.substr(
    mappingKey.lastIndexOf(propertiesAttribute) + `${propertiesAttribute}.`.length
  );
  const mapping = `${fieldMapping}fields.${fieldType}`;
  if (!!get(indexMappings, mapping)) {
    return true;
  }

  // If the path is for a flattened type field, we'll assume the mappings are defined.
  const keys = key.split('.');
  for (let i = 0; i < keys.length; i++) {
    const path = `properties.${keys.slice(0, i + 1).join('.properties.')}`;
    if (get(indexMappings, path)?.type === 'flattened') {
      return true;
    }
  }

  return false;
};

export const validateKuery = (
  kuery: string | undefined,
  allowedTypes: string[],
  indexMapping: IndexMapping,
  skipNormalization?: boolean
) => {
  let isValid = true;
  let error: string | undefined;

  if (!kuery) {
    isValid = true;
  }
  try {
    if (kuery && indexMapping) {
      const astFilter = esKuery.fromKueryExpression(kuery);
      const validationObject = validateFilterKueryNode({