"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskValidator = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _lodash = require("lodash"); /* * 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; you may not use this file except in compliance with the Elastic License * 2.0. */ class TaskValidator { constructor({ definitions, allowReadingInvalidState, logger }) { (0, _defineProperty2.default)(this, "logger", void 0); (0, _defineProperty2.default)(this, "definitions", void 0); (0, _defineProperty2.default)(this, "allowReadingInvalidState", void 0); (0, _defineProperty2.default)(this, "cachedGetLatestStateSchema", void 0); (0, _defineProperty2.default)(this, "cachedExtendSchema", void 0); this.logger = logger; this.definitions = definitions; this.allowReadingInvalidState = allowReadingInvalidState; this.cachedGetLatestStateSchema = (0, _lodash.memoize)(getLatestStateSchema, taskTypeDef => taskTypeDef.type); this.cachedExtendSchema = (0, _lodash.memoize)(extendSchema, // We need to cache two outcomes per task type (unknowns: ignore and unknowns: forbid) options => `${options.taskType}|unknowns:${options.unknowns}`); } getValidatedTaskInstanceFromReading(task, options = { validate: true }) { if (!options.validate) { return task; } // In the scenario the task is unused / deprecated and Kibana needs to manipulate the task, // we'll do a pass-through for those if (!this.definitions.has(task.taskType)) { return task; } const taskTypeDef = this.definitions.get(task.taskType); const latestStateSchema = this.cachedGetLatestStateSchema(taskTypeDef); // TODO: Remove once all task types have defined their state schema. // https://github.com/elastic/kibana/issues/159347 // Otherwise, failures on read / write would occur. (don't forget to unskip test) if (!latestStateSchema) { return task; } let state = task.state; try { state = this.getValidatedStateSchema(this.migrateTaskState(task.state, task.stateVersion, taskTypeDef, latestStateSchema), task.taskType, latestStateSchema, 'ignore'); } catch (e) { if (!this.allowReadingInvalidState) { throw e; } this.logger.debug(`[${task.taskType}][${task.id}] Failed to validate the task's state. Allowing read operation to proceed because allow_reading_invalid_state is true. Error: ${e.message}`); } return { ...task, state }; } getValidatedTaskInstanceForUpdating(task, options = { validate: true }) { if (!options.validate) { return task; } // In the scenario the task is unused / deprecated and Kibana needs to manipulate the task, // we'll do a pass-through for those if (!this.definitions.has(task.taskType)) { return task; } const taskTypeDef = this.definitions.get(task.taskType); const latestStateSchema = this.cachedGetLatestStateSchema(taskTypeDef); // TODO: Remove once all task types have defined their state schema. // https://github.com/elastic/kibana/issues/159347 // Otherwise, failures on read / write would occur. (don't forget to unskip test) if (!latestStateSchema) { return task; } // We are doing a write operation which must validate against the latest state schema return { ...task, state: this.getValidatedStateSchema(task.state, task.taskType, latestStateSchema, 'forbid'), stateVersion: latestStateSchema === null || latestStateSchema === void 0 ? void 0 : latestStateSchema.version }; } migrateTaskState(state, currentVersion, taskTypeDef, latestStateSchema) { if (!latestStateSchema || currentVersion && currentVersion >= latestStateSchema.version) { return state; } let migratedState = state; for (let i = currentVersion || 1; i <= latestStateSchema.version; i++) { if (!taskTypeDef.stateSchemaByVersion || !taskTypeDef.stateSchemaByVersion[`${i}`]) { throw new Error(`[TaskValidator] state schema for ${taskTypeDef.type} missing version: ${i}`); } migratedState = taskTypeDef.stateSchemaByVersion[i].up(migratedState); try { taskTypeDef.stateSchemaByVersion[i].schema.validate(migratedState); } catch (e) { throw new Error(`[TaskValidator] failed to migrate to version ${i} because the data returned from the up migration doesn't match the schema: ${e.message}`); } } return migratedState; } getValidatedStateSchema(state, taskType, latestStateSchema, unknowns) { if (!latestStateSchema) { throw new Error(`[TaskValidator] stateSchemaByVersion not defined for task type: ${taskType}`); } return this.cachedExtendSchema({ unknowns, taskType, latestStateSchema }).validate(state); } } exports.TaskValidator = TaskValidator; function extendSchema(options) { if (!options.latestStateSchema) { throw new Error(`[TaskValidator] stateSchemaByVersion not defined for task type: ${options.taskType}`); } return options.latestStateSchema.schema.extendsDeep({ unknowns: options.unknowns }); } function getLatestStateSchema(taskTypeDef) { if (!taskTypeDef.stateSchemaByVersion) { return; } const versions = Object.keys(taskTypeDef.stateSchemaByVersion).map(v => parseInt(v, 10)); const latest = (0, _lodash.max)(versions); if (latest === undefined) { return; } return { version: latest, schema: taskTypeDef.stateSchemaByVersion[latest].schema, up: taskTypeDef.stateSchemaByVersion[latest].up }; }