import uuid from 'uuid' ; import { schema } from '@kbn/config-schema' ; import { RulesClient , ConstructorOptions } from '../rules_client' ; import { savedObjectsClientMock , loggingSystemMock } from '../../../../../../src/core/server/mocks' ; import { taskManagerMock } from '../../../../task_manager/server/mocks' ; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock' ; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock' ; import { IntervalSchedule , InvalidatePendingApiKey } from '../../types' ; import { RecoveredActionGroup } from '../../../common' ; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks' ; import { actionsAuthorizationMock } from '../../../../actions/server/mocks' ; import { AlertingAuthorization } from '../../authorization/alerting_authorization' ; import { resolvable } from '../../test_utils' ; import { ActionsAuthorization , ActionsClient } from '../../../../actions/server' ; import { TaskStatus } from '../../../../task_manager/server' ; import { httpServerMock } from '../../../../../../src/core/server/mocks' ; import { auditServiceMock } from '../../../../security/server/audit/index.mock' ; import { getBeforeSetup , setGlobalDate } from './lib' ; jest . mock ( '../../../../../../src/core/server/saved_objects/service/lib/utils' , ( ) => ( { SavedObjectsUtils : { generateId : ( ) => 'mock-saved-object-id' , } , } ) ) ; const taskManager = taskManagerMock . createStart ( ) ; const ruleTypeRegistry = ruleTypeRegistryMock . create ( ) ; const unsecuredSavedObjectsClient = savedObjectsClientMock . create ( ) ; const encryptedSavedObjects = encryptedSavedObjectsMock . createClient ( ) ; const authorization = alertingAuthorizationMock . create ( ) ; const actionsAuthorization = actionsAuthorizationMock . create ( ) ; const auditLogger = auditServiceMock . create ( ) . asScoped ( httpServerMock . createKibanaRequest ( ) ) ; const kibanaVersion = 'v7.10.0' ; const rulesClientParams = { taskManager , ruleTypeRegistry , unsecuredSavedObjectsClient , authorization : authorization as unknown as AlertingAuthorization , actionsAuthorization : actionsAuthorization as unknown as ActionsAuthorization , spaceId : 'default' , namespace : 'default' , getUserName : jest . fn ( ) , createAPIKey : jest . fn ( ) , logger : loggingSystemMock . create ( ) . get ( ) , encryptedSavedObjectsClient : encryptedSavedObjects , getActionsClient : jest . fn ( ) , getEventLogClient : jest . fn ( ) , kibanaVersion , auditLogger , } ; beforeEach ( ( ) => { getBeforeSetup ( rulesClientParams , taskManager , ruleTypeRegistry ) ; ( auditLogger . log as jest . Mock ) . mockClear ( ) ; } ) ; setGlobalDate ( ) ; describe ( 'update()' , ( ) => { let rulesClient ; let actionsClient ; const existingAlert = { id : '1' , type : 'alert' , attributes : { enabled : true , tags : [ 'foo' ] , alertTypeId : 'myType' , schedule : { interval : '10s' } , consumer : 'myApp' , scheduledTaskId : 'task-123' , params : { } , throttle : null , notifyWhen : null , actions : [ { group : 'default' , id : '1' , actionTypeId : '1' , actionRef : '1' , params : { foo : true , } , } , ] , } , references : [ ] , version : '123' , } ; const existingDecryptedAlert = { ... existingAlert , attributes : { ... existingAlert . attributes , apiKey : Buffer . from ( '123:abc' ) . toString ( 'base64' ) , } , } ; beforeEach ( async ( ) => { rulesClient = new RulesClient ( rulesClientParams ) ; actionsClient = ( await rulesClientParams . getActionsClient ( ) ) as jest . Mocked < ActionsClient > ; actionsClient . getBulk . mockReset ( ) ; actionsClient . getBulk . mockResolvedValue ( [ { id : '1' , actionTypeId : 'test' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'email connector' , isPreconfigured : false , } , ] ) ; rulesClientParams . getActionsClient . mockResolvedValue ( actionsClient ) ; unsecuredSavedObjectsClient . get . mockResolvedValue ( existingAlert ) ; encryptedSavedObjects . getDecryptedAsInternalUser . mockResolvedValue ( existingDecryptedAlert ) ; ruleTypeRegistry . get . mockReturnValue ( { id : 'myType' , name : 'Test' , actionGroups : [ { id : 'default' , name : 'Default' } , { id : 'custom' , name : 'Not the Default' } , ] , defaultActionGroupId : 'default' , minimumLicenseRequired : 'basic' , isExportable : true , recoveryActionGroup : RecoveredActionGroup , async executor ( ) { } , producer : 'alerts' , minimumScheduleInterval : '5s' , } ) ; } ) ; test ( 'updates given parameters' , async ( ) => { actionsClient . getBulk . mockReset ( ) ; actionsClient . getBulk . mockResolvedValue ( [ { id : '1' , actionTypeId : 'test' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'email connector' , isPreconfigured : false , } , { id : '2' , actionTypeId : 'test2' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'email connector' , isPreconfigured : false , } , ] ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : true , schedule : { interval : '10s' } , params : { bar : true , } , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , { group : 'default' , actionRef : 'action_1' , actionTypeId : 'test' , params : { foo : true , } , } , { group : 'default' , actionRef : 'action_2' , actionTypeId : 'test2' , params : { foo : true , } , } , ] , notifyWhen : 'onActiveAlert' , scheduledTaskId : 'task-123' , createdAt : new Date ( ) . toISOString ( ) , updatedAt : new Date ( ) . toISOString ( ) , } , references : [ { name : 'action_0' , type : 'action' , id : '1' , } , { name : 'action_1' , type : 'action' , id : '1' , } , { name : 'action_2' , type : 'action' , id : '2' , } , ] , } ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'api_key_pending_invalidation' , attributes : { apiKeyId : '234' , createdAt : '2019-02-12T21:01:22.479Z' , } , references : [ ] , } ) ; const result = await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : 'onActiveAlert' , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '2' , params : { foo : true , } , } , ] , } , } ) ; expect ( result ) . toMatchInlineSnapshot ( `
      Object {
        "actions": Array [
          Object {
            "actionTypeId": "test",
            "group": "default",
            "id": "1",
            "params": Object {
              "foo": true,
            },
          },
          Object {
            "actionTypeId": "test",
            "group": "default",
            "id": "1",
            "params": Object {
              "foo": true,
            },
          },
          Object {
            "actionTypeId": "test2",
            "group": "default",
            "id": "2",
            "params": Object {
              "foo": true,
            },
          },
        ],
        "createdAt": 2019-02-12T21:01:22.479Z,
        "enabled": true,
        "id": "1",
        "notifyWhen": "onActiveAlert",
        "params": Object {
          "bar": true,
        },
        "schedule": Object {
          "interval": "10s",
        },
        "scheduledTaskId": "task-123",
        "updatedAt": 2019-02-12T21:01:22.479Z,
      }
    ` ) ; expect ( encryptedSavedObjects . getDecryptedAsInternalUser ) . toHaveBeenCalledWith ( 'alert' , '1' , { namespace : 'default' , } ) ; expect ( unsecuredSavedObjectsClient . get ) . not . toHaveBeenCalled ( ) ; expect ( unsecuredSavedObjectsClient . create ) . toHaveBeenCalledTimes ( 2 ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] ) . toHaveLength ( 3 ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 0 ] ) . toEqual ( 'alert' ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 1 ] ) . toMatchInlineSnapshot ( `
      Object {
        "actions": Array [
          Object {
            "actionRef": "action_0",
            "actionTypeId": "test",
            "group": "default",
            "params": Object {
              "foo": true,
            },
          },
          Object {
            "actionRef": "action_1",
            "actionTypeId": "test",
            "group": "default",
            "params": Object {
              "foo": true,
            },
          },
          Object {
            "actionRef": "action_2",
            "actionTypeId": "test2",
            "group": "default",
            "params": Object {
              "foo": true,
            },
          },
        ],
        "alertTypeId": "myType",
        "apiKey": null,
        "apiKeyOwner": null,
        "consumer": "myApp",
        "enabled": true,
        "meta": Object {
          "versionApiKeyLastmodified": "v7.10.0",
        },
        "name": "abc",
        "notifyWhen": "onActiveAlert",
        "params": Object {
          "bar": true,
        },
        "schedule": Object {
          "interval": "10s",
        },
        "scheduledTaskId": "task-123",
        "tags": Array [
          "foo",
        ],
        "throttle": null,
        "updatedAt": "2019-02-12T21:01:22.479Z",
        "updatedBy": "elastic",
      }
    ` ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 2 ] ) . toMatchInlineSnapshot ( `
      Object {
        "id": "1",
        "overwrite": true,
        "references": Array [
          Object {
            "id": "1",
            "name": "action_0",
            "type": "action",
          },
          Object {
            "id": "1",
            "name": "action_1",
            "type": "action",
          },
          Object {
            "id": "2",
            "name": "action_2",
            "type": "action",
          },
        ],
        "version": "123",
      }
    ` ) ; expect ( actionsClient . isActionTypeEnabled ) . toHaveBeenCalledWith ( 'test' , { notifyUsage : true } ) ; expect ( actionsClient . isActionTypeEnabled ) . toHaveBeenCalledWith ( 'test2' , { notifyUsage : true } ) ; } ) ; test ( 'should update a rule with some preconfigured actions' , async ( ) => { actionsClient . getBulk . mockReset ( ) ; actionsClient . getBulk . mockResolvedValue ( [ { id : '1' , actionTypeId : 'test' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'email connector' , isPreconfigured : false , } , { id : '2' , actionTypeId : 'test2' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'another email connector' , isPreconfigured : false , } , { id : 'preconfigured' , actionTypeId : 'test' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'preconfigured email connector' , isPreconfigured : true , } , ] ) ; actionsClient . isPreconfigured . mockReset ( ) ; actionsClient . isPreconfigured . mockReturnValueOnce ( false ) ; actionsClient . isPreconfigured . mockReturnValueOnce ( true ) ; actionsClient . isPreconfigured . mockReturnValueOnce ( true ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : true , schedule : { interval : '10s' } , params : { bar : true , } , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , { group : 'default' , actionRef : 'preconfigured:preconfigured' , actionTypeId : 'test' , params : { foo : true , } , } , { group : 'custom' , actionRef : 'preconfigured:preconfigured' , actionTypeId : 'test' , params : { foo : true , } , } , ] , notifyWhen : 'onActiveAlert' , scheduledTaskId : 'task-123' , createdAt : new Date ( ) . toISOString ( ) , updatedAt : new Date ( ) . toISOString ( ) , } , references : [ { name : 'action_0' , type : 'action' , id : '1' , } , { name : 'param:soRef_0' , type : 'someSavedObjectType' , id : '9' , } , ] , } ) ; const result = await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : 'onActiveAlert' , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : 'preconfigured' , params : { foo : true , } , } , { group : 'custom' , id : 'preconfigured' , params : { foo : true , } , } , ] , } , } ) ; expect ( unsecuredSavedObjectsClient . create ) . toHaveBeenNthCalledWith ( 1 , 'alert' , { actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , { group : 'default' , actionRef : 'preconfigured:preconfigured' , actionTypeId : 'test' , params : { foo : true , } , } , { group : 'custom' , actionRef : 'preconfigured:preconfigured' , actionTypeId : 'test' , params : { foo : true , } , } , ] , alertTypeId : 'myType' , apiKey : null , apiKeyOwner : null , consumer : 'myApp' , enabled : true , meta : { versionApiKeyLastmodified : 'v7.10.0' } , name : 'abc' , notifyWhen : 'onActiveAlert' , params : { bar : true } , schedule : { interval : '10s' } , scheduledTaskId : 'task-123' , tags : [ 'foo' ] , throttle : null , updatedAt : '2019-02-12T21:01:22.479Z' , updatedBy : 'elastic' , } , { id : '1' , overwrite : true , references : [ { id : '1' , name : 'action_0' , type : 'action' } ] , version : '123' , } ) ; expect ( result ) . toMatchInlineSnapshot ( `
      Object {
        "actions": Array [
          Object {
            "actionTypeId": "test",
            "group": "default",
            "id": "1",
            "params": Object {
              "foo": true,
            },
          },
          Object {
            "actionTypeId": "test",
            "group": "default",
            "id": "preconfigured",
            "params": Object {
              "foo": true,
            },
          },
          Object {
            "actionTypeId": "test",
            "group": "custom",
            "id": "preconfigured",
            "params": Object {
              "foo": true,
            },
          },
        ],
        "createdAt": 2019-02-12T21:01:22.479Z,
        "enabled": true,
        "id": "1",
        "notifyWhen": "onActiveAlert",
        "params": Object {
          "bar": true,
        },
        "schedule": Object {
          "interval": "10s",
        },
        "scheduledTaskId": "task-123",
        "updatedAt": 2019-02-12T21:01:22.479Z,
      }
    ` ) ; expect ( encryptedSavedObjects . getDecryptedAsInternalUser ) . toHaveBeenCalledWith ( 'alert' , '1' , { namespace : 'default' , } ) ; expect ( unsecuredSavedObjectsClient . get ) . not . toHaveBeenCalled ( ) ; expect ( actionsClient . isPreconfigured ) . toHaveBeenCalledTimes ( 3 ) ; } ) ; test ( 'should call useSavedObjectReferences.extractReferences and useSavedObjectReferences.injectReferences if defined for rule type' , async ( ) => { const ruleParams = { bar : true , parameterThatIsSavedObjectId : '9' , } ; const extractReferencesFn = jest . fn ( ) . mockReturnValue ( { params : { bar : true , parameterThatIsSavedObjectRef : 'soRef_0' , } , references : [ { name : 'soRef_0' , type : 'someSavedObjectType' , id : '9' , } , ] , } ) ; const injectReferencesFn = jest . fn ( ) . mockReturnValue ( { bar : true , parameterThatIsSavedObjectId : '9' , } ) ; ruleTypeRegistry . get . mockImplementation ( ( ) => ( { id : 'myType' , name : 'Test' , actionGroups : [ { id : 'default' , name : 'Default' } ] , recoveryActionGroup : RecoveredActionGroup , defaultActionGroupId : 'default' , minimumLicenseRequired : 'basic' , isExportable : true , async executor ( ) { } , producer : 'alerts' , useSavedObjectReferences : { extractReferences : extractReferencesFn , injectReferences : injectReferencesFn , } , } ) ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : true , schedule : { interval : '10s' } , params : { bar : true , parameterThatIsSavedObjectRef : 'soRef_0' , } , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , ] , notifyWhen : 'onActiveAlert' , scheduledTaskId : 'task-123' , createdAt : new Date ( ) . toISOString ( ) , updatedAt : new Date ( ) . toISOString ( ) , } , references : [ { name : 'action_0' , type : 'action' , id : '1' , } , { name : 'param:soRef_0' , type : 'someSavedObjectType' , id : '9' , } , ] , } ) ; const result = await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : ruleParams , throttle : null , notifyWhen : 'onActiveAlert' , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ; expect ( extractReferencesFn ) . toHaveBeenCalledWith ( ruleParams ) ; expect ( unsecuredSavedObjectsClient . create ) . toHaveBeenCalledWith ( 'alert' , { actions : [ { actionRef : 'action_0' , actionTypeId : 'test' , group : 'default' , params : { foo : true } } , ] , alertTypeId : 'myType' , apiKey : null , apiKeyOwner : null , consumer : 'myApp' , enabled : true , meta : { versionApiKeyLastmodified : 'v7.10.0' } , name : 'abc' , notifyWhen : 'onActiveAlert' , params : { bar : true , parameterThatIsSavedObjectRef : 'soRef_0' } , schedule : { interval : '10s' } , scheduledTaskId : 'task-123' , tags : [ 'foo' ] , throttle : null , updatedAt : '2019-02-12T21:01:22.479Z' , updatedBy : 'elastic' , } , { id : '1' , overwrite : true , references : [ { id : '1' , name : 'action_0' , type : 'action' } , { id : '9' , name : 'param:soRef_0' , type : 'someSavedObjectType' } , ] , version : '123' , } ) ; expect ( injectReferencesFn ) . toHaveBeenCalledWith ( { bar : true , parameterThatIsSavedObjectRef : 'soRef_0' , } , [ { id : '9' , name : 'soRef_0' , type : 'someSavedObjectType' } ] ) ; expect ( result ) . toMatchInlineSnapshot ( `
      Object {
        "actions": Array [
          Object {
            "actionTypeId": "test",
            "group": "default",
            "id": "1",
            "params": Object {
              "foo": true,
            },
          },
        ],
        "createdAt": 2019-02-12T21:01:22.479Z,
        "enabled": true,
        "id": "1",
        "notifyWhen": "onActiveAlert",
        "params": Object {
          "bar": true,
          "parameterThatIsSavedObjectId": "9",
        },
        "schedule": Object {
          "interval": "10s",
        },
        "scheduledTaskId": "task-123",
        "updatedAt": 2019-02-12T21:01:22.479Z,
      }
    ` ) ; } ) ; it ( 'calls the createApiKey function' , async ( ) => { rulesClientParams . createAPIKey . mockResolvedValueOnce ( { apiKeysEnabled : true , result : { id : '123' , name : '123' , api_key : 'abc' } , } ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : true , schedule : { interval : '10s' } , params : { bar : true , } , createdAt : new Date ( ) . toISOString ( ) , updatedAt : new Date ( ) . toISOString ( ) , notifyWhen : 'onThrottleInterval' , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , ] , apiKey : Buffer . from ( '123:abc' ) . toString ( 'base64' ) , scheduledTaskId : 'task-123' , } , updated_at : new Date ( ) . toISOString ( ) , references : [ { name : 'action_0' , type : 'action' , id : '1' , } , ] , } ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'api_key_pending_invalidation' , attributes : { apiKeyId : '234' , createdAt : '2019-02-12T21:01:22.479Z' , } , references : [ ] , } ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'api_key_pending_invalidation' , attributes : { apiKeyId : '234' , createdAt : '2019-02-12T21:01:22.479Z' , } , references : [ ] , } ) ; const result = await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : '5m' , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ; expect ( result ) . toMatchInlineSnapshot ( `
      Object {
        "actions": Array [
          Object {
            "actionTypeId": "test",
            "group": "default",
            "id": "1",
            "params": Object {
              "foo": true,
            },
          },
        ],
        "apiKey": "MTIzOmFiYw==",
        "createdAt": 2019-02-12T21:01:22.479Z,
        "enabled": true,
        "id": "1",
        "notifyWhen": "onThrottleInterval",
        "params": Object {
          "bar": true,
        },
        "schedule": Object {
          "interval": "10s",
        },
        "scheduledTaskId": "task-123",
        "updatedAt": 2019-02-12T21:01:22.479Z,
      }
    ` ) ; expect ( unsecuredSavedObjectsClient . create ) . toHaveBeenCalledTimes ( 2 ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] ) . toHaveLength ( 3 ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 0 ] ) . toEqual ( 'alert' ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 1 ] ) . toMatchInlineSnapshot ( `
      Object {
        "actions": Array [
          Object {
            "actionRef": "action_0",
            "actionTypeId": "test",
            "group": "default",
            "params": Object {
              "foo": true,
            },
          },
        ],
        "alertTypeId": "myType",
        "apiKey": "MTIzOmFiYw==",
        "apiKeyOwner": "elastic",
        "consumer": "myApp",
        "enabled": true,
        "meta": Object {
          "versionApiKeyLastmodified": "v7.10.0",
        },
        "name": "abc",
        "notifyWhen": "onThrottleInterval",
        "params": Object {
          "bar": true,
        },
        "schedule": Object {
          "interval": "10s",
        },
        "scheduledTaskId": "task-123",
        "tags": Array [
          "foo",
        ],
        "throttle": "5m",
        "updatedAt": "2019-02-12T21:01:22.479Z",
        "updatedBy": "elastic",
      }
    ` ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 2 ] ) . toMatchInlineSnapshot ( `
      Object {
        "id": "1",
        "overwrite": true,
        "references": Array [
          Object {
            "id": "1",
            "name": "action_0",
            "type": "action",
          },
        ],
        "version": "123",
      }
    ` ) ; } ) ; it ( `doesn't call the createAPIKey function when alert is disabled` , async ( ) => { encryptedSavedObjects . getDecryptedAsInternalUser . mockResolvedValue ( { ... existingDecryptedAlert , attributes : { ... existingDecryptedAlert . attributes , enabled : false , } , } ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : false , schedule : { interval : '10s' } , params : { bar : true , } , notifyWhen : 'onThrottleInterval' , createdAt : new Date ( ) . toISOString ( ) , updatedAt : new Date ( ) . toISOString ( ) , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , ] , scheduledTaskId : 'task-123' , apiKey : null , } , updated_at : new Date ( ) . toISOString ( ) , references : [ { name : 'action_0' , type : 'action' , id : '1' , } , ] , } ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'api_key_pending_invalidation' , attributes : { apiKeyId : '234' , createdAt : '2019-02-12T21:01:22.479Z' , } , references : [ ] , } ) ; const result = await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : '5m' , notifyWhen : 'onThrottleInterval' , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ; expect ( rulesClientParams . createAPIKey ) . not . toHaveBeenCalled ( ) ; expect ( result ) . toMatchInlineSnapshot ( `
      Object {
        "actions": Array [
          Object {
            "actionTypeId": "test",
            "group": "default",
            "id": "1",
            "params": Object {
              "foo": true,
            },
          },
        ],
        "apiKey": null,
        "createdAt": 2019-02-12T21:01:22.479Z,
        "enabled": false,
        "id": "1",
        "notifyWhen": "onThrottleInterval",
        "params": Object {
          "bar": true,
        },
        "schedule": Object {
          "interval": "10s",
        },
        "scheduledTaskId": "task-123",
        "updatedAt": 2019-02-12T21:01:22.479Z,
      }
    ` ) ; expect ( unsecuredSavedObjectsClient . create ) . toHaveBeenCalledTimes ( 2 ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] ) . toHaveLength ( 3 ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 0 ] ) . toEqual ( 'alert' ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 1 ] ) . toMatchInlineSnapshot ( `
      Object {
        "actions": Array [
          Object {
            "actionRef": "action_0",
            "actionTypeId": "test",
            "group": "default",
            "params": Object {
              "foo": true,
            },
          },
        ],
        "alertTypeId": "myType",
        "apiKey": null,
        "apiKeyOwner": null,
        "consumer": "myApp",
        "enabled": false,
        "meta": Object {
          "versionApiKeyLastmodified": "v7.10.0",
        },
        "name": "abc",
        "notifyWhen": "onThrottleInterval",
        "params": Object {
          "bar": true,
        },
        "schedule": Object {
          "interval": "10s",
        },
        "scheduledTaskId": "task-123",
        "tags": Array [
          "foo",
        ],
        "throttle": "5m",
        "updatedAt": "2019-02-12T21:01:22.479Z",
        "updatedBy": "elastic",
      }
    ` ) ; expect ( unsecuredSavedObjectsClient . create . mock . calls [ 0 ] [ 2 ] ) . toMatchInlineSnapshot ( `
      Object {
        "id": "1",
        "overwrite": true,
        "references": Array [
          Object {
            "id": "1",
            "name": "action_0",
            "type": "action",
          },
        ],
        "version": "123",
      }
    ` ) ; } ) ; it ( 'throws an error if API key creation throws' , async ( ) => { rulesClientParams . createAPIKey . mockImplementation ( ( ) => { throw new Error ( 'no' ) ; } ) ; expect ( async ( ) => await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : 'onActiveAlert' , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '2' , params : { foo : true , } , } , ] , } , } ) ) . rejects . toThrowErrorMatchingInlineSnapshot ( `"Error updating rule: could not create API key - no"` ) ; } ) ; it ( 'should validate params' , async ( ) => { ruleTypeRegistry . get . mockReturnValueOnce ( { id : '123' , name : 'Test' , actionGroups : [ { id : 'default' , name : 'Default' } ] , defaultActionGroupId : 'default' , minimumLicenseRequired : 'basic' , isExportable : true , recoveryActionGroup : RecoveredActionGroup , validate : { params : schema . object ( { param1 : schema . string ( ) , } ) , } , async executor ( ) { } , producer : 'alerts' , } ) ; await expect ( rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ) . rejects . toThrowErrorMatchingInlineSnapshot ( `"params invalid: [param1]: expected value of type [string] but got [undefined]"` ) ; } ) ; it ( 'should trim alert name in the API key name' , async ( ) => { unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : false , name : ' my alert name ' , schedule : { interval : '10s' } , params : { bar : true , } , createdAt : new Date ( ) . toISOString ( ) , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , ] , scheduledTaskId : 'task-123' , apiKey : null , } , updated_at : new Date ( ) . toISOString ( ) , references : [ { name : 'action_0' , type : 'action' , id : '1' , } , ] , } ) ; await rulesClient . update ( { id : '1' , data : { ... existingAlert . attributes , name : ' my alert name ' , } , } ) ; expect ( rulesClientParams . createAPIKey ) . toHaveBeenCalledWith ( 'Alerting: myType/my alert name' ) ; } ) ; it ( 'swallows error when invalidate API key throws' , async ( ) => { unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : true , schedule : { interval : '10s' } , params : { bar : true , } , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , ] , scheduledTaskId : 'task-123' , } , references : [ { name : 'action_0' , type : 'action' , id : '1' , } , ] , } ) ; unsecuredSavedObjectsClient . create . mockRejectedValueOnce ( new Error ( 'Fail' ) ) ; await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ; expect ( rulesClientParams . logger . error ) . toHaveBeenCalledWith ( 'Failed to mark for API key [id="MTIzOmFiYw=="] for invalidation: Fail' ) ; } ) ; it ( 'swallows error when getDecryptedAsInternalUser throws' , async ( ) => { encryptedSavedObjects . getDecryptedAsInternalUser . mockRejectedValue ( new Error ( 'Fail' ) ) ; actionsClient . getBulk . mockReset ( ) ; actionsClient . getBulk . mockResolvedValue ( [ { id : '1' , actionTypeId : 'test' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'email connector' , isPreconfigured : false , } , { id : '2' , actionTypeId : 'test2' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'email connector' , isPreconfigured : false , } , ] ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : true , schedule : { interval : '10s' } , params : { bar : true , } , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , { group : 'default' , actionRef : 'action_1' , actionTypeId : 'test' , params : { foo : true , } , } , { group : 'default' , actionRef : 'action_2' , actionTypeId : 'test2' , params : { foo : true , } , } , ] , scheduledTaskId : 'task-123' , createdAt : new Date ( ) . toISOString ( ) , } , updated_at : new Date ( ) . toISOString ( ) , references : [ { name : 'action_0' , type : 'action' , id : '1' , } , { name : 'action_1' , type : 'action' , id : '1' , } , { name : 'action_2' , type : 'action' , id : '2' , } , ] , } ) ; await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : '5m' , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '2' , params : { foo : true , } , } , ] , } , } ) ; expect ( unsecuredSavedObjectsClient . get ) . toHaveBeenCalledWith ( 'alert' , '1' ) ; expect ( rulesClientParams . logger . error ) . toHaveBeenCalledWith ( 'update(): Failed to load API key to invalidate on alert 1: Fail' ) ; } ) ; test ( 'throws when unsecuredSavedObjectsClient update fails and invalidates newly created API key' , async ( ) => { rulesClientParams . createAPIKey . mockResolvedValueOnce ( { apiKeysEnabled : true , result : { id : '234' , name : '234' , api_key : 'abc' } , } ) ; unsecuredSavedObjectsClient . create . mockRejectedValue ( new Error ( 'Fail' ) ) ; await expect ( rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ) . rejects . toThrowErrorMatchingInlineSnapshot ( `"Fail"` ) ; expect ( ( unsecuredSavedObjectsClient . create . mock . calls [ 1 ] [ 1 ] as InvalidatePendingApiKey ) . apiKeyId ) . toBe ( '234' ) ; } ) ; describe ( 'updating an alert schedule' , ( ) => { function mockApiCalls ( alertId , taskId , currentSchedule , updatedSchedule ) { ruleTypeRegistry . get . mockReturnValueOnce ( { id : '123' , name : 'Test' , actionGroups : [ { id : 'default' , name : 'Default' } ] , defaultActionGroupId : 'default' , minimumLicenseRequired : 'basic' , isExportable : true , recoveryActionGroup : RecoveredActionGroup , async executor ( ) { } , producer : 'alerts' , } ) ; encryptedSavedObjects . getDecryptedAsInternalUser . mockResolvedValueOnce ( { id : alertId , type : 'alert' , attributes : { actions : [ ] , enabled : true , alertTypeId : '123' , schedule : currentSchedule , scheduledTaskId : 'task-123' , } , references : [ ] , version : '123' , } ) ; taskManager . schedule . mockResolvedValueOnce ( { id : taskId , taskType : 'alerting:123' , scheduledAt : new Date ( ) , attempts : 1 , status : TaskStatus . Idle , runAt : new Date ( ) , startedAt : null , retryAt : null , state : { } , params : { } , ownerId : null , } ) ; unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : alertId , type : 'alert' , attributes : { enabled : true , schedule : updatedSchedule , actions : [ { group : 'default' , actionRef : 'action_0' , actionTypeId : 'test' , params : { foo : true , } , } , ] , scheduledTaskId : taskId , } , references : [ { name : 'action_0' , type : 'action' , id : alertId , } , ] , } ) ; taskManager . runNow . mockReturnValueOnce ( Promise . resolve ( { id : alertId } ) ) ; } test ( 'updating the alert schedule should rerun the task immediately' , async ( ) => { const alertId = uuid . v4 ( ) ; const taskId = uuid . v4 ( ) ; mockApiCalls ( alertId , taskId , { interval : '60m' } , { interval : '10s' } ) ; await rulesClient . update ( { id : alertId , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ; expect ( taskManager . runNow ) . toHaveBeenCalledWith ( taskId ) ; } ) ; test ( 'updating the alert without changing the schedule should not rerun the task' , async ( ) => { const alertId = uuid . v4 ( ) ; const taskId = uuid . v4 ( ) ; mockApiCalls ( alertId , taskId , { interval : '10s' } , { interval : '10s' } ) ; await rulesClient . update ( { id : alertId , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ; expect ( taskManager . runNow ) . not . toHaveBeenCalled ( ) ; } ) ; test ( 'updating the alert should not wait for the rerun the task to complete' , async ( ) => { const alertId = uuid . v4 ( ) ; const taskId = uuid . v4 ( ) ; mockApiCalls ( alertId , taskId , { interval : '10s' } , { interval : '30s' } ) ; const resolveAfterAlertUpdatedCompletes = resolvable < { id : string ; } > ( ) ; taskManager . runNow . mockReset ( ) ; taskManager . runNow . mockReturnValue ( resolveAfterAlertUpdatedCompletes ) ; await rulesClient . update ( { id : alertId , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ; expect ( taskManager . runNow ) . toHaveBeenCalled ( ) ; resolveAfterAlertUpdatedCompletes . resolve ( { id : alertId } ) ; } ) ; test ( 'logs when the rerun of an alerts underlying task fails' , async ( ) => { const alertId = uuid . v4 ( ) ; const taskId = uuid . v4 ( ) ; mockApiCalls ( alertId , taskId , { interval : '10s' } , { interval : '30s' } ) ; taskManager . runNow . mockReset ( ) ; taskManager . runNow . mockRejectedValue ( new Error ( 'Failed to run alert' ) ) ; await rulesClient . update ( { id : alertId , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , ] , } , } ) ; expect ( taskManager . runNow ) . toHaveBeenCalled ( ) ; expect ( rulesClientParams . logger . error ) . toHaveBeenCalledWith ( `Alert update failed to run its underlying task. TaskManager runNow failed with Error: Failed to run alert` ) ; } ) ; } ) ; test ( 'throws error when updating action using connector with missing secrets' , async ( ) => { actionsClient . getBulk . mockReset ( ) ; actionsClient . getBulk . mockResolvedValueOnce ( [ { id : '1' , actionTypeId : 'test' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : false , name : 'email connector' , isPreconfigured : false , } , { id : '2' , actionTypeId : 'tes2' , config : { from : 'me@me.com' , hasAuth : false , host : 'hello' , port : 22 , secure : null , service : null , } , isMissingSecrets : true , name : 'another connector' , isPreconfigured : false , } , ] ) ; await expect ( rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : 'onActiveAlert' , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '2' , params : { foo : true , } , } , ] , } , } ) ) . rejects . toThrowErrorMatchingInlineSnapshot ( `"Invalid connectors: another connector"` ) ; expect ( unsecuredSavedObjectsClient . create ) . not . toHaveBeenCalled ( ) ; expect ( taskManager . schedule ) . not . toHaveBeenCalled ( ) ; } ) ; describe ( 'authorization' , ( ) => { beforeEach ( ( ) => { unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { alertTypeId : 'myType' , consumer : 'myApp' , enabled : true , schedule : { interval : '10s' } , params : { bar : true , } , actions : [ ] , scheduledTaskId : 'task-123' , createdAt : new Date ( ) . toISOString ( ) , } , updated_at : new Date ( ) . toISOString ( ) , references : [ ] , } ) ; } ) ; test ( 'ensures user is authorised to update this type of alert under the consumer' , async ( ) => { await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ ] , } , } ) ; expect ( authorization . ensureAuthorized ) . toHaveBeenCalledWith ( { entity : 'rule' , consumer : 'myApp' , operation : 'update' , ruleTypeId : 'myType' , } ) ; } ) ; test ( 'throws when user is not authorised to update this type of alert' , async ( ) => { authorization . ensureAuthorized . mockRejectedValue ( new Error ( `Unauthorized to update a "myType" alert for "myApp"` ) ) ; await expect ( rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : null , actions : [ ] , } , } ) ) . rejects . toMatchInlineSnapshot ( `[Error: Unauthorized to update a "myType" alert for "myApp"]` ) ; expect ( authorization . ensureAuthorized ) . toHaveBeenCalledWith ( { entity : 'rule' , consumer : 'myApp' , operation : 'update' , ruleTypeId : 'myType' , } ) ; } ) ; } ) ; describe ( 'auditLogger' , ( ) => { beforeEach ( ( ) => { unsecuredSavedObjectsClient . create . mockResolvedValueOnce ( { id : '1' , type : 'alert' , attributes : { enabled : true , schedule : { interval : '10s' } , params : { bar : true , } , actions : [ ] , scheduledTaskId : 'task-123' , createdAt : new Date ( ) . toISOString ( ) , } , updated_at : new Date ( ) . toISOString ( ) , references : [ ] , } ) ; } ) ; test ( 'logs audit event when updating a rule' , async ( ) => { await rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , actions : [ ] , notifyWhen : null , } , } ) ; expect ( auditLogger . log ) . toHaveBeenCalledWith ( expect . objectContaining ( { event : expect . objectContaining ( { action : 'rule_update' , outcome : 'unknown' , } ) , kibana : { saved_object : { id : '1' , type : 'alert' } } , } ) ) ; } ) ; test ( 'logs audit event when not authorised to update a rule' , async ( ) => { authorization . ensureAuthorized . mockRejectedValue ( new Error ( 'Unauthorized' ) ) ; await expect ( rulesClient . update ( { id : '1' , data : { schedule : { interval : '10s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , actions : [ ] , notifyWhen : null , } , } ) ) . rejects . toThrow ( ) ; expect ( auditLogger . log ) . toHaveBeenCalledWith ( expect . objectContaining ( { event : expect . objectContaining ( { outcome : 'failure' , action : 'rule_update' , } ) , kibana : { saved_object : { id : '1' , type : 'alert' , } , } , error : { code : 'Error' , message : 'Unauthorized' , } , } ) ) ; } ) ; } ) ; test ( 'throws error when updating with an interval less than the minimum configured one' , async ( ) => { await expect ( rulesClient . update ( { id : '1' , data : { schedule : { interval : '1s' } , name : 'abc' , tags : [ 'foo' ] , params : { bar : true , } , throttle : null , notifyWhen : 'onActiveAlert' , actions : [ { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '1' , params : { foo : true , } , } , { group : 'default' , id : '2' , params : { foo : true , } , } , ] , } , } ) ) . rejects . toThrowErrorMatchingInlineSnapshot ( `"Error updating rule: the interval is less than the minimum interval of 5s"` ) ; expect ( unsecuredSavedObjectsClient . create ) . not . toHaveBeenCalled ( ) ; expect ( taskManager . schedule ) . not . toHaveBeenCalled ( ) ; } ) ; } ) ;