"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;