import Semver from 'semver' ; import Boom from '@hapi/boom' ; import { omit , isEqual , map , uniq , pick , truncate , trim , mapValues } from 'lodash' ; import { i18n } from '@kbn/i18n' ; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey' ; import { Logger , SavedObjectsClientContract , SavedObjectReference , SavedObject , PluginInitializerContext , SavedObjectsUtils , SavedObjectAttributes , } from '../../../../../src/core/server' ; import { esKuery } from '../../../../../src/plugins/data/server' ; import { ActionsClient , ActionsAuthorization } from '../../../actions/server' ; import { Alert , PartialAlert , RawRule , RuleTypeRegistry , AlertAction , IntervalSchedule , SanitizedAlert , RuleTaskState , AlertSummary , AlertExecutionStatusValues , AlertNotifyWhenType , AlertTypeParams , ResolvedSanitizedRule , AlertWithLegacyId , SanitizedRuleWithLegacyId , PartialAlertWithLegacyId , RawAlertInstance , } from '../types' ; import { validateRuleTypeParams , ruleExecutionStatusFromRaw , getAlertNotifyWhenType } from '../lib' ; import { GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult , InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult , } from '../../../security/server' ; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server' ; import { TaskManagerStartContract } from '../../../task_manager/server' ; import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance' ; import { RegistryRuleType , UntypedNormalizedRuleType } from '../rule_type_registry' ; import { AlertingAuthorization , WriteOperations , ReadOperations , AlertingAuthorizationEntity , AlertingAuthorizationFilterType , AlertingAuthorizationFilterOpts , } from '../authorization' ; import { IEvent , IEventLogClient , IEventLogger , SAVED_OBJECT_REL_PRIMARY , } from '../../../event_log/server' ; import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date' ; import { alertSummaryFromEventLog } from '../lib/alert_summary_from_event_log' ; import { AuditLogger } from '../../../security/server' ; import { parseDuration } from '../../common/parse_duration' ; import { retryIfConflicts } from '../lib/retry_if_conflicts' ; import { partiallyUpdateAlert } from '../saved_objects' ; import { markApiKeyForInvalidation } from '../invalidate_pending_api_keys/mark_api_key_for_invalidation' ; import { ruleAuditEvent , RuleAuditAction } from './audit_events' ; import { KueryNode , nodeBuilder } from '../../../../../src/plugins/data/common' ; import { mapSortField } from './lib' ; import { getRuleExecutionStatusPending } from '../lib/rule_execution_status' ; import { AlertInstance } from '../alert_instance' ; import { EVENT_LOG_ACTIONS } from '../plugin' ; import { createAlertEventLogRecordObject } from '../lib/create_alert_event_log_record_object' ; export interface RegistryAlertTypeWithAuth extends RegistryRuleType { authorizedConsumers : string [ ] ; } type NormalizedAlertAction = Omit < AlertAction , 'actionTypeId' > ; export type CreateAPIKeyResult = { apiKeysEnabled : false ; } | { apiKeysEnabled : true ; result : SecurityPluginGrantAPIKeyResult ; } ; export type InvalidateAPIKeyResult = { apiKeysEnabled : false ; } | { apiKeysEnabled : true ; result : SecurityPluginInvalidateAPIKeyResult ; } ; export interface RuleAggregation { status : { buckets : Array < { key : string ; doc_count : number ; } > ; } ; muted : { buckets : Array < { key : number ; key_as_string : string ; doc_count : number ; } > ; } ; enabled : { buckets : Array < { key : number ; key_as_string : string ; doc_count : number ; } > ; } ; } export interface ConstructorOptions { logger : Logger ; taskManager : TaskManagerStartContract ; unsecuredSavedObjectsClient : SavedObjectsClientContract ; authorization : AlertingAuthorization ; actionsAuthorization : ActionsAuthorization ; ruleTypeRegistry : RuleTypeRegistry ; encryptedSavedObjectsClient : EncryptedSavedObjectsClient ; spaceId ? : string ; namespace ? : string ; getUserName : ( ) => Promise < string | null > ; createAPIKey : ( name : string ) => Promise < CreateAPIKeyResult > ; getActionsClient : ( ) => Promise < ActionsClient > ; getEventLogClient : ( ) => Promise < IEventLogClient > ; kibanaVersion : PluginInitializerContext [ 'env' ] [ 'packageInfo' ] [ 'version' ] ; auditLogger ? : AuditLogger ; eventLogger ? : IEventLogger ; } export interface MuteOptions extends IndexType { alertId : string ; alertInstanceId : string ; } export interface FindOptions extends IndexType { perPage ? : number ; page ? : number ; search ? : string ; defaultSearchOperator ? : 'AND' | 'OR' ; searchFields ? : string [ ] ; sortField ? : string ; sortOrder ? : estypes . SortOrder ; hasReference ? : { type : string ; id : string ; } ; fields ? : string [ ] ; filter ? : string ; } export interface AggregateOptions extends IndexType { search ? : string ; defaultSearchOperator ? : 'AND' | 'OR' ; searchFields ? : string [ ] ; hasReference ? : { type : string ; id : string ; } ; filter ? : string ; } interface IndexType { [ key ] : unknown ; } export interface AggregateResult { alertExecutionStatus : { [ status ] : number ; } ; ruleEnabledStatus ? : { enabled : number ; disabled : number ; } ; ruleMutedStatus ? : { muted : number ; unmuted : number ; } ; } export interface FindResult < Params extends AlertTypeParams > { page : number ; perPage : number ; total : number ; data : Array < SanitizedAlert < Params > > ; } export interface CreateOptions < Params extends AlertTypeParams > { data : Omit < Alert < Params > , 'id' | 'createdBy' | 'updatedBy' | 'createdAt' | 'updatedAt' | 'apiKey' | 'apiKeyOwner' | 'muteAll' | 'mutedInstanceIds' | 'actions' | 'executionStatus' > & { actions : NormalizedAlertAction [ ] ; } ; options ? : { id ? : string ; migrationVersion ? : Record < string , string > ; } ; } export interface UpdateOptions < Params extends AlertTypeParams > { id : string ; data : { name : string ; tags : string [ ] ; schedule : IntervalSchedule ; actions : NormalizedAlertAction [ ] ; params : Params ; throttle : string | null ; notifyWhen : AlertNotifyWhenType | null ; } ; } export interface GetAlertSummaryParams { id : string ; dateStart ? : string ; } const extractedSavedObjectParamReferenceNamePrefix = 'param:' ; const preconfiguredConnectorActionRefPrefix = 'preconfigured:' ; const alertingAuthorizationFilterOpts = { type : AlertingAuthorizationFilterType . KQL , fieldNames : { ruleTypeId : 'alert.attributes.alertTypeId' , consumer : 'alert.attributes.consumer' } , } ; export class RulesClient { private readonly logger : Logger ; private readonly getUserName : ( ) => Promise < string | null > ; private readonly spaceId ? : string ; private readonly namespace ? : string ; private readonly taskManager : TaskManagerStartContract ; private readonly unsecuredSavedObjectsClient : SavedObjectsClientContract ; private readonly authorization : AlertingAuthorization ; private readonly ruleTypeRegistry : RuleTypeRegistry ; private readonly createAPIKey : ( name : string ) => Promise < CreateAPIKeyResult > ; private readonly getActionsClient : ( ) => Promise < ActionsClient > ; private readonly actionsAuthorization : ActionsAuthorization ; private readonly getEventLogClient : ( ) => Promise < IEventLogClient > ; private readonly encryptedSavedObjectsClient : EncryptedSavedObjectsClient ; private readonly kibanaVersion ! : PluginInitializerContext [ 'env' ] [ 'packageInfo' ] [ 'version' ] ; private readonly auditLogger ? : AuditLogger ; private readonly eventLogger ? : IEventLogger ; constructor ( { ruleTypeRegistry , unsecuredSavedObjectsClient , authorization , taskManager , logger , spaceId , namespace , getUserName , createAPIKey , encryptedSavedObjectsClient , getActionsClient , actionsAuthorization , getEventLogClient , kibanaVersion , auditLogger , eventLogger , } ) { this . logger = logger ; this . getUserName = getUserName ; this . spaceId = spaceId ; this . namespace = namespace ; this . taskManager = taskManager ; this . ruleTypeRegistry = ruleTypeRegistry ; this . unsecuredSavedObjectsClient = unsecuredSavedObjectsClient ; this . authorization = authorization ; this . createAPIKey = createAPIKey ; this . encryptedSavedObjectsClient = encryptedSavedObjectsClient ; this . getActionsClient = getActionsClient ; this . actionsAuthorization = actionsAuthorization ; this . getEventLogClient = getEventLogClient ; this . kibanaVersion = kibanaVersion ; this . auditLogger = auditLogger ; this . eventLogger = eventLogger ; } public async create < Params extends AlertTypeParams = never > ( { data , options , } ) { const id = options ?. id || SavedObjectsUtils . generateId ( ) ; try { await this . authorization . ensureAuthorized ( { ruleTypeId : data . alertTypeId , consumer : data . consumer , operation : WriteOperations . Create , entity : AlertingAuthorizationEntity . Rule , } ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . CREATE , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . ruleTypeRegistry . ensureRuleTypeEnabled ( data . alertTypeId ) ; const ruleType = this . ruleTypeRegistry . get ( data . alertTypeId ) ; const validatedAlertTypeParams = validateRuleTypeParams ( data . params , ruleType . validate ?. params ) ; const username = await this . getUserName ( ) ; let createdAPIKey = null ; try { createdAPIKey = data . enabled ? await this . createAPIKey ( this . generateAPIKeyName ( ruleType . id , data . name ) ) : null ; } catch ( error ) { throw Boom . badRequest ( `Error creating rule: could not create API key - ${ error . message }` ) ; } await this . validateActions ( ruleType , data . actions ) ; if ( ruleType . minimumScheduleInterval ) { const intervalInMs = parseDuration ( data . schedule . interval ) ; const minimumScheduleIntervalInMs = parseDuration ( ruleType . minimumScheduleInterval ) ; if ( intervalInMs < minimumScheduleIntervalInMs ) { throw Boom . badRequest ( `Error updating rule: the interval is less than the minimum interval of ${ ruleType . minimumScheduleInterval }` ) ; } } const { references , params : updatedParams , actions , } = await this . extractReferences ( ruleType , data . actions , validatedAlertTypeParams ) ; const createTime = Date . now ( ) ; const legacyId = Semver . lt ( this . kibanaVersion , '8.0.0' ) ? id : null ; const notifyWhen = getAlertNotifyWhenType ( data . notifyWhen , data . throttle ) ; const rawRule = { ... data , ... this . apiKeyAsAlertAttributes ( createdAPIKey , username ) , legacyId , actions , createdBy : username , updatedBy : username , createdAt : new Date ( createTime ) . toISOString ( ) , updatedAt : new Date ( createTime ) . toISOString ( ) , params : updatedParams as RawRule [ 'params' ] , muteAll : false , mutedInstanceIds : [ ] , notifyWhen , executionStatus : getRuleExecutionStatusPending ( new Date ( ) . toISOString ( ) ) , } ; this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . CREATE , outcome : 'unknown' , savedObject : { type : 'alert' , id } , } ) ) ; let createdAlert ; try { createdAlert = await this . unsecuredSavedObjectsClient . create ( 'alert' , this . updateMeta ( rawRule ) , { ... options , references , id , } ) ; } catch ( e ) { markApiKeyForInvalidation ( { apiKey : rawRule . apiKey } , this . logger , this . unsecuredSavedObjectsClient ) ; throw e ; } if ( data . enabled ) { let scheduledTask ; try { scheduledTask = await this . scheduleRule ( createdAlert . id , rawRule . alertTypeId , data . schedule , true ) ; } catch ( e ) { try { await this . unsecuredSavedObjectsClient . delete ( 'alert' , createdAlert . id ) ; } catch ( err ) { this . logger . error ( `Failed to cleanup alert "${ createdAlert . id }" after scheduling task failed. Error: ${ err . message }` ) ; } throw e ; } await this . unsecuredSavedObjectsClient . update < RawRule > ( 'alert' , createdAlert . id , { scheduledTaskId : scheduledTask . id , } ) ; createdAlert . attributes . scheduledTaskId = scheduledTask . id ; } return this . getAlertFromRaw < Params > ( createdAlert . id , createdAlert . attributes . alertTypeId , createdAlert . attributes , references ) ; } public async get < Params extends AlertTypeParams = never > ( { id , includeLegacyId = false , } ) { const result = await this . unsecuredSavedObjectsClient . get < RawRule > ( 'alert' , id ) ; try { await this . authorization . ensureAuthorized ( { ruleTypeId : result . attributes . alertTypeId , consumer : result . attributes . consumer , operation : ReadOperations . Get , entity : AlertingAuthorizationEntity . Rule , } ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . GET , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . GET , savedObject : { type : 'alert' , id } , } ) ) ; return this . getAlertFromRaw < Params > ( result . id , result . attributes . alertTypeId , result . attributes , result . references , includeLegacyId ) ; } public async resolve < Params extends AlertTypeParams = never > ( { id , includeLegacyId , } ) { const { saved_object : result , ... resolveResponse } = await this . unsecuredSavedObjectsClient . resolve < RawRule > ( 'alert' , id ) ; try { await this . authorization . ensureAuthorized ( { ruleTypeId : result . attributes . alertTypeId , consumer : result . attributes . consumer , operation : ReadOperations . Get , entity : AlertingAuthorizationEntity . Rule , } ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . RESOLVE , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . RESOLVE , savedObject : { type : 'alert' , id } , } ) ) ; const rule = this . getAlertFromRaw < Params > ( result . id , result . attributes . alertTypeId , result . attributes , result . references , includeLegacyId ) ; return { ... rule , ... resolveResponse , } ; } public async getAlertState ( { id } ) { const alert = await this . get ( { id } ) ; await this . authorization . ensureAuthorized ( { ruleTypeId : alert . alertTypeId , consumer : alert . consumer , operation : ReadOperations . GetRuleState , entity : AlertingAuthorizationEntity . Rule , } ) ; if ( alert . scheduledTaskId ) { const { state } = taskInstanceToAlertTaskInstance ( await this . taskManager . get ( alert . scheduledTaskId ) , alert ) ; return state ; } } public async getAlertSummary ( { id , dateStart } ) { this . logger . debug ( `getAlertSummary(): getting alert ${ id }` ) ; const rule = ( await this . get ( { id , includeLegacyId : true } ) ) as SanitizedRuleWithLegacyId ; await this . authorization . ensureAuthorized ( { ruleTypeId : rule . alertTypeId , consumer : rule . consumer , operation : ReadOperations . GetAlertSummary , entity : AlertingAuthorizationEntity . Rule , } ) ; const dateNow = new Date ( ) ; const durationMillis = parseDuration ( rule . schedule . interval ) * 60 ; const defaultDateStart = new Date ( dateNow . valueOf ( ) - durationMillis ) ; const parsedDateStart = parseDate ( dateStart , 'dateStart' , defaultDateStart ) ; const eventLogClient = await this . getEventLogClient ( ) ; this . logger . debug ( `getAlertSummary(): search the event log for rule ${ id }` ) ; let events ; try { const queryResults = await eventLogClient . findEventsBySavedObjectIds ( 'alert' , [ id ] , { page : 1 , per_page : 10000 , start : parsedDateStart . toISOString ( ) , end : dateNow . toISOString ( ) , sort_order : 'desc' , } , rule . legacyId !== null ? [ rule . legacyId ] : undefined ) ; events = queryResults . data ; } catch ( err ) { this . logger . debug ( `rulesClient.getAlertSummary(): error searching event log for rule ${ id }: ${ err . message }` ) ; events = [ ] ; } return alertSummaryFromEventLog ( { rule , events , dateStart : parsedDateStart . toISOString ( ) , dateEnd : dateNow . toISOString ( ) , } ) ; } public async find < Params extends AlertTypeParams = never > ( { options : { fields , ... options } = { } , } = { } ) { let authorizationTuple ; try { authorizationTuple = await this . authorization . getFindAuthorizationFilter ( AlertingAuthorizationEntity . Rule , alertingAuthorizationFilterOpts ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . FIND , error , } ) ) ; throw error ; } const { filter : authorizationFilter , ensureRuleTypeIsAuthorized } = authorizationTuple ; const { page , per_page : perPage , total , saved_objects : data , } = await this . unsecuredSavedObjectsClient . find < RawRule > ( { ... options , sortField : mapSortField ( options . sortField ) , filter : ( authorizationFilter && options . filter ? nodeBuilder . and ( [ esKuery . fromKueryExpression ( options . filter ) , authorizationFilter as KueryNode , ] ) : authorizationFilter ) ?? options . filter , fields : fields ? this . includeFieldsRequiredForAuthentication ( fields ) : fields , type : 'alert' , } ) ; const authorizedData = data . map ( ( { id , attributes , references } ) => { try { ensureRuleTypeIsAuthorized ( attributes . alertTypeId , attributes . consumer , AlertingAuthorizationEntity . Rule ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . FIND , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } return this . getAlertFromRaw < Params > ( id , attributes . alertTypeId , fields ? ( pick ( attributes , fields ) as RawRule ) : attributes , references ) ; } ) ; authorizedData . forEach ( ( { id } ) => this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . FIND , savedObject : { type : 'alert' , id } , } ) ) ) ; return { page , perPage , total , data : authorizedData , } ; } public async aggregate ( { options : { fields , filter , ... options } = { } , } = { } ) { let authorizationTuple ; try { authorizationTuple = await this . authorization . getFindAuthorizationFilter ( AlertingAuthorizationEntity . Rule , alertingAuthorizationFilterOpts ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . AGGREGATE , error , } ) ) ; throw error ; } const { filter : authorizationFilter } = authorizationTuple ; const resp = await this . unsecuredSavedObjectsClient . find < RawRule , RuleAggregation > ( { ... options , filter : ( authorizationFilter && filter ? nodeBuilder . and ( [ esKuery . fromKueryExpression ( filter ) , authorizationFilter as KueryNode ] ) : authorizationFilter ) ?? filter , page : 1 , perPage : 0 , type : 'alert' , aggs : { status : { terms : { field : 'alert.attributes.executionStatus.status' } , } , enabled : { terms : { field : 'alert.attributes.enabled' } , } , muted : { terms : { field : 'alert.attributes.muteAll' } , } , } , } ) ; if ( ! resp . aggregations ) { const placeholder = { alertExecutionStatus : { } , ruleEnabledStatus : { enabled : 0 , disabled : 0 , } , ruleMutedStatus : { muted : 0 , unmuted : 0 , } , } ; for ( const key of AlertExecutionStatusValues ) { placeholder . alertExecutionStatus [ key ] = 0 ; } return placeholder ; } const alertExecutionStatus = resp . aggregations . status . buckets . map ( ( { key , doc_count : docCount } ) => ( { [ key ] : docCount , } ) ) ; const ret = { alertExecutionStatus : alertExecutionStatus . reduce ( ( acc , curr ) => Object . assign ( acc , curr ) , { } ) , } ; for ( const key of AlertExecutionStatusValues ) { if ( ! ret . alertExecutionStatus . hasOwnProperty ( key ) ) { ret . alertExecutionStatus [ key ] = 0 ; } } const enabledBuckets = resp . aggregations . enabled . buckets ; ret . ruleEnabledStatus = { enabled : enabledBuckets . find ( ( bucket ) => bucket . key === 1 ) ?. doc_count ?? 0 , disabled : enabledBuckets . find ( ( bucket ) => bucket . key === 0 ) ?. doc_count ?? 0 , } ; const mutedBuckets = resp . aggregations . muted . buckets ; ret . ruleMutedStatus = { muted : mutedBuckets . find ( ( bucket ) => bucket . key === 1 ) ?. doc_count ?? 0 , unmuted : mutedBuckets . find ( ( bucket ) => bucket . key === 0 ) ?. doc_count ?? 0 , } ; return ret ; } public async delete ( { id } ) { return await retryIfConflicts ( this . logger , `rulesClient.delete('${ id }')` , async ( ) => await this . deleteWithOCC ( { id } ) ) ; } private async deleteWithOCC ( { id } ) { let taskIdToRemove ; let apiKeyToInvalidate = null ; let attributes ; try { const decryptedAlert = await this . encryptedSavedObjectsClient . getDecryptedAsInternalUser < RawRule > ( 'alert' , id , { namespace : this . namespace , } ) ; apiKeyToInvalidate = decryptedAlert . attributes . apiKey ; taskIdToRemove = decryptedAlert . attributes . scheduledTaskId ; attributes = decryptedAlert . attributes ; } catch ( e ) { this . logger . error ( `delete(): Failed to load API key to invalidate on alert ${ id }: ${ e . message }` ) ; const alert = await this . unsecuredSavedObjectsClient . get < RawRule > ( 'alert' , id ) ; taskIdToRemove = alert . attributes . scheduledTaskId ; attributes = alert . attributes ; } try { await this . authorization . ensureAuthorized ( { ruleTypeId : attributes . alertTypeId , consumer : attributes . consumer , operation : WriteOperations . Delete , entity : AlertingAuthorizationEntity . Rule , } ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . DELETE , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . DELETE , outcome : 'unknown' , savedObject : { type : 'alert' , id } , } ) ) ; const removeResult = await this . unsecuredSavedObjectsClient . delete ( 'alert' , id ) ; await Promise . all ( [ taskIdToRemove ? this . taskManager . removeIfExists ( taskIdToRemove ) : null , apiKeyToInvalidate ? markApiKeyForInvalidation ( { apiKey : apiKeyToInvalidate } , this . logger , this . unsecuredSavedObjectsClient ) : null , ] ) ; return removeResult ; } public async update < Params extends AlertTypeParams = never > ( { id , data , } ) { return await retryIfConflicts ( this . logger , `rulesClient.update('${ id }')` , async ( ) => await this . updateWithOCC < Params > ( { id , data } ) ) ; } private async updateWithOCC < Params extends AlertTypeParams > ( { id , data , } ) { let alertSavedObject ; try { alertSavedObject = await this . encryptedSavedObjectsClient . getDecryptedAsInternalUser < RawRule > ( 'alert' , id , { namespace : this . namespace , } ) ; } catch ( e ) { this . logger . error ( `update(): Failed to load API key to invalidate on alert ${ id }: ${ e . message }` ) ; alertSavedObject = await this . unsecuredSavedObjectsClient . get < RawRule > ( 'alert' , id ) ; } try { await this . authorization . ensureAuthorized ( { ruleTypeId : alertSavedObject . attributes . alertTypeId , consumer : alertSavedObject . attributes . consumer , operation : WriteOperations . Update , entity : AlertingAuthorizationEntity . Rule , } ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . UPDATE , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . UPDATE , outcome : 'unknown' , savedObject : { type : 'alert' , id } , } ) ) ; this . ruleTypeRegistry . ensureRuleTypeEnabled ( alertSavedObject . attributes . alertTypeId ) ; const updateResult = await this . updateAlert < Params > ( { id , data } , alertSavedObject ) ; await Promise . all ( [ alertSavedObject . attributes . apiKey ? markApiKeyForInvalidation ( { apiKey : alertSavedObject . attributes . apiKey } , this . logger , this . unsecuredSavedObjectsClient ) : null , ( async ( ) => { if ( updateResult . scheduledTaskId && ! isEqual ( alertSavedObject . attributes . schedule , updateResult . schedule ) ) { this . taskManager . runNow ( updateResult . scheduledTaskId ) . then ( ( ) => { this . logger . debug ( `Alert update has rescheduled the underlying task: ${ updateResult . scheduledTaskId }` ) ; } ) . catch ( ( err ) => { this . logger . error ( `Alert update failed to run its underlying task. TaskManager runNow failed with Error: ${ err . message }` ) ; } ) ; } } ) ( ) , ] ) ; return updateResult ; } private async updateAlert < Params extends AlertTypeParams > ( { id , data } , { attributes , version } ) { const ruleType = this . ruleTypeRegistry . get ( attributes . alertTypeId ) ; const validatedAlertTypeParams = validateRuleTypeParams ( data . params , ruleType . validate ?. params ) ; await this . validateActions ( ruleType , data . actions ) ; if ( ruleType . minimumScheduleInterval ) { const intervalInMs = parseDuration ( data . schedule . interval ) ; const minimumScheduleIntervalInMs = parseDuration ( ruleType . minimumScheduleInterval ) ; if ( intervalInMs < minimumScheduleIntervalInMs ) { throw Boom . badRequest ( `Error updating rule: the interval is less than the minimum interval of ${ ruleType . minimumScheduleInterval }` ) ; } } const { references , params : updatedParams , actions , } = await this . extractReferences ( ruleType , data . actions , validatedAlertTypeParams ) ; const username = await this . getUserName ( ) ; let createdAPIKey = null ; try { createdAPIKey = attributes . enabled ? await this . createAPIKey ( this . generateAPIKeyName ( ruleType . id , data . name ) ) : null ; } catch ( error ) { throw Boom . badRequest ( `Error updating rule: could not create API key - ${ error . message }` ) ; } const apiKeyAttributes = this . apiKeyAsAlertAttributes ( createdAPIKey , username ) ; const notifyWhen = getAlertNotifyWhenType ( data . notifyWhen , data . throttle ) ; let updatedObject ; const createAttributes = this . updateMeta ( { ... attributes , ... data , ... apiKeyAttributes , params : updatedParams as RawRule [ 'params' ] , actions , notifyWhen , updatedBy : username , updatedAt : new Date ( ) . toISOString ( ) , } ) ; try { updatedObject = await this . unsecuredSavedObjectsClient . create < RawRule > ( 'alert' , createAttributes , { id , overwrite : true , version , references , } ) ; } catch ( e ) { markApiKeyForInvalidation ( { apiKey : createAttributes . apiKey } , this . logger , this . unsecuredSavedObjectsClient ) ; throw e ; } return this . getPartialAlertFromRaw ( id , ruleType , updatedObject . attributes , updatedObject . references ) ; } private apiKeyAsAlertAttributes ( apiKey , username ) { return apiKey && apiKey . apiKeysEnabled ? { apiKeyOwner : username , apiKey : Buffer . from ( `${ apiKey . result . id }:${ apiKey . result . api_key }` ) . toString ( 'base64' ) , } : { apiKeyOwner : null , apiKey : null , } ; } public async updateApiKey ( { id } ) { return await retryIfConflicts ( this . logger , `rulesClient.updateApiKey('${ id }')` , async ( ) => await this . updateApiKeyWithOCC ( { id } ) ) ; } private async updateApiKeyWithOCC ( { id } ) { let apiKeyToInvalidate = null ; let attributes ; let version ; try { const decryptedAlert = await this . encryptedSavedObjectsClient . getDecryptedAsInternalUser < RawRule > ( 'alert' , id , { namespace : this . namespace , } ) ; apiKeyToInvalidate = decryptedAlert . attributes . apiKey ; attributes = decryptedAlert . attributes ; version = decryptedAlert . version ; } catch ( e ) { this . logger . error ( `updateApiKey(): Failed to load API key to invalidate on alert ${ id }: ${ e . message }` ) ; const alert = await this . unsecuredSavedObjectsClient . get < RawRule > ( 'alert' , id ) ; attributes = alert . attributes ; version = alert . version ; } try { await this . authorization . ensureAuthorized ( { ruleTypeId : attributes . alertTypeId , consumer : attributes . consumer , operation : WriteOperations . UpdateApiKey , entity : AlertingAuthorizationEntity . Rule , } ) ; if ( attributes . actions . length ) { await this . actionsAuthorization . ensureAuthorized ( 'execute' ) ; } } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . UPDATE_API_KEY , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } const username = await this . getUserName ( ) ; let createdAPIKey = null ; try { createdAPIKey = await this . createAPIKey ( this . generateAPIKeyName ( attributes . alertTypeId , attributes . name ) ) ; } catch ( error ) { throw Boom . badRequest ( `Error updating API key for rule: could not create API key - ${ error . message }` ) ; } const updateAttributes = this . updateMeta ( { ... attributes , ... this . apiKeyAsAlertAttributes ( createdAPIKey , username ) , updatedAt : new Date ( ) . toISOString ( ) , updatedBy : username , } ) ; this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . UPDATE_API_KEY , outcome : 'unknown' , savedObject : { type : 'alert' , id } , } ) ) ; this . ruleTypeRegistry . ensureRuleTypeEnabled ( attributes . alertTypeId ) ; try { await this . unsecuredSavedObjectsClient . update ( 'alert' , id , updateAttributes , { version } ) ; } catch ( e ) { markApiKeyForInvalidation ( { apiKey : updateAttributes . apiKey } , this . logger , this . unsecuredSavedObjectsClient ) ; throw e ; } if ( apiKeyToInvalidate ) { await markApiKeyForInvalidation ( { apiKey : apiKeyToInvalidate } , this . logger , this . unsecuredSavedObjectsClient ) ; } } public async enable ( { id } ) { return await retryIfConflicts ( this . logger , `rulesClient.enable('${ id }')` , async ( ) => await this . enableWithOCC ( { id } ) ) ; } private async enableWithOCC ( { id } ) { let apiKeyToInvalidate = null ; let attributes ; let version ; try { const decryptedAlert = await this . encryptedSavedObjectsClient . getDecryptedAsInternalUser < RawRule > ( 'alert' , id , { namespace : this . namespace , } ) ; apiKeyToInvalidate = decryptedAlert . attributes . apiKey ; attributes = decryptedAlert . attributes ; version = decryptedAlert . version ; } catch ( e ) { this . logger . error ( `enable(): Failed to load API key to invalidate on alert ${ id }: ${ e . message }` ) ; const alert = await this . unsecuredSavedObjectsClient . get < RawRule > ( 'alert' , id ) ; attributes = alert . attributes ; version = alert . version ; } try { await this . authorization . ensureAuthorized ( { ruleTypeId : attributes . alertTypeId , consumer : attributes . consumer , operation : WriteOperations . Enable , entity : AlertingAuthorizationEntity . Rule , } ) ; if ( attributes . actions . length ) { await this . actionsAuthorization . ensureAuthorized ( 'execute' ) ; } } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . ENABLE , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . ENABLE , outcome : 'unknown' , savedObject : { type : 'alert' , id } , } ) ) ; this . ruleTypeRegistry . ensureRuleTypeEnabled ( attributes . alertTypeId ) ; if ( attributes . enabled === false ) { const username = await this . getUserName ( ) ; let createdAPIKey = null ; try { createdAPIKey = await this . createAPIKey ( this . generateAPIKeyName ( attributes . alertTypeId , attributes . name ) ) ; } catch ( error ) { throw Boom . badRequest ( `Error enabling rule: could not create API key - ${ error . message }` ) ; } const updateAttributes = this . updateMeta ( { ... attributes , enabled : true , ... this . apiKeyAsAlertAttributes ( createdAPIKey , username ) , updatedBy : username , updatedAt : new Date ( ) . toISOString ( ) , executionStatus : { status : 'pending' , lastDuration : 0 , lastExecutionDate : new Date ( ) . toISOString ( ) , error : null , } , } ) ; try { await this . unsecuredSavedObjectsClient . update ( 'alert' , id , updateAttributes , { version } ) ; } catch ( e ) { markApiKeyForInvalidation ( { apiKey : updateAttributes . apiKey } , this . logger , this . unsecuredSavedObjectsClient ) ; throw e ; } const scheduledTask = await this . scheduleRule ( id , attributes . alertTypeId , attributes . schedule as IntervalSchedule , false ) ; await this . unsecuredSavedObjectsClient . update ( 'alert' , id , { scheduledTaskId : scheduledTask . id , } ) ; if ( apiKeyToInvalidate ) { await markApiKeyForInvalidation ( { apiKey : apiKeyToInvalidate } , this . logger , this . unsecuredSavedObjectsClient ) ; } } } public async disable ( { id } ) { return await retryIfConflicts ( this . logger , `rulesClient.disable('${ id }')` , async ( ) => await this . disableWithOCC ( { id } ) ) ; } private async disableWithOCC ( { id } ) { let apiKeyToInvalidate = null ; let attributes ; let version ; try { const decryptedAlert = await this . encryptedSavedObjectsClient . getDecryptedAsInternalUser < RawRule > ( 'alert' , id , { namespace : this . namespace , } ) ; apiKeyToInvalidate = decryptedAlert . attributes . apiKey ; attributes = decryptedAlert . attributes ; version = decryptedAlert . version ; } catch ( e ) { this . logger . error ( `disable(): Failed to load API key to invalidate on alert ${ id }: ${ e . message }` ) ; const alert = await this . unsecuredSavedObjectsClient . get < RawRule > ( 'alert' , id ) ; attributes = alert . attributes ; version = alert . version ; } if ( this . eventLogger && attributes . scheduledTaskId ) { try { const { state } = taskInstanceToAlertTaskInstance ( await this . taskManager . get ( attributes . scheduledTaskId ) , attributes as unknown as SanitizedAlert ) ; const recoveredAlertInstances = mapValues < Record < string , RawAlertInstance > , AlertInstance > ( state . alertInstances ?? { } , ( rawAlertInstance ) => new AlertInstance ( rawAlertInstance ) ) ; const recoveredAlertInstanceIds = Object . keys ( recoveredAlertInstances ) ; for ( const instanceId of recoveredAlertInstanceIds ) { const { group : actionGroup , subgroup : actionSubgroup } = recoveredAlertInstances [ instanceId ] . getLastScheduledActions ( ) ?? { } ; const instanceState = recoveredAlertInstances [ instanceId ] . getState ( ) ; const message = `instance '${ instanceId }' has recovered due to the rule was disabled` ; const event = createAlertEventLogRecordObject ( { ruleId : id , ruleName : attributes . name , ruleType : this . ruleTypeRegistry . get ( attributes . alertTypeId ) , instanceId , action : EVENT_LOG_ACTIONS . recoveredInstance , message , state : instanceState , group : actionGroup , subgroup : actionSubgroup , namespace : this . namespace , savedObjects : [ { id , type : 'alert' , typeId : attributes . alertTypeId , relation : SAVED_OBJECT_REL_PRIMARY , } , ] , } ) ; this . eventLogger . logEvent ( event ) ; } } catch ( error ) { this . logger . warn ( `rulesClient.disable('${ id }') - Could not write recovery events - ${ error . message }` ) ; } } try { await this . authorization . ensureAuthorized ( { ruleTypeId : attributes . alertTypeId , consumer : attributes . consumer , operation : WriteOperations . Disable , entity : AlertingAuthorizationEntity . Rule , } ) ; } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . DISABLE , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . DISABLE , outcome : 'unknown' , savedObject : { type : 'alert' , id } , } ) ) ; this . ruleTypeRegistry . ensureRuleTypeEnabled ( attributes . alertTypeId ) ; if ( attributes . enabled === true ) { await this . unsecuredSavedObjectsClient . update ( 'alert' , id , this . updateMeta ( { ... attributes , enabled : false , scheduledTaskId : null , apiKey : null , apiKeyOwner : null , updatedBy : await this . getUserName ( ) , updatedAt : new Date ( ) . toISOString ( ) , } ) , { version } ) ; await Promise . all ( [ attributes . scheduledTaskId ? this . taskManager . removeIfExists ( attributes . scheduledTaskId ) : null , apiKeyToInvalidate ? await markApiKeyForInvalidation ( { apiKey : apiKeyToInvalidate } , this . logger , this . unsecuredSavedObjectsClient ) : null , ] ) ; } } public async muteAll ( { id } ) { return await retryIfConflicts ( this . logger , `rulesClient.muteAll('${ id }')` , async ( ) => await this . muteAllWithOCC ( { id } ) ) ; } private async muteAllWithOCC ( { id } ) { const { attributes , version } = await this . unsecuredSavedObjectsClient . get < RawRule > ( 'alert' , id ) ; try { await this . authorization . ensureAuthorized ( { ruleTypeId : attributes . alertTypeId , consumer : attributes . consumer , operation : WriteOperations . MuteAll , entity : AlertingAuthorizationEntity . Rule , } ) ; if ( attributes . actions . length ) { await this . actionsAuthorization . ensureAuthorized ( 'execute' ) ; } } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . MUTE , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . MUTE , outcome : 'unknown' , savedObject : { type : 'alert' , id } , } ) ) ; this . ruleTypeRegistry . ensureRuleTypeEnabled ( attributes . alertTypeId ) ; const updateAttributes = this . updateMeta ( { muteAll : true , mutedInstanceIds : [ ] , updatedBy : await this . getUserName ( ) , updatedAt : new Date ( ) . toISOString ( ) , } ) ; const updateOptions = { version } ; await partiallyUpdateAlert ( this . unsecuredSavedObjectsClient , id , updateAttributes , updateOptions ) ; } public async unmuteAll ( { id } ) { return await retryIfConflicts ( this . logger , `rulesClient.unmuteAll('${ id }')` , async ( ) => await this . unmuteAllWithOCC ( { id } ) ) ; } private async unmuteAllWithOCC ( { id } ) { const { attributes , version } = await this . unsecuredSavedObjectsClient . get < RawRule > ( 'alert' , id ) ; try { await this . authorization . ensureAuthorized ( { ruleTypeId : attributes . alertTypeId , consumer : attributes . consumer , operation : WriteOperations . UnmuteAll , entity : AlertingAuthorizationEntity . Rule , } ) ; if ( attributes . actions . length ) { await this . actionsAuthorization . ensureAuthorized ( 'execute' ) ; } } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . UNMUTE , savedObject : { type : 'alert' , id } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . UNMUTE , outcome : 'unknown' , savedObject : { type : 'alert' , id } , } ) ) ; this . ruleTypeRegistry . ensureRuleTypeEnabled ( attributes . alertTypeId ) ; const updateAttributes = this . updateMeta ( { muteAll : false , mutedInstanceIds : [ ] , updatedBy : await this . getUserName ( ) , updatedAt : new Date ( ) . toISOString ( ) , } ) ; const updateOptions = { version } ; await partiallyUpdateAlert ( this . unsecuredSavedObjectsClient , id , updateAttributes , updateOptions ) ; } public async muteInstance ( { alertId , alertInstanceId } ) { return await retryIfConflicts ( this . logger , `rulesClient.muteInstance('${ alertId }')` , async ( ) => await this . muteInstanceWithOCC ( { alertId , alertInstanceId } ) ) ; } private async muteInstanceWithOCC ( { alertId , alertInstanceId } ) { const { attributes , version } = await this . unsecuredSavedObjectsClient . get < Alert > ( 'alert' , alertId ) ; try { await this . authorization . ensureAuthorized ( { ruleTypeId : attributes . alertTypeId , consumer : attributes . consumer , operation : WriteOperations . MuteAlert , entity : AlertingAuthorizationEntity . Rule , } ) ; if ( attributes . actions . length ) { await this . actionsAuthorization . ensureAuthorized ( 'execute' ) ; } } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . MUTE_ALERT , savedObject : { type : 'alert' , id : alertId } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . MUTE_ALERT , outcome : 'unknown' , savedObject : { type : 'alert' , id : alertId } , } ) ) ; this . ruleTypeRegistry . ensureRuleTypeEnabled ( attributes . alertTypeId ) ; const mutedInstanceIds = attributes . mutedInstanceIds || [ ] ; if ( ! attributes . muteAll && ! mutedInstanceIds . includes ( alertInstanceId ) ) { mutedInstanceIds . push ( alertInstanceId ) ; await this . unsecuredSavedObjectsClient . update ( 'alert' , alertId , this . updateMeta ( { mutedInstanceIds , updatedBy : await this . getUserName ( ) , updatedAt : new Date ( ) . toISOString ( ) , } ) , { version } ) ; } } public async unmuteInstance ( { alertId , alertInstanceId } ) { return await retryIfConflicts ( this . logger , `rulesClient.unmuteInstance('${ alertId }')` , async ( ) => await this . unmuteInstanceWithOCC ( { alertId , alertInstanceId } ) ) ; } private async unmuteInstanceWithOCC ( { alertId , alertInstanceId , } ) { const { attributes , version } = await this . unsecuredSavedObjectsClient . get < Alert > ( 'alert' , alertId ) ; try { await this . authorization . ensureAuthorized ( { ruleTypeId : attributes . alertTypeId , consumer : attributes . consumer , operation : WriteOperations . UnmuteAlert , entity : AlertingAuthorizationEntity . Rule , } ) ; if ( attributes . actions . length ) { await this . actionsAuthorization . ensureAuthorized ( 'execute' ) ; } } catch ( error ) { this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . UNMUTE_ALERT , savedObject : { type : 'alert' , id : alertId } , error , } ) ) ; throw error ; } this . auditLogger ?. log ( ruleAuditEvent ( { action : RuleAuditAction . UNMUTE_ALERT , outcome : 'unknown' , savedObject : { type : 'alert' , id : alertId } , } ) ) ; this . ruleTypeRegistry . ensureRuleTypeEnabled ( attributes . alertTypeId ) ; const mutedInstanceIds = attributes . mutedInstanceIds || [ ] ; if ( ! attributes . muteAll && mutedInstanceIds . includes ( alertInstanceId ) ) { await this . unsecuredSavedObjectsClient . update < RawRule > ( 'alert' , alertId , this . updateMeta ( { updatedBy : await this . getUserName ( ) , updatedAt : new Date ( ) . toISOString ( ) , mutedInstanceIds : mutedInstanceIds . filter ( ( id ) => id !== alertInstanceId ) , } ) , { version } ) ; } } public async listAlertTypes ( ) { return await this . authorization . filterByRuleTypeAuthorization ( this . ruleTypeRegistry . list ( ) , [ ReadOperations . Get , WriteOperations . Create ] , AlertingAuthorizationEntity . Rule ) ; } public getSpaceId ( ) { return this . spaceId ; } private async scheduleRule ( id , ruleTypeId , schedule , throwOnConflict ) { const taskInstance = { id , taskType : `alerting:${ ruleTypeId }` , schedule , params : { alertId : id , spaceId : this . spaceId , } , state : { previousStartedAt : null , alertTypeState : { } , alertInstances : { } , } , scope : [ 'alerting' ] , } ; try { return await this . taskManager . schedule ( taskInstance ) ; } catch ( err ) { if ( err . statusCode === 409 && ! throwOnConflict ) { return taskInstance ; } throw err ; } } private injectReferencesIntoActions ( alertId , actions , references ) { return actions . map ( ( action ) => { if ( action . actionRef . startsWith ( preconfiguredConnectorActionRefPrefix ) ) { return { ... omit ( action , 'actionRef' ) , id : action . actionRef . replace ( preconfiguredConnectorActionRefPrefix , '' ) , } ; } const reference = references . find ( ( ref ) => ref . name === action . actionRef ) ; if ( ! reference ) { throw new Error ( `Action reference "${ action . actionRef }" not found in alert id: ${ alertId }` ) ; } return { ... omit ( action , 'actionRef' ) , id : reference . id , } ; } ) as Alert [ 'actions' ] ; } private getAlertFromRaw < Params extends AlertTypeParams > ( id , ruleTypeId , rawRule , references , includeLegacyId = false ) { const ruleType = this . ruleTypeRegistry . get ( ruleTypeId ) ; const res = this . getPartialAlertFromRaw < Params > ( id , ruleType , rawRule , references , includeLegacyId ) ; if ( includeLegacyId ) { return res as AlertWithLegacyId ; } return omit ( res , [ 'legacyId' ] ) as Alert ; } private getPartialAlertFromRaw < Params extends AlertTypeParams > ( id , ruleType , { createdAt , updatedAt , meta , notifyWhen , legacyId , scheduledTaskId , params , executionStatus , schedule , actions , ... partialRawRule } , references , includeLegacyId = false ) { const rule = { id , notifyWhen , ... partialRawRule , schedule : schedule as IntervalSchedule , actions : actions ? this . injectReferencesIntoActions ( id , actions , references || [ ] ) : [ ] , params : this . injectReferencesIntoParams ( id , ruleType , params , references || [ ] ) as Params , ... ( updatedAt ? { updatedAt : new Date ( updatedAt ) } : { } ) , ... ( createdAt ? { createdAt : new Date ( createdAt ) } : { } ) , ... ( scheduledTaskId ? { scheduledTaskId } : { } ) , ... ( executionStatus ? { executionStatus : ruleExecutionStatusFromRaw ( this . logger , id , executionStatus ) } : { } ) , } ; return includeLegacyId ? ( { ... rule , legacyId } as PartialAlertWithLegacyId < Params > ) : ( rule as PartialAlert < Params > ) ; } private async validateActions ( alertType , actions ) { if ( actions . length === 0 ) { return ; } const actionsClient = await this . getActionsClient ( ) ; const actionIds = [ ... new Set ( actions . map ( ( action ) => action . id ) ) ] ; const actionResults = ( await actionsClient . getBulk ( actionIds ) ) || [ ] ; const actionsUsingConnectorsWithMissingSecrets = actionResults . filter ( ( result ) => result . isMissingSecrets ) ; if ( actionsUsingConnectorsWithMissingSecrets . length ) { throw Boom . badRequest ( i18n . translate ( 'xpack.alerting.rulesClient.validateActions.misconfiguredConnector' , { defaultMessage : 'Invalid connectors: {groups}' , values : { groups : actionsUsingConnectorsWithMissingSecrets . map ( ( connector ) => connector . name ) . join ( ', ' ) , } , } ) ) ; } const { actionGroups : alertTypeActionGroups } = alertType ; const usedAlertActionGroups = actions . map ( ( action ) => action . group ) ; const availableAlertTypeActionGroups = new Set ( map ( alertTypeActionGroups , 'id' ) ) ; const invalidActionGroups = usedAlertActionGroups . filter ( ( group ) => ! availableAlertTypeActionGroups . has ( group ) ) ; if ( invalidActionGroups . length ) { throw Boom . badRequest ( i18n . translate ( 'xpack.alerting.rulesClient.validateActions.invalidGroups' , { defaultMessage : 'Invalid action groups: {groups}' , values : { groups : invalidActionGroups . join ( ', ' ) , } , } ) ) ; } } private async extractReferences < Params extends AlertTypeParams , ExtractedParams extends AlertTypeParams > ( ruleType , ruleActions , ruleParams ) { const { references : actionReferences , actions } = await this . denormalizeActions ( ruleActions ) ; const extractedRefsAndParams = ruleType ?. useSavedObjectReferences ?. extractReferences ? ruleType . useSavedObjectReferences . extractReferences ( ruleParams ) : null ; const extractedReferences = extractedRefsAndParams ?. references ?? [ ] ; const params = ( extractedRefsAndParams ?. params as ExtractedParams ) ?? ruleParams ; const paramReferences = extractedReferences . map ( ( reference ) => ( { ... reference , name : `${ extractedSavedObjectParamReferenceNamePrefix }${ reference . name }` , } ) ) ; const references = [ ... actionReferences , ... paramReferences ] ; return { actions , params , references , } ; } private injectReferencesIntoParams < Params extends AlertTypeParams , ExtractedParams extends AlertTypeParams > ( ruleId , ruleType , ruleParams , references ) { try { const paramReferences = references . filter ( ( reference ) => reference . name . startsWith ( extractedSavedObjectParamReferenceNamePrefix ) ) . map ( ( reference ) => ( { ... reference , name : reference . name . replace ( extractedSavedObjectParamReferenceNamePrefix , '' ) , } ) ) ; return ruleParams && ruleType ?. useSavedObjectReferences ?. injectReferences ? ( ruleType . useSavedObjectReferences . injectReferences ( ruleParams as ExtractedParams , paramReferences ) as Params ) : ( ruleParams as Params ) ; } catch ( err ) { throw Boom . badRequest ( `Error injecting reference into rule params for rule id ${ ruleId } - ${ err . message }` ) ; } } private async denormalizeActions ( alertActions ) { const references = [ ] ; const actions = [ ] ; if ( alertActions . length ) { const actionsClient = await this . getActionsClient ( ) ; const actionIds = [ ... new Set ( alertActions . map ( ( alertAction ) => alertAction . id ) ) ] ; const actionResults = await actionsClient . getBulk ( actionIds ) ; const actionTypeIds = [ ... new Set ( actionResults . map ( ( action ) => action . actionTypeId ) ) ] ; actionTypeIds . forEach ( ( id ) => { actionsClient . isActionTypeEnabled ( id , { notifyUsage : true } ) ; } ) ; alertActions . forEach ( ( { id , ... alertAction } , i ) => { const actionResultValue = actionResults . find ( ( action ) => action . id === id ) ; if ( actionResultValue ) { if ( actionsClient . isPreconfigured ( id ) ) { actions . push ( { ... alertAction , actionRef : `${ preconfiguredConnectorActionRefPrefix }${ id }` , actionTypeId : actionResultValue . actionTypeId , } ) ; } else { const actionRef = `action_${ i }` ; references . push ( { id , name : actionRef , type : 'action' , } ) ; actions . push ( { ... alertAction , actionRef , actionTypeId : actionResultValue . actionTypeId , } ) ; } } else { actions . push ( { ... alertAction , actionRef : '' , actionTypeId : '' , } ) ; } } ) ; } return { actions , references , } ; } private includeFieldsRequiredForAuthentication ( fields ) { return uniq ( [ ... fields , 'alertTypeId' , 'consumer' ] ) ; } private generateAPIKeyName ( alertTypeId , alertName ) { return truncate ( `Alerting: ${ alertTypeId }/${ trim ( alertName ) }` , { length : 256 } ) ; } private updateMeta < T extends Partial < RawRule > > ( alertAttributes ) { if ( alertAttributes . hasOwnProperty ( 'apiKey' ) || alertAttributes . hasOwnProperty ( 'apiKeyOwner' ) ) { alertAttributes . meta = alertAttributes . meta ?? { } ; alertAttributes . meta . versionApiKeyLastmodified = this . kibanaVersion ; } return alertAttributes ; } } function parseDate ( dateString , propertyName , defaultValue ) { if ( dateString === undefined ) { return defaultValue ; } const parsedDate = parseIsoOrRelativeDate ( dateString ) ; if ( parsedDate === undefined ) { throw Boom . badRequest ( i18n . translate ( 'xpack.alerting.rulesClient.invalidDate' , { defaultMessage : 'Invalid date for parameter {field}: "{dateValue}"' , values : { field : propertyName , dateValue : dateString , } , } ) ) ; } return parsedDate ; }