"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.performFind = void 0; var _boom = _interopRequireDefault(require("@hapi/boom")); var _coreElasticsearchServerInternal = require("@kbn/core-elasticsearch-server-internal"); var _coreSavedObjectsServer = require("@kbn/core-saved-objects-server"); var _coreSavedObjectsUtilsServer = require("@kbn/core-saved-objects-utils-server"); var _search = require("../search"); var _utils = require("../utils"); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0 and the Server Side Public License, v 1; you may not use this file except * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ const performFind = async ({ options, internalOptions }, { registry, helpers, allowedTypes: rawAllowedTypes, mappings, client, migrator, extensions = {} }) => { var _authorizationResult3; const { common: commonHelper, encryption: encryptionHelper, serializer: serializerHelper, migration: migrationHelper } = helpers; const { securityExtension, spacesExtension } = extensions; let namespaces; const { disableExtensions } = internalOptions; if (disableExtensions || !spacesExtension) { var _options$namespaces; namespaces = (_options$namespaces = options.namespaces) !== null && _options$namespaces !== void 0 ? _options$namespaces : [_coreSavedObjectsUtilsServer.DEFAULT_NAMESPACE_STRING]; // If the consumer specified `namespaces: []`, throw a Bad Request error if (namespaces.length === 0) throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.createBadRequestError('options.namespaces cannot be an empty array'); } const { search, defaultSearchOperator = 'OR', searchFields, rootSearchFields, hasReference, hasReferenceOperator, hasNoReference, hasNoReferenceOperator, page = _coreSavedObjectsUtilsServer.FIND_DEFAULT_PAGE, perPage = _coreSavedObjectsUtilsServer.FIND_DEFAULT_PER_PAGE, pit, searchAfter, sortField, sortOrder, fields, type, filter, preference, aggs, migrationVersionCompatibility } = options; if (!type) { throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.createBadRequestError('options.type must be a string or an array of strings'); } else if (preference !== null && preference !== void 0 && preference.length && pit) { throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.createBadRequestError('options.preference must be excluded when options.pit is used'); } const types = Array.isArray(type) ? type : [type]; const allowedTypes = types.filter(t => rawAllowedTypes.includes(t)); if (allowedTypes.length === 0) { return _coreSavedObjectsUtilsServer.SavedObjectsUtils.createEmptyFindResponse(options); } if (searchFields && !Array.isArray(searchFields)) { throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.createBadRequestError('options.searchFields must be an array'); } if (fields && !Array.isArray(fields)) { throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.createBadRequestError('options.fields must be an array'); } let kueryNode; if (filter) { try { kueryNode = (0, _search.validateConvertFilterToKueryNode)(allowedTypes, filter, mappings); } catch (e) { if (e.name === 'KQLSyntaxError') { throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.createBadRequestError(`KQLSyntaxError: ${e.message}`); } else { throw e; } } } let aggsObject; if (aggs) { try { aggsObject = (0, _search.validateAndConvertAggregations)(allowedTypes, aggs, mappings); } catch (e) { throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.createBadRequestError(`Invalid aggregation: ${e.message}`); } } if (!disableExtensions && spacesExtension) { try { namespaces = await spacesExtension.getSearchableNamespaces(options.namespaces); } catch (err) { if (_boom.default.isBoom(err) && err.output.payload.statusCode === 403) { // The user is not authorized to access any space, return an empty response. return _coreSavedObjectsUtilsServer.SavedObjectsUtils.createEmptyFindResponse(options); } throw err; } if (namespaces.length === 0) { // The user is authorized to access *at least one space*, but not any of the spaces they requested; return an empty response. return _coreSavedObjectsUtilsServer.SavedObjectsUtils.createEmptyFindResponse(options); } } // We have to first perform an initial authorization check so that we can construct the search DSL accordingly const spacesToAuthorize = new Set(namespaces); const typesToAuthorize = new Set(types); let typeToNamespacesMap; let authorizationResult; if (!disableExtensions && securityExtension) { var _authorizationResult, _authorizationResult2; authorizationResult = await securityExtension.authorizeFind({ namespaces: spacesToAuthorize, types: typesToAuthorize }); if (((_authorizationResult = authorizationResult) === null || _authorizationResult === void 0 ? void 0 : _authorizationResult.status) === 'unauthorized') { // If the user is unauthorized to find *anything* they requested, return an empty response return _coreSavedObjectsUtilsServer.SavedObjectsUtils.createEmptyFindResponse(options); } if (((_authorizationResult2 = authorizationResult) === null || _authorizationResult2 === void 0 ? void 0 : _authorizationResult2.status) === 'partially_authorized') { typeToNamespacesMap = new Map(); for (const [objType, entry] of authorizationResult.typeMap) { if (!entry.find) continue; // This ensures that the query DSL can filter only for object types that the user is authorized to access for a given space const { authorizedSpaces, isGloballyAuthorized } = entry.find; typeToNamespacesMap.set(objType, isGloballyAuthorized ? namespaces : authorizedSpaces); } } } const esOptions = { // If `pit` is provided, we drop the `index`, otherwise ES returns 400. index: pit ? undefined : commonHelper.getIndicesForTypes(allowedTypes), // If `searchAfter` is provided, we drop `from` as it will not be used for pagination. from: searchAfter ? undefined : perPage * (page - 1), _source: (0, _utils.includedFields)(allowedTypes, fields), preference, rest_total_hits_as_int: true, size: perPage, body: { size: perPage, seq_no_primary_term: true, from: perPage * (page - 1), _source: (0, _utils.includedFields)(allowedTypes, fields), ...(aggsObject ? { aggs: aggsObject } : {}), ...(0, _search.getSearchDsl)(mappings, registry, { search, defaultSearchOperator, searchFields, pit, rootSearchFields, type: allowedTypes, searchAfter, sortField, sortOrder, namespaces, typeToNamespacesMap, // If defined, this takes precedence over the `type` and `namespaces` fields hasReference, hasReferenceOperator, hasNoReference, hasNoReferenceOperator, kueryNode }) } }; const { body, statusCode, headers } = await client.search(esOptions, { ignore: [404], meta: true }); if (statusCode === 404) { if (!(0, _coreElasticsearchServerInternal.isSupportedEsServer)(headers)) { throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError(); } // 404 is only possible here if the index is missing, which // we don't want to leak, see "404s from missing index" above return _coreSavedObjectsUtilsServer.SavedObjectsUtils.createEmptyFindResponse(options); } let result; try { result = { ...(body.aggregations ? { aggregations: body.aggregations } : {}), page, per_page: perPage, total: body.hits.total, saved_objects: body.hits.hits.map(hit => { let savedObject = serializerHelper.rawToSavedObject(hit, { migrationVersionCompatibility }); // can't migrate a document with partial attributes if (!fields) { savedObject = migrationHelper.migrateStorageDocument(savedObject); } return { ...savedObject, score: hit._score, sort: hit.sort }; }), pit_id: body.pit_id }; } catch (error) { throw _coreSavedObjectsServer.SavedObjectsErrorHelpers.decorateGeneralError(error, 'Failed to migrate document to the latest version.'); } if (disableExtensions) { return result; } // Now that we have a full set of results with all existing namespaces for each object, // we need an updated authorization type map to pass on to the redact method const redactTypeMap = await (securityExtension === null || securityExtension === void 0 ? void 0 : securityExtension.getFindRedactTypeMap({ previouslyCheckedNamespaces: spacesToAuthorize, objects: result.saved_objects.map(obj => { var _obj$namespaces; return { type: obj.type, id: obj.id, existingNamespaces: (_obj$namespaces = obj.namespaces) !== null && _obj$namespaces !== void 0 ? _obj$namespaces : [] }; }) })); return encryptionHelper.optionallyDecryptAndRedactBulkResult(result, redactTypeMap !== null && redactTypeMap !== void 0 ? redactTypeMap : (_authorizationResult3 = authorizationResult) === null || _authorizationResult3 === void 0 ? void 0 : _authorizationResult3.typeMap // If the redact type map is valid, use that one; otherwise, fall back to the authorization check ); }; exports.performFind = performFind;