From 9cfa2b0e52457c74d094119fd5b004c242bba8f7 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Thu, 2 Apr 2020 14:54:32 -0700 Subject: [PATCH 01/18] Implemented actions server API for supporting preconfigured connectors defined in kibana.yaml --- .../actions/server/actions_client.mock.ts | 1 + .../actions/server/actions_client.test.ts | 113 ++++++++ .../plugins/actions/server/actions_client.ts | 50 +++- x-pack/plugins/actions/server/config.test.ts | 40 +++ x-pack/plugins/actions/server/config.ts | 13 + x-pack/plugins/actions/server/plugin.test.ts | 33 ++- x-pack/plugins/actions/server/plugin.ts | 25 +- .../actions/server/routes/get_all.test.ts | 117 ++++++++ .../plugins/actions/server/routes/get_all.ts | 42 +++ x-pack/plugins/actions/server/routes/index.ts | 1 + x-pack/plugins/actions/server/types.ts | 6 + .../alerting_api_integration/common/config.ts | 26 ++ .../tests/actions/delete.ts | 24 ++ .../security_and_spaces/tests/actions/get.ts | 27 ++ .../tests/actions/get_all.ts | 264 ++++++++++++++++++ .../tests/actions/index.ts | 1 + .../tests/actions/update.ts | 32 +++ .../spaces_only/tests/actions/delete.ts | 7 + .../spaces_only/tests/actions/get.ts | 12 + .../spaces_only/tests/actions/get_all.ts | 115 ++++++++ .../spaces_only/tests/actions/index.ts | 1 + .../spaces_only/tests/actions/update.ts | 20 ++ 22 files changed, 962 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/actions/server/routes/get_all.test.ts create mode 100644 x-pack/plugins/actions/server/routes/get_all.ts create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts diff --git a/x-pack/plugins/actions/server/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client.mock.ts index 8a39d68f40bb6e..9ee2811e5c98ea 100644 --- a/x-pack/plugins/actions/server/actions_client.mock.ts +++ b/x-pack/plugins/actions/server/actions_client.mock.ts @@ -15,6 +15,7 @@ const createActionsClientMock = () => { find: jest.fn(), delete: jest.fn(), update: jest.fn(), + getAll: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 0df07ad58fb9e3..e13e3b83135409 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -51,6 +51,7 @@ beforeEach(() => { savedObjectsClient, scopedClusterClient, defaultKibanaIndex, + preconfiguredConnectors: [], }); }); @@ -83,6 +84,7 @@ describe('create()', () => { }); expect(result).toEqual({ id: '1', + isPreconfigured: false, name: 'my name', actionTypeId: 'my-action-type', config: {}, @@ -178,6 +180,7 @@ describe('create()', () => { }); expect(result).toEqual({ id: '1', + isPreconfigured: false, name: 'my name', actionTypeId: 'my-action-type', config: { @@ -226,6 +229,7 @@ describe('create()', () => { savedObjectsClient, scopedClusterClient, defaultKibanaIndex, + preconfiguredConnectors: [], }); const savedObjectCreateResult = { @@ -305,6 +309,7 @@ describe('get()', () => { const result = await actionsClient.get({ id: '1' }); expect(result).toEqual({ id: '1', + isPreconfigured: false, }); expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); expect(savedObjectsClient.get.mock.calls[0]).toMatchInlineSnapshot(` @@ -314,6 +319,41 @@ describe('get()', () => { ] `); }); + + test('return predefined connector with id', async () => { + actionsClient = new ActionsClient({ + actionTypeRegistry, + savedObjectsClient, + scopedClusterClient, + defaultKibanaIndex, + preconfiguredConnectors: [ + { + id: 'testPreconfigured', + actionTypeId: '.slack', + secrets: { + test: 'test1', + }, + isPreconfigured: true, + name: 'test', + config: { + foo: 'bar', + }, + }, + ], + }); + + const result = await actionsClient.get({ id: 'testPreconfigured' }); + expect(result).toEqual({ + id: 'testPreconfigured', + actionTypeId: '.slack', + isPreconfigured: true, + name: 'test', + config: { + foo: 'bar', + }, + }); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + }); }); describe('find()', () => { @@ -349,6 +389,7 @@ describe('find()', () => { data: [ { id: '1', + isPreconfigured: false, config: { foo: 'bar', }, @@ -367,6 +408,76 @@ describe('find()', () => { }); }); +describe('getAll()', () => { + test('calls savedObjectsClient with parameters', async () => { + const expectedResult = { + total: 1, + per_page: 10, + page: 1, + saved_objects: [ + { + id: '1', + type: 'type', + attributes: { + config: { + foo: 'bar', + }, + }, + references: [], + }, + ], + }; + savedObjectsClient.find.mockResolvedValueOnce(expectedResult); + scopedClusterClient.callAsInternalUser.mockResolvedValueOnce({ + aggregations: { + '1': { doc_count: 6 }, + testPreconfigured: { doc_count: 2 }, + }, + }); + + actionsClient = new ActionsClient({ + actionTypeRegistry, + savedObjectsClient, + scopedClusterClient, + defaultKibanaIndex, + preconfiguredConnectors: [ + { + id: 'testPreconfigured', + actionTypeId: '.slack', + secrets: {}, + isPreconfigured: true, + name: 'test', + config: { + foo: 'bar', + }, + }, + ], + }); + const result = await actionsClient.getAll(); + expect(result).toEqual([ + { + id: '1', + isPreconfigured: false, + config: { + foo: 'bar', + }, + referencedByCount: 6, + }, + { + id: 'testPreconfigured', + actionTypeId: '.slack', + secrets: {}, + isPreconfigured: true, + name: 'test', + config: { + foo: 'bar', + }, + referencedByCount: 2, + }, + ]); + }); +}); + describe('delete()', () => { test('calls savedObjectsClient with id', async () => { const expectedResult = Symbol(); @@ -420,6 +531,7 @@ describe('update()', () => { }); expect(result).toEqual({ id: 'my-action', + isPreconfigured: false, actionTypeId: 'my-action-type', name: 'my name', config: {}, @@ -524,6 +636,7 @@ describe('update()', () => { }); expect(result).toEqual({ id: 'my-action', + isPreconfigured: false, actionTypeId: 'my-action-type', name: 'my name', config: { diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 129829850f9c1b..98afcd6f69405a 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -13,7 +13,7 @@ import { import { ActionTypeRegistry } from './action_type_registry'; import { validateConfig, validateSecrets } from './lib'; -import { ActionResult, FindActionResult, RawAction } from './types'; +import { ActionResult, FindActionResult, RawAction, PreConfiguredAction } from './types'; interface ActionUpdate extends SavedObjectAttributes { name: string; @@ -58,6 +58,7 @@ interface ConstructorOptions { scopedClusterClient: IScopedClusterClient; actionTypeRegistry: ActionTypeRegistry; savedObjectsClient: SavedObjectsClientContract; + preconfiguredConnectors: PreConfiguredAction[]; } interface UpdateOptions { @@ -70,17 +71,20 @@ export class ActionsClient { private readonly scopedClusterClient: IScopedClusterClient; private readonly savedObjectsClient: SavedObjectsClientContract; private readonly actionTypeRegistry: ActionTypeRegistry; + private readonly preconfiguredConnectors: PreConfiguredAction[]; constructor({ actionTypeRegistry, defaultKibanaIndex, scopedClusterClient, savedObjectsClient, + preconfiguredConnectors, }: ConstructorOptions) { this.actionTypeRegistry = actionTypeRegistry; this.savedObjectsClient = savedObjectsClient; this.scopedClusterClient = scopedClusterClient; this.defaultKibanaIndex = defaultKibanaIndex; + this.preconfiguredConnectors = preconfiguredConnectors; } /** @@ -106,6 +110,7 @@ export class ActionsClient { actionTypeId: result.attributes.actionTypeId, name: result.attributes.name, config: result.attributes.config, + isPreconfigured: false, }; } @@ -113,6 +118,9 @@ export class ActionsClient { * Update action */ public async update({ id, action }: UpdateOptions): Promise { + if (this.preconfiguredConnectors.find(pConnector => pConnector.id === id) !== undefined) { + throw Error(`Preconfigured connector ${id} is not allowed to update.`); + } const existingObject = await this.savedObjectsClient.get('action', id); const { actionTypeId } = existingObject.attributes; const { name, config, secrets } = action; @@ -134,6 +142,7 @@ export class ActionsClient { actionTypeId: result.attributes.actionTypeId as string, name: result.attributes.name as string, config: result.attributes.config as Record, + isPreconfigured: false, }; } @@ -141,6 +150,19 @@ export class ActionsClient { * Get an action */ public async get({ id }: { id: string }): Promise { + const preconfiguredConnector = this.preconfiguredConnectors.find( + pConnector => pConnector.id === id + ); + if (preconfiguredConnector !== undefined) { + return { + id, + actionTypeId: preconfiguredConnector.actionTypeId, + name: preconfiguredConnector.name, + config: preconfiguredConnector.config, + description: preconfiguredConnector.description, + isPreconfigured: true, + }; + } const result = await this.savedObjectsClient.get('action', id); return { @@ -148,11 +170,31 @@ export class ActionsClient { actionTypeId: result.attributes.actionTypeId, name: result.attributes.name, config: result.attributes.config, + isPreconfigured: false, }; } /** - * Find actions + * Get all actions connectors with preconfigured connectors + */ + public async getAll(): Promise { + const savedObjectsActions = ( + await this.savedObjectsClient.find({ + perPage: 10000, + type: 'action', + }) + ).saved_objects.map(actionFromSavedObject); + + const mergedResult = [...savedObjectsActions, ...this.preconfiguredConnectors]; + return await injectExtraFindData( + this.defaultKibanaIndex, + this.scopedClusterClient, + mergedResult + ); + } + + /** + * Find actions (only saved objects actions) */ public async find({ options = {} }: FindOptions): Promise { const findResult = await this.savedObjectsClient.find({ @@ -178,6 +220,9 @@ export class ActionsClient { * Delete action */ public async delete({ id }: { id: string }) { + if (this.preconfiguredConnectors.find(pConnector => pConnector.id === id) !== undefined) { + throw Error(`Preconfigured connector ${id} is not allowed to delete.`); + } return await this.savedObjectsClient.delete('action', id); } } @@ -186,6 +231,7 @@ function actionFromSavedObject(savedObject: SavedObject): ActionResul return { id: savedObject.id, ...savedObject.attributes, + isPreconfigured: false, }; } diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index 67b7553c4a7361..9c5ce9ae2abf41 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -14,6 +14,46 @@ describe('config validation', () => { "enabledActionTypes": Array [ "*", ], + "preconfigured": Array [], + "whitelistedHosts": Array [ + "*", + ], + } + `); + }); + + test('action with preconfigured connectors', () => { + const config: Record = { + preconfigured: [ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }, + ], + }; + expect(configSchema.validate(config)).toMatchInlineSnapshot(` + Object { + "enabled": true, + "enabledActionTypes": Array [ + "*", + ], + "preconfigured": Array [ + Object { + "actionTypeId": ".slack", + "config": Object { + "webhookUrl": "https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz", + }, + "description": "Send a message to the #xyz channel", + "id": "my-slack1", + "name": "Slack #xyz", + "secrets": Object {}, + }, + ], "whitelistedHosts": Array [ "*", ], diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 9e4795be6c2082..821cd00e977f79 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -21,6 +21,19 @@ export const configSchema = schema.object({ defaultValue: [WhitelistedHosts.Any], } ), + preconfigured: schema.arrayOf( + schema.object({ + id: schema.string(), + name: schema.string(), + actionTypeId: schema.string(), + description: schema.nullable(schema.string()), + config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + }), + { + defaultValue: [], + } + ), }); export type ActionsConfig = TypeOf; diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 383f84590fbc6d..6215b08df81d4f 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -31,7 +31,34 @@ describe('Actions Plugin', () => { let pluginsSetup: jest.Mocked; beforeEach(() => { - context = coreMock.createPluginInitializerContext(); + context = coreMock.createPluginInitializerContext({ + preconfigured: [ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + description: 'Send a notification to system ABC', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + secrets: { + xyzSecret1: 'credential1', + xyzSecret2: 'credential2', + }, + }, + ], + }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); @@ -160,7 +187,9 @@ describe('Actions Plugin', () => { let pluginsStart: jest.Mocked; beforeEach(() => { - const context = coreMock.createPluginInitializerContext(); + const context = coreMock.createPluginInitializerContext({ + preconfigured: [], + }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); coreStart = coreMock.createStart(); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index ce31e62bc9b8e8..49bafee3d783f8 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -30,7 +30,7 @@ import { LICENSE_TYPE } from '../../licensing/common/types'; import { SpacesPluginSetup, SpacesServiceSetup } from '../../spaces/server'; import { ActionsConfig } from './config'; -import { Services, ActionType } from './types'; +import { Services, ActionType, PreConfiguredAction } from './types'; import { ActionExecutor, TaskRunnerFactory, LicenseState, ILicenseState } from './lib'; import { ActionsClient } from './actions_client'; import { ActionTypeRegistry } from './action_type_registry'; @@ -45,6 +45,7 @@ import { createActionRoute, deleteActionRoute, findActionRoute, + getAllActionRoute, getActionRoute, updateActionRoute, listActionTypesRoute, @@ -97,6 +98,7 @@ export class ActionsPlugin implements Plugin, Plugi private eventLogger?: IEventLogger; private isESOUsingEphemeralEncryptionKey?: boolean; private readonly telemetryLogger: Logger; + private readonly preconfiguredConnectors: PreConfiguredAction[]; constructor(initContext: PluginInitializerContext) { this.config = initContext.config @@ -113,6 +115,7 @@ export class ActionsPlugin implements Plugin, Plugi this.logger = initContext.logger.get('actions'); this.telemetryLogger = initContext.logger.get('telemetry'); + this.preconfiguredConnectors = []; } public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise { @@ -151,8 +154,13 @@ export class ActionsPlugin implements Plugin, Plugi // get executions count const taskRunnerFactory = new TaskRunnerFactory(actionExecutor); - const actionsConfigUtils = getActionsConfigurationUtilities( - (await this.config) as ActionsConfig + const actionsConfig = (await this.config) as ActionsConfig; + const actionsConfigUtils = getActionsConfigurationUtilities(actionsConfig); + + this.preconfiguredConnectors.push( + ...actionsConfig.preconfigured.map( + connector => ({ ...connector, isPreconfigured: true } as PreConfiguredAction) + ) ); const actionTypeRegistry = new ActionTypeRegistry({ taskRunnerFactory, @@ -198,6 +206,7 @@ export class ActionsPlugin implements Plugin, Plugi deleteActionRoute(router, this.licenseState); getActionRoute(router, this.licenseState); findActionRoute(router, this.licenseState); + getAllActionRoute(router, this.licenseState); updateActionRoute(router, this.licenseState); listActionTypesRoute(router, this.licenseState); executeActionRoute(router, this.licenseState, actionExecutor); @@ -226,6 +235,7 @@ export class ActionsPlugin implements Plugin, Plugi kibanaIndex, adminClient, isESOUsingEphemeralEncryptionKey, + preconfiguredConnectors, } = this; actionExecutor!.initialize({ @@ -271,6 +281,7 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex: await kibanaIndex, scopedClusterClient: adminClient!.asScoped(request), + preconfiguredConnectors, }); }, }; @@ -289,7 +300,12 @@ export class ActionsPlugin implements Plugin, Plugi private createRouteHandlerContext = ( defaultKibanaIndex: string ): IContextProvider, 'actions'> => { - const { actionTypeRegistry, adminClient, isESOUsingEphemeralEncryptionKey } = this; + const { + actionTypeRegistry, + adminClient, + isESOUsingEphemeralEncryptionKey, + preconfiguredConnectors, + } = this; return async function actionsRouteHandlerContext(context, request) { return { getActionsClient: () => { @@ -303,6 +319,7 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex, scopedClusterClient: adminClient!.asScoped(request), + preconfiguredConnectors, }); }, listTypes: actionTypeRegistry!.list.bind(actionTypeRegistry!), diff --git a/x-pack/plugins/actions/server/routes/get_all.test.ts b/x-pack/plugins/actions/server/routes/get_all.test.ts new file mode 100644 index 00000000000000..6499427b8c1a53 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/get_all.test.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getAllActionRoute } from './get_all'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib'; +import { mockHandlerArguments } from './_mock_handler_arguments'; + +jest.mock('../lib/verify_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getAllActionRoute', () => { + it('get all actions with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router: RouterMock = mockRouter.create(); + + getAllActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/_getAll"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const actionsClient = { + getAll: jest.fn().mockResolvedValueOnce([]), + }; + + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Array [], + } + `); + + expect(actionsClient.getAll).toHaveBeenCalledTimes(1); + + expect(res.ok).toHaveBeenCalledWith({ + body: [], + }); + }); + + it('ensures the license allows getting all actions', async () => { + const licenseState = licenseStateMock.create(); + const router: RouterMock = mockRouter.create(); + + getAllActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/_getAll"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const actionsClient = { + getAll: jest.fn().mockResolvedValueOnce([]), + }; + + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents getting all actions', async () => { + const licenseState = licenseStateMock.create(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + getAllActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/_getAll"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const actionsClient = { + getAll: jest.fn().mockResolvedValueOnce([]), + }; + + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/get_all.ts b/x-pack/plugins/actions/server/routes/get_all.ts new file mode 100644 index 00000000000000..c70a13bc01c9f8 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/get_all.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { ILicenseState, verifyApiAccess } from '../lib'; +import { BASE_ACTION_API_PATH } from '../../common'; + +export const getAllActionRoute = (router: IRouter, licenseState: ILicenseState) => { + router.get( + { + path: `${BASE_ACTION_API_PATH}/_getAll`, + validate: {}, + options: { + tags: ['access:actions-read'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + if (!context.actions) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); + } + const actionsClient = context.actions.getActionsClient(); + const result = await actionsClient.getAll(); + return res.ok({ + body: result, + }); + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts index 33191132bece55..c498ad2b513677 100644 --- a/x-pack/plugins/actions/server/routes/index.ts +++ b/x-pack/plugins/actions/server/routes/index.ts @@ -7,6 +7,7 @@ export { createActionRoute } from './create'; export { deleteActionRoute } from './delete'; export { findActionRoute } from './find'; +export { getAllActionRoute } from './get_all'; export { getActionRoute } from './get'; export { updateActionRoute } from './update'; export { listActionTypesRoute } from './list_action_types'; diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 999e739e77060c..9c1807579222d4 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -55,6 +55,12 @@ export interface ActionResult { actionTypeId: string; name: string; config: Record; + isPreconfigured: boolean; + description?: string; +} + +export interface PreConfiguredAction extends ActionResult { + secrets: Record; } export interface FindActionResult extends ActionResult { diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 5fb1afa7d584f2..78d8c655aa4f8b 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -77,6 +77,32 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', + `--xpack.actions.preconfigured=${JSON.stringify([ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + description: 'Send a notification to system ABC', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + secrets: { + xyzSecret1: 'credential1', + xyzSecret2: 'credential2', + }, + }, + ])}`, ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/delete.ts index 6fca330887c3e6..b3b6a548f0dc73 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/delete.ts @@ -137,6 +137,30 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); } }); + + it(`shouldn't delete action from preconfigured list`, async () => { + const response = await supertestWithoutAuth + .delete(`${getUrlPrefix(space.id)}/api/action/my-slack1`) + .auth(user.username, user.password) + .set('kbn-xsrf', 'foo'); + + expect(response.statusCode).to.eql(400); + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'global_read at space1': + case 'space_1_all at space2': + case 'space_1_all at space1': + case 'superuser at space1': + expect(response.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: 'Preconfigured connector my-slack1 is not allowed to delete.', + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); }); } }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts index bed4c805aaf573..45b9b803556bd1 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts @@ -115,6 +115,33 @@ export default function getActionTests({ getService }: FtrProviderContext) { throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); } }); + + it('should handle get preconfigured action request appropriately', async () => { + const response = await supertestWithoutAuth + .get(`${getUrlPrefix(space.id)}/api/action/my-slack1`) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(200); + expect(response.body).to.eql({ + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); }); } }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts new file mode 100644 index 00000000000000..fb731c91d73754 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -0,0 +1,264 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { UserAtSpaceScenarios } from '../../scenarios'; +import { getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function getAllActionTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + describe('getAll', () => { + const objectRemover = new ObjectRemover(supertest); + + afterEach(() => objectRemover.removeAll()); + + for (const scenario of UserAtSpaceScenarios) { + const { user, space } = scenario; + describe(scenario.id, () => { + it('should handle get all action request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }) + .expect(200); + objectRemover.add(space.id, createdAction.id, 'action'); + + const response = await supertestWithoutAuth + .get(`${getUrlPrefix(space.id)}/api/action/_getAll`) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; + case 'global_read at space1': + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(200); + expect(response.body).to.eql([ + { + id: createdAction.id, + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + referencedByCount: 0, + }, + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + referencedByCount: 0, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + description: 'Send a notification to system ABC', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + referencedByCount: 0, + }, + ]); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle get all request appropriately with proper referencedByCount', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }) + .expect(200); + objectRemover.add(space.id, createdAction.id, 'action'); + + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alert`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + actions: [ + { + group: 'default', + id: createdAction.id, + params: {}, + }, + { + group: 'default', + id: 'my-slack1', + params: { + message: 'test', + }, + }, + ], + }) + ) + .expect(200); + objectRemover.add(space.id, createdAlert.id, 'alert'); + + const response = await supertestWithoutAuth + .get(`${getUrlPrefix(space.id)}/api/action/_getAll`) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; + case 'global_read at space1': + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(200); + expect(response.body).to.eql([ + { + id: createdAction.id, + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + referencedByCount: 1, + }, + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + referencedByCount: 1, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + description: 'Send a notification to system ABC', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + referencedByCount: 0, + }, + ]); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it(`shouldn't get actions from another space`, async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }) + .expect(200); + objectRemover.add(space.id, createdAction.id, 'action'); + + const response = await supertestWithoutAuth + .get(`${getUrlPrefix('other')}/api/action/_getAll`) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; + case 'global_read at space1': + case 'superuser at space1': + expect(response.statusCode).to.eql(200); + expect(response.body).to.eql([ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + referencedByCount: 0, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + description: 'Send a notification to system ABC', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + referencedByCount: 0, + }, + ]); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + }); + } + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts index c6960a4eedd256..4f228c13d6c68f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts @@ -20,6 +20,7 @@ export default function actionsTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./execute')); loadTestFile(require.resolve('./find')); + loadTestFile(require.resolve('./get_all')); loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./list_action_types')); loadTestFile(require.resolve('./update')); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts index a792efede07ee0..3d1067b6f6c813 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts @@ -307,6 +307,38 @@ export default function updateActionTests({ getService }: FtrProviderContext) { throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); } }); + + it(`shouldn't update action from preconfigured list`, async () => { + const response = await supertestWithoutAuth + .put(`${getUrlPrefix(space.id)}/api/action/custom-system-abc-connector`) + .auth(user.username, user.password) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action updated', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }); + expect(response.statusCode).to.eql(404); + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + case 'space_1_all at space1': + case 'superuser at space1': + expect(response.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: `Preconfigured connector custom-system-abc-connector is not allowed to update.`, + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); }); } }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/delete.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/delete.ts index 283e51352c2726..80f0a45a2cfbda 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/delete.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/delete.ts @@ -76,5 +76,12 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { message: 'Saved object [action/2] not found', }); }); + + it(`shouldn't delete action from preconfigured list`, async () => { + await supertest + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/action/my-slack1`) + .set('kbn-xsrf', 'foo') + .expect(400, 'Preconfigured connector my-slack1 is not allowed to delete.'); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts index 0f896bfaa0af9b..1338ce24b02356 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts @@ -71,5 +71,17 @@ export default function getActionTests({ getService }: FtrProviderContext) { message: `Saved object [action/${createdAction.id}] not found`, }); }); + + it('should handle get action request from preconfigured list', async () => { + await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/action/my-slack1`).expect(200, { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts new file mode 100644 index 00000000000000..54a396e083355f --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Spaces } from '../../scenarios'; +import { getUrlPrefix, ObjectRemover } from '../../../common/lib'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function getAllActionTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('getAll', () => { + const objectRemover = new ObjectRemover(supertest); + + afterEach(() => objectRemover.removeAll()); + + it('should handle get all action request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAction.id, 'action'); + + await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/action/_getAll`).expect(200, [ + { + id: createdAction.id, + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + referencedByCount: 0, + }, + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + referencedByCount: 0, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + description: 'Send a notification to system ABC', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + referencedByCount: 0, + }, + ]); + }); + + it(`shouldn't get all action from another space`, async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAction.id, 'action'); + + await supertest.get(`${getUrlPrefix(Spaces.other.id)}/api/action/_getAll`).expect(200, [ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + referencedByCount: 0, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + description: 'Send a notification to system ABC', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + referencedByCount: 0, + }, + ]); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts index fb2be8c86f4e8b..d82cb6d2e11c92 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts @@ -12,6 +12,7 @@ export default function actionsTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./find')); + loadTestFile(require.resolve('./get_all')); loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./list_action_types')); loadTestFile(require.resolve('./update')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts index fb0c5e13c0720d..ddd9881b7837ed 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts @@ -99,5 +99,25 @@ export default function updateActionTests({ getService }: FtrProviderContext) { message: `Saved object [action/${createdAction.id}] not found`, }); }); + + it(`shouldn't update action from preconfigured list`, async () => { + await supertest + .put(`${getUrlPrefix(Spaces.space1.id)}/api/action/my-slack1`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action updated', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }) + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: `Preconfigured connector custom-system-abc-connector is not allowed to update.`, + }); + }); }); } From cf47ef1dad6134fd4edae81929df69505e322eaf Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Thu, 2 Apr 2020 20:50:27 -0700 Subject: [PATCH 02/18] Fixed type check --- .../lib/detection_engine/routes/__mocks__/request_responses.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 3b24b722f73561..7c57929897e3aa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -383,6 +383,7 @@ export const createActionResult = (): ActionResult => ({ actionTypeId: 'action-id-1', name: '', config: {}, + isPreconfigured: false, }); export const nonRuleAlert = () => ({ @@ -512,6 +513,7 @@ export const updateActionResult = (): ActionResult => ({ actionTypeId: 'action-id-1', name: '', config: {}, + isPreconfigured: false, }); export const getMockPrivilegesResult = () => ({ From 9fa5f7438ba6c7d0b9652b765c5f68504cf29cdb Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Fri, 3 Apr 2020 12:19:49 -0700 Subject: [PATCH 03/18] Fixed due to comments and extended functional tests --- .../alerts/configuration/configuration.tsx | 2 +- .../scripts/get_action_instances.sh | 2 +- x-pack/plugins/actions/README.md | 10 +- .../actions/server/actions_client.test.ts | 52 ---- .../plugins/actions/server/actions_client.ts | 88 +++--- x-pack/plugins/actions/server/config.test.ts | 2 - x-pack/plugins/actions/server/config.ts | 1 - ...defined_connector_disabled_modification.ts | 24 ++ x-pack/plugins/actions/server/plugin.ts | 2 - .../actions/server/routes/find.test.ts | 152 ---------- x-pack/plugins/actions/server/routes/find.ts | 95 ------ x-pack/plugins/actions/server/routes/index.ts | 1 - x-pack/plugins/actions/server/types.ts | 1 - .../lib/action_connector_api.test.ts | 2 +- .../alerting_api_integration/common/config.ts | 2 - .../tests/actions/create.ts | 1 + .../security_and_spaces/tests/actions/find.ts | 278 ------------------ .../tests/actions/get_all.ts | 14 +- .../tests/actions/index.ts | 1 - .../spaces_only/tests/actions/create.ts | 1 + .../spaces_only/tests/actions/find.ts | 92 ------ .../spaces_only/tests/actions/get_all.ts | 9 +- .../spaces_only/tests/actions/index.ts | 1 - .../spaces_only/tests/actions/update.ts | 1 + 24 files changed, 85 insertions(+), 749 deletions(-) create mode 100644 x-pack/plugins/actions/server/lib/errors/predefined_connector_disabled_modification.ts delete mode 100644 x-pack/plugins/actions/server/routes/find.test.ts delete mode 100644 x-pack/plugins/actions/server/routes/find.ts delete mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/find.ts delete mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/actions/find.ts diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx index 0933cd22db7c9f..eaa474ba177b18 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx @@ -61,7 +61,7 @@ export const AlertsConfiguration: React.FC = ( async function fetchEmailActions() { const kibanaActions = await kfetch({ method: 'GET', - pathname: `/api/action/_find`, + pathname: `/api/action/_getAll`, }); const actions = kibanaActions.data.filter( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_action_instances.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_action_instances.sh index 7804439ce07345..750c5574f4a729 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_action_instances.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_action_instances.sh @@ -13,5 +13,5 @@ set -e # https://github.com/elastic/kibana/blob/master/x-pack/legacy/plugins/actions/README.md#get-apiaction_find-find-actions curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X GET ${KIBANA_URL}${SPACE_URL}/api/action/_find \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/action/_getAll \ | jq . diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index d217d26e84836b..3daa36bf792c84 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -28,7 +28,7 @@ Table of Contents - [RESTful API](#restful-api) - [`POST /api/action`: Create action](#post-apiaction-create-action) - [`DELETE /api/action/{id}`: Delete action](#delete-apiactionid-delete-action) - - [`GET /api/action/_find`: Find actions](#get-apiactionfind-find-actions) + - [`GET /api/action/_getAll`: Get all actions](#get-apiaction-get-all-actions) - [`GET /api/action/{id}`: Get action](#get-apiactionid-get-action) - [`GET /api/action/types`: List action types](#get-apiactiontypes-list-action-types) - [`PUT /api/action/{id}`: Update action](#put-apiactionid-update-action) @@ -174,11 +174,13 @@ Params: | -------- | --------------------------------------------- | ------ | | id | The id of the action you're trying to delete. | string | -### `GET /api/action/_find`: Find actions +### `GET /api/action/_getAll`: Get all actions -Params: +No parameters. -See the [saved objects API documentation for find](https://www.elastic.co/guide/en/kibana/master/saved-objects-api-find.html). All the properties are the same except that you cannot pass in `type`. +Return all actions from saved objects merged with predefined list. +Use the [saved objects API for find](https://www.elastic.co/guide/en/kibana/master/saved-objects-api-find.html) with the proprties: `type: 'action'` and `perPage: 10000`. +List of predefined connectors should be set up in Kibana.yaml. ### `GET /api/action/{id}`: Get action diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index e13e3b83135409..50cebc1fdca9d3 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -356,58 +356,6 @@ describe('get()', () => { }); }); -describe('find()', () => { - test('calls savedObjectsClient with parameters', async () => { - const expectedResult = { - total: 1, - per_page: 10, - page: 1, - saved_objects: [ - { - id: '1', - type: 'type', - attributes: { - config: { - foo: 'bar', - }, - }, - references: [], - }, - ], - }; - savedObjectsClient.find.mockResolvedValueOnce(expectedResult); - scopedClusterClient.callAsInternalUser.mockResolvedValueOnce({ - aggregations: { - '1': { doc_count: 6 }, - }, - }); - const result = await actionsClient.find({}); - expect(result).toEqual({ - total: 1, - perPage: 10, - page: 1, - data: [ - { - id: '1', - isPreconfigured: false, - config: { - foo: 'bar', - }, - referencedByCount: 6, - }, - ], - }); - expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); - expect(savedObjectsClient.find.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "type": "action", - }, - ] - `); - }); -}); - describe('getAll()', () => { test('calls savedObjectsClient with parameters', async () => { const expectedResult = { diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 98afcd6f69405a..46bfe90e38aad6 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -11,9 +11,16 @@ import { SavedObject, } from 'src/core/server'; +import { i18n } from '@kbn/i18n'; import { ActionTypeRegistry } from './action_type_registry'; import { validateConfig, validateSecrets } from './lib'; import { ActionResult, FindActionResult, RawAction, PreConfiguredAction } from './types'; +import { PredefinedConnectorDisabledModificationError } from './lib/errors/predefined_connector_disabled_modification'; + +// We are assuming there won't be many actions. This is why we will load +// all the actions in advance and assume the total count to not go over 100 or so. +// We'll set this max setting assuming it's never reached. +export const MAX_ACTIONS_RETURNED = 10000; interface ActionUpdate extends SavedObjectAttributes { name: string; @@ -29,30 +36,6 @@ interface CreateOptions { action: Action; } -interface FindOptions { - options?: { - perPage?: number; - page?: number; - search?: string; - defaultSearchOperator?: 'AND' | 'OR'; - searchFields?: string[]; - sortField?: string; - hasReference?: { - type: string; - id: string; - }; - fields?: string[]; - filter?: string; - }; -} - -interface FindResult { - page: number; - perPage: number; - total: number; - data: FindActionResult[]; -} - interface ConstructorOptions { defaultKibanaIndex: string; scopedClusterClient: IScopedClusterClient; @@ -119,7 +102,15 @@ export class ActionsClient { */ public async update({ id, action }: UpdateOptions): Promise { if (this.preconfiguredConnectors.find(pConnector => pConnector.id === id) !== undefined) { - throw Error(`Preconfigured connector ${id} is not allowed to update.`); + throw new PredefinedConnectorDisabledModificationError( + i18n.translate('xpack.actions.serverSideErrors.predefinedConnectorUpdatedisabled', { + defaultMessage: 'Preconfigured connector {id} is not allowed to update.', + values: { + id, + }, + }), + 'update' + ); } const existingObject = await this.savedObjectsClient.get('action', id); const { actionTypeId } = existingObject.attributes; @@ -159,7 +150,6 @@ export class ActionsClient { actionTypeId: preconfiguredConnector.actionTypeId, name: preconfiguredConnector.name, config: preconfiguredConnector.config, - description: preconfiguredConnector.description, isPreconfigured: true, }; } @@ -180,12 +170,21 @@ export class ActionsClient { public async getAll(): Promise { const savedObjectsActions = ( await this.savedObjectsClient.find({ - perPage: 10000, + perPage: MAX_ACTIONS_RETURNED, type: 'action', }) ).saved_objects.map(actionFromSavedObject); - const mergedResult = [...savedObjectsActions, ...this.preconfiguredConnectors]; + const mergedResult = [ + ...savedObjectsActions, + ...this.preconfiguredConnectors.map(preconfiguredConnector => ({ + id: preconfiguredConnector.id, + actionTypeId: preconfiguredConnector.actionTypeId, + name: preconfiguredConnector.name, + config: preconfiguredConnector.config, + isPreconfigured: true, + })), + ].sort((a, b) => a.name.localeCompare(b.name)); return await injectExtraFindData( this.defaultKibanaIndex, this.scopedClusterClient, @@ -193,35 +192,20 @@ export class ActionsClient { ); } - /** - * Find actions (only saved objects actions) - */ - public async find({ options = {} }: FindOptions): Promise { - const findResult = await this.savedObjectsClient.find({ - ...options, - type: 'action', - }); - - const data = await injectExtraFindData( - this.defaultKibanaIndex, - this.scopedClusterClient, - findResult.saved_objects.map(actionFromSavedObject) - ); - - return { - page: findResult.page, - perPage: findResult.per_page, - total: findResult.total, - data, - }; - } - /** * Delete action */ public async delete({ id }: { id: string }) { if (this.preconfiguredConnectors.find(pConnector => pConnector.id === id) !== undefined) { - throw Error(`Preconfigured connector ${id} is not allowed to delete.`); + throw new PredefinedConnectorDisabledModificationError( + i18n.translate('xpack.actions.serverSideErrors.predefinedConnectorUpdatedisabled', { + defaultMessage: 'Preconfigured connector {id} is not allowed to delete.', + values: { + id, + }, + }), + 'delete' + ); } return await this.savedObjectsClient.delete('action', id); } diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index 9c5ce9ae2abf41..7e65cc8d4c0762 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -29,7 +29,6 @@ describe('config validation', () => { id: 'my-slack1', actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -48,7 +47,6 @@ describe('config validation', () => { "config": Object { "webhookUrl": "https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz", }, - "description": "Send a message to the #xyz channel", "id": "my-slack1", "name": "Slack #xyz", "secrets": Object {}, diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 821cd00e977f79..ffc6dbc64c3568 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -26,7 +26,6 @@ export const configSchema = schema.object({ id: schema.string(), name: schema.string(), actionTypeId: schema.string(), - description: schema.nullable(schema.string()), config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), }), diff --git a/x-pack/plugins/actions/server/lib/errors/predefined_connector_disabled_modification.ts b/x-pack/plugins/actions/server/lib/errors/predefined_connector_disabled_modification.ts new file mode 100644 index 00000000000000..03d19c4c3b869f --- /dev/null +++ b/x-pack/plugins/actions/server/lib/errors/predefined_connector_disabled_modification.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaResponseFactory } from '../../../../../../src/core/server'; +import { ErrorThatHandlesItsOwnResponse } from './types'; + +export type PredefinedConnectorDisabledFrom = 'update' | 'delete'; + +export class PredefinedConnectorDisabledModificationError extends Error + implements ErrorThatHandlesItsOwnResponse { + public readonly disabledFrom: PredefinedConnectorDisabledFrom; + + constructor(message: string, disabledFrom: PredefinedConnectorDisabledFrom) { + super(message); + this.disabledFrom = disabledFrom; + } + + public sendResponse(res: KibanaResponseFactory) { + return res.badRequest({ body: { message: this.message } }); + } +} diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 49bafee3d783f8..95425209125f0c 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -44,7 +44,6 @@ import { getActionsConfigurationUtilities } from './actions_config'; import { createActionRoute, deleteActionRoute, - findActionRoute, getAllActionRoute, getActionRoute, updateActionRoute, @@ -205,7 +204,6 @@ export class ActionsPlugin implements Plugin, Plugi createActionRoute(router, this.licenseState); deleteActionRoute(router, this.licenseState); getActionRoute(router, this.licenseState); - findActionRoute(router, this.licenseState); getAllActionRoute(router, this.licenseState); updateActionRoute(router, this.licenseState); listActionTypesRoute(router, this.licenseState); diff --git a/x-pack/plugins/actions/server/routes/find.test.ts b/x-pack/plugins/actions/server/routes/find.test.ts deleted file mode 100644 index 1b130421fa71fa..00000000000000 --- a/x-pack/plugins/actions/server/routes/find.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { findActionRoute } from './find'; -import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib'; -import { mockHandlerArguments } from './_mock_handler_arguments'; - -jest.mock('../lib/verify_api_access.ts', () => ({ - verifyApiAccess: jest.fn(), -})); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -describe('findActionRoute', () => { - it('finds actions with proper parameters', async () => { - const licenseState = licenseStateMock.create(); - const router: RouterMock = mockRouter.create(); - - findActionRoute(router, licenseState); - - const [config, handler] = router.get.mock.calls[0]; - - expect(config.path).toMatchInlineSnapshot(`"/api/action/_find"`); - expect(config.options).toMatchInlineSnapshot(` - Object { - "tags": Array [ - "access:actions-read", - ], - } - `); - - const findResult = { - page: 1, - perPage: 1, - total: 0, - data: [], - }; - const actionsClient = { - find: jest.fn().mockResolvedValueOnce(findResult), - }; - - const [context, req, res] = mockHandlerArguments( - { actionsClient }, - { - query: { - per_page: 1, - page: 1, - default_search_operator: 'OR', - }, - }, - ['ok'] - ); - - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "data": Array [], - "page": 1, - "perPage": 1, - "total": 0, - }, - } - `); - - expect(actionsClient.find).toHaveBeenCalledTimes(1); - expect(actionsClient.find.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "options": Object { - "defaultSearchOperator": "OR", - "fields": undefined, - "filter": undefined, - "page": 1, - "perPage": 1, - "search": undefined, - "sortField": undefined, - "sortOrder": undefined, - }, - }, - ] - `); - - expect(res.ok).toHaveBeenCalledWith({ - body: findResult, - }); - }); - - it('ensures the license allows finding actions', async () => { - const licenseState = licenseStateMock.create(); - const router: RouterMock = mockRouter.create(); - - findActionRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - const actionsClient = { - find: jest.fn().mockResolvedValueOnce({ - page: 1, - perPage: 1, - total: 0, - data: [], - }), - }; - - const [context, req, res] = mockHandlerArguments(actionsClient, { - query: { - per_page: 1, - page: 1, - default_search_operator: 'OR', - }, - }); - - await handler(context, req, res); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('ensures the license check prevents finding actions', async () => { - const licenseState = licenseStateMock.create(); - const router: RouterMock = mockRouter.create(); - - (verifyApiAccess as jest.Mock).mockImplementation(() => { - throw new Error('OMG'); - }); - - findActionRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - const [context, req, res] = mockHandlerArguments( - {}, - { - query: { - per_page: 1, - page: 1, - default_search_operator: 'OR', - }, - }, - ['ok'] - ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); -}); diff --git a/x-pack/plugins/actions/server/routes/find.ts b/x-pack/plugins/actions/server/routes/find.ts deleted file mode 100644 index 45b967629a2a85..00000000000000 --- a/x-pack/plugins/actions/server/routes/find.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; -import { FindOptions } from '../../../alerting/server'; -import { ILicenseState, verifyApiAccess } from '../lib'; -import { BASE_ACTION_API_PATH } from '../../common'; - -// config definition -const querySchema = schema.object({ - per_page: schema.number({ defaultValue: 20, min: 0 }), - page: schema.number({ defaultValue: 1, min: 1 }), - search: schema.maybe(schema.string()), - default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { - defaultValue: 'OR', - }), - search_fields: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])), - sort_field: schema.maybe(schema.string()), - sort_order: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), - has_reference: schema.maybe( - // use nullable as maybe is currently broken - // in config-schema - schema.nullable( - schema.object({ - type: schema.string(), - id: schema.string(), - }) - ) - ), - fields: schema.maybe(schema.arrayOf(schema.string())), - filter: schema.maybe(schema.string()), -}); - -export const findActionRoute = (router: IRouter, licenseState: ILicenseState) => { - router.get( - { - path: `${BASE_ACTION_API_PATH}/_find`, - validate: { - query: querySchema, - }, - options: { - tags: ['access:actions-read'], - }, - }, - router.handleLegacyErrors(async function( - context: RequestHandlerContext, - req: KibanaRequest, any, any>, - res: KibanaResponseFactory - ): Promise> { - verifyApiAccess(licenseState); - if (!context.actions) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); - } - const actionsClient = context.actions.getActionsClient(); - const query = req.query; - const options: FindOptions['options'] = { - perPage: query.per_page, - page: query.page, - search: query.search, - defaultSearchOperator: query.default_search_operator, - sortField: query.sort_field, - fields: query.fields, - filter: query.filter, - sortOrder: query.sort_order, - }; - - if (query.search_fields) { - options.searchFields = Array.isArray(query.search_fields) - ? query.search_fields - : [query.search_fields]; - } - - if (query.has_reference) { - options.hasReference = query.has_reference; - } - - const findResult = await actionsClient.find({ - options, - }); - return res.ok({ - body: findResult, - }); - }) - ); -}; diff --git a/x-pack/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts index c498ad2b513677..94f9ec1c943646 100644 --- a/x-pack/plugins/actions/server/routes/index.ts +++ b/x-pack/plugins/actions/server/routes/index.ts @@ -6,7 +6,6 @@ export { createActionRoute } from './create'; export { deleteActionRoute } from './delete'; -export { findActionRoute } from './find'; export { getAllActionRoute } from './get_all'; export { getActionRoute } from './get'; export { updateActionRoute } from './update'; diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 9c1807579222d4..92e38d77314f83 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -56,7 +56,6 @@ export interface ActionResult { name: string; config: Record; isPreconfigured: boolean; - description?: string; } export interface PreConfiguredAction extends ActionResult { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts index ee68b7e269c342..5d3c3111d4868b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts @@ -56,7 +56,7 @@ describe('loadAllActions', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/action/_find", + "/api/action/_getAll", Object { "query": Object { "per_page": 10000, diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 78d8c655aa4f8b..3307d89dbade03 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -82,7 +82,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) id: 'my-slack1', actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -90,7 +89,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) { id: 'custom-system-abc-connector', actionTypeId: 'system-abc-action-type', - description: 'Send a notification to system ABC', name: 'System ABC', config: { xyzConfig1: 'value1', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/create.ts index 43a3861491467f..922315eba5a5c6 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/create.ts @@ -55,6 +55,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, response.body.id, 'action'); expect(response.body).to.eql({ id: response.body.id, + isPreconfigured: false, name: 'My action', actionTypeId: 'test.index-record', config: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/find.ts deleted file mode 100644 index 89c5b4f451f82b..00000000000000 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/find.ts +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { UserAtSpaceScenarios } from '../../scenarios'; -import { getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function findActionTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - - describe('find', () => { - const objectRemover = new ObjectRemover(supertest); - - afterEach(() => objectRemover.removeAll()); - - for (const scenario of UserAtSpaceScenarios) { - const { user, space } = scenario; - describe(scenario.id, () => { - it('should handle find action request appropriately', async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - secrets: { - encrypted: 'This value should be encrypted', - }, - }) - .expect(200); - objectRemover.add(space.id, createdAction.id, 'action'); - - const response = await supertestWithoutAuth - .get( - `${getUrlPrefix( - space.id - )}/api/action/_find?search=test.index-record&search_fields=actionTypeId` - ) - .auth(user.username, user.password); - - switch (scenario.id) { - case 'no_kibana_privileges at space1': - case 'space_1_all at space2': - expect(response.statusCode).to.eql(404); - expect(response.body).to.eql({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }); - break; - case 'global_read at space1': - case 'superuser at space1': - case 'space_1_all at space1': - expect(response.statusCode).to.eql(200); - expect(response.body).to.eql({ - page: 1, - perPage: 20, - total: 1, - data: [ - { - id: createdAction.id, - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - referencedByCount: 0, - }, - ], - }); - break; - default: - throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); - } - }); - - it('should handle find action request with filter appropriately', async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - secrets: { - encrypted: 'This value should be encrypted', - }, - }) - .expect(200); - objectRemover.add(space.id, createdAction.id, 'action'); - - const response = await supertestWithoutAuth - .get( - `${getUrlPrefix( - space.id - )}/api/action/_find?filter=action.attributes.actionTypeId:test.index-record` - ) - .auth(user.username, user.password); - - switch (scenario.id) { - case 'no_kibana_privileges at space1': - case 'space_1_all at space2': - expect(response.statusCode).to.eql(404); - expect(response.body).to.eql({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }); - break; - case 'global_read at space1': - case 'superuser at space1': - case 'space_1_all at space1': - expect(response.statusCode).to.eql(200); - expect(response.body).to.eql({ - page: 1, - perPage: 20, - total: 1, - data: [ - { - id: createdAction.id, - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - referencedByCount: 0, - }, - ], - }); - break; - default: - throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); - } - }); - - it('should handle find request appropriately with proper referencedByCount', async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - secrets: { - encrypted: 'This value should be encrypted', - }, - }) - .expect(200); - objectRemover.add(space.id, createdAction.id, 'action'); - - const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) - .set('kbn-xsrf', 'foo') - .send( - getTestAlertData({ - actions: [ - { - group: 'default', - id: createdAction.id, - params: {}, - }, - ], - }) - ) - .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert'); - - const response = await supertestWithoutAuth - .get( - `${getUrlPrefix( - space.id - )}/api/action/_find?filter=action.attributes.actionTypeId:test.index-record` - ) - .auth(user.username, user.password); - - switch (scenario.id) { - case 'no_kibana_privileges at space1': - case 'space_1_all at space2': - expect(response.statusCode).to.eql(404); - expect(response.body).to.eql({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }); - break; - case 'global_read at space1': - case 'superuser at space1': - case 'space_1_all at space1': - expect(response.statusCode).to.eql(200); - expect(response.body).to.eql({ - page: 1, - perPage: 20, - total: 1, - data: [ - { - id: createdAction.id, - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - referencedByCount: 1, - }, - ], - }); - break; - default: - throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); - } - }); - - it(`shouldn't find action from another space`, async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - secrets: { - encrypted: 'This value should be encrypted', - }, - }) - .expect(200); - objectRemover.add(space.id, createdAction.id, 'action'); - - const response = await supertestWithoutAuth - .get( - `${getUrlPrefix( - 'other' - )}/api/action/_find?search=test.index-record&search_fields=actionTypeId` - ) - .auth(user.username, user.password); - - switch (scenario.id) { - case 'no_kibana_privileges at space1': - case 'space_1_all at space2': - case 'space_1_all at space1': - expect(response.statusCode).to.eql(404); - expect(response.body).to.eql({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }); - break; - case 'global_read at space1': - case 'superuser at space1': - expect(response.statusCode).to.eql(200); - expect(response.body).to.eql({ - page: 1, - perPage: 20, - total: 0, - data: [], - }); - break; - default: - throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); - } - }); - }); - } - }); -} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts index fb731c91d73754..f088ea9d769ea4 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -60,6 +60,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql([ { id: createdAction.id, + isPreconfigured: false, name: 'My action', actionTypeId: 'test.index-record', config: { @@ -69,9 +70,9 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, { id: 'my-slack1', + isPreconfigured: true, actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -79,8 +80,8 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, { id: 'custom-system-abc-connector', + isPreconfigured: true, actionTypeId: 'system-abc-action-type', - description: 'Send a notification to system ABC', name: 'System ABC', config: { xyzConfig1: 'value1', @@ -158,6 +159,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql([ { id: createdAction.id, + isPreconfigured: false, name: 'My action', actionTypeId: 'test.index-record', config: { @@ -167,9 +169,9 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, { id: 'my-slack1', + isPreconfigured: true, actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -177,8 +179,8 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, { id: 'custom-system-abc-connector', + isPreconfigured: true, actionTypeId: 'system-abc-action-type', - description: 'Send a notification to system ABC', name: 'System ABC', config: { xyzConfig1: 'value1', @@ -232,9 +234,9 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql([ { id: 'my-slack1', + isPreconfigured: true, actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -242,8 +244,8 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, { id: 'custom-system-abc-connector', + isPreconfigured: true, actionTypeId: 'system-abc-action-type', - description: 'Send a notification to system ABC', name: 'System ABC', config: { xyzConfig1: 'value1', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts index 4f228c13d6c68f..d7ec2e78ccb308 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts @@ -19,7 +19,6 @@ export default function actionsTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./execute')); - loadTestFile(require.resolve('./find')); loadTestFile(require.resolve('./get_all')); loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./list_action_types')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts index efd707b59cd343..c70c289194abbd 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts @@ -37,6 +37,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { objectRemover.add(Spaces.space1.id, response.body.id, 'action'); expect(response.body).to.eql({ id: response.body.id, + isPreconfigured: false, name: 'My action', actionTypeId: 'test.index-record', config: { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/find.ts deleted file mode 100644 index acbc9edc1f2fba..00000000000000 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/find.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function findActionTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - describe('find', () => { - const objectRemover = new ObjectRemover(supertest); - - afterEach(() => objectRemover.removeAll()); - - it('should handle find action request appropriately', async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - secrets: { - encrypted: 'This value should be encrypted', - }, - }) - .expect(200); - objectRemover.add(Spaces.space1.id, createdAction.id, 'action'); - - await supertest - .get( - `${getUrlPrefix( - Spaces.space1.id - )}/api/action/_find?search=test.index-record&search_fields=actionTypeId` - ) - .expect(200, { - page: 1, - perPage: 20, - total: 1, - data: [ - { - id: createdAction.id, - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - referencedByCount: 0, - }, - ], - }); - }); - - it(`shouldn't find action from another space`, async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - secrets: { - encrypted: 'This value should be encrypted', - }, - }) - .expect(200); - objectRemover.add(Spaces.space1.id, createdAction.id, 'action'); - - await supertest - .get( - `${getUrlPrefix( - Spaces.other.id - )}/api/action/_find?search=test.index-record&search_fields=actionTypeId` - ) - .expect(200, { - page: 1, - perPage: 20, - total: 0, - data: [], - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts index 54a396e083355f..aaf06d7df53144 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts @@ -37,6 +37,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/action/_getAll`).expect(200, [ { id: createdAction.id, + isPreconfigured: false, name: 'My action', actionTypeId: 'test.index-record', config: { @@ -46,9 +47,9 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, { id: 'my-slack1', + isPreconfigured: true, actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -56,8 +57,8 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, { id: 'custom-system-abc-connector', + isPreconfigured: true, actionTypeId: 'system-abc-action-type', - description: 'Send a notification to system ABC', name: 'System ABC', config: { xyzConfig1: 'value1', @@ -89,9 +90,9 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { await supertest.get(`${getUrlPrefix(Spaces.other.id)}/api/action/_getAll`).expect(200, [ { id: 'my-slack1', + isPreconfigured: true, actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -99,8 +100,8 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, { id: 'custom-system-abc-connector', + isPreconfigured: true, actionTypeId: 'system-abc-action-type', - description: 'Send a notification to system ABC', name: 'System ABC', config: { xyzConfig1: 'value1', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts index d82cb6d2e11c92..75544b7fd41697 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts @@ -11,7 +11,6 @@ export default function actionsTests({ loadTestFile }: FtrProviderContext) { describe('Actions', () => { loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); - loadTestFile(require.resolve('./find')); loadTestFile(require.resolve('./get_all')); loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./list_action_types')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts index ddd9881b7837ed..06b77ece729ced 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts @@ -48,6 +48,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { }) .expect(200, { id: createdAction.id, + isPreconfigured: false, actionTypeId: 'test.index-record', name: 'My action updated', config: { From 8afc89cbf23ff0b4a6e6b92a082de5d1ae1d3c40 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Fri, 3 Apr 2020 14:41:40 -0700 Subject: [PATCH 04/18] Fixed tests and renamed connectors --- x-pack/plugins/actions/README.md | 2 +- .../actions/server/actions_client.mock.ts | 1 - .../actions/server/actions_client.test.ts | 13 +- .../plugins/actions/server/actions_client.ts | 56 ++++---- x-pack/plugins/actions/server/config.test.ts | 2 +- ...onfigured_action_disabled_modification.ts} | 8 +- x-pack/plugins/actions/server/plugin.ts | 17 +-- .../plugins/actions/server/routes/delete.ts | 13 +- .../api/cases/configure/get_connectors.ts | 8 +- .../lib/action_connector_api.test.ts | 19 +-- .../application/lib/action_connector_api.ts | 24 +--- .../action_connector_form/action_form.tsx | 2 +- .../actions_connectors_list.test.tsx | 125 +++++++----------- .../components/actions_connectors_list.tsx | 2 +- .../components/alerts_list.test.tsx | 28 +--- .../security_and_spaces/tests/actions/get.ts | 3 +- .../tests/actions/update.ts | 3 +- .../actions/builtin_action_types/es_index.ts | 4 + .../spaces_only/tests/actions/delete.ts | 6 +- .../spaces_only/tests/actions/get.ts | 3 +- .../tests/actions/type_not_enabled.ts | 2 + .../spaces_only/tests/actions/update.ts | 4 +- 22 files changed, 146 insertions(+), 199 deletions(-) rename x-pack/plugins/actions/server/lib/errors/{predefined_connector_disabled_modification.ts => preconfigured_action_disabled_modification.ts} (71%) diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 3daa36bf792c84..04dfe5fba31a28 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -180,7 +180,7 @@ No parameters. Return all actions from saved objects merged with predefined list. Use the [saved objects API for find](https://www.elastic.co/guide/en/kibana/master/saved-objects-api-find.html) with the proprties: `type: 'action'` and `perPage: 10000`. -List of predefined connectors should be set up in Kibana.yaml. +List of predefined actions should be set up in Kibana.yaml. ### `GET /api/action/{id}`: Get action diff --git a/x-pack/plugins/actions/server/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client.mock.ts index 9ee2811e5c98ea..431bfb1e99c3bd 100644 --- a/x-pack/plugins/actions/server/actions_client.mock.ts +++ b/x-pack/plugins/actions/server/actions_client.mock.ts @@ -12,7 +12,6 @@ const createActionsClientMock = () => { const mocked: jest.Mocked = { create: jest.fn(), get: jest.fn(), - find: jest.fn(), delete: jest.fn(), update: jest.fn(), getAll: jest.fn(), diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 50cebc1fdca9d3..955e1569380a5d 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -51,7 +51,7 @@ beforeEach(() => { savedObjectsClient, scopedClusterClient, defaultKibanaIndex, - preconfiguredConnectors: [], + preconfiguredActions: [], }); }); @@ -229,7 +229,7 @@ describe('create()', () => { savedObjectsClient, scopedClusterClient, defaultKibanaIndex, - preconfiguredConnectors: [], + preconfiguredActions: [], }); const savedObjectCreateResult = { @@ -320,13 +320,13 @@ describe('get()', () => { `); }); - test('return predefined connector with id', async () => { + test('return predefined action with id', async () => { actionsClient = new ActionsClient({ actionTypeRegistry, savedObjectsClient, scopedClusterClient, defaultKibanaIndex, - preconfiguredConnectors: [ + preconfiguredActions: [ { id: 'testPreconfigured', actionTypeId: '.slack', @@ -367,6 +367,7 @@ describe('getAll()', () => { id: '1', type: 'type', attributes: { + name: 'test', config: { foo: 'bar', }, @@ -388,7 +389,7 @@ describe('getAll()', () => { savedObjectsClient, scopedClusterClient, defaultKibanaIndex, - preconfiguredConnectors: [ + preconfiguredActions: [ { id: 'testPreconfigured', actionTypeId: '.slack', @@ -406,6 +407,7 @@ describe('getAll()', () => { { id: '1', isPreconfigured: false, + name: 'test', config: { foo: 'bar', }, @@ -414,7 +416,6 @@ describe('getAll()', () => { { id: 'testPreconfigured', actionTypeId: '.slack', - secrets: {}, isPreconfigured: true, name: 'test', config: { diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 46bfe90e38aad6..5b7a6d5bf9e7a7 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -15,7 +15,7 @@ import { i18n } from '@kbn/i18n'; import { ActionTypeRegistry } from './action_type_registry'; import { validateConfig, validateSecrets } from './lib'; import { ActionResult, FindActionResult, RawAction, PreConfiguredAction } from './types'; -import { PredefinedConnectorDisabledModificationError } from './lib/errors/predefined_connector_disabled_modification'; +import { PreconfiguredActionDisabledModificationError } from './lib/errors/preconfigured_action_disabled_modification'; // We are assuming there won't be many actions. This is why we will load // all the actions in advance and assume the total count to not go over 100 or so. @@ -41,7 +41,7 @@ interface ConstructorOptions { scopedClusterClient: IScopedClusterClient; actionTypeRegistry: ActionTypeRegistry; savedObjectsClient: SavedObjectsClientContract; - preconfiguredConnectors: PreConfiguredAction[]; + preconfiguredActions: PreConfiguredAction[]; } interface UpdateOptions { @@ -54,20 +54,20 @@ export class ActionsClient { private readonly scopedClusterClient: IScopedClusterClient; private readonly savedObjectsClient: SavedObjectsClientContract; private readonly actionTypeRegistry: ActionTypeRegistry; - private readonly preconfiguredConnectors: PreConfiguredAction[]; + private readonly preconfiguredActions: PreConfiguredAction[]; constructor({ actionTypeRegistry, defaultKibanaIndex, scopedClusterClient, savedObjectsClient, - preconfiguredConnectors, + preconfiguredActions, }: ConstructorOptions) { this.actionTypeRegistry = actionTypeRegistry; this.savedObjectsClient = savedObjectsClient; this.scopedClusterClient = scopedClusterClient; this.defaultKibanaIndex = defaultKibanaIndex; - this.preconfiguredConnectors = preconfiguredConnectors; + this.preconfiguredActions = preconfiguredActions; } /** @@ -101,10 +101,13 @@ export class ActionsClient { * Update action */ public async update({ id, action }: UpdateOptions): Promise { - if (this.preconfiguredConnectors.find(pConnector => pConnector.id === id) !== undefined) { - throw new PredefinedConnectorDisabledModificationError( - i18n.translate('xpack.actions.serverSideErrors.predefinedConnectorUpdatedisabled', { - defaultMessage: 'Preconfigured connector {id} is not allowed to update.', + if ( + this.preconfiguredActions.find(preconfiguredAction => preconfiguredAction.id === id) !== + undefined + ) { + throw new PreconfiguredActionDisabledModificationError( + i18n.translate('xpack.actions.serverSideErrors.predefinedActionUpdatedisabled', { + defaultMessage: 'Preconfigured action {id} is not allowed to update.', values: { id, }, @@ -141,15 +144,15 @@ export class ActionsClient { * Get an action */ public async get({ id }: { id: string }): Promise { - const preconfiguredConnector = this.preconfiguredConnectors.find( - pConnector => pConnector.id === id + const preconfiguredActionsList = this.preconfiguredActions.find( + preconfiguredAction => preconfiguredAction.id === id ); - if (preconfiguredConnector !== undefined) { + if (preconfiguredActionsList !== undefined) { return { id, - actionTypeId: preconfiguredConnector.actionTypeId, - name: preconfiguredConnector.name, - config: preconfiguredConnector.config, + actionTypeId: preconfiguredActionsList.actionTypeId, + name: preconfiguredActionsList.name, + config: preconfiguredActionsList.config, isPreconfigured: true, }; } @@ -165,7 +168,7 @@ export class ActionsClient { } /** - * Get all actions connectors with preconfigured connectors + * Get all actions with preconfigured list */ public async getAll(): Promise { const savedObjectsActions = ( @@ -177,11 +180,11 @@ export class ActionsClient { const mergedResult = [ ...savedObjectsActions, - ...this.preconfiguredConnectors.map(preconfiguredConnector => ({ - id: preconfiguredConnector.id, - actionTypeId: preconfiguredConnector.actionTypeId, - name: preconfiguredConnector.name, - config: preconfiguredConnector.config, + ...this.preconfiguredActions.map(preconfiguredAction => ({ + id: preconfiguredAction.id, + actionTypeId: preconfiguredAction.actionTypeId, + name: preconfiguredAction.name, + config: preconfiguredAction.config, isPreconfigured: true, })), ].sort((a, b) => a.name.localeCompare(b.name)); @@ -196,10 +199,13 @@ export class ActionsClient { * Delete action */ public async delete({ id }: { id: string }) { - if (this.preconfiguredConnectors.find(pConnector => pConnector.id === id) !== undefined) { - throw new PredefinedConnectorDisabledModificationError( - i18n.translate('xpack.actions.serverSideErrors.predefinedConnectorUpdatedisabled', { - defaultMessage: 'Preconfigured connector {id} is not allowed to delete.', + if ( + this.preconfiguredActions.find(preconfiguredAction => preconfiguredAction.id === id) !== + undefined + ) { + throw new PreconfiguredActionDisabledModificationError( + i18n.translate('xpack.actions.serverSideErrors.predefinedActionUpdatedisabled', { + defaultMessage: 'Preconfigured action {id} is not allowed to delete.', values: { id, }, diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index 7e65cc8d4c0762..51e87dbd75b48d 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -22,7 +22,7 @@ describe('config validation', () => { `); }); - test('action with preconfigured connectors', () => { + test('action with preconfigured actions', () => { const config: Record = { preconfigured: [ { diff --git a/x-pack/plugins/actions/server/lib/errors/predefined_connector_disabled_modification.ts b/x-pack/plugins/actions/server/lib/errors/preconfigured_action_disabled_modification.ts similarity index 71% rename from x-pack/plugins/actions/server/lib/errors/predefined_connector_disabled_modification.ts rename to x-pack/plugins/actions/server/lib/errors/preconfigured_action_disabled_modification.ts index 03d19c4c3b869f..884353e132b9c5 100644 --- a/x-pack/plugins/actions/server/lib/errors/predefined_connector_disabled_modification.ts +++ b/x-pack/plugins/actions/server/lib/errors/preconfigured_action_disabled_modification.ts @@ -7,13 +7,13 @@ import { KibanaResponseFactory } from '../../../../../../src/core/server'; import { ErrorThatHandlesItsOwnResponse } from './types'; -export type PredefinedConnectorDisabledFrom = 'update' | 'delete'; +export type PreconfiguredActionDisabledFrom = 'update' | 'delete'; -export class PredefinedConnectorDisabledModificationError extends Error +export class PreconfiguredActionDisabledModificationError extends Error implements ErrorThatHandlesItsOwnResponse { - public readonly disabledFrom: PredefinedConnectorDisabledFrom; + public readonly disabledFrom: PreconfiguredActionDisabledFrom; - constructor(message: string, disabledFrom: PredefinedConnectorDisabledFrom) { + constructor(message: string, disabledFrom: PreconfiguredActionDisabledFrom) { super(message); this.disabledFrom = disabledFrom; } diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 95425209125f0c..bbbd02fd30ea0e 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -97,7 +97,7 @@ export class ActionsPlugin implements Plugin, Plugi private eventLogger?: IEventLogger; private isESOUsingEphemeralEncryptionKey?: boolean; private readonly telemetryLogger: Logger; - private readonly preconfiguredConnectors: PreConfiguredAction[]; + private readonly preconfiguredActions: PreConfiguredAction[]; constructor(initContext: PluginInitializerContext) { this.config = initContext.config @@ -114,7 +114,7 @@ export class ActionsPlugin implements Plugin, Plugi this.logger = initContext.logger.get('actions'); this.telemetryLogger = initContext.logger.get('telemetry'); - this.preconfiguredConnectors = []; + this.preconfiguredActions = []; } public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise { @@ -156,9 +156,10 @@ export class ActionsPlugin implements Plugin, Plugi const actionsConfig = (await this.config) as ActionsConfig; const actionsConfigUtils = getActionsConfigurationUtilities(actionsConfig); - this.preconfiguredConnectors.push( + this.preconfiguredActions.push( ...actionsConfig.preconfigured.map( - connector => ({ ...connector, isPreconfigured: true } as PreConfiguredAction) + preconfiguredAction => + ({ ...preconfiguredAction, isPreconfigured: true } as PreConfiguredAction) ) ); const actionTypeRegistry = new ActionTypeRegistry({ @@ -233,7 +234,7 @@ export class ActionsPlugin implements Plugin, Plugi kibanaIndex, adminClient, isESOUsingEphemeralEncryptionKey, - preconfiguredConnectors, + preconfiguredActions, } = this; actionExecutor!.initialize({ @@ -279,7 +280,7 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex: await kibanaIndex, scopedClusterClient: adminClient!.asScoped(request), - preconfiguredConnectors, + preconfiguredActions, }); }, }; @@ -302,7 +303,7 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry, adminClient, isESOUsingEphemeralEncryptionKey, - preconfiguredConnectors, + preconfiguredActions, } = this; return async function actionsRouteHandlerContext(context, request) { return { @@ -317,7 +318,7 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex, scopedClusterClient: adminClient!.asScoped(request), - preconfiguredConnectors, + preconfiguredActions, }); }, listTypes: actionTypeRegistry!.list.bind(actionTypeRegistry!), diff --git a/x-pack/plugins/actions/server/routes/delete.ts b/x-pack/plugins/actions/server/routes/delete.ts index cddebb3a8e31e3..ffd1f0faabbabc 100644 --- a/x-pack/plugins/actions/server/routes/delete.ts +++ b/x-pack/plugins/actions/server/routes/delete.ts @@ -17,7 +17,7 @@ import { IKibanaResponse, KibanaResponseFactory, } from 'kibana/server'; -import { ILicenseState, verifyApiAccess } from '../lib'; +import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; const paramSchema = schema.object({ @@ -46,8 +46,15 @@ export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState) } const actionsClient = context.actions.getActionsClient(); const { id } = req.params; - await actionsClient.delete({ id }); - return res.noContent(); + try { + await actionsClient.delete({ id }); + return res.noContent(); + } catch (e) { + if (isErrorThatHandlesItsOwnResponse(e)) { + return e.sendResponse(res); + } + throw e; + } }) ); }; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts index b7d4977d16b178..96d0d7e318b311 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts @@ -28,11 +28,9 @@ export function initCaseConfigureGetActionConnector({ caseService, router }: Rou throw Boom.notFound('Action client have not been found'); } - const results = await actionsClient.find({ - options: { - filter: `action.attributes.actionTypeId: ${CASE_SERVICE_NOW_ACTION}`, - }, - }); + const results = (await actionsClient.getAll()).filter( + action => action.actionTypeId === CASE_SERVICE_NOW_ACTION + ); return response.ok({ body: { ...results } }); } catch (error) { return response.customError(wrapError(error)); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts index 5d3c3111d4868b..ebcdd2d788f121 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts @@ -43,27 +43,14 @@ describe('loadActionTypes', () => { }); describe('loadAllActions', () => { - test('should call find actions API', async () => { - const resolvedValue = { - page: 1, - perPage: 10000, - total: 0, - data: [], - }; - http.get.mockResolvedValueOnce(resolvedValue); + test('should call getAll actions API', async () => { + http.get.mockResolvedValueOnce([]); const result = await loadAllActions({ http }); - expect(result).toEqual(resolvedValue); + expect(result).toEqual([]); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ "/api/action/_getAll", - Object { - "query": Object { - "per_page": 10000, - "sort_field": "name.keyword", - "sort_order": "asc", - }, - }, ] `); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts index 26ad97f05849d4..e82d268accdd82 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts @@ -8,32 +8,12 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ACTION_API_PATH } from '../constants'; import { ActionConnector, ActionConnectorWithoutId, ActionType } from '../../types'; -// We are assuming there won't be many actions. This is why we will load -// all the actions in advance and assume the total count to not go over 100 or so. -// We'll set this max setting assuming it's never reached. -const MAX_ACTIONS_RETURNED = 10000; - export async function loadActionTypes({ http }: { http: HttpSetup }): Promise { return await http.get(`${BASE_ACTION_API_PATH}/types`); } -export async function loadAllActions({ - http, -}: { - http: HttpSetup; -}): Promise<{ - page: number; - perPage: number; - total: number; - data: ActionConnector[]; -}> { - return await http.get(`${BASE_ACTION_API_PATH}/_find`, { - query: { - per_page: MAX_ACTIONS_RETURNED, - sort_field: 'name.keyword', - sort_order: 'asc', - }, - }); +export async function loadAllActions({ http }: { http: HttpSetup }): Promise { + return await http.get(`${BASE_ACTION_API_PATH}/_getAll`); } export async function createActionConnector({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 3ade4e6368f96e..6b011ac84bc6f1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -129,7 +129,7 @@ export const ActionForm = ({ async function loadConnectors() { try { const actionsResponse = await loadAllActions({ http }); - setConnectors(actionsResponse.data); + setConnectors(actionsResponse); } catch (e) { toastNotifications.addDanger({ title: i18n.translate( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 9331fe17046949..4fa1e7e4c6e4df 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -29,12 +29,7 @@ describe('actions_connectors_list component empty', () => { const { loadAllActions, loadActionTypes } = jest.requireMock( '../../../lib/action_connector_api' ); - loadAllActions.mockResolvedValueOnce({ - page: 1, - perPage: 10000, - total: 0, - data: [], - }); + loadAllActions.mockResolvedValueOnce([]); loadActionTypes.mockResolvedValueOnce([ { id: 'test', @@ -111,27 +106,22 @@ describe('actions_connectors_list component with items', () => { const { loadAllActions, loadActionTypes } = jest.requireMock( '../../../lib/action_connector_api' ); - loadAllActions.mockResolvedValueOnce({ - page: 1, - perPage: 10000, - total: 2, - data: [ - { - id: '1', - actionTypeId: 'test', - description: 'My test', - referencedByCount: 1, - config: {}, - }, - { - id: '2', - actionTypeId: 'test2', - description: 'My test 2', - referencedByCount: 1, - config: {}, - }, - ], - }); + loadAllActions.mockResolvedValueOnce([ + { + id: '1', + actionTypeId: 'test', + description: 'My test', + referencedByCount: 1, + config: {}, + }, + { + id: '2', + actionTypeId: 'test2', + description: 'My test 2', + referencedByCount: 1, + config: {}, + }, + ]); loadActionTypes.mockResolvedValueOnce([ { id: 'test', @@ -214,12 +204,7 @@ describe('actions_connectors_list component empty with show only capability', () const { loadAllActions, loadActionTypes } = jest.requireMock( '../../../lib/action_connector_api' ); - loadAllActions.mockResolvedValueOnce({ - page: 1, - perPage: 10000, - total: 0, - data: [], - }); + loadAllActions.mockResolvedValueOnce([]); loadActionTypes.mockResolvedValueOnce([ { id: 'test', @@ -289,27 +274,22 @@ describe('actions_connectors_list with show only capability', () => { const { loadAllActions, loadActionTypes } = jest.requireMock( '../../../lib/action_connector_api' ); - loadAllActions.mockResolvedValueOnce({ - page: 1, - perPage: 10000, - total: 2, - data: [ - { - id: '1', - actionTypeId: 'test', - description: 'My test', - referencedByCount: 1, - config: {}, - }, - { - id: '2', - actionTypeId: 'test2', - description: 'My test 2', - referencedByCount: 1, - config: {}, - }, - ], - }); + loadAllActions.mockResolvedValueOnce([ + { + id: '1', + actionTypeId: 'test', + description: 'My test', + referencedByCount: 1, + config: {}, + }, + { + id: '2', + actionTypeId: 'test2', + description: 'My test 2', + referencedByCount: 1, + config: {}, + }, + ]); loadActionTypes.mockResolvedValueOnce([ { id: 'test', @@ -384,27 +364,22 @@ describe('actions_connectors_list component with disabled items', () => { const { loadAllActions, loadActionTypes } = jest.requireMock( '../../../lib/action_connector_api' ); - loadAllActions.mockResolvedValueOnce({ - page: 1, - perPage: 10000, - total: 2, - data: [ - { - id: '1', - actionTypeId: 'test', - description: 'My test', - referencedByCount: 1, - config: {}, - }, - { - id: '2', - actionTypeId: 'test2', - description: 'My test 2', - referencedByCount: 1, - config: {}, - }, - ], - }); + loadAllActions.mockResolvedValueOnce([ + { + id: '1', + actionTypeId: 'test', + description: 'My test', + referencedByCount: 1, + config: {}, + }, + { + id: '2', + actionTypeId: 'test2', + description: 'My test 2', + referencedByCount: 1, + config: {}, + }, + ]); loadActionTypes.mockResolvedValueOnce([ { id: 'test', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index fc07171347e5e5..81693e1d2d9d14 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -110,7 +110,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { setIsLoadingActions(true); try { const actionsResponse = await loadAllActions({ http }); - setActions(actionsResponse.data); + setActions(actionsResponse); } catch (e) { toastNotifications.addDanger({ title: i18n.translate( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 108cc724aa4074..66aa02e1930a3e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -72,12 +72,7 @@ describe('alerts_list component empty', () => { }, ]); loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]); - loadAllActions.mockResolvedValue({ - page: 1, - perPage: 10000, - total: 0, - data: [], - }); + loadAllActions.mockResolvedValue([]); const mockes = coreMock.createSetup(); const [ @@ -196,12 +191,7 @@ describe('alerts_list component with items', () => { }, ]); loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]); - loadAllActions.mockResolvedValue({ - page: 1, - perPage: 10000, - total: 0, - data: [], - }); + loadAllActions.mockResolvedValue([]); const mockes = coreMock.createSetup(); const [ { @@ -286,12 +276,7 @@ describe('alerts_list component empty with show only capability', () => { }, ]); loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]); - loadAllActions.mockResolvedValue({ - page: 1, - perPage: 10000, - total: 0, - data: [], - }); + loadAllActions.mockResolvedValue([]); const mockes = coreMock.createSetup(); const [ { @@ -405,12 +390,7 @@ describe('alerts_list with show only capability', () => { }, ]); loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]); - loadAllActions.mockResolvedValue({ - page: 1, - perPage: 10000, - total: 0, - data: [], - }); + loadAllActions.mockResolvedValue([]); const mockes = coreMock.createSetup(); const [ { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts index 45b9b803556bd1..bad3f271750b52 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts @@ -59,6 +59,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(200); expect(response.body).to.eql({ id: createdAction.id, + isPreconfigured: false, actionTypeId: 'test.index-record', name: 'My action', config: { @@ -132,7 +133,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { id: 'my-slack1', actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', + isPreconfigured: true, config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts index 3d1067b6f6c813..868dcfbbc4d42c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts @@ -69,6 +69,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(200); expect(response.body).to.eql({ id: createdAction.id, + isPreconfigured: false, actionTypeId: 'test.index-record', name: 'My action updated', config: { @@ -332,7 +333,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ statusCode: 400, error: 'Bad Request', - message: `Preconfigured connector custom-system-abc-connector is not allowed to update.`, + message: `Preconfigured action custom-system-abc-connector is not allowed to update.`, }); break; default: diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts index 3713e9c24419f7..874d42ac04736a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts @@ -38,6 +38,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, + isPreconfigured: false, name: 'An index action', actionTypeId: '.index', config: { @@ -55,6 +56,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, + isPreconfigured: false, name: 'An index action', actionTypeId: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: false, executionTimeField: null }, @@ -77,6 +79,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdActionWithIndex).to.eql({ id: createdActionWithIndex.id, + isPreconfigured: false, name: 'An index action with index config', actionTypeId: '.index', config: { @@ -94,6 +97,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(fetchedActionWithIndex).to.eql({ id: fetchedActionWithIndex.id, + isPreconfigured: false, name: 'An index action with index config', actionTypeId: '.index', config: { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/delete.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/delete.ts index 80f0a45a2cfbda..26a811d2cc512e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/delete.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/delete.ts @@ -81,7 +81,11 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { await supertest .delete(`${getUrlPrefix(Spaces.space1.id)}/api/action/my-slack1`) .set('kbn-xsrf', 'foo') - .expect(400, 'Preconfigured connector my-slack1 is not allowed to delete.'); + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: `Preconfigured action my-slack1 is not allowed to delete.`, + }); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts index 1338ce24b02356..421e82fe99631a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts @@ -38,6 +38,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { .get(`${getUrlPrefix(Spaces.space1.id)}/api/action/${createdAction.id}`) .expect(200, { id: createdAction.id, + isPreconfigured: false, actionTypeId: 'test.index-record', name: 'My action', config: { @@ -75,9 +76,9 @@ export default function getActionTests({ getService }: FtrProviderContext) { it('should handle get action request from preconfigured list', async () => { await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/action/my-slack1`).expect(200, { id: 'my-slack1', + isPreconfigured: true, actionTypeId: '.slack', name: 'Slack #xyz', - description: 'Send a message to the #xyz channel', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts index 18a0ecc23c1e14..2593f342a8a862 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts @@ -63,6 +63,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) actionTypeId: 'test.not-enabled', config: {}, id: 'uuid-actionId', + isPreconfigured: false, name: 'an action created before test.not-enabled was disabled', }); }); @@ -89,6 +90,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) actionTypeId: 'test.not-enabled', config: {}, id: 'uuid-actionId', + isPreconfigured: false, name: 'an action created before test.not-enabled was disabled', }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts index 06b77ece729ced..05d26aaaed2ec3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts @@ -103,7 +103,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it(`shouldn't update action from preconfigured list`, async () => { await supertest - .put(`${getUrlPrefix(Spaces.space1.id)}/api/action/my-slack1`) + .put(`${getUrlPrefix(Spaces.space1.id)}/api/action/custom-system-abc-connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action updated', @@ -117,7 +117,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { .expect(400, { statusCode: 400, error: 'Bad Request', - message: `Preconfigured connector custom-system-abc-connector is not allowed to update.`, + message: `Preconfigured action custom-system-abc-connector is not allowed to update.`, }); }); }); From 50c55ff910db5be6efafd34b37123be89dfebe6f Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 6 Apr 2020 07:33:50 -0700 Subject: [PATCH 05/18] fixed jest tests --- .../actions/builtin_action_types/email.ts | 2 ++ .../actions/builtin_action_types/es_index.ts | 4 ++++ .../actions/builtin_action_types/pagerduty.ts | 2 ++ .../builtin_action_types/server_log.ts | 2 ++ .../builtin_action_types/servicenow.ts | 2 ++ .../actions/builtin_action_types/slack.ts | 2 ++ .../actions/builtin_action_types/webhook.ts | 2 ++ .../tests/actions/delete.ts | 12 +++++++--- .../security_and_spaces/tests/actions/get.ts | 7 ++++++ .../tests/actions/update.ts | 11 +++++++-- .../common/config.ts | 24 +++++++++++++++++++ 11 files changed, 65 insertions(+), 5 deletions(-) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts index e228f6c1f81c6f..6001dd531cfae8 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts @@ -39,6 +39,7 @@ export default function emailTest({ getService }: FtrProviderContext) { createdActionId = createdAction.id; expect(createdAction).to.eql({ id: createdActionId, + isPreconfigured: false, name: 'An email action', actionTypeId: '.email', config: { @@ -58,6 +59,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, + isPreconfigured: false, name: 'An email action', actionTypeId: '.email', config: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts index 01eaf92da33fe5..612eba858ea0b0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts @@ -40,6 +40,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, + isPreconfigured: false, name: 'An index action', actionTypeId: '.index', config: { @@ -57,6 +58,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, + isPreconfigured: false, name: 'An index action', actionTypeId: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: false, executionTimeField: null }, @@ -79,6 +81,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdActionWithIndex).to.eql({ id: createdActionWithIndex.id, + isPreconfigured: false, name: 'An index action with index config', actionTypeId: '.index', config: { @@ -96,6 +99,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(fetchedActionWithIndex).to.eql({ id: fetchedActionWithIndex.id, + isPreconfigured: false, name: 'An index action with index config', actionTypeId: '.index', config: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts index cfc04663c6a4f3..eeb0818b5fbabd 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts @@ -50,6 +50,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, + isPreconfigured: false, name: 'A pagerduty action', actionTypeId: '.pagerduty', config: { @@ -65,6 +66,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, + isPreconfigured: false, name: 'A pagerduty action', actionTypeId: '.pagerduty', config: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/server_log.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/server_log.ts index f4ea568cf08c34..e9d3e6c542442b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/server_log.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/server_log.ts @@ -31,6 +31,7 @@ export default function serverLogTest({ getService }: FtrProviderContext) { serverLogActionId = createdAction.id; expect(createdAction).to.eql({ id: createdAction.id, + isPreconfigured: false, name: 'A server.log action', actionTypeId: '.server-log', config: {}, @@ -44,6 +45,7 @@ export default function serverLogTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, + isPreconfigured: false, name: 'A server.log action', actionTypeId: '.server-log', config: {}, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts index 48f348e1b834d8..054f8f6141817e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts @@ -101,6 +101,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, + isPreconfigured: false, name: 'A servicenow action', actionTypeId: '.servicenow', config: { @@ -117,6 +118,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, + isPreconfigured: false, name: 'A servicenow action', actionTypeId: '.servicenow', config: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts index 8afa43bfea21e6..e00589b7e85b7b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts @@ -47,6 +47,7 @@ export default function slackTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, + isPreconfigured: false, name: 'A slack action', actionTypeId: '.slack', config: {}, @@ -60,6 +61,7 @@ export default function slackTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, + isPreconfigured: false, name: 'A slack action', actionTypeId: '.slack', config: {}, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts index da83dbf8c47e26..fd996ea4507bab 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts @@ -92,6 +92,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, + isPreconfigured: false, name: 'A generic Webhook action', actionTypeId: '.webhook', config: { @@ -108,6 +109,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, + isPreconfigured: false, name: 'A generic Webhook action', actionTypeId: '.webhook', config: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/delete.ts index b3b6a548f0dc73..011e47cf11b394 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/delete.ts @@ -144,17 +144,23 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { .auth(user.username, user.password) .set('kbn-xsrf', 'foo'); - expect(response.statusCode).to.eql(400); switch (scenario.id) { case 'no_kibana_privileges at space1': case 'global_read at space1': case 'space_1_all at space2': - case 'space_1_all at space1': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; case 'superuser at space1': + case 'space_1_all at space1': expect(response.body).to.eql({ statusCode: 400, error: 'Bad Request', - message: 'Preconfigured connector my-slack1 is not allowed to delete.', + message: 'Preconfigured action my-slack1 is not allowed to delete.', }); break; default: diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts index bad3f271750b52..5dc7dc23204b4e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts @@ -125,6 +125,13 @@ export default function getActionTests({ getService }: FtrProviderContext) { switch (scenario.id) { case 'no_kibana_privileges at space1': case 'space_1_all at space2': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; case 'global_read at space1': case 'superuser at space1': case 'space_1_all at space1': diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts index 868dcfbbc4d42c..6cafbeb8c6ea8b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts @@ -323,13 +323,20 @@ export default function updateActionTests({ getService }: FtrProviderContext) { encrypted: 'This value should be encrypted', }, }); - expect(response.statusCode).to.eql(404); + switch (scenario.id) { case 'no_kibana_privileges at space1': case 'space_1_all at space2': case 'global_read at space1': - case 'space_1_all at space1': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; case 'superuser at space1': + case 'space_1_all at space1': expect(response.body).to.eql({ statusCode: 400, error: 'Bad Request', diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts index 89ebd902834b95..e7e4e75e5c5f43 100644 --- a/x-pack/test/detection_engine_api_integration/common/config.ts +++ b/x-pack/test/detection_engine_api_integration/common/config.ts @@ -78,6 +78,30 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) 'some.non.existent.com', ])}`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, + `--xpack.actions.preconfigured=${JSON.stringify([ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + secrets: { + xyzSecret1: 'credential1', + xyzSecret2: 'credential2', + }, + }, + ])}`, '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), From 74336238e6c96eb3ced4db599a21ed2e780792ee Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 6 Apr 2020 11:11:20 -0700 Subject: [PATCH 06/18] Fixed type checks --- .../alerts/configuration/step1.test.tsx | 2 ++ .../public/containers/case/configure/api.ts | 4 +-- .../case/configure/use_connectors.tsx | 2 +- x-pack/plugins/actions/common/types.ts | 1 + x-pack/plugins/actions/server/index.ts | 8 +++++- x-pack/plugins/actions/server/plugin.ts | 2 ++ .../plugins/alerting/server/alerts_client.ts | 26 +++++++++++++++++-- .../alerting/server/alerts_client_factory.ts | 5 ++++ x-pack/plugins/alerting/server/plugin.ts | 1 + .../case/common/api/cases/configure.ts | 7 ----- .../api/cases/configure/get_connectors.ts | 2 +- .../builtin_action_types/email.test.tsx | 4 +++ .../builtin_action_types/webhook.test.tsx | 2 ++ .../lib/action_connector_api.test.ts | 4 ++- .../connector_edit_flyout.test.tsx | 1 + .../connector_reducer.test.ts | 1 + .../triggers_actions_ui/public/types.ts | 1 + 17 files changed, 58 insertions(+), 15 deletions(-) diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx index 650294c29e9a50..19a1a61d00a424 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx @@ -27,6 +27,7 @@ describe('Step1', () => { actionTypeId: '1abc', name: 'Testing', config: {}, + isPreconfigured: false, }, ]; const selectedEmailActionId = emailActions[0].id; @@ -83,6 +84,7 @@ describe('Step1', () => { actionTypeId: '.email', name: '', config: {}, + isPreconfigured: false, }, ], selectedEmailActionId: NEW_ACTION_ID, diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts index ed47cdc62a1b67..c24081c777a968 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts @@ -6,7 +6,7 @@ import { isEmpty } from 'lodash/fp'; import { - CasesConnectorsFindResult, + Connector, CasesConfigurePatch, CasesConfigureResponse, CasesConfigureRequest, @@ -18,7 +18,7 @@ import { ApiProps } from '../types'; import { convertToCamelCase, decodeCaseConfigureResponse } from '../utils'; import { CaseConfigure } from './types'; -export const fetchConnectors = async ({ signal }: ApiProps): Promise => { +export const fetchConnectors = async ({ signal }: ApiProps): Promise => { const response = await KibanaServices.get().http.fetch( `${CASES_CONFIGURE_URL}/connectors/_find`, { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx index d31dcdbee2a146..30108ecf33874a 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx @@ -31,7 +31,7 @@ export const useConnectors = (): ReturnConnectors => { const res = await fetchConnectors({ signal: abortCtrl.signal }); if (!didCancel) { setLoading(false); - setConnectors(res.data); + setConnectors(res); } } catch (error) { if (!didCancel) { diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index f3042a701211f7..61b338d47b9f50 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -20,4 +20,5 @@ export interface ActionResult { actionTypeId: string; name: string; config: Record; + isPreconfigured: boolean; } diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index eee2ae352fe3d0..88553c314112f3 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -11,7 +11,13 @@ import { ActionsClient as ActionsClientClass } from './actions_client'; export type ActionsClient = PublicMethodsOf; -export { ActionsPlugin, ActionResult, ActionTypeExecutorOptions, ActionType } from './types'; +export { + ActionsPlugin, + ActionResult, + ActionTypeExecutorOptions, + ActionType, + PreConfiguredAction, +} from './types'; export { PluginSetupContract, PluginStartContract } from './plugin'; export const plugin = (initContext: PluginInitializerContext) => new ActionsPlugin(initContext); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index bbbd02fd30ea0e..34c9e7aa9e8b8f 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -67,6 +67,7 @@ export interface PluginStartContract { isActionTypeEnabled(id: string): boolean; execute(options: ExecuteOptions): Promise; getActionsClientWithRequest(request: KibanaRequest): Promise>; + preconfiguredActions: PreConfiguredAction[]; } export interface ActionsPluginsSetup { @@ -283,6 +284,7 @@ export class ActionsPlugin implements Plugin, Plugi preconfiguredActions, }); }, + preconfiguredActions, }; } diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index 5538b44b69fcbb..c3b897befab195 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -13,6 +13,7 @@ import { SavedObjectReference, SavedObject, } from 'src/core/server'; +import { PreConfiguredAction } from '../../actions/server'; import { Alert, PartialAlert, @@ -53,6 +54,7 @@ interface ConstructorOptions { getUserName: () => Promise; createAPIKey: () => Promise; invalidateAPIKey: (params: InvalidateAPIKeyParams) => Promise; + preconfiguredActions: PreConfiguredAction[]; } export interface FindOptions { @@ -123,6 +125,7 @@ export class AlertsClient { private readonly invalidateAPIKey: ( params: InvalidateAPIKeyParams ) => Promise; + private preconfiguredActions: PreConfiguredAction[]; encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; constructor({ @@ -136,6 +139,7 @@ export class AlertsClient { createAPIKey, invalidateAPIKey, encryptedSavedObjectsPlugin, + preconfiguredActions, }: ConstructorOptions) { this.logger = logger; this.getUserName = getUserName; @@ -147,6 +151,7 @@ export class AlertsClient { this.createAPIKey = createAPIKey; this.invalidateAPIKey = invalidateAPIKey; this.encryptedSavedObjectsPlugin = encryptedSavedObjectsPlugin; + this.preconfiguredActions = preconfiguredActions; } public async create({ data, options }: CreateOptions): Promise { @@ -659,11 +664,28 @@ export class AlertsClient { private async denormalizeActions( alertActions: NormalizedAlertAction[] ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { + const actionMap = new Map(); + // map preconfigured actions + for (const alertAction of alertActions) { + const action = this.preconfiguredActions.find( + preconfiguredAction => preconfiguredAction.id === alertAction.id + ); + if (action !== undefined) { + actionMap.set(action.id, action); + } + } // Fetch action objects in bulk - const actionIds = [...new Set(alertActions.map(alertAction => alertAction.id))]; + // Excluding preconfigured actions to avoid an not found error, which is already mapped + const actionIds = [ + ...new Set( + alertActions + .filter(alertAction => !actionMap.has(alertAction.id)) + .map(alertAction => alertAction.id) + ), + ]; const bulkGetOpts = actionIds.map(id => ({ id, type: 'action' })); const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts); - const actionMap = new Map(); + for (const action of bulkGetResult.saved_objects) { if (action.error) { throw Boom.badRequest( diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.ts b/x-pack/plugins/alerting/server/alerts_client_factory.ts index fd480658e236ac..734417e72733e3 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { PreConfiguredAction } from '../../actions/server'; import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { KibanaRequest, Logger, SavedObjectsClientContract } from '../../../../src/core/server'; @@ -19,6 +20,7 @@ export interface AlertsClientFactoryOpts { getSpaceId: (request: KibanaRequest) => string | undefined; spaceIdToNamespace: SpaceIdToNamespaceFunction; encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; + preconfiguredActions: PreConfiguredAction[]; } export class AlertsClientFactory { @@ -30,6 +32,7 @@ export class AlertsClientFactory { private getSpaceId!: (request: KibanaRequest) => string | undefined; private spaceIdToNamespace!: SpaceIdToNamespaceFunction; private encryptedSavedObjectsPlugin!: EncryptedSavedObjectsPluginStart; + private preconfiguredActions!: PreConfiguredAction[]; public initialize(options: AlertsClientFactoryOpts) { if (this.isInitialized) { @@ -43,6 +46,7 @@ export class AlertsClientFactory { this.securityPluginSetup = options.securityPluginSetup; this.spaceIdToNamespace = options.spaceIdToNamespace; this.encryptedSavedObjectsPlugin = options.encryptedSavedObjectsPlugin; + this.preconfiguredActions = options.preconfiguredActions; } public create( @@ -100,6 +104,7 @@ export class AlertsClientFactory { result: invalidateAPIKeyResult, }; }, + preconfiguredActions: this.preconfiguredActions, }); } } diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 90e274df3a5ee8..172a1062263453 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -218,6 +218,7 @@ export class AlertingPlugin { getSpaceId(request: KibanaRequest) { return spaces?.getSpaceId(request); }, + preconfiguredActions: plugins.actions.preconfiguredActions, }); taskRunnerFactory.initialize({ diff --git a/x-pack/plugins/case/common/api/cases/configure.ts b/x-pack/plugins/case/common/api/cases/configure.ts index 9b210c2aa05adc..d92af587d0e92b 100644 --- a/x-pack/plugins/case/common/api/cases/configure.ts +++ b/x-pack/plugins/case/common/api/cases/configure.ts @@ -61,13 +61,6 @@ export type CasesConnectorConfiguration = rt.TypeOf action.actionTypeId === CASE_SERVICE_NOW_ACTION ); - return response.ok({ body: { ...results } }); + return response.ok({ body: results }); } catch (error) { return response.customError(wrapError(error)); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.test.tsx index a7d479f922ed1b..af9e34071fd095 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.test.tsx @@ -39,6 +39,7 @@ describe('connector validation', () => { id: 'test', actionTypeId: '.email', name: 'email', + isPreconfigured: false, config: { from: 'test@test.com', port: 2323, @@ -66,6 +67,7 @@ describe('connector validation', () => { }, id: 'test', actionTypeId: '.email', + isPreconfigured: false, name: 'email', config: { from: 'test@test.com', @@ -117,6 +119,7 @@ describe('connector validation', () => { }, id: 'test', actionTypeId: '.email', + isPreconfigured: false, name: 'email', config: { from: 'test@test.com', @@ -144,6 +147,7 @@ describe('connector validation', () => { }, id: 'test', actionTypeId: '.email', + isPreconfigured: false, name: 'email', config: { from: 'test@test.com', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.test.tsx index fdb60bd2d91466..c4489a316d2c00 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.test.tsx @@ -39,6 +39,7 @@ describe('webhook connector validation', () => { id: 'test', actionTypeId: '.webhook', name: 'webhook', + isPreconfigured: false, config: { method: 'PUT', url: 'http:\\test', @@ -106,6 +107,7 @@ describe('WebhookActionConnectorFields renders', () => { }, id: 'test', actionTypeId: '.webhook', + isPreconfigured: false, name: 'webhook', config: { method: 'PUT', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts index ebcdd2d788f121..e9cf2a270d180d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts @@ -60,6 +60,7 @@ describe('createActionConnector', () => { test('should call create action API', async () => { const connector: ActionConnectorWithoutId = { actionTypeId: 'test', + isPreconfigured: false, name: 'My test', config: {}, secrets: {}, @@ -73,7 +74,7 @@ describe('createActionConnector', () => { Array [ "/api/action", Object { - "body": "{\\"actionTypeId\\":\\"test\\",\\"name\\":\\"My test\\",\\"config\\":{},\\"secrets\\":{}}", + "body": "{\\"actionTypeId\\":\\"test\\",\\"isPreconfigured\\":false,\\"name\\":\\"My test\\",\\"config\\":{},\\"secrets\\":{}}", }, ] `); @@ -85,6 +86,7 @@ describe('updateActionConnector', () => { const id = '123'; const connector: ActionConnectorWithoutId = { actionTypeId: 'test', + isPreconfigured: false, name: 'My test', config: {}, secrets: {}, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index f9aa2cad8bfc60..2c063ea6b4fa64 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -47,6 +47,7 @@ describe('connector_edit_flyout', () => { actionTypeId: 'test-action-type-id', actionType: 'test-action-type-name', name: 'action-connector', + isPreconfigured: false, referencedByCount: 0, config: {}, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts index df7e5d8fe9a78d..e469a501089128 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts @@ -15,6 +15,7 @@ describe('connector reducer', () => { actionTypeId: 'test-action-type-id', name: 'action-connector', referencedByCount: 0, + isPreconfigured: false, config: {}, }; }); diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 7dfaa7b918f705..31c77833cc0e87 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -66,6 +66,7 @@ export interface ActionConnector { name: string; referencedByCount?: number; config: Record; + isPreconfigured: boolean; } export type ActionConnectorWithoutId = Omit; From 104478a54ffd390317bad3eca1807a1aeaf0fa66 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 6 Apr 2020 11:29:14 -0700 Subject: [PATCH 07/18] Fixed failing alert save --- .../plugins/alerting/server/alerts_client.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index c3b897befab195..4d07e720fdfb50 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -683,16 +683,18 @@ export class AlertsClient { .map(alertAction => alertAction.id) ), ]; - const bulkGetOpts = actionIds.map(id => ({ id, type: 'action' })); - const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts); - - for (const action of bulkGetResult.saved_objects) { - if (action.error) { - throw Boom.badRequest( - `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}` - ); + if (actionIds.length > 0) { + const bulkGetOpts = actionIds.map(id => ({ id, type: 'action' })); + const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts); + + for (const action of bulkGetResult.saved_objects) { + if (action.error) { + throw Boom.badRequest( + `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}` + ); + } + actionMap.set(action.id, action); } - actionMap.set(action.id, action); } // Extract references and set actionTypeId const references: SavedObjectReference[] = []; @@ -703,10 +705,14 @@ export class AlertsClient { name: actionRef, type: 'action', }); + const actionMapValue = actionMap.get(id); + const actionTypeId = actionIds.find(actionId => actionId === id) + ? actionMapValue.attributes.actionTypeId + : actionMapValue.actionTypeId; return { ...alertAction, actionRef, - actionTypeId: actionMap.get(id).attributes.actionTypeId, + actionTypeId, }; }); return { From 8323688d4736ee617c2bf2ba570bc78b2d3fe26a Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 6 Apr 2020 11:35:40 -0700 Subject: [PATCH 08/18] Fixed alert client tests --- x-pack/plugins/alerting/server/alerts_client.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/alerting/server/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client.test.ts index 0e929ff457fbd2..a9ff5ee8ecdc62 100644 --- a/x-pack/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client.test.ts @@ -30,6 +30,7 @@ const alertsClientParams = { invalidateAPIKey: jest.fn(), logger: loggingServiceMock.create().get(), encryptedSavedObjectsPlugin: encryptedSavedObjects, + preconfiguredActions: [], }; beforeEach(() => { From 6d936acff1a0795baa9a13a5965567c159fd9d33 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 6 Apr 2020 11:57:50 -0700 Subject: [PATCH 09/18] fixed type checks --- x-pack/plugins/actions/server/mocks.ts | 1 + x-pack/plugins/alerting/server/alerts_client_factory.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index 75396f2aad897d..bc4268bb698723 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -21,6 +21,7 @@ const createStartMock = () => { execute: jest.fn(), isActionTypeEnabled: jest.fn(), getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClientMock.create()), + preconfiguredActions: [], }; return mock; }; diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts index 4c74ca54a0d2f4..19aed51c9fca3f 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts @@ -28,6 +28,7 @@ const alertsClientFactoryParams: jest.Mocked = { getSpaceId: jest.fn(), spaceIdToNamespace: jest.fn(), encryptedSavedObjectsPlugin: encryptedSavedObjectsMock.createStart(), + preconfiguredActions: [], }; const fakeRequest: Request = { headers: {}, From 5682ecb68d1956eef910dec332c55d2fe0abb3f3 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 6 Apr 2020 13:11:39 -0700 Subject: [PATCH 10/18] Fixed language check error --- .../plugins/actions/server/actions_client.ts | 4 +-- .../action_form.test.tsx | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 5b7a6d5bf9e7a7..58281aee832e95 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -106,7 +106,7 @@ export class ActionsClient { undefined ) { throw new PreconfiguredActionDisabledModificationError( - i18n.translate('xpack.actions.serverSideErrors.predefinedActionUpdatedisabled', { + i18n.translate('xpack.actions.serverSideErrors.predefinedActionUpdateDisabled', { defaultMessage: 'Preconfigured action {id} is not allowed to update.', values: { id, @@ -204,7 +204,7 @@ export class ActionsClient { undefined ) { throw new PreconfiguredActionDisabledModificationError( - i18n.translate('xpack.actions.serverSideErrors.predefinedActionUpdatedisabled', { + i18n.translate('xpack.actions.serverSideErrors.predefinedActionDeleteDisabled', { defaultMessage: 'Preconfigured action {id} is not allowed to delete.', values: { id, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 89d37c4d00a112..db83186d234f37 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -11,6 +11,10 @@ import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult, Alert, AlertAction } from '../../../types'; import { ActionForm } from './action_form'; +jest.mock('../../lib/action_connector_api', () => ({ + loadAllActions: jest.fn(), + loadActionTypes: jest.fn(), +})); const actionTypeRegistry = actionTypeRegistryMock.create(); describe('action_form', () => { let deps: any; @@ -73,6 +77,17 @@ describe('action_form', () => { let wrapper: ReactWrapper; async function setup() { + const { loadAllActions } = jest.requireMock('../../lib/action_connector_api'); + loadAllActions.mockResolvedValueOnce([ + { + secrets: {}, + id: 'test', + actionTypeId: actionType.id, + name: 'Test connector', + config: {}, + isPreconfigured: false, + }, + ]); const mockes = coreMock.createSetup(); deps = { toastNotifications: mockes.notifications.toasts, @@ -183,6 +198,20 @@ describe('action_form', () => { ).toBeFalsy(); }); + it('renders alert action form', async () => { + await setup(); + expect(wrapper).toMatchSnapshot(); + const actionOption = wrapper.find( + `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` + ); + expect(actionOption.exists()).toBeTruthy(); + expect( + wrapper + .find(`EuiToolTip [data-test-subj="${actionType.id}-ActionTypeSelectOption"]`) + .exists() + ).toBeFalsy(); + }); + it(`doesn't render action types disabled by config`, async () => { await setup(); const actionOption = wrapper.find( From 3d8b430fedd7d620cb549b430821fb5342c7b441 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 6 Apr 2020 15:05:59 -0700 Subject: [PATCH 11/18] Fixed jest tests --- .../__snapshots__/step1.test.tsx.snap | 1 + x-pack/plugins/actions/server/config.ts | 4 ++-- .../server/alerts_client_factory.test.ts | 1 + .../action_form.test.tsx | 14 ----------- .../common/config.ts | 24 ------------------- 5 files changed, 4 insertions(+), 40 deletions(-) diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap index 94d951a94fe29a..cb1081c0c14da6 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap @@ -25,6 +25,7 @@ exports[`Step1 editing should allow for editing 1`] = ` "actionTypeId": "1abc", "config": Object {}, "id": "1", + "isPreconfigured": false, "name": "Testing", } } diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index ffc6dbc64c3568..1f04efd1941b4b 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -23,9 +23,9 @@ export const configSchema = schema.object({ ), preconfigured: schema.arrayOf( schema.object({ - id: schema.string(), + id: schema.string({ minLength: 1 }), name: schema.string(), - actionTypeId: schema.string(), + actionTypeId: schema.string({ minLength: 1 }), config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), }), diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts index 19aed51c9fca3f..951d18a33b35f1 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts @@ -68,6 +68,7 @@ test('creates an alerts client with proper constructor arguments', async () => { createAPIKey: expect.any(Function), invalidateAPIKey: expect.any(Function), encryptedSavedObjectsPlugin: alertsClientFactoryParams.encryptedSavedObjectsPlugin, + preconfiguredActions: [], }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index db83186d234f37..41564146bb84d9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -198,20 +198,6 @@ describe('action_form', () => { ).toBeFalsy(); }); - it('renders alert action form', async () => { - await setup(); - expect(wrapper).toMatchSnapshot(); - const actionOption = wrapper.find( - `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` - ); - expect(actionOption.exists()).toBeTruthy(); - expect( - wrapper - .find(`EuiToolTip [data-test-subj="${actionType.id}-ActionTypeSelectOption"]`) - .exists() - ).toBeFalsy(); - }); - it(`doesn't render action types disabled by config`, async () => { await setup(); const actionOption = wrapper.find( diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts index e7e4e75e5c5f43..89ebd902834b95 100644 --- a/x-pack/test/detection_engine_api_integration/common/config.ts +++ b/x-pack/test/detection_engine_api_integration/common/config.ts @@ -78,30 +78,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) 'some.non.existent.com', ])}`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, - `--xpack.actions.preconfigured=${JSON.stringify([ - { - id: 'my-slack1', - actionTypeId: '.slack', - name: 'Slack #xyz', - config: { - webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', - }, - }, - { - id: 'custom-system-abc-connector', - actionTypeId: 'system-abc-action-type', - name: 'System ABC', - config: { - xyzConfig1: 'value1', - xyzConfig2: 'value2', - listOfThings: ['a', 'b', 'c', 'd'], - }, - secrets: { - xyzSecret1: 'credential1', - xyzSecret2: 'credential2', - }, - }, - ])}`, '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), From 3b783307ee5f6124a4113540e670f3ead18c49f4 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 6 Apr 2020 16:11:49 -0700 Subject: [PATCH 12/18] Added missing comments and docs --- x-pack/plugins/actions/README.md | 1 + x-pack/plugins/alerting/server/alerts_client.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 04dfe5fba31a28..82cc09f5e9eca6 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -92,6 +92,7 @@ Built-In-Actions are configured using the _xpack.actions_ namespoace under _kiba | _xpack.actions._**enabled** | Feature toggle which enabled Actions in Kibana. | boolean | | _xpack.actions._**whitelistedHosts** | Which _hostnames_ are whitelisted for the Built-In-Action? This list should contain hostnames of every external service you wish to interact with using Webhooks, Email or any other built in Action. Note that you may use the string "\*" in place of a specific hostname to enable Kibana to target any URL, but keep in mind the potential use of such a feature to execute [SSRF](https://www.owasp.org/index.php/Server_Side_Request_Forgery) attacks from your server. | Array | | _xpack.actions._**enabledActionTypes** | A list of _actionTypes_ id's that are enabled. A "\*" may be used as an element to indicate all registered actionTypes should be enabled. The actionTypes registered for Kibana are `.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, `.webhook`. Default: `["*"]` | Array | +| _xpack.actions._**preconfigured** | A list of preconfigured actions. Default: `[]` | Array | #### Whitelisting Built-in Action Types diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index 4d07e720fdfb50..6f8478df58a533 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -706,6 +706,8 @@ export class AlertsClient { type: 'action', }); const actionMapValue = actionMap.get(id); + // if action is a save object, than actionTypeId should be under attributes property + // if action is a preconfigured, than actionTypeId is the action property const actionTypeId = actionIds.find(actionId => actionId === id) ? actionMapValue.attributes.actionTypeId : actionMapValue.actionTypeId; From fc5699525f88772f05c4e900f0155180075881ee Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 7 Apr 2020 09:56:59 -0700 Subject: [PATCH 13/18] fixed due to comments --- x-pack/plugins/actions/server/actions_client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 58281aee832e95..8f73bfb31ea4de 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -18,7 +18,7 @@ import { ActionResult, FindActionResult, RawAction, PreConfiguredAction } from ' import { PreconfiguredActionDisabledModificationError } from './lib/errors/preconfigured_action_disabled_modification'; // We are assuming there won't be many actions. This is why we will load -// all the actions in advance and assume the total count to not go over 100 or so. +// all the actions in advance and assume the total count to not go over 10000. // We'll set this max setting assuming it's never reached. export const MAX_ACTIONS_RETURNED = 10000; From d47551160f36b39bf37a50609b5690b10f08e66b Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 7 Apr 2020 11:26:55 -0700 Subject: [PATCH 14/18] Fixed json config for preconfigured --- .../alerting_api_integration/common/config.ts | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 3307d89dbade03..ddf82b921f40e2 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -77,30 +77,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', - `--xpack.actions.preconfigured=${JSON.stringify([ - { - id: 'my-slack1', - actionTypeId: '.slack', - name: 'Slack #xyz', - config: { - webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', - }, - }, - { - id: 'custom-system-abc-connector', - actionTypeId: 'system-abc-action-type', - name: 'System ABC', - config: { - xyzConfig1: 'value1', - xyzConfig2: 'value2', - listOfThings: ['a', 'b', 'c', 'd'], - }, - secrets: { - xyzSecret1: 'credential1', - xyzSecret2: 'credential2', - }, - }, - ])}`, + `--xpack.actions.preconfigured=[{"id":"my-slack1","actionTypeId":".slack","name":"Slack #xyz","config":{"webhookUrl":"https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz"}},{"id":"custom-system-abc-connector","actionTypeId":"system-abc-action-type","name":"System ABC","config":{"xyzConfig1":"value1","xyzConfig2":"value2","listOfThings":["a","b","c","d"]},"secrets":{"xyzSecret1":"credential1","xyzSecret2":"credential2"}}]`, ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, From 5669b63b49be25a11d8094de4f12f9f4f16bdefe Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 7 Apr 2020 13:37:15 -0700 Subject: [PATCH 15/18] fixed type check, reverted config --- .../configure_cases/__mock__/index.tsx | 2 ++ .../alerting_api_integration/common/config.ts | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx index a3df3664398ad2..135f0f2a7e26d7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx @@ -21,6 +21,7 @@ export const connectors: Connector[] = [ id: '123', actionTypeId: '.servicenow', name: 'My Connector', + isPreconfigured: false, config: { apiUrl: 'https://instance1.service-now.com', casesConfiguration: { @@ -48,6 +49,7 @@ export const connectors: Connector[] = [ id: '456', actionTypeId: '.servicenow', name: 'My Connector 2', + isPreconfigured: false, config: { apiUrl: 'https://instance2.service-now.com', casesConfiguration: { diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index ddf82b921f40e2..3307d89dbade03 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -77,7 +77,30 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', - `--xpack.actions.preconfigured=[{"id":"my-slack1","actionTypeId":".slack","name":"Slack #xyz","config":{"webhookUrl":"https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz"}},{"id":"custom-system-abc-connector","actionTypeId":"system-abc-action-type","name":"System ABC","config":{"xyzConfig1":"value1","xyzConfig2":"value2","listOfThings":["a","b","c","d"]},"secrets":{"xyzSecret1":"credential1","xyzSecret2":"credential2"}}]`, + `--xpack.actions.preconfigured=${JSON.stringify([ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + secrets: { + xyzSecret1: 'credential1', + xyzSecret2: 'credential2', + }, + }, + ])}`, ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, From 3320dad52abbcfbf2e11b3d7bc2b24241e98afa3 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 7 Apr 2020 14:15:08 -0700 Subject: [PATCH 16/18] config experiment with json stringify --- .../alerting_api_integration/common/config.ts | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 3307d89dbade03..cff58871410634 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -77,30 +77,34 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', - `--xpack.actions.preconfigured=${JSON.stringify([ - { - id: 'my-slack1', - actionTypeId: '.slack', - name: 'Slack #xyz', - config: { - webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + `--xpack.actions.preconfigured=${JSON.stringify( + [ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, }, - }, - { - id: 'custom-system-abc-connector', - actionTypeId: 'system-abc-action-type', - name: 'System ABC', - config: { - xyzConfig1: 'value1', - xyzConfig2: 'value2', - listOfThings: ['a', 'b', 'c', 'd'], + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + secrets: { + xyzSecret1: 'credential1', + xyzSecret2: 'credential2', + }, }, - secrets: { - xyzSecret1: 'credential1', - xyzSecret2: 'credential2', - }, - }, - ])}`, + ], + null, + 2 + )}`, ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, From 1b7ece61e3a99334146a9f40c4f9c63ee498788b Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 7 Apr 2020 15:03:05 -0700 Subject: [PATCH 17/18] revert experiment --- .../alerting_api_integration/common/config.ts | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index cff58871410634..3307d89dbade03 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -77,34 +77,30 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', - `--xpack.actions.preconfigured=${JSON.stringify( - [ - { - id: 'my-slack1', - actionTypeId: '.slack', - name: 'Slack #xyz', - config: { - webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', - }, + `--xpack.actions.preconfigured=${JSON.stringify([ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, - { - id: 'custom-system-abc-connector', - actionTypeId: 'system-abc-action-type', - name: 'System ABC', - config: { - xyzConfig1: 'value1', - xyzConfig2: 'value2', - listOfThings: ['a', 'b', 'c', 'd'], - }, - secrets: { - xyzSecret1: 'credential1', - xyzSecret2: 'credential2', - }, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], }, - ], - null, - 2 - )}`, + secrets: { + xyzSecret1: 'credential1', + xyzSecret2: 'credential2', + }, + }, + ])}`, ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, From 3a45ea4fc6d150b7496c6979b78aa6ca5962134e Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Wed, 8 Apr 2020 08:01:13 -0700 Subject: [PATCH 18/18] Removed the spaces from connector names in config --- .../test/alerting_api_integration/common/config.ts | 4 ++-- .../security_and_spaces/tests/actions/get.ts | 2 +- .../security_and_spaces/tests/actions/get_all.ts | 12 ++++++------ .../spaces_only/tests/actions/get.ts | 2 +- .../spaces_only/tests/actions/get_all.ts | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 3307d89dbade03..4d32a5ae9f53c4 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -81,7 +81,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) { id: 'my-slack1', actionTypeId: '.slack', - name: 'Slack #xyz', + name: 'Slack#xyz', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -89,7 +89,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) { id: 'custom-system-abc-connector', actionTypeId: 'system-abc-action-type', - name: 'System ABC', + name: 'SystemABC', config: { xyzConfig1: 'value1', xyzConfig2: 'value2', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts index 5dc7dc23204b4e..c84b089d48c85e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts @@ -139,7 +139,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ id: 'my-slack1', actionTypeId: '.slack', - name: 'Slack #xyz', + name: 'Slack#xyz', isPreconfigured: true, config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts index f088ea9d769ea4..80b512f3fb5e3f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -72,7 +72,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'my-slack1', isPreconfigured: true, actionTypeId: '.slack', - name: 'Slack #xyz', + name: 'Slack#xyz', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -82,7 +82,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'custom-system-abc-connector', isPreconfigured: true, actionTypeId: 'system-abc-action-type', - name: 'System ABC', + name: 'SystemABC', config: { xyzConfig1: 'value1', xyzConfig2: 'value2', @@ -171,7 +171,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'my-slack1', isPreconfigured: true, actionTypeId: '.slack', - name: 'Slack #xyz', + name: 'Slack#xyz', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -181,7 +181,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'custom-system-abc-connector', isPreconfigured: true, actionTypeId: 'system-abc-action-type', - name: 'System ABC', + name: 'SystemABC', config: { xyzConfig1: 'value1', xyzConfig2: 'value2', @@ -236,7 +236,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'my-slack1', isPreconfigured: true, actionTypeId: '.slack', - name: 'Slack #xyz', + name: 'Slack#xyz', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -246,7 +246,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'custom-system-abc-connector', isPreconfigured: true, actionTypeId: 'system-abc-action-type', - name: 'System ABC', + name: 'SystemABC', config: { xyzConfig1: 'value1', xyzConfig2: 'value2', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts index 421e82fe99631a..a4a13441fb7668 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts @@ -78,7 +78,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { id: 'my-slack1', isPreconfigured: true, actionTypeId: '.slack', - name: 'Slack #xyz', + name: 'Slack#xyz', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts index aaf06d7df53144..517c64f178af5b 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts @@ -49,7 +49,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'my-slack1', isPreconfigured: true, actionTypeId: '.slack', - name: 'Slack #xyz', + name: 'Slack#xyz', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -59,7 +59,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'custom-system-abc-connector', isPreconfigured: true, actionTypeId: 'system-abc-action-type', - name: 'System ABC', + name: 'SystemABC', config: { xyzConfig1: 'value1', xyzConfig2: 'value2', @@ -92,7 +92,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'my-slack1', isPreconfigured: true, actionTypeId: '.slack', - name: 'Slack #xyz', + name: 'Slack#xyz', config: { webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, @@ -102,7 +102,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { id: 'custom-system-abc-connector', isPreconfigured: true, actionTypeId: 'system-abc-action-type', - name: 'System ABC', + name: 'SystemABC', config: { xyzConfig1: 'value1', xyzConfig2: 'value2',