diff --git a/api_docs/actions.json b/api_docs/actions.json index 1cc9b98324960..1b1d7f240877f 100644 --- a/api_docs/actions.json +++ b/api_docs/actions.json @@ -647,7 +647,9 @@ "section": "def-server.ActionResult", "text": "ActionResult" }, - ">>; delete: ({ id }: { id: string; }) => Promise<{}>; create: ({ action: { actionTypeId, name, config, secrets }, }: CreateOptions) => Promise<", + ">>; delete: ({ id }: { id: string; }) => Promise<{}>; create: ({ action: { actionTypeId, name, config, secrets }, }: ", + "CreateOptions", + ") => Promise<", { "pluginId": "actions", "scope": "server", @@ -655,23 +657,15 @@ "section": "def-server.ActionResult", "text": "ActionResult" }, - ">>; update: ({ id, action }: UpdateOptions) => Promise<", + ">>; update: ({ id, action }: ", + "UpdateOptions", + ") => Promise<", { "pluginId": "actions", "scope": "server", "docId": "kibActionsPluginApi", "section": "def-server.ActionResult", "text": "ActionResult" - }, - ">>; execute: ({ actionId, params, source, }: Pick<", - "ExecuteOptions", - ", \"source\" | \"params\" | \"actionId\">) => Promise<", - { - "pluginId": "actions", - "scope": "common", - "docId": "kibActionsPluginApi", - "section": "def-common.ActionTypeExecutorResult", - "text": "ActionTypeExecutorResult" } ], "initialIsOpen": false @@ -1071,8 +1065,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 94, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L94" + "lineNumber": 86, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L86" } } ], @@ -1080,15 +1074,15 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 88, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L88" + "lineNumber": 80, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L80" } } ], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 87, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L87" + "lineNumber": 79, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L79" }, "lifecycle": "setup", "initialIsOpen": true @@ -1119,8 +1113,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 99, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L99" + "lineNumber": 91, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L91" } }, { @@ -1138,15 +1132,15 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 99, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L99" + "lineNumber": 91, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L91" } } ], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 99, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L99" + "lineNumber": 91, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L91" } } ], @@ -1154,8 +1148,8 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 99, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L99" + "lineNumber": 91, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L91" } }, { @@ -1177,8 +1171,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 101, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L101" + "lineNumber": 93, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L93" } }, { @@ -1191,8 +1185,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 102, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L102" + "lineNumber": 94, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L94" } }, { @@ -1210,15 +1204,15 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 103, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L103" + "lineNumber": 95, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L95" } } ], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 103, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L103" + "lineNumber": 95, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L95" } } ], @@ -1226,8 +1220,8 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 100, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L100" + "lineNumber": 92, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L92" } }, { @@ -1272,8 +1266,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 105, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L105" + "lineNumber": 97, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L97" } } ], @@ -1281,8 +1275,8 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 105, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L105" + "lineNumber": 97, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L97" } }, { @@ -1327,8 +1321,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 106, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L106" + "lineNumber": 98, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L98" } } ], @@ -1336,8 +1330,8 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 106, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L106" + "lineNumber": 98, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L98" } }, { @@ -1348,8 +1342,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 107, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L107" + "lineNumber": 99, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L99" }, "signature": [ { @@ -1381,8 +1375,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 109, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L109" + "lineNumber": 101, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L101" } }, { @@ -1395,8 +1389,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 110, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L110" + "lineNumber": 102, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L102" } }, { @@ -1409,8 +1403,8 @@ "description": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 111, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L111" + "lineNumber": 103, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L103" } } ], @@ -1418,15 +1412,15 @@ "returnComment": [], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 108, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L108" + "lineNumber": 100, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L100" } } ], "source": { "path": "x-pack/plugins/actions/server/plugin.ts", - "lineNumber": 98, - "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L98" + "lineNumber": 90, + "link": "https://github.com/elastic/kibana/tree/7.xx-pack/plugins/actions/server/plugin.ts#L90" }, "lifecycle": "start", "initialIsOpen": true diff --git a/docs/api/actions-and-connectors.asciidoc b/docs/api/actions-and-connectors.asciidoc index 17e7ea1b7672a..5480cdd57f691 100644 --- a/docs/api/actions-and-connectors.asciidoc +++ b/docs/api/actions-and-connectors.asciidoc @@ -3,21 +3,23 @@ Manage Actions and Connectors. -The following action APIs are available: +The following connector APIs are available: -* <> to retrieve a single action by ID +* <> to retrieve a single connector by ID -* <> to retrieve all actions +* <> to retrieve all connectors -* <> to retrieve a list of all action types +* <> to retrieve a list of all connector types -* <> to create actions +* <> to create connectors -* <> to update the attributes for an existing action +* <> to update the attributes for an existing connector -* <> to execute an action by ID +* <> to execute a connector by ID -* <> to delete an action by ID +* <> to delete a connector by ID + +For deprecated APIs, refer to <>. For information about the actions and connectors that {kib} supports, refer to <>. @@ -28,3 +30,11 @@ include::actions-and-connectors/create.asciidoc[] include::actions-and-connectors/update.asciidoc[] include::actions-and-connectors/execute.asciidoc[] include::actions-and-connectors/delete.asciidoc[] +include::actions-and-connectors/legacy/index.asciidoc[] +include::actions-and-connectors/legacy/get.asciidoc[] +include::actions-and-connectors/legacy/get_all.asciidoc[] +include::actions-and-connectors/legacy/list.asciidoc[] +include::actions-and-connectors/legacy/create.asciidoc[] +include::actions-and-connectors/legacy/update.asciidoc[] +include::actions-and-connectors/legacy/execute.asciidoc[] +include::actions-and-connectors/legacy/delete.asciidoc[] diff --git a/docs/api/actions-and-connectors/create.asciidoc b/docs/api/actions-and-connectors/create.asciidoc index 230dad22d3bed..c9a09e890ea6d 100644 --- a/docs/api/actions-and-connectors/create.asciidoc +++ b/docs/api/actions-and-connectors/create.asciidoc @@ -1,17 +1,17 @@ [[actions-and-connectors-api-create]] -=== Create action API +=== Create connector API ++++ -Create action API +Create connector API ++++ -Creates an action. +Creates a connector. [[actions-and-connectors-api-create-request]] ==== Request -`POST :/api/actions/action` +`POST :/api/actions/connector` -`POST :/s//api/actions/action` +`POST :/s//api/actions/connector` [[actions-and-connectors-api-create-path-params]] ==== Path parameters @@ -23,18 +23,18 @@ Creates an action. ==== Request body `name`:: - (Required, string) The display name for the action. + (Required, string) The display name for the connector. -`actionTypeId`:: - (Required, string) The action type ID for the action. +`connector_type_id`:: + (Required, string) The connector type ID for the connector. `config`:: - (Required, object) The configuration for the action. Configuration properties vary depending on - the action type. For information about the configuration properties, refer to <>. + (Required, object) The configuration for the connector. Configuration properties vary depending on + the connector type. For information about the configuration properties, refer to <>. `secrets`:: - (Required, object) The secrets configuration for the action. Secrets configuration properties vary - depending on the action type. For information about the secrets configuration properties, refer to <>. + (Required, object) The secrets configuration for the connector. Secrets configuration properties vary + depending on the connector type. For information about the secrets configuration properties, refer to <>. + WARNING: Remember these values. You must provide them each time you call the <> API. @@ -49,10 +49,10 @@ WARNING: Remember these values. You must provide them each time you call the <Delete action API +Delete connector API ++++ -Deletes an action by ID. +Deletes an connector by ID. -WARNING: When you delete an action, _it cannot be recovered_. +WARNING: When you delete a connector, _it cannot be recovered_. [[actions-and-connectors-api-delete-request]] ==== Request -`DELETE :/api/actions/action/` +`DELETE :/api/actions/connector/` -`DELETE :/s//api/actions/action/` +`DELETE :/s//api/actions/connector/` [[actions-and-connectors-api-delete-path-params]] ==== Path parameters `id`:: - (Required, string) The ID of the action. + (Required, string) The ID of the connector. `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. @@ -34,6 +34,6 @@ WARNING: When you delete an action, _it cannot be recovered_. [source,sh] -------------------------------------------------- -$ curl -X DELETE api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad +$ curl -X DELETE api/actions/connector/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad -------------------------------------------------- // KIBANA diff --git a/docs/api/actions-and-connectors/execute.asciidoc b/docs/api/actions-and-connectors/execute.asciidoc index 05a27988578ff..b87380907f7bb 100644 --- a/docs/api/actions-and-connectors/execute.asciidoc +++ b/docs/api/actions-and-connectors/execute.asciidoc @@ -1,23 +1,23 @@ [[actions-and-connectors-api-execute]] -=== Execute action API +=== Execute connector API ++++ -Execute action API +Execute connector API ++++ -Executes an action by ID. +Executes a connector by ID. [[actions-and-connectors-api-execute-request]] ==== Request -`POST :/api/actions/action//_execute` +`POST :/api/actions/connector//_execute` -`POST :/s//api/actions/action//_execute` +`POST :/s//api/actions/connector//_execute` [[actions-and-connectors-api-execute-params]] ==== Path parameters `id`:: - (Required, string) The ID of the action. + (Required, string) The ID of the connector. `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. @@ -26,8 +26,8 @@ Executes an action by ID. ==== Request body `params`:: - (Required, object) The parameters of the action. Parameter properties vary depending on - the action type. For information about the parameter properties, refer to <>. + (Required, object) The parameters of the connector. Parameter properties vary depending on + the connector type. For information about the parameter properties, refer to <>. [[actions-and-connectors-api-execute-codes]] ==== Response code @@ -40,7 +40,7 @@ Executes an action by ID. [source,sh] -------------------------------------------------- -$ curl -X POST api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad/_execute -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +$ curl -X POST api/actions/connector/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad/_execute -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' { "params": { "documents": [ @@ -83,6 +83,6 @@ The API returns the following: } ] }, - "actionId": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad" + "connector_id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad" } -------------------------------------------------- diff --git a/docs/api/actions-and-connectors/get.asciidoc b/docs/api/actions-and-connectors/get.asciidoc index 51af187257d42..33d37a4add4dd 100644 --- a/docs/api/actions-and-connectors/get.asciidoc +++ b/docs/api/actions-and-connectors/get.asciidoc @@ -1,23 +1,23 @@ [[actions-and-connectors-api-get]] -=== Get action API +=== Get connector API ++++ -Get action API +Get connector API ++++ -Retrieves an action by ID. +Retrieves a connector by ID. [[actions-and-connectors-api-get-request]] ==== Request -`GET :/api/actions/action/` +`GET :/api/actions/connector/` -`GET :/s//api/actions/action/` +`GET :/s//api/actions/connector/` [[actions-and-connectors-api-get-params]] ==== Path parameters `id`:: - (Required, string) The ID of the action. + (Required, string) The ID of the connector. `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. @@ -33,7 +33,7 @@ Retrieves an action by ID. [source,sh] -------------------------------------------------- -$ curl -X GET api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad +$ curl -X GET api/actions/connector/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad -------------------------------------------------- // KIBANA @@ -43,13 +43,13 @@ The API returns the following: -------------------------------------------------- { "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "actionTypeId": ".index", - "name": "my-action", + "connector_type_id": ".index", + "name": "my-connector", "config": { "index": "test-index", "refresh": false, "executionTimeField": null }, - "isPreconfigured": false + "is_preconfigured": false } -------------------------------------------------- diff --git a/docs/api/actions-and-connectors/get_all.asciidoc b/docs/api/actions-and-connectors/get_all.asciidoc index 7a8025d0d215e..8b4977d61e741 100644 --- a/docs/api/actions-and-connectors/get_all.asciidoc +++ b/docs/api/actions-and-connectors/get_all.asciidoc @@ -4,14 +4,14 @@ Get all actions API ++++ -Retrieves all actions. +Retrieves all connectors. [[actions-and-connectors-api-get-all-request]] ==== Request -`GET :/api/actions` +`GET :/api/actions/connectors` -`GET :/s//api/actions` +`GET :/s//api/actions/connectors` [[actions-and-connectors-api-get-all-path-params]] ==== Path parameters @@ -30,7 +30,7 @@ Retrieves all actions. [source,sh] -------------------------------------------------- -$ curl -X GET api/actions +$ curl -X GET api/actions/connectors -------------------------------------------------- // KIBANA @@ -40,21 +40,23 @@ The API returns the following: -------------------------------------------------- [ { - "id": "preconfigured-mail-action", - "actionTypeId": ".email", - "name": "email: preconfigured-mail-action", - "isPreconfigured": true + "id": "preconfigured-mail-connector", + "connector_type_id": ".email", + "name": "email: preconfigured-mail-connector", + "is_preconfigured": true, + "referenced_by_count": 1 }, { "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "actionTypeId": ".index", - "name": "my-action", + "connector_type_id": ".index", + "name": "my-connector", "config": { "index": "test-index", "refresh": false, "executionTimeField": null }, - "isPreconfigured": false + "is_preconfigured": false, + "referenced_by_count": 3 } ] -------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/create.asciidoc b/docs/api/actions-and-connectors/legacy/create.asciidoc new file mode 100644 index 0000000000000..faf6227f01947 --- /dev/null +++ b/docs/api/actions-and-connectors/legacy/create.asciidoc @@ -0,0 +1,82 @@ +[[actions-and-connectors-legacy-api-create]] +==== Legacy Create connector API +++++ +Legacy Create connector API +++++ + +deprecated::[7.13.0] + +Please use the <> instead. + +Creates a connector. + +[[actions-and-connectors-legacy-api-create-request]] +===== Request + +`POST :/api/actions/action` + +`POST :/s//api/actions/action` + +[[actions-and-connectors-legacy-api-create-path-params]] +===== Path parameters + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[actions-and-connectors-legacy-api-create-request-body]] +===== Request body + +`name`:: + (Required, string) The display name for the connector. + +`actionTypeId`:: + (Required, string) The connector type ID for the connector. + +`config`:: + (Required, object) The configuration for the connector. Configuration properties vary depending on + the connector type. For information about the configuration properties, refer to <>. + +`secrets`:: + (Required, object) The secrets configuration for the connector. Secrets configuration properties vary + depending on the connector type. For information about the secrets configuration properties, refer to <>. ++ +WARNING: Remember these values. You must provide them each time you call the <> API. + +[[actions-and-connectors-legacy-api-create-request-codes]] +===== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-legacy-api-create-example]] +===== Example + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/actions/action -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "name": "my-connector", + "actionTypeId": ".index", + "config": { + "index": "test-index" + } +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "my-connector", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/delete.asciidoc b/docs/api/actions-and-connectors/legacy/delete.asciidoc new file mode 100644 index 0000000000000..b02f1011fd9b4 --- /dev/null +++ b/docs/api/actions-and-connectors/legacy/delete.asciidoc @@ -0,0 +1,43 @@ +[[actions-and-connectors-legacy-api-delete]] +==== Legacy Delete connector API +++++ +Legacy Delete connector API +++++ + +deprecated::[7.13.0] + +Please use the <> instead. + +Deletes a connector by ID. + +WARNING: When you delete an connector, _it cannot be recovered_. + +[[actions-and-connectors-legacy-api-delete-request]] +===== Request + +`DELETE :/api/actions/action/` + +`DELETE :/s//api/actions/action/` + +[[actions-and-connectors-legacy-api-delete-path-params]] +===== Path parameters + +`id`:: + (Required, string) The ID of the connector. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[actions-and-connectors-legacy-api-delete-response-codes]] +===== Response code + +`200`:: + Indicates a successful call. + +===== Example + +[source,sh] +-------------------------------------------------- +$ curl -X DELETE api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad +-------------------------------------------------- +// KIBANA diff --git a/docs/api/actions-and-connectors/legacy/execute.asciidoc b/docs/api/actions-and-connectors/legacy/execute.asciidoc new file mode 100644 index 0000000000000..30cb18c54aa69 --- /dev/null +++ b/docs/api/actions-and-connectors/legacy/execute.asciidoc @@ -0,0 +1,92 @@ +[[actions-and-connectors-legacy-api-execute]] +==== Legacy Execute connector API +++++ +Legacy Execute connector API +++++ + +deprecated::[7.13.0] + +Please use the <> instead. + +Executes a connector by ID. + +[[actions-and-connectors-legacy-api-execute-request]] +===== Request + +`POST :/api/actions/action//_execute` + +`POST :/s//api/actions/action//_execute` + +[[actions-and-connectors-legacy-api-execute-params]] +===== Path parameters + +`id`:: + (Required, string) The ID of the connector. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[actions-and-connectors-legacy-api-execute-request-body]] +===== Request body + +`params`:: + (Required, object) The parameters of the connector. Parameter properties vary depending on + the connector type. For information about the parameter properties, refer to <>. + +[[actions-and-connectors-legacy-api-execute-codes]] +===== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-legacy-api-execute-example]] +===== Example + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad/_execute -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "params": { + "documents": [ + { + "id": "test_doc_id", + "name": "test_doc_name", + "message": "hello, world" + } + ] + } +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "status": "ok", + "data": { + "took": 197, + "errors": false, + "items": [ + { + "index": { + "_index": "updated-index", + "_id": "iKyijHcBKCsmXNFrQe3T", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 0, + "_primary_term": 1, + "status": 201 + } + } + ] + }, + "actionId": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad" +} +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/get.asciidoc b/docs/api/actions-and-connectors/legacy/get.asciidoc new file mode 100644 index 0000000000000..cf8cc1b6b677e --- /dev/null +++ b/docs/api/actions-and-connectors/legacy/get.asciidoc @@ -0,0 +1,59 @@ +[[actions-and-connectors-legacy-api-get]] +==== Legacy Get connector API +++++ +Legacy Get connector API +++++ + +deprecated::[7.13.0] + +Please use the <> instead. + +Retrieves a connector by ID. + +[[actions-and-connectors-legacy-api-get-request]] +===== Request + +`GET :/api/actions/action/` + +`GET :/s//api/actions/action/` + +[[actions-and-connectors-legacy-api-get-params]] +===== Path parameters + +`id`:: + (Required, string) The ID of the action. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[actions-and-connectors-legacy-api-get-codes]] +===== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-legacy-api-get-example]] +===== Example + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "my-connector", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/get_all.asciidoc b/docs/api/actions-and-connectors/legacy/get_all.asciidoc new file mode 100644 index 0000000000000..24ad446d95d95 --- /dev/null +++ b/docs/api/actions-and-connectors/legacy/get_all.asciidoc @@ -0,0 +1,64 @@ +[[actions-and-connectors-legacy-api-get-all]] +==== Legacy Get all connector API +++++ +Legacy Get all connector API +++++ + +deprecated::[7.13.0] + +Please use the <> instead. + +Retrieves all connectors. + +[[actions-and-connectors-legacy-api-get-all-request]] +===== Request + +`GET :/api/actions` + +`GET :/s//api/actions` + +[[actions-and-connectors-legacy-api-get-all-path-params]] +===== Path parameters + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[actions-and-connectors-legacy-api-get-all-codes]] +===== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-legacy-api-get-all-example]] +===== Example + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/actions +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +[ + { + "id": "preconfigured-mail-action", + "actionTypeId": ".email", + "name": "email: preconfigured-mail-action", + "isPreconfigured": true + }, + { + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "my-action", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false + } +] +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/index.asciidoc b/docs/api/actions-and-connectors/legacy/index.asciidoc new file mode 100644 index 0000000000000..859dd652de984 --- /dev/null +++ b/docs/api/actions-and-connectors/legacy/index.asciidoc @@ -0,0 +1,4 @@ +[[actions-and-connectors-legacy-apis]] +=== Deprecated 7.x APIs + +These APIs are deprecated and will be removed as of 8.0. diff --git a/docs/api/actions-and-connectors/legacy/list.asciidoc b/docs/api/actions-and-connectors/legacy/list.asciidoc new file mode 100644 index 0000000000000..86026f332d917 --- /dev/null +++ b/docs/api/actions-and-connectors/legacy/list.asciidoc @@ -0,0 +1,71 @@ +[[actions-and-connectors-legacy-api-list]] +==== Legacy List connector types API +++++ +Legacy List all connector types API +++++ + +deprecated::[7.13.0] + +Please use the <> instead. + +Retrieves a list of all connector types. + +[[actions-and-connectors-legacy-api-list-request]] +===== Request + +`GET :/api/actions/list_action_types` + +`GET :/s//api/actions/list_action_types` + +[[actions-and-connectors-legacy-api-list-path-params]] +===== Path parameters + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[actions-and-connectors-legacy-api-list-codes]] +===== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-legacy-api-list-example]] +===== Example + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/actions/list_action_types +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +[ + { + "id": ".email", <1> + "name": "Email", <2> + "minimumLicenseRequired": "gold", <3> + "enabled": false, <4> + "enabledInConfig": true, <5> + "enabledInLicense": false <6> + }, + { + "id": ".index", + "name": "Index", + "minimumLicenseRequired": "basic", + "enabled": true, + "enabledInConfig": true, + "enabledInLicense": true + } +] +-------------------------------------------------- + + +<1> `id` - The unique ID of the connector type. +<2> `name` - The name of the connector type. +<3> `minimumLicenseRequired` - The license required to use the connector type. +<4> `enabled` - Specifies if the connector type is enabled or disabled in {kib}. +<5> `enabledInConfig` - Specifies if the connector type is enabled or enabled in the {kib} .yml file. +<6> `enabledInLicense` - Specifies if the connector type is enabled or disabled in the license. diff --git a/docs/api/actions-and-connectors/legacy/update.asciidoc b/docs/api/actions-and-connectors/legacy/update.asciidoc new file mode 100644 index 0000000000000..c2e841988717a --- /dev/null +++ b/docs/api/actions-and-connectors/legacy/update.asciidoc @@ -0,0 +1,77 @@ +[[actions-and-connectors-legacy-api-update]] +==== Legacy Update connector API +++++ +Legacy Update connector API +++++ + +deprecated::[7.13.0] + +Please use the <> instead. + +Updates the attributes for an existing connector. + +[[actions-and-connectors-legacy-api-update-request]] +===== Request + +`PUT :/api/actions/action/` + +`PUT :/s//api/actions/action/` + +[[actions-and-connectors-legacy-api-update-params]] +===== Path parameters + +`id`:: + (Required, string) The ID of the connector. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[actions-and-connectors-legacy-api-update-request-body]] +===== Request body + +`name`:: + (Required, string) The new name of the connector. + +`config`:: + (Required, object) The new connector configuration. Configuration properties vary depending on the connector type. For information about the configuration properties, refer to <>. + +`secrets`:: + (Required, object) The updated secrets configuration for the connector. Secrets properties vary depending on the connector type. For information about the secrets configuration properties, refer to <>. + +[[actions-and-connectors-legacy-api-update-codes]] +===== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-legacy-api-update-example]] +===== Example + +[source,sh] +-------------------------------------------------- +$ curl -X PUT api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "name": "updated-connector", + "config": { + "index": "updated-index" + } +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "updated-connector", + "config": { + "index": "updated-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/list.asciidoc b/docs/api/actions-and-connectors/list.asciidoc index 3647bf06c98e0..941f7b4376e91 100644 --- a/docs/api/actions-and-connectors/list.asciidoc +++ b/docs/api/actions-and-connectors/list.asciidoc @@ -1,17 +1,17 @@ [[actions-and-connectors-api-list]] -=== List action types API +=== List connector types API ++++ -List all action types API +List all connector types API ++++ -Retrieves a list of all action types. +Retrieves a list of all connector types. [[actions-and-connectors-api-list-request]] ==== Request -`GET :/api/actions/list_action_types` +`GET :/api/actions/connector_types` -`GET :/s//api/actions/list_action_types` +`GET :/s//api/actions/connector_types` [[actions-and-connectors-api-list-path-params]] ==== Path parameters @@ -30,7 +30,7 @@ Retrieves a list of all action types. [source,sh] -------------------------------------------------- -$ curl -X GET api/actions/list_action_types +$ curl -X GET api/actions/connector_types -------------------------------------------------- // KIBANA @@ -42,26 +42,26 @@ The API returns the following: { "id": ".email", <1> "name": "Email", <2> - "minimumLicenseRequired": "gold", <3> + "minimum_license_required": "gold", <3> "enabled": false, <4> - "enabledInConfig": true, <5> - "enabledInLicense": false <6> + "enabled_in_config": true, <5> + "enabled_in_license": false <6> }, { "id": ".index", "name": "Index", - "minimumLicenseRequired": "basic", + "minimum_license_required": "basic", "enabled": true, - "enabledInConfig": true, - "enabledInLicense": true + "enabled_in_config": true, + "enabled_in_license": true } ] -------------------------------------------------- -<1> `id` - The unique ID of the action type. -<2> `name` - The name of the action type. -<3> `minimumLicenseRequired` - The license required to use the action type. -<4> `enabled` - Specifies if the action type is enabled or disabled in {kib}. -<5> `enabledInConfig` - Specifies if the action type is enabled or enabled in the {kib} .yml file. -<6> `enabledInLicense` - Specifies if the action type is enabled or disabled in the license. +<1> `id` - The unique ID of the connector type. +<2> `name` - The name of the connector type. +<3> `minimum_license_required` - The license required to use the connector type. +<4> `enabled` - Specifies if the connector type is enabled or disabled in {kib}. +<5> `enabled_in_config` - Specifies if the connector type is enabled or enabled in the {kib} .yml file. +<6> `enabled_in_license` - Specifies if the connector type is enabled or disabled in the license. diff --git a/docs/api/actions-and-connectors/update.asciidoc b/docs/api/actions-and-connectors/update.asciidoc index 46e6d91cf9e97..6c4e6040bdfb5 100644 --- a/docs/api/actions-and-connectors/update.asciidoc +++ b/docs/api/actions-and-connectors/update.asciidoc @@ -1,23 +1,23 @@ [[actions-and-connectors-api-update]] -=== Update action API +=== Update connector API ++++ -Update action API +Update connector API ++++ -Updates the attributes for an existing action. +Updates the attributes for an existing connector. [[actions-and-connectors-api-update-request]] ==== Request -`PUT :/api/actions/action/` +`PUT :/api/actions/connector/` -`PUT :/s//api/actions/action/` +`PUT :/s//api/actions/connector/` [[actions-and-connectors-api-update-params]] ==== Path parameters `id`:: - (Required, string) The ID of the action. + (Required, string) The ID of the connector. `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. @@ -26,13 +26,13 @@ Updates the attributes for an existing action. ==== Request body `name`:: - (Required, string) The new name of the action. + (Required, string) The new name of the connector. `config`:: - (Required, object) The new action configuration. Configuration properties vary depending on the action type. For information about the configuration properties, refer to <>. + (Required, object) The new connector configuration. Configuration properties vary depending on the connector type. For information about the configuration properties, refer to <>. `secrets`:: - (Required, object) The updated secrets configuration for the action. Secrets properties vary depending on the action type. For information about the secrets configuration properties, refer to <>. + (Required, object) The updated secrets configuration for the connector. Secrets properties vary depending on the connector type. For information about the secrets configuration properties, refer to <>. [[actions-and-connectors-api-update-codes]] ==== Response code @@ -45,9 +45,9 @@ Updates the attributes for an existing action. [source,sh] -------------------------------------------------- -$ curl -X PUT api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +$ curl -X PUT api/actions/connector/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' { - "name": "updated-action", + "name": "updated-connector", "config": { "index": "updated-index" } @@ -61,13 +61,13 @@ The API returns the following: -------------------------------------------------- { "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "actionTypeId": ".index", - "name": "updated-action", + "connector_type_id": ".index", + "name": "updated-connector", "config": { "index": "updated-index", "refresh": false, "executionTimeField": null }, - "isPreconfigured": false + "is_preconfigured": false } -------------------------------------------------- diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index c941bc1fab772..2703cbad32460 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -56,7 +56,7 @@ interface Action extends ActionUpdate { actionTypeId: string; } -interface CreateOptions { +export interface CreateOptions { action: Action; } @@ -73,7 +73,7 @@ interface ConstructorOptions { auditLogger?: AuditLogger; } -interface UpdateOptions { +export interface UpdateOptions { id: string; action: ActionUpdate; } diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index c4159c80e806f..d9d34e99bffea 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -50,15 +50,7 @@ import { import { getActionsConfigurationUtilities } from './actions_config'; -import { - createActionRoute, - deleteActionRoute, - getAllActionRoute, - getActionRoute, - updateActionRoute, - listActionTypesRoute, - executeActionRoute, -} from './routes'; +import { defineRoutes } from './routes'; import { IEventLogger, IEventLogService } from '../../event_log/server'; import { initializeActionsTelemetry, scheduleActionsTelemetry } from './usage/task'; import { @@ -237,14 +229,7 @@ export class ActionsPlugin implements Plugin(); - createActionRoute(router, this.licenseState); - deleteActionRoute(router, this.licenseState); - getActionRoute(router, this.licenseState); - getAllActionRoute(router, this.licenseState); - updateActionRoute(router, this.licenseState); - listActionTypesRoute(router, this.licenseState); - executeActionRoute(router, this.licenseState); + defineRoutes(core.http.createRouter(), this.licenseState); return { registerType: < diff --git a/x-pack/plugins/actions/server/routes/connector_types.test.ts b/x-pack/plugins/actions/server/routes/connector_types.test.ts new file mode 100644 index 0000000000000..ebbdc703556d5 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/connector_types.test.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { connectorTypesRoute } from './connector_types'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; +import { LicenseType } from '../../../../plugins/licensing/server'; +import { actionsClientMock } from '../mocks'; +import { verifyAccessAndContext } from './verify_access_and_context'; + +jest.mock('./verify_access_and_context.ts', () => ({ + verifyAccessAndContext: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); + (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); +}); + +describe('connectorTypesRoute', () => { + it('lists action types with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + connectorTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector_types"`); + + const listTypes = [ + { + id: '1', + name: 'name', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, + }, + ]; + + const actionsClient = actionsClientMock.create(); + actionsClient.listTypes.mockResolvedValueOnce(listTypes); + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Array [ + Object { + "enabled": true, + "enabled_in_config": true, + "enabled_in_license": true, + "id": "1", + "minimum_license_required": "gold", + "name": "name", + }, + ], + } + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: [ + { + id: '1', + name: 'name', + enabled: true, + enabled_in_config: true, + enabled_in_license: true, + minimum_license_required: 'gold', + }, + ], + }); + }); + + it('ensures the license allows listing action types', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + connectorTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector_types"`); + + const listTypes = [ + { + id: '1', + name: 'name', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, + }, + ]; + + const actionsClient = actionsClientMock.create(); + actionsClient.listTypes.mockResolvedValueOnce(listTypes); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); + }); + + it('ensures the license check prevents listing action types', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { + throw new Error('OMG'); + }); + + connectorTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector_types"`); + + const listTypes = [ + { + id: '1', + name: 'name', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold' as LicenseType, + }, + ]; + + const actionsClient = actionsClientMock.create(); + actionsClient.listTypes.mockResolvedValueOnce(listTypes); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/connector_types.ts b/x-pack/plugins/actions/server/routes/connector_types.ts new file mode 100644 index 0000000000000..d686ddbdaee70 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/connector_types.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../lib'; +import { ActionType, BASE_ACTION_API_PATH } from '../../common'; +import { ActionsRequestHandlerContext } from '../types'; +import { verifyAccessAndContext } from './verify_access_and_context'; +import { RewriteResponseCase } from './rewrite_request_case'; + +const rewriteBodyRes: RewriteResponseCase = (results) => { + return results.map(({ enabledInConfig, enabledInLicense, minimumLicenseRequired, ...res }) => ({ + ...res, + enabled_in_config: enabledInConfig, + enabled_in_license: enabledInLicense, + minimum_license_required: minimumLicenseRequired, + })); +}; + +export const connectorTypesRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${BASE_ACTION_API_PATH}/connector_types`, + validate: {}, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = context.actions.getActionsClient(); + return res.ok({ + body: rewriteBodyRes(await actionsClient.listTypes()), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/create.test.ts b/x-pack/plugins/actions/server/routes/create.test.ts index b917dbcc23ff2..e5d8e6f5861f3 100644 --- a/x-pack/plugins/actions/server/routes/create.test.ts +++ b/x-pack/plugins/actions/server/routes/create.test.ts @@ -8,16 +8,18 @@ import { createActionRoute } from './create'; import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess, ActionTypeDisabledError } from '../lib'; -import { mockHandlerArguments } from './_mock_handler_arguments'; +import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; import { actionsClientMock } from '../actions_client.mock'; +import { verifyAccessAndContext } from './verify_access_and_context'; +import { omit } from 'lodash'; -jest.mock('../lib/verify_api_access.ts', () => ({ - verifyApiAccess: jest.fn(), +jest.mock('./verify_access_and_context.ts', () => ({ + verifyAccessAndContext: jest.fn(), })); beforeEach(() => { jest.resetAllMocks(); + (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); }); describe('createActionRoute', () => { @@ -29,7 +31,7 @@ describe('createActionRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions/action"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector"`); const createResult = { id: '1', @@ -39,6 +41,12 @@ describe('createActionRoute', () => { isPreconfigured: false, }; + const createApiResult = { + ...omit(createResult, ['actionTypeId', 'isPreconfigured']), + connector_type_id: createResult.actionTypeId, + is_preconfigured: createResult.isPreconfigured, + }; + const actionsClient = actionsClientMock.create(); actionsClient.create.mockResolvedValueOnce(createResult); @@ -47,7 +55,7 @@ describe('createActionRoute', () => { { body: { name: 'My name', - actionTypeId: 'abc', + connector_type_id: 'abc', config: { foo: true }, secrets: {}, }, @@ -55,7 +63,7 @@ describe('createActionRoute', () => { ['ok'] ); - expect(await handler(context, req, res)).toEqual({ body: createResult }); + expect(await handler(context, req, res)).toEqual({ body: createApiResult }); expect(actionsClient.create).toHaveBeenCalledTimes(1); expect(actionsClient.create.mock.calls[0]).toMatchInlineSnapshot(` @@ -74,7 +82,7 @@ describe('createActionRoute', () => { `); expect(res.ok).toHaveBeenCalledWith({ - body: createResult, + body: createApiResult, }); }); @@ -95,18 +103,28 @@ describe('createActionRoute', () => { isPreconfigured: false, }); - const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: { + name: 'My name', + connector_type_id: 'abc', + config: { foo: true }, + secrets: {}, + }, + } + ); await handler(context, req, res); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); it('ensures the license check prevents creating actions', async () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); - (verifyApiAccess as jest.Mock).mockImplementation(() => { + (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { throw new Error('OMG'); }); @@ -123,28 +141,18 @@ describe('createActionRoute', () => { isPreconfigured: false, }); - const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: { + name: 'My name', + connector_type_id: 'abc', + config: { foo: true }, + secrets: {}, + }, + } + ); expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('ensures the action type gets validated for the license', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - createActionRoute(router, licenseState); - - const [, handler] = router.post.mock.calls[0]; - - const actionsClient = actionsClientMock.create(); - actionsClient.create.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); - - const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok', 'forbidden']); - - await handler(context, req, res); - - expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); }); }); diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts index a64264412fc47..e1717891231db 100644 --- a/x-pack/plugins/actions/server/routes/create.ts +++ b/x-pack/plugins/actions/server/routes/create.ts @@ -8,46 +8,54 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from 'kibana/server'; import { ActionResult, ActionsRequestHandlerContext } from '../types'; -import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; +import { ILicenseState } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; +import { verifyAccessAndContext } from './verify_access_and_context'; +import { RewriteRequestCase, RewriteResponseCase } from './rewrite_request_case'; +import { CreateOptions } from '../actions_client'; export const bodySchema = schema.object({ name: schema.string(), - actionTypeId: schema.string(), + connector_type_id: schema.string(), config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), }); +const rewriteBodyReq: RewriteRequestCase = ({ + connector_type_id: actionTypeId, + name, + config, + secrets, +}) => ({ actionTypeId, name, config, secrets }); +const rewriteBodyRes: RewriteResponseCase = ({ + actionTypeId, + isPreconfigured, + ...res +}) => ({ + ...res, + connector_type_id: actionTypeId, + is_preconfigured: isPreconfigured, +}); + export const createActionRoute = ( router: IRouter, licenseState: ILicenseState ) => { router.post( { - path: `${BASE_ACTION_API_PATH}/action`, + path: `${BASE_ACTION_API_PATH}/connector`, validate: { body: bodySchema, }, }, - router.handleLegacyErrors(async function (context, req, res) { - verifyApiAccess(licenseState); - - if (!context.actions) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); - } - const actionsClient = context.actions.getActionsClient(); - const action = req.body; - try { - const actionRes: ActionResult = await actionsClient.create({ action }); + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = context.actions.getActionsClient(); + const action = rewriteBodyReq(req.body); return res.ok({ - body: actionRes, + body: rewriteBodyRes(await actionsClient.create({ action })), }); - } catch (e) { - if (isErrorThatHandlesItsOwnResponse(e)) { - return e.sendResponse(res); - } - throw e; - } - }) + }) + ) ); }; diff --git a/x-pack/plugins/actions/server/routes/delete.test.ts b/x-pack/plugins/actions/server/routes/delete.test.ts index fa7777fb508ce..438f366e5cb73 100644 --- a/x-pack/plugins/actions/server/routes/delete.test.ts +++ b/x-pack/plugins/actions/server/routes/delete.test.ts @@ -8,16 +8,17 @@ import { deleteActionRoute } from './delete'; import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib'; -import { mockHandlerArguments } from './_mock_handler_arguments'; +import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; import { actionsClientMock } from '../mocks'; +import { verifyAccessAndContext } from './verify_access_and_context'; -jest.mock('../lib/verify_api_access.ts', () => ({ - verifyApiAccess: jest.fn(), +jest.mock('./verify_access_and_context.ts', () => ({ + verifyAccessAndContext: jest.fn(), })); beforeEach(() => { jest.resetAllMocks(); + (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); }); describe('deleteActionRoute', () => { @@ -29,7 +30,7 @@ describe('deleteActionRoute', () => { const [config, handler] = router.delete.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions/action/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector/{id}"`); const actionsClient = actionsClientMock.create(); actionsClient.delete.mockResolvedValueOnce({}); @@ -78,14 +79,14 @@ describe('deleteActionRoute', () => { await handler(context, req, res); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); it('ensures the license check prevents deleting actions', async () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); - (verifyApiAccess as jest.Mock).mockImplementation(() => { + (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { throw new Error('OMG'); }); @@ -105,6 +106,6 @@ describe('deleteActionRoute', () => { expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); }); diff --git a/x-pack/plugins/actions/server/routes/delete.ts b/x-pack/plugins/actions/server/routes/delete.ts index 84da01dd2bb20..653ce32e69eb0 100644 --- a/x-pack/plugins/actions/server/routes/delete.ts +++ b/x-pack/plugins/actions/server/routes/delete.ts @@ -7,9 +7,10 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from 'kibana/server'; -import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; +import { ILicenseState } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; import { ActionsRequestHandlerContext } from '../types'; +import { verifyAccessAndContext } from './verify_access_and_context'; const paramSchema = schema.object({ id: schema.string(), @@ -21,27 +22,18 @@ export const deleteActionRoute = ( ) => { router.delete( { - path: `${BASE_ACTION_API_PATH}/action/{id}`, + path: `${BASE_ACTION_API_PATH}/connector/{id}`, validate: { params: paramSchema, }, }, - router.handleLegacyErrors(async function (context, req, res) { - verifyApiAccess(licenseState); - if (!context.actions) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); - } - const actionsClient = context.actions.getActionsClient(); - const { id } = req.params; - try { + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; await actionsClient.delete({ id }); return res.noContent(); - } catch (e) { - if (isErrorThatHandlesItsOwnResponse(e)) { - return e.sendResponse(res); - } - throw e; - } - }) + }) + ) ); }; diff --git a/x-pack/plugins/actions/server/routes/execute.test.ts b/x-pack/plugins/actions/server/routes/execute.test.ts index e07591f404e2f..4b12bf3111c1f 100644 --- a/x-pack/plugins/actions/server/routes/execute.test.ts +++ b/x-pack/plugins/actions/server/routes/execute.test.ts @@ -8,17 +8,19 @@ import { executeActionRoute } from './execute'; import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { verifyApiAccess, ActionTypeDisabledError, asHttpRequestExecutionSource } from '../lib'; +import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; +import { asHttpRequestExecutionSource } from '../lib'; import { actionsClientMock } from '../actions_client.mock'; import { ActionTypeExecutorResult } from '../types'; +import { verifyAccessAndContext } from './verify_access_and_context'; -jest.mock('../lib/verify_api_access.ts', () => ({ - verifyApiAccess: jest.fn(), +jest.mock('./verify_access_and_context.ts', () => ({ + verifyAccessAndContext: jest.fn(), })); beforeEach(() => { jest.resetAllMocks(); + (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); }); describe('executeActionRoute', () => { @@ -45,7 +47,7 @@ describe('executeActionRoute', () => { ); const executeResult = { - actionId: '1', + connector_id: '1', status: 'ok', }; @@ -53,7 +55,7 @@ describe('executeActionRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions/action/{id}/_execute"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector/{id}/_execute"`); expect(await handler(context, req, res)).toEqual({ body: executeResult }); @@ -131,7 +133,7 @@ describe('executeActionRoute', () => { await handler(context, req, res); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); it('ensures the license check prevents action execution', async () => { @@ -144,7 +146,7 @@ describe('executeActionRoute', () => { status: 'ok', }); - (verifyApiAccess as jest.Mock).mockImplementation(() => { + (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { throw new Error('OMG'); }); @@ -163,31 +165,6 @@ describe('executeActionRoute', () => { expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('ensures the action type gets validated for the license', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - const actionsClient = actionsClientMock.create(); - actionsClient.execute.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); - - const [context, req, res] = mockHandlerArguments( - { actionsClient }, - { - body: {}, - params: {}, - }, - ['ok', 'forbidden'] - ); - - executeActionRoute(router, licenseState); - - const [, handler] = router.post.mock.calls[0]; - - await handler(context, req, res); - - expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); }); diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/execute.ts index f87f1ee0d0890..0d1bee83ed047 100644 --- a/x-pack/plugins/actions/server/routes/execute.ts +++ b/x-pack/plugins/actions/server/routes/execute.ts @@ -7,11 +7,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from 'kibana/server'; -import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; +import { ILicenseState } from '../lib'; import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../types'; import { BASE_ACTION_API_PATH } from '../../common'; import { asHttpRequestExecutionSource } from '../lib/action_execution_source'; +import { verifyAccessAndContext } from './verify_access_and_context'; +import { RewriteResponseCase } from './rewrite_request_case'; const paramSchema = schema.object({ id: schema.string(), @@ -21,29 +23,33 @@ const bodySchema = schema.object({ params: schema.recordOf(schema.string(), schema.any()), }); +const rewriteBodyRes: RewriteResponseCase> = ({ + actionId, + serviceMessage, + ...res +}) => ({ + ...res, + connector_id: actionId, + ...(serviceMessage ? { service_message: serviceMessage } : {}), +}); + export const executeActionRoute = ( router: IRouter, licenseState: ILicenseState ) => { router.post( { - path: `${BASE_ACTION_API_PATH}/action/{id}/_execute`, + path: `${BASE_ACTION_API_PATH}/connector/{id}/_execute`, validate: { body: bodySchema, params: paramSchema, }, }, - router.handleLegacyErrors(async function (context, req, res) { - verifyApiAccess(licenseState); - - if (!context.actions) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); - } - - const actionsClient = context.actions.getActionsClient(); - const { params } = req.body; - const { id } = req.params; - try { + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = context.actions.getActionsClient(); + const { params } = req.body; + const { id } = req.params; const body: ActionTypeExecutorResult = await actionsClient.execute({ params, actionId: id, @@ -51,15 +57,10 @@ export const executeActionRoute = ( }); return body ? res.ok({ - body, + body: rewriteBodyRes(body), }) : res.noContent(); - } catch (e) { - if (isErrorThatHandlesItsOwnResponse(e)) { - return e.sendResponse(res); - } - throw e; - } - }) + }) + ) ); }; diff --git a/x-pack/plugins/actions/server/routes/get.test.ts b/x-pack/plugins/actions/server/routes/get.test.ts index d2625f8f434a5..6a42f3b27370e 100644 --- a/x-pack/plugins/actions/server/routes/get.test.ts +++ b/x-pack/plugins/actions/server/routes/get.test.ts @@ -8,16 +8,17 @@ import { getActionRoute } from './get'; import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib'; -import { mockHandlerArguments } from './_mock_handler_arguments'; +import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; import { actionsClientMock } from '../actions_client.mock'; +import { verifyAccessAndContext } from './verify_access_and_context'; -jest.mock('../lib/verify_api_access.ts', () => ({ - verifyApiAccess: jest.fn(), +jest.mock('./verify_access_and_context.ts', () => ({ + verifyAccessAndContext: jest.fn(), })); beforeEach(() => { jest.resetAllMocks(); + (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); }); describe('getActionRoute', () => { @@ -29,7 +30,7 @@ describe('getActionRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions/action/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector/{id}"`); const getResult = { id: '1', @@ -53,10 +54,10 @@ describe('getActionRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { - "actionTypeId": "2", "config": Object {}, + "connector_type_id": "2", "id": "1", - "isPreconfigured": false, + "is_preconfigured": false, "name": "action name", }, } @@ -66,7 +67,13 @@ describe('getActionRoute', () => { expect(actionsClient.get.mock.calls[0][0].id).toEqual('1'); expect(res.ok).toHaveBeenCalledWith({ - body: getResult, + body: { + id: '1', + connector_type_id: '2', + name: 'action name', + config: {}, + is_preconfigured: false, + }, }); }); @@ -97,14 +104,14 @@ describe('getActionRoute', () => { await handler(context, req, res); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); it('ensures the license check prevents getting actions', async () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); - (verifyApiAccess as jest.Mock).mockImplementation(() => { + (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { throw new Error('OMG'); }); @@ -131,6 +138,6 @@ describe('getActionRoute', () => { expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); }); diff --git a/x-pack/plugins/actions/server/routes/get.ts b/x-pack/plugins/actions/server/routes/get.ts index 436b6bed9970f..63f89d6b3ca49 100644 --- a/x-pack/plugins/actions/server/routes/get.ts +++ b/x-pack/plugins/actions/server/routes/get.ts @@ -7,35 +7,45 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from 'kibana/server'; -import { ILicenseState, verifyApiAccess } from '../lib'; +import { ILicenseState } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; -import { ActionsRequestHandlerContext } from '../types'; +import { ActionResult, ActionsRequestHandlerContext } from '../types'; +import { verifyAccessAndContext } from './verify_access_and_context'; +import { RewriteResponseCase } from './rewrite_request_case'; const paramSchema = schema.object({ id: schema.string(), }); +const rewriteBodyRes: RewriteResponseCase = ({ + actionTypeId, + isPreconfigured, + ...res +}) => ({ + ...res, + connector_type_id: actionTypeId, + is_preconfigured: isPreconfigured, +}); + export const getActionRoute = ( router: IRouter, licenseState: ILicenseState ) => { router.get( { - path: `${BASE_ACTION_API_PATH}/action/{id}`, + path: `${BASE_ACTION_API_PATH}/connector/{id}`, validate: { params: paramSchema, }, }, - router.handleLegacyErrors(async function (context, req, res) { - verifyApiAccess(licenseState); - if (!context.actions) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); - } - const actionsClient = context.actions.getActionsClient(); - const { id } = req.params; - return res.ok({ - body: await actionsClient.get({ id }), - }); - }) + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; + return res.ok({ + body: rewriteBodyRes(await actionsClient.get({ id })), + }); + }) + ) ); }; diff --git a/x-pack/plugins/actions/server/routes/get_all.test.ts b/x-pack/plugins/actions/server/routes/get_all.test.ts index c57a2ef6d7526..69684457f3dd8 100644 --- a/x-pack/plugins/actions/server/routes/get_all.test.ts +++ b/x-pack/plugins/actions/server/routes/get_all.test.ts @@ -8,16 +8,17 @@ import { getAllActionRoute } from './get_all'; import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib'; -import { mockHandlerArguments } from './_mock_handler_arguments'; +import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; import { actionsClientMock } from '../actions_client.mock'; +import { verifyAccessAndContext } from './verify_access_and_context'; -jest.mock('../lib/verify_api_access.ts', () => ({ - verifyApiAccess: jest.fn(), +jest.mock('./verify_access_and_context.ts', () => ({ + verifyAccessAndContext: jest.fn(), })); beforeEach(() => { jest.resetAllMocks(); + (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); }); describe('getAllActionRoute', () => { @@ -29,7 +30,7 @@ describe('getAllActionRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connectors"`); const actionsClient = actionsClientMock.create(); actionsClient.getAll.mockResolvedValueOnce([]); @@ -57,7 +58,7 @@ describe('getAllActionRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connectors"`); const actionsClient = actionsClientMock.create(); actionsClient.getAll.mockResolvedValueOnce([]); @@ -66,14 +67,14 @@ describe('getAllActionRoute', () => { await handler(context, req, res); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); it('ensures the license check prevents getting all actions', async () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); - (verifyApiAccess as jest.Mock).mockImplementation(() => { + (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { throw new Error('OMG'); }); @@ -81,7 +82,7 @@ describe('getAllActionRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connectors"`); const actionsClient = actionsClientMock.create(); actionsClient.getAll.mockResolvedValueOnce([]); @@ -90,6 +91,6 @@ describe('getAllActionRoute', () => { expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); }); diff --git a/x-pack/plugins/actions/server/routes/get_all.ts b/x-pack/plugins/actions/server/routes/get_all.ts index 825950fa3412c..32f48e32ab278 100644 --- a/x-pack/plugins/actions/server/routes/get_all.ts +++ b/x-pack/plugins/actions/server/routes/get_all.ts @@ -6,9 +6,20 @@ */ import { IRouter } from 'kibana/server'; -import { ILicenseState, verifyApiAccess } from '../lib'; +import { ILicenseState } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; -import { ActionsRequestHandlerContext } from '../types'; +import { ActionsRequestHandlerContext, FindActionResult } from '../types'; +import { verifyAccessAndContext } from './verify_access_and_context'; +import { RewriteResponseCase } from './rewrite_request_case'; + +const rewriteBodyRes: RewriteResponseCase = (results) => { + return results.map(({ actionTypeId, isPreconfigured, referencedByCount, ...res }) => ({ + ...res, + connector_type_id: actionTypeId, + is_preconfigured: isPreconfigured, + referenced_by_count: referencedByCount, + })); +}; export const getAllActionRoute = ( router: IRouter, @@ -16,19 +27,17 @@ export const getAllActionRoute = ( ) => { router.get( { - path: `${BASE_ACTION_API_PATH}`, + path: `${BASE_ACTION_API_PATH}/connectors`, validate: {}, }, - router.handleLegacyErrors(async function (context, req, res) { - 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, - }); - }) + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = context.actions.getActionsClient(); + const result = await actionsClient.getAll(); + return res.ok({ + body: rewriteBodyRes(result), + }); + }) + ) ); }; diff --git a/x-pack/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts index 14f71269db5ff..a236e514ef78d 100644 --- a/x-pack/plugins/actions/server/routes/index.ts +++ b/x-pack/plugins/actions/server/routes/index.ts @@ -5,10 +5,29 @@ * 2.0. */ -export { createActionRoute } from './create'; -export { deleteActionRoute } from './delete'; -export { getAllActionRoute } from './get_all'; -export { getActionRoute } from './get'; -export { updateActionRoute } from './update'; -export { listActionTypesRoute } from './list_action_types'; -export { executeActionRoute } from './execute'; +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../lib'; +import { ActionsRequestHandlerContext } from '../types'; +import { createActionRoute } from './create'; +import { deleteActionRoute } from './delete'; +import { executeActionRoute } from './execute'; +import { getActionRoute } from './get'; +import { getAllActionRoute } from './get_all'; +import { connectorTypesRoute } from './connector_types'; +import { updateActionRoute } from './update'; +import { defineLegacyRoutes } from './legacy'; + +export function defineRoutes( + router: IRouter, + licenseState: ILicenseState +) { + defineLegacyRoutes(router, licenseState); + + createActionRoute(router, licenseState); + deleteActionRoute(router, licenseState); + getActionRoute(router, licenseState); + getAllActionRoute(router, licenseState); + updateActionRoute(router, licenseState); + connectorTypesRoute(router, licenseState); + executeActionRoute(router, licenseState); +} diff --git a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/actions/server/routes/legacy/_mock_handler_arguments.ts similarity index 75% rename from x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts rename to x-pack/plugins/actions/server/routes/legacy/_mock_handler_arguments.ts index c27699ba665dc..2a2452b02cc7d 100644 --- a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/actions/server/routes/legacy/_mock_handler_arguments.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; +import { KibanaRequest, KibanaResponseFactory } from 'kibana/server'; import { identity } from 'lodash'; import type { MethodKeysOf } from '@kbn/utility-types'; -import { httpServerMock } from '../../../../../src/core/server/mocks'; -import { ActionType } from '../../common'; -import { ActionsClientMock, actionsClientMock } from '../actions_client.mock'; +import { httpServerMock } from '../../../../../../src/core/server/mocks'; +import { ActionType } from '../../../common'; +import { ActionsClientMock, actionsClientMock } from '../../actions_client.mock'; +import { ActionsRequestHandlerContext } from '../../types'; export function mockHandlerArguments( { @@ -19,7 +20,7 @@ export function mockHandlerArguments( }: { actionsClient?: ActionsClientMock; listTypes?: ActionType[] }, req: unknown, res?: Array> -): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { +): [ActionsRequestHandlerContext, KibanaRequest, KibanaResponseFactory] { const listTypes = jest.fn(() => listTypesRes); return [ ({ @@ -37,7 +38,7 @@ export function mockHandlerArguments( ); }, }, - } as unknown) as RequestHandlerContext, + } as unknown) as ActionsRequestHandlerContext, req as KibanaRequest, mockResponseFactory(res), ]; diff --git a/x-pack/plugins/actions/server/routes/legacy/create.test.ts b/x-pack/plugins/actions/server/routes/legacy/create.test.ts new file mode 100644 index 0000000000000..3993319d1471f --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/create.test.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createActionRoute } from './create'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../../actions_client.mock'; +import { verifyAccessAndContext } from '../verify_access_and_context'; + +jest.mock('../verify_access_and_context.ts', () => ({ + verifyAccessAndContext: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); + (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); +}); + +describe('createActionRoute', () => { + it('creates an action with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + createActionRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions/action"`); + + const createResult = { + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + isPreconfigured: false, + }; + + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce(createResult); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: { + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + secrets: {}, + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ body: createResult }); + + expect(actionsClient.create).toHaveBeenCalledTimes(1); + expect(actionsClient.create.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "action": Object { + "actionTypeId": "abc", + "config": Object { + "foo": true, + }, + "name": "My name", + "secrets": Object {}, + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: createResult, + }); + }); + + it('ensures the license allows creating actions', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + createActionRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce({ + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + isPreconfigured: false, + }); + + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); + + await handler(context, req, res); + + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); + }); + + it('ensures the license check prevents creating actions', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { + throw new Error('OMG'); + }); + + createActionRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + const actionsClient = actionsClientMock.create(); + actionsClient.create.mockResolvedValueOnce({ + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + isPreconfigured: false, + }); + + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/legacy/create.ts b/x-pack/plugins/actions/server/routes/legacy/create.ts new file mode 100644 index 0000000000000..caed699641673 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/create.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ActionsRequestHandlerContext } from '../../types'; +import { ILicenseState } from '../../lib'; +import { BASE_ACTION_API_PATH } from '../../../common'; +import { verifyAccessAndContext } from '../verify_access_and_context'; + +export const bodySchema = schema.object({ + name: schema.string(), + actionTypeId: schema.string(), + config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), +}); + +export const createActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ACTION_API_PATH}/action`, + validate: { + body: bodySchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = context.actions.getActionsClient(); + const action = req.body; + return res.ok({ + body: await actionsClient.create({ action }), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/legacy/delete.test.ts b/x-pack/plugins/actions/server/routes/legacy/delete.test.ts new file mode 100644 index 0000000000000..cee78d998d62a --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/delete.test.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { deleteActionRoute } from './delete'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../../mocks'; + +jest.mock('../../lib/verify_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('deleteActionRoute', () => { + it('deletes an action with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + deleteActionRoute(router, licenseState); + + const [config, handler] = router.delete.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions/action/{id}"`); + + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(actionsClient.delete).toHaveBeenCalledTimes(1); + expect(actionsClient.delete.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the license allows deleting actions', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + deleteActionRoute(router, licenseState); + + const [, handler] = router.delete.mock.calls[0]; + + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + } + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents deleting actions', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + deleteActionRoute(router, licenseState); + + const [, handler] = router.delete.mock.calls[0]; + + const actionsClient = actionsClientMock.create(); + actionsClient.delete.mockResolvedValueOnce({}); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + id: '1', + } + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/legacy/delete.ts b/x-pack/plugins/actions/server/routes/legacy/delete.ts new file mode 100644 index 0000000000000..9b3c449607b4a --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/delete.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../../lib'; +import { BASE_ACTION_API_PATH } from '../../../common'; +import { ActionsRequestHandlerContext } from '../../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const deleteActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.delete( + { + path: `${BASE_ACTION_API_PATH}/action/{id}`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors(async function (context, req, res) { + verifyApiAccess(licenseState); + if (!context.actions) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); + } + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; + 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/actions/server/routes/legacy/execute.test.ts b/x-pack/plugins/actions/server/routes/legacy/execute.test.ts new file mode 100644 index 0000000000000..2ac53ddaaedf6 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/execute.test.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { executeActionRoute } from './execute'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { verifyApiAccess, ActionTypeDisabledError, asHttpRequestExecutionSource } from '../../lib'; +import { actionsClientMock } from '../../actions_client.mock'; +import { ActionTypeExecutorResult } from '../../types'; + +jest.mock('../../lib/verify_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('executeActionRoute', () => { + it('executes an action with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockResolvedValueOnce({ status: 'ok', actionId: '1' }); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: { + params: { + someData: 'data', + }, + }, + params: { + id: '1', + }, + }, + ['ok'] + ); + + const executeResult = { + actionId: '1', + status: 'ok', + }; + + executeActionRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions/action/{id}/_execute"`); + + expect(await handler(context, req, res)).toEqual({ body: executeResult }); + + expect(actionsClient.execute).toHaveBeenCalledWith({ + actionId: '1', + params: { + someData: 'data', + }, + source: asHttpRequestExecutionSource(req), + }); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('returns a "204 NO CONTENT" when the executor returns a nullish value', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockResolvedValueOnce( + (null as unknown) as ActionTypeExecutorResult + ); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: { + params: {}, + }, + params: { + id: '1', + }, + }, + ['noContent'] + ); + + executeActionRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(actionsClient.execute).toHaveBeenCalledWith({ + actionId: '1', + params: {}, + source: asHttpRequestExecutionSource(req), + }); + + expect(res.ok).not.toHaveBeenCalled(); + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the license allows action execution', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockResolvedValue({ + actionId: '1', + status: 'ok', + }); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: {}, + params: {}, + }, + ['ok'] + ); + + executeActionRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents action execution', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockResolvedValue({ + actionId: '1', + status: 'ok', + }); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: {}, + params: {}, + }, + ['ok'] + ); + + executeActionRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the action type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: {}, + params: {}, + }, + ['ok', 'forbidden'] + ); + + executeActionRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/legacy/execute.ts b/x-pack/plugins/actions/server/routes/legacy/execute.ts new file mode 100644 index 0000000000000..f6ddec1d01c20 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/execute.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../../lib'; + +import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../../types'; +import { BASE_ACTION_API_PATH } from '../../../common'; +import { asHttpRequestExecutionSource } from '../../lib/action_execution_source'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const bodySchema = schema.object({ + params: schema.recordOf(schema.string(), schema.any()), +}); + +export const executeActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ACTION_API_PATH}/action/{id}/_execute`, + validate: { + body: bodySchema, + params: paramSchema, + }, + }, + router.handleLegacyErrors(async function (context, req, res) { + verifyApiAccess(licenseState); + + if (!context.actions) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); + } + + const actionsClient = context.actions.getActionsClient(); + const { params } = req.body; + const { id } = req.params; + try { + const body: ActionTypeExecutorResult = await actionsClient.execute({ + params, + actionId: id, + source: asHttpRequestExecutionSource(req), + }); + return body + ? res.ok({ + body, + }) + : res.noContent(); + } catch (e) { + if (isErrorThatHandlesItsOwnResponse(e)) { + return e.sendResponse(res); + } + throw e; + } + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/legacy/get.test.ts b/x-pack/plugins/actions/server/routes/legacy/get.test.ts new file mode 100644 index 0000000000000..4d1265030141f --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/get.test.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getActionRoute } from './get'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../../actions_client.mock'; + +jest.mock('../../lib/verify_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getActionRoute', () => { + it('gets an action with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions/action/{id}"`); + + const getResult = { + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + isPreconfigured: false, + }; + + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce(getResult); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "actionTypeId": "2", + "config": Object {}, + "id": "1", + "isPreconfigured": false, + "name": "action name", + }, + } + `); + + expect(actionsClient.get).toHaveBeenCalledTimes(1); + expect(actionsClient.get.mock.calls[0][0].id).toEqual('1'); + + expect(res.ok).toHaveBeenCalledWith({ + body: getResult, + }); + }); + + it('ensures the license allows getting actions', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getActionRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce({ + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + isPreconfigured: false, + }); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents getting actions', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + getActionRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + const actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValueOnce({ + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + isPreconfigured: false, + }); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/legacy/get.ts b/x-pack/plugins/actions/server/routes/legacy/get.ts new file mode 100644 index 0000000000000..44780d4f8a14b --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/get.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ILicenseState, verifyApiAccess } from '../../lib'; +import { BASE_ACTION_API_PATH } from '../../../common'; +import { ActionsRequestHandlerContext } from '../../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const getActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${BASE_ACTION_API_PATH}/action/{id}`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors(async function (context, req, res) { + verifyApiAccess(licenseState); + if (!context.actions) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); + } + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; + return res.ok({ + body: await actionsClient.get({ id }), + }); + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/legacy/get_all.test.ts b/x-pack/plugins/actions/server/routes/legacy/get_all.test.ts new file mode 100644 index 0000000000000..7f1003e564614 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/get_all.test.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getAllActionRoute } from './get_all'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../../actions_client.mock'; + +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 = httpServiceMock.createRouter(); + + getAllActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions"`); + + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.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 = httpServiceMock.createRouter(); + + getAllActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions"`); + + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.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 = httpServiceMock.createRouter(); + + (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/actions"`); + + const actionsClient = actionsClientMock.create(); + actionsClient.getAll.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/legacy/get_all.ts b/x-pack/plugins/actions/server/routes/legacy/get_all.ts new file mode 100644 index 0000000000000..9ea5024d8672b --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/get_all.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { ILicenseState, verifyApiAccess } from '../../lib'; +import { BASE_ACTION_API_PATH } from '../../../common'; +import { ActionsRequestHandlerContext } from '../../types'; + +export const getAllActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${BASE_ACTION_API_PATH}`, + validate: {}, + }, + router.handleLegacyErrors(async function (context, req, res) { + 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/legacy/index.ts b/x-pack/plugins/actions/server/routes/legacy/index.ts new file mode 100644 index 0000000000000..1a22cd9be5681 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/index.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../../lib'; +import { ActionsRequestHandlerContext } from '../../types'; +import { createActionRoute } from './create'; +import { deleteActionRoute } from './delete'; +import { getAllActionRoute } from './get_all'; +import { getActionRoute } from './get'; +import { updateActionRoute } from './update'; +import { listActionTypesRoute } from './list_action_types'; +import { executeActionRoute } from './execute'; + +export function defineLegacyRoutes( + router: IRouter, + licenseState: ILicenseState +) { + createActionRoute(router, licenseState); + deleteActionRoute(router, licenseState); + getActionRoute(router, licenseState); + getAllActionRoute(router, licenseState); + updateActionRoute(router, licenseState); + listActionTypesRoute(router, licenseState); + executeActionRoute(router, licenseState); +} diff --git a/x-pack/plugins/actions/server/routes/list_action_types.test.ts b/x-pack/plugins/actions/server/routes/legacy/list_action_types.test.ts similarity index 93% rename from x-pack/plugins/actions/server/routes/list_action_types.test.ts rename to x-pack/plugins/actions/server/routes/legacy/list_action_types.test.ts index 102805e019420..e49dd251136ad 100644 --- a/x-pack/plugins/actions/server/routes/list_action_types.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/list_action_types.test.ts @@ -7,13 +7,13 @@ import { listActionTypesRoute } from './list_action_types'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib'; import { mockHandlerArguments } from './_mock_handler_arguments'; -import { LicenseType } from '../../../../plugins/licensing/server'; -import { actionsClientMock } from '../mocks'; +import { LicenseType } from '../../../../../plugins/licensing/server'; +import { actionsClientMock } from '../../mocks'; -jest.mock('../lib/verify_api_access.ts', () => ({ +jest.mock('../../lib/verify_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/actions/server/routes/list_action_types.ts b/x-pack/plugins/actions/server/routes/legacy/list_action_types.ts similarity index 83% rename from x-pack/plugins/actions/server/routes/list_action_types.ts rename to x-pack/plugins/actions/server/routes/legacy/list_action_types.ts index 72aba7b6dbbd2..814f5fffd35ff 100644 --- a/x-pack/plugins/actions/server/routes/list_action_types.ts +++ b/x-pack/plugins/actions/server/routes/legacy/list_action_types.ts @@ -6,9 +6,9 @@ */ import { IRouter } from 'kibana/server'; -import { ILicenseState, verifyApiAccess } from '../lib'; -import { BASE_ACTION_API_PATH } from '../../common'; -import { ActionsRequestHandlerContext } from '../types'; +import { ILicenseState, verifyApiAccess } from '../../lib'; +import { BASE_ACTION_API_PATH } from '../../../common'; +import { ActionsRequestHandlerContext } from '../../types'; export const listActionTypesRoute = ( router: IRouter, diff --git a/x-pack/plugins/actions/server/routes/legacy/update.test.ts b/x-pack/plugins/actions/server/routes/legacy/update.test.ts new file mode 100644 index 0000000000000..0ce49751753b2 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/update.test.ts @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { updateActionRoute } from './update'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess, ActionTypeDisabledError } from '../../lib'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { actionsClientMock } from '../../actions_client.mock'; + +jest.mock('../../lib/verify_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('updateActionRoute', () => { + it('updates an action with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateActionRoute(router, licenseState); + + const [config, handler] = router.put.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/actions/action/{id}"`); + + const updateResult = { + id: '1', + actionTypeId: 'my-action-type-id', + name: 'My name', + config: { foo: true }, + isPreconfigured: false, + }; + + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { + id: '1', + }, + body: { + name: 'My name', + config: { foo: true }, + secrets: { key: 'i8oh34yf9783y39' }, + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ body: updateResult }); + + expect(actionsClient.update).toHaveBeenCalledTimes(1); + expect(actionsClient.update.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "action": Object { + "config": Object { + "foo": true, + }, + "name": "My name", + "secrets": Object { + "key": "i8oh34yf9783y39", + }, + }, + "id": "1", + }, + ] + `); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('ensures the license allows deleting actions', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateActionRoute(router, licenseState); + + const [, handler] = router.put.mock.calls[0]; + + const updateResult = { + id: '1', + actionTypeId: 'my-action-type-id', + name: 'My name', + config: { foo: true }, + isPreconfigured: false, + }; + + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { + id: '1', + }, + body: { + name: 'My name', + config: { foo: true }, + secrets: { key: 'i8oh34yf9783y39' }, + }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents deleting actions', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + updateActionRoute(router, licenseState); + + const [, handler] = router.put.mock.calls[0]; + + const updateResult = { + id: '1', + actionTypeId: 'my-action-type-id', + name: 'My name', + config: { foo: true }, + isPreconfigured: false, + }; + + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockResolvedValueOnce(updateResult); + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { + id: '1', + }, + body: { + name: 'My name', + config: { foo: true }, + secrets: { key: 'i8oh34yf9783y39' }, + }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the action type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateActionRoute(router, licenseState); + + const [, handler] = router.put.mock.calls[0]; + + const actionsClient = actionsClientMock.create(); + actionsClient.update.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); + + const [context, req, res] = mockHandlerArguments({ actionsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/legacy/update.ts b/x-pack/plugins/actions/server/routes/legacy/update.ts new file mode 100644 index 0000000000000..6cbcd56af5048 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/legacy/update.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../../lib'; +import { BASE_ACTION_API_PATH } from '../../../common'; +import { ActionsRequestHandlerContext } from '../../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const bodySchema = schema.object({ + name: schema.string(), + config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), +}); + +export const updateActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.put( + { + path: `${BASE_ACTION_API_PATH}/action/{id}`, + validate: { + body: bodySchema, + params: paramSchema, + }, + }, + router.handleLegacyErrors(async function (context, req, res) { + verifyApiAccess(licenseState); + if (!context.actions) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); + } + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; + const { name, config, secrets } = req.body; + + try { + return res.ok({ + body: await actionsClient.update({ + id, + action: { name, config, secrets }, + }), + }); + } catch (e) { + if (isErrorThatHandlesItsOwnResponse(e)) { + return e.sendResponse(res); + } + throw e; + } + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/rewrite_request_case.ts b/x-pack/plugins/actions/server/routes/rewrite_request_case.ts new file mode 100644 index 0000000000000..c11a82f4af5ac --- /dev/null +++ b/x-pack/plugins/actions/server/routes/rewrite_request_case.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +type RenameActionToConnector = K extends `actionTypeId` + ? `connectorTypeId` + : K extends `actionId` + ? `connectorId` + : K; + +export type AsApiContract = { + [K in keyof T as CamelToSnake>>]: T[K]; +}; + +export type RewriteRequestCase = (requested: AsApiContract) => T; +export type RewriteResponseCase = ( + responded: T +) => T extends Array ? Array> : AsApiContract; + +/** + * This type maps Camel Case strings into their Snake Case version. + * This is achieved by checking each character and, if it is an uppercase character, it is mapped to an + * underscore followed by a lowercase one. + * + * The reason there are two ternaries is that, for perfformance reasons, TS limits its + * character parsing to ~15 characters. + * To get around this we use the second turnery to parse 2 characters at a time, which allows us to support + * strings that are 30 characters long. + * + * If you get the TS #2589 error ("Type instantiation is excessively deep and possibly infinite") then most + * likely you have a string that's longer than 30 characters. + * Address this by reducing the length if possible, otherwise, you'll need to add a 3rd ternary which + * parses 3 chars at a time :grimace: + * + * For more details see this PR comment: https://github.com/microsoft/TypeScript/pull/40336#issuecomment-686723087 + */ +type CamelToSnake = string extends T + ? string + : T extends `${infer C0}${infer C1}${infer R}` + ? `${C0 extends Uppercase ? '_' : ''}${Lowercase}${C1 extends Uppercase + ? '_' + : ''}${Lowercase}${CamelToSnake}` + : T extends `${infer C0}${infer R}` + ? `${C0 extends Uppercase ? '_' : ''}${Lowercase}${CamelToSnake}` + : ''; diff --git a/x-pack/plugins/actions/server/routes/update.test.ts b/x-pack/plugins/actions/server/routes/update.test.ts index ed539522c00d5..653fcba75da95 100644 --- a/x-pack/plugins/actions/server/routes/update.test.ts +++ b/x-pack/plugins/actions/server/routes/update.test.ts @@ -8,16 +8,17 @@ import { updateActionRoute } from './update'; import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess, ActionTypeDisabledError } from '../lib'; -import { mockHandlerArguments } from './_mock_handler_arguments'; +import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; import { actionsClientMock } from '../actions_client.mock'; +import { verifyAccessAndContext } from './verify_access_and_context'; -jest.mock('../lib/verify_api_access.ts', () => ({ - verifyApiAccess: jest.fn(), +jest.mock('./verify_access_and_context.ts', () => ({ + verifyAccessAndContext: jest.fn(), })); beforeEach(() => { jest.resetAllMocks(); + (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); }); describe('updateActionRoute', () => { @@ -29,7 +30,7 @@ describe('updateActionRoute', () => { const [config, handler] = router.put.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions/action/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector/{id}"`); const updateResult = { id: '1', @@ -57,7 +58,15 @@ describe('updateActionRoute', () => { ['ok'] ); - expect(await handler(context, req, res)).toEqual({ body: updateResult }); + expect(await handler(context, req, res)).toEqual({ + body: { + id: '1', + connector_type_id: 'my-action-type-id', + name: 'My name', + config: { foo: true }, + is_preconfigured: false, + }, + }); expect(actionsClient.update).toHaveBeenCalledTimes(1); expect(actionsClient.update.mock.calls[0]).toMatchInlineSnapshot(` @@ -116,14 +125,14 @@ describe('updateActionRoute', () => { await handler(context, req, res); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); it('ensures the license check prevents deleting actions', async () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); - (verifyApiAccess as jest.Mock).mockImplementation(() => { + (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { throw new Error('OMG'); }); @@ -159,27 +168,6 @@ describe('updateActionRoute', () => { expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('ensures the action type gets validated for the license', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - updateActionRoute(router, licenseState); - - const [, handler] = router.put.mock.calls[0]; - - const actionsClient = actionsClientMock.create(); - actionsClient.update.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); - - const [context, req, res] = mockHandlerArguments({ actionsClient }, { params: {}, body: {} }, [ - 'ok', - 'forbidden', - ]); - - await handler(context, req, res); - - expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); }); diff --git a/x-pack/plugins/actions/server/routes/update.ts b/x-pack/plugins/actions/server/routes/update.ts index 97cc11a5d0ad4..af55fa32b76ca 100644 --- a/x-pack/plugins/actions/server/routes/update.ts +++ b/x-pack/plugins/actions/server/routes/update.ts @@ -7,9 +7,11 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from 'kibana/server'; -import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; +import { ILicenseState } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; -import { ActionsRequestHandlerContext } from '../types'; +import { ActionResult, ActionsRequestHandlerContext } from '../types'; +import { verifyAccessAndContext } from './verify_access_and_context'; +import { RewriteResponseCase } from './rewrite_request_case'; const paramSchema = schema.object({ id: schema.string(), @@ -21,40 +23,43 @@ const bodySchema = schema.object({ secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), }); +const rewriteBodyRes: RewriteResponseCase = ({ + actionTypeId, + isPreconfigured, + ...res +}) => ({ + ...res, + connector_type_id: actionTypeId, + is_preconfigured: isPreconfigured, +}); + export const updateActionRoute = ( router: IRouter, licenseState: ILicenseState ) => { router.put( { - path: `${BASE_ACTION_API_PATH}/action/{id}`, + path: `${BASE_ACTION_API_PATH}/connector/{id}`, validate: { body: bodySchema, params: paramSchema, }, }, - router.handleLegacyErrors(async function (context, req, res) { - verifyApiAccess(licenseState); - if (!context.actions) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); - } - const actionsClient = context.actions.getActionsClient(); - const { id } = req.params; - const { name, config, secrets } = req.body; + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; + const { name, config, secrets } = req.body; - try { return res.ok({ - body: await actionsClient.update({ - id, - action: { name, config, secrets }, - }), + body: rewriteBodyRes( + await actionsClient.update({ + id, + action: { name, config, secrets }, + }) + ), }); - } catch (e) { - if (isErrorThatHandlesItsOwnResponse(e)) { - return e.sendResponse(res); - } - throw e; - } - }) + }) + ) ); }; diff --git a/x-pack/plugins/actions/server/routes/verify_access_and_context.test.ts b/x-pack/plugins/actions/server/routes/verify_access_and_context.test.ts new file mode 100644 index 0000000000000..f13bc7279e346 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/verify_access_and_context.test.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess, ActionTypeDisabledError } from '../lib'; +import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; +import { actionsClientMock } from '../actions_client.mock'; +import { verifyAccessAndContext } from './verify_access_and_context'; + +jest.mock('../lib/verify_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('verifyAccessAndContext', () => { + it('ensures the license allows creating actions', async () => { + const licenseState = licenseStateMock.create(); + + const handler = jest.fn(); + const verify = verifyAccessAndContext(licenseState, handler); + + const actionsClient = actionsClientMock.create(); + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); + + await verify(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents creating actions', async () => { + const licenseState = licenseStateMock.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + const handler = jest.fn(); + const verify = verifyAccessAndContext(licenseState, handler); + + const actionsClient = actionsClientMock.create(); + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); + + await expect(verify(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('supports error that handle their own response', async () => { + const licenseState = licenseStateMock.create(); + + const handler = jest.fn(); + const verify = verifyAccessAndContext(licenseState, handler); + + const actionsClient = actionsClientMock.create(); + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok', 'forbidden']); + + handler.mockRejectedValue(new ActionTypeDisabledError('Fail', 'license_invalid')); + + await expect(verify(context, req, res)).resolves.toMatchObject({ body: { message: 'Fail' } }); + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/verify_access_and_context.ts b/x-pack/plugins/actions/server/routes/verify_access_and_context.ts new file mode 100644 index 0000000000000..3f1fe4b1c229f --- /dev/null +++ b/x-pack/plugins/actions/server/routes/verify_access_and_context.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RequestHandler } from 'kibana/server'; +import { ILicenseState, isErrorThatHandlesItsOwnResponse, verifyApiAccess } from '../lib'; +import { ActionsRequestHandlerContext } from '../types'; + +type ActionsRequestHandlerWrapper = ( + licenseState: ILicenseState, + handler: RequestHandler +) => RequestHandler; + +export const verifyAccessAndContext: ActionsRequestHandlerWrapper = (licenseState, handler) => { + return async (context, request, response) => { + verifyApiAccess(licenseState); + + if (!context.actions) { + return response.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); + } + + try { + return await handler(context, request, response); + } catch (e) { + if (isErrorThatHandlesItsOwnResponse(e)) { + return e.sendResponse(response); + } + throw e; + } + }; +}; 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 a15bd0a268df5..518bfcef9e0d3 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 @@ -18,11 +18,11 @@ export default function emailTest({ getService }: FtrProviderContext) { it('should return 200 when creating an email action successfully', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An email action', - actionTypeId: '.email', + connector_type_id: '.email', config: { service: '__json', from: 'bob@example.com', @@ -38,9 +38,9 @@ export default function emailTest({ getService }: FtrProviderContext) { createdActionId = createdAction.id; expect(createdAction).to.eql({ id: createdActionId, - isPreconfigured: false, + is_preconfigured: false, name: 'An email action', - actionTypeId: '.email', + connector_type_id: '.email', config: { service: '__json', hasAuth: true, @@ -54,14 +54,14 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(typeof createdActionId).to.be('string'); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdActionId}`) + .get(`/api/actions/connector/${createdActionId}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'An email action', - actionTypeId: '.email', + connector_type_id: '.email', config: { from: 'bob@example.com', service: '__json', @@ -75,7 +75,7 @@ export default function emailTest({ getService }: FtrProviderContext) { it('should return the message data when firing the __json service', async () => { await supertest - .post(`/api/actions/action/${createdActionId}/_execute`) + .post(`/api/actions/connector/${createdActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -119,7 +119,7 @@ export default function emailTest({ getService }: FtrProviderContext) { it('should render html from markdown', async () => { await supertest - .post(`/api/actions/action/${createdActionId}/_execute`) + .post(`/api/actions/connector/${createdActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -142,7 +142,7 @@ export default function emailTest({ getService }: FtrProviderContext) { it('should allow customizing the kibana footer link', async () => { await supertest - .post(`/api/actions/action/${createdActionId}/_execute`) + .post(`/api/actions/connector/${createdActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -169,11 +169,11 @@ export default function emailTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating an email action with an invalid config', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An email action', - actionTypeId: '.email', + connector_type_id: '.email', config: {}, }) .expect(400) @@ -189,11 +189,11 @@ export default function emailTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating an email action with a server not added to allowedHosts', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An email action', - actionTypeId: '.email', + connector_type_id: '.email', config: { service: 'gmail', // not added to allowedHosts in the config for this test from: 'bob@example.com', @@ -214,11 +214,11 @@ export default function emailTest({ getService }: FtrProviderContext) { }); await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An email action', - actionTypeId: '.email', + connector_type_id: '.email', config: { host: 'stmp.gmail.com', // not added to allowedHosts in the config for this test port: 666, @@ -242,11 +242,11 @@ export default function emailTest({ getService }: FtrProviderContext) { it('should handle creating an email action with a server added to allowedHosts', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An email action', - actionTypeId: '.email', + connector_type_id: '.email', config: { host: 'some.non.existent.com', // added to allowedHosts in the config for this test port: 666, @@ -263,11 +263,11 @@ export default function emailTest({ getService }: FtrProviderContext) { it('should handle an email action with no auth', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An email action with no auth', - actionTypeId: '.email', + connector_type_id: '.email', config: { service: '__json', from: 'jim@example.com', @@ -276,7 +276,7 @@ export default function emailTest({ getService }: FtrProviderContext) { .expect(200); await supertest - .post(`/api/actions/action/${createdAction.id}/_execute`) + .post(`/api/actions/connector/${createdAction.id}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { 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 dfc3de5609a18..432598e66bcc4 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 @@ -26,11 +26,11 @@ export default function indexTest({ getService }: FtrProviderContext) { it('should be created successfully', async () => { // create action with no config const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An index action', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, }, @@ -40,9 +40,9 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'An index action', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: false, @@ -53,24 +53,24 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(typeof createdActionID).to.be('string'); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdActionID}`) + .get(`/api/actions/connector/${createdActionID}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'An index action', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: false, executionTimeField: null }, }); // create action with all config props const { body: createdActionWithIndex } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An index action with index config', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: true, @@ -81,9 +81,9 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdActionWithIndex).to.eql({ id: createdActionWithIndex.id, - isPreconfigured: false, + is_preconfigured: false, name: 'An index action with index config', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: true, @@ -94,14 +94,14 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(typeof createdActionIDWithIndex).to.be('string'); const { body: fetchedActionWithIndex } = await supertest - .get(`/api/actions/action/${createdActionIDWithIndex}`) + .get(`/api/actions/connector/${createdActionIDWithIndex}`) .expect(200); expect(fetchedActionWithIndex).to.eql({ id: fetchedActionWithIndex.id, - isPreconfigured: false, + is_preconfigured: false, name: 'An index action with index config', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: true, @@ -112,11 +112,11 @@ export default function indexTest({ getService }: FtrProviderContext) { it('should respond with error when creation unsuccessful', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An index action', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: 666 }, }) .expect(400) @@ -132,11 +132,11 @@ export default function indexTest({ getService }: FtrProviderContext) { it('should execute successly when expected for a single body', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An index action', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: true, @@ -145,7 +145,7 @@ export default function indexTest({ getService }: FtrProviderContext) { }) .expect(200); const { body: result } = await supertest - .post(`/api/actions/action/${createdAction.id}/_execute`) + .post(`/api/actions/connector/${createdAction.id}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -162,11 +162,11 @@ export default function indexTest({ getService }: FtrProviderContext) { it('should execute successly when expected for with multiple bodies', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An index action', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: true, @@ -175,7 +175,7 @@ export default function indexTest({ getService }: FtrProviderContext) { }) .expect(200); const { body: result } = await supertest - .post(`/api/actions/action/${createdAction.id}/_execute`) + .post(`/api/actions/connector/${createdAction.id}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -206,11 +206,11 @@ export default function indexTest({ getService }: FtrProviderContext) { it('should execute successly with refresh false', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An index action', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: false, @@ -220,7 +220,7 @@ export default function indexTest({ getService }: FtrProviderContext) { }) .expect(200); const { body: result } = await supertest - .post(`/api/actions/action/${createdAction.id}/_execute`) + .post(`/api/actions/connector/${createdAction.id}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -235,11 +235,11 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(items.length).to.be.lessThan(2); const { body: createdActionWithRefresh } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An index action', - actionTypeId: '.index', + connector_type_id: '.index', config: { index: ES_TEST_INDEX_NAME, refresh: true, @@ -248,7 +248,7 @@ export default function indexTest({ getService }: FtrProviderContext) { }) .expect(200); const { body: result2 } = await supertest - .post(`/api/actions/action/${createdActionWithRefresh.id}/_execute`) + .post(`/api/actions/connector/${createdActionWithRefresh.id}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts index 69c1e54486d65..48f042473c14e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts @@ -24,7 +24,7 @@ export default function indexTest({ getService }: FtrProviderContext) { it('should execute successfully when expected for a single body', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${ACTION_ID}/_execute`) + .post(`/api/actions/connector/${ACTION_ID}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts index 8bd0b8a790d40..d2e2b4be9b94c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts @@ -60,11 +60,11 @@ export default function jiraTest({ getService }: FtrProviderContext) { describe('Jira - Action Creation', () => { it('should return 200 when creating a jira action successfully', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A jira action', - actionTypeId: '.jira', + connector_type_id: '.jira', config: { ...mockJira.config, apiUrl: jiraSimulatorURL, @@ -75,9 +75,9 @@ export default function jiraTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A jira action', - actionTypeId: '.jira', + connector_type_id: '.jira', config: { apiUrl: jiraSimulatorURL, projectKey: mockJira.config.projectKey, @@ -85,14 +85,14 @@ export default function jiraTest({ getService }: FtrProviderContext) { }); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdAction.id}`) + .get(`/api/actions/connector/${createdAction.id}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A jira action', - actionTypeId: '.jira', + connector_type_id: '.jira', config: { apiUrl: jiraSimulatorURL, projectKey: mockJira.config.projectKey, @@ -102,11 +102,11 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a jira action with no apiUrl', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A jira action', - actionTypeId: '.jira', + connector_type_id: '.jira', config: { projectKey: 'CK' }, }) .expect(400) @@ -122,11 +122,11 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a jira action with no projectKey', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A jira action', - actionTypeId: '.jira', + connector_type_id: '.jira', config: { apiUrl: jiraSimulatorURL }, }) .expect(400) @@ -142,11 +142,11 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a jira action with a not present in allowedHosts apiUrl', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A jira action', - actionTypeId: '.jira', + connector_type_id: '.jira', config: { apiUrl: 'http://jira.mynonexistent.com', projectKey: mockJira.config.projectKey, @@ -166,11 +166,11 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a jira action without secrets', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A jira action', - actionTypeId: '.jira', + connector_type_id: '.jira', config: { apiUrl: jiraSimulatorURL, projectKey: mockJira.config.projectKey, @@ -195,11 +195,11 @@ export default function jiraTest({ getService }: FtrProviderContext) { before(async () => { const { body } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A jira simulator', - actionTypeId: '.jira', + connector_type_id: '.jira', config: { apiUrl: jiraSimulatorURL, projectKey: mockJira.config.projectKey, @@ -220,14 +220,14 @@ export default function jiraTest({ getService }: FtrProviderContext) { describe('Validation', () => { it('should handle failing with a simulated success without action', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: {}, }) .then((resp: any) => { - expect(Object.keys(resp.body)).to.eql(['status', 'actionId', 'message', 'retry']); - expect(resp.body.actionId).to.eql(simulatedActionId); + expect(Object.keys(resp.body)).to.eql(['status', 'message', 'retry', 'connector_id']); + expect(resp.body.connector_id).to.eql(simulatedActionId); expect(resp.body.status).to.eql('error'); expect(resp.body.retry).to.eql(false); // Node.js 12 oddity: @@ -261,14 +261,14 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without unsupported action', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { subAction: 'non-supported' }, }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -279,14 +279,14 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without subActionParams', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { subAction: 'pushToService' }, }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -297,7 +297,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without title', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -312,7 +312,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -323,7 +323,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without commentId', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -340,7 +340,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -351,7 +351,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without comment message', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -367,7 +367,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -378,7 +378,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success when labels containing a space', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -395,7 +395,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -408,7 +408,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { describe('Execution', () => { it('should handle creating an incident without comments', async () => { const { body } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -427,7 +427,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { expect(proxyHaveBeenCalled).to.equal(true); expect(body).to.eql({ status: 'ok', - actionId: simulatedActionId, + connector_id: simulatedActionId, data: { id: '123', title: 'CK-1', 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 405085c4f3ffb..dcdd8e86ec177 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 @@ -45,11 +45,11 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { it('should return successfully when passed valid create parameters', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A pagerduty action', - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', config: { apiUrl: pagerdutySimulatorURL, }, @@ -61,9 +61,9 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A pagerduty action', - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', config: { apiUrl: pagerdutySimulatorURL, }, @@ -72,14 +72,14 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(typeof createdAction.id).to.be('string'); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdAction.id}`) + .get(`/api/actions/connector/${createdAction.id}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A pagerduty action', - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', config: { apiUrl: pagerdutySimulatorURL, }, @@ -88,11 +88,11 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { it('should return unsuccessfully when passed invalid create parameters', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A pagerduty action', - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', config: { apiUrl: pagerdutySimulatorURL, }, @@ -111,11 +111,11 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { it('should return unsuccessfully when default pagerduty url is not present in allowedHosts', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A pagerduty action', - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', secrets: {}, }) .expect(400) @@ -131,11 +131,11 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { it('should create pagerduty simulator action successfully', async () => { const { body: createdSimulatedAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A pagerduty simulator', - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', config: { apiUrl: pagerdutySimulatorURL, }, @@ -150,7 +150,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { it('should handle executing with a simulated success', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -162,7 +162,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(proxyHaveBeenCalled).to.equal(true); expect(result).to.eql({ status: 'ok', - actionId: simulatedActionId, + connector_id: simulatedActionId, data: { message: 'Event processed', status: 'success', @@ -172,7 +172,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { it('should handle a 40x pagerduty error', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -186,7 +186,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { it('should handle a 429 pagerduty error', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -204,7 +204,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { it('should handle a 500 pagerduty error', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts index 682714758c0a0..18f082c688907 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts @@ -63,11 +63,11 @@ export default function resilientTest({ getService }: FtrProviderContext) { describe('IBM Resilient - Action Creation', () => { it('should return 200 when creating a ibm resilient action successfully', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An IBM Resilient action', - actionTypeId: '.resilient', + connector_type_id: '.resilient', config: { ...mockResilient.config, apiUrl: resilientSimulatorURL, @@ -78,9 +78,9 @@ export default function resilientTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'An IBM Resilient action', - actionTypeId: '.resilient', + connector_type_id: '.resilient', config: { apiUrl: resilientSimulatorURL, orgId: mockResilient.config.orgId, @@ -88,14 +88,14 @@ export default function resilientTest({ getService }: FtrProviderContext) { }); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdAction.id}`) + .get(`/api/actions/connector/${createdAction.id}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'An IBM Resilient action', - actionTypeId: '.resilient', + connector_type_id: '.resilient', config: { apiUrl: resilientSimulatorURL, orgId: mockResilient.config.orgId, @@ -105,11 +105,11 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a ibm resilient action with no apiUrl', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An IBM Resilient', - actionTypeId: '.resilient', + connector_type_id: '.resilient', config: { orgId: '201' }, }) .expect(400) @@ -125,11 +125,11 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a ibm resilient action with no orgId', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An IBM Resilient', - actionTypeId: '.resilient', + connector_type_id: '.resilient', config: { apiUrl: resilientSimulatorURL }, }) .expect(400) @@ -145,11 +145,11 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a ibm resilient action with a not present in allowedHosts apiUrl', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An IBM Resilient', - actionTypeId: '.resilient', + connector_type_id: '.resilient', config: { apiUrl: 'http://resilient.mynonexistent.com', orgId: mockResilient.config.orgId, @@ -169,11 +169,11 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a ibm resilient action without secrets', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'An IBM Resilient', - actionTypeId: '.resilient', + connector_type_id: '.resilient', config: { apiUrl: resilientSimulatorURL, orgId: mockResilient.config.orgId, @@ -197,11 +197,11 @@ export default function resilientTest({ getService }: FtrProviderContext) { let proxyHaveBeenCalled = false; before(async () => { const { body } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A ibm resilient simulator', - actionTypeId: '.resilient', + connector_type_id: '.resilient', config: { apiUrl: resilientSimulatorURL, orgId: mockResilient.config.orgId, @@ -222,14 +222,14 @@ export default function resilientTest({ getService }: FtrProviderContext) { describe('Validation', () => { it('should handle failing with a simulated success without action', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: {}, }) .then((resp: any) => { - expect(Object.keys(resp.body)).to.eql(['status', 'actionId', 'message', 'retry']); - expect(resp.body.actionId).to.eql(simulatedActionId); + expect(Object.keys(resp.body)).to.eql(['status', 'message', 'retry', 'connector_id']); + expect(resp.body.connector_id).to.eql(simulatedActionId); expect(resp.body.status).to.eql('error'); expect(resp.body.retry).to.eql(false); // Node.js 12 oddity: @@ -263,14 +263,14 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without unsupported action', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { subAction: 'non-supported' }, }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -281,14 +281,14 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without subActionParams', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { subAction: 'pushToService' }, }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -299,7 +299,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without title', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -314,7 +314,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -325,7 +325,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without commentId', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -341,7 +341,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -352,7 +352,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without comment message', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -368,7 +368,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -381,7 +381,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { describe('Execution', () => { it('should handle creating an incident without comments', async () => { const { body } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -397,7 +397,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { expect(proxyHaveBeenCalled).to.equal(true); expect(body).to.eql({ status: 'ok', - actionId: simulatedActionId, + connector_id: simulatedActionId, data: { id: '123', title: '123', 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 5357c94d7e0f2..3e8b11ee62162 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 @@ -18,41 +18,41 @@ export default function serverLogTest({ getService }: FtrProviderContext) { it('should return 200 when creating a builtin server-log action', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A server.log action', - actionTypeId: '.server-log', + connector_type_id: '.server-log', }) .expect(200); serverLogActionId = createdAction.id; expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A server.log action', - actionTypeId: '.server-log', + connector_type_id: '.server-log', config: {}, }); expect(typeof createdAction.id).to.be('string'); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdAction.id}`) + .get(`/api/actions/connector/${createdAction.id}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A server.log action', - actionTypeId: '.server-log', + connector_type_id: '.server-log', config: {}, }); }); it('should handle firing the action', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${serverLogActionId}/_execute`) + .post(`/api/actions/connector/${serverLogActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { 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 2d584f764e5e4..264e9cf42d97e 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 @@ -65,11 +65,11 @@ export default function servicenowTest({ getService }: FtrProviderContext) { describe('ServiceNow - Action Creation', () => { it('should return 200 when creating a servicenow action successfully', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A servicenow action', - actionTypeId: '.servicenow', + connector_type_id: '.servicenow', config: { apiUrl: servicenowSimulatorURL, }, @@ -79,23 +79,23 @@ export default function servicenowTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A servicenow action', - actionTypeId: '.servicenow', + connector_type_id: '.servicenow', config: { apiUrl: servicenowSimulatorURL, }, }); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdAction.id}`) + .get(`/api/actions/connector/${createdAction.id}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A servicenow action', - actionTypeId: '.servicenow', + connector_type_id: '.servicenow', config: { apiUrl: servicenowSimulatorURL, }, @@ -104,11 +104,11 @@ export default function servicenowTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a servicenow action with no apiUrl', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A servicenow action', - actionTypeId: '.servicenow', + connector_type_id: '.servicenow', config: {}, }) .expect(400) @@ -124,11 +124,11 @@ export default function servicenowTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a servicenow action with a not present in allowedHosts apiUrl', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A servicenow action', - actionTypeId: '.servicenow', + connector_type_id: '.servicenow', config: { apiUrl: 'http://servicenow.mynonexistent.com', }, @@ -147,11 +147,11 @@ export default function servicenowTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a servicenow action without secrets', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A servicenow action', - actionTypeId: '.servicenow', + connector_type_id: '.servicenow', config: { apiUrl: servicenowSimulatorURL, }, @@ -174,11 +174,11 @@ export default function servicenowTest({ getService }: FtrProviderContext) { let proxyHaveBeenCalled = false; before(async () => { const { body } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A servicenow simulator', - actionTypeId: '.servicenow', + connector_type_id: '.servicenow', config: { apiUrl: servicenowSimulatorURL, }, @@ -198,14 +198,14 @@ export default function servicenowTest({ getService }: FtrProviderContext) { describe('Validation', () => { it('should handle failing with a simulated success without action', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: {}, }) .then((resp: any) => { - expect(Object.keys(resp.body)).to.eql(['status', 'actionId', 'message', 'retry']); - expect(resp.body.actionId).to.eql(simulatedActionId); + expect(Object.keys(resp.body)).to.eql(['status', 'message', 'retry', 'connector_id']); + expect(resp.body.connector_id).to.eql(simulatedActionId); expect(resp.body.status).to.eql('error'); expect(resp.body.retry).to.eql(false); // Node.js 12 oddity: @@ -239,14 +239,14 @@ export default function servicenowTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without unsupported action', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { subAction: 'non-supported' }, }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -257,14 +257,14 @@ export default function servicenowTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without subActionParams', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { subAction: 'pushToService' }, }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -275,7 +275,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without title', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -287,7 +287,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -298,7 +298,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without commentId', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -314,7 +314,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -325,7 +325,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { it('should handle failing with a simulated success without comment message', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -341,7 +341,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -353,7 +353,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { describe('getChoices', () => { it('should fail when field is not provided', async () => { await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -363,7 +363,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { }) .then((resp: any) => { expect(resp.body).to.eql({ - actionId: simulatedActionId, + connector_id: simulatedActionId, status: 'error', retry: false, message: @@ -377,7 +377,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { describe('Execution', () => { it('should handle creating an incident without comments', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -393,7 +393,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { expect(proxyHaveBeenCalled).to.equal(true); expect(result).to.eql({ status: 'ok', - actionId: simulatedActionId, + connector_id: simulatedActionId, data: { id: '123', title: 'INC01', @@ -406,7 +406,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { describe('getChoices', () => { it('should get choices', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -419,7 +419,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { expect(proxyHaveBeenCalled).to.equal(true); expect(result).to.eql({ status: 'ok', - actionId: simulatedActionId, + connector_id: simulatedActionId, data: [ { dependent_value: '', 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 42cc93d50ea11..85c46ff98acd1 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 @@ -45,11 +45,11 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should return 200 when creating a slack action successfully', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A slack action', - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: { webhookUrl: slackSimulatorURL, }, @@ -58,34 +58,34 @@ export default function slackTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A slack action', - actionTypeId: '.slack', + connector_type_id: '.slack', config: {}, }); expect(typeof createdAction.id).to.be('string'); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdAction.id}`) + .get(`/api/actions/connector/${createdAction.id}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A slack action', - actionTypeId: '.slack', + connector_type_id: '.slack', config: {}, }); }); it('should respond with a 400 Bad Request when creating a slack action with no webhookUrl', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A slack action', - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: {}, }) .expect(400) @@ -101,11 +101,11 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a slack action with not present in allowedHosts webhookUrl', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A slack action', - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: { webhookUrl: 'http://slack.mynonexistent.com/other/stuff/in/the/path', }, @@ -122,11 +122,11 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should respond with a 400 Bad Request when creating a slack action with a webhookUrl with no hostname', async () => { await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A slack action', - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: { webhookUrl: 'fee-fi-fo-fum', }, @@ -144,11 +144,11 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should create our slack simulator action successfully', async () => { const { body: createdSimulatedAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'foo') .send({ name: 'A slack simulator', - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: { webhookUrl: slackSimulatorURL, }, @@ -160,7 +160,7 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should handle firing with a simulated success', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -174,7 +174,7 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should handle an empty message error', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -188,7 +188,7 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should handle a 40x slack error', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -203,7 +203,7 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should handle a 429 slack error', async () => { const dateStart = new Date().getTime(); const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { @@ -221,7 +221,7 @@ export default function slackTest({ getService }: FtrProviderContext) { it('should handle a 500 slack error', async () => { const { body: result } = await supertest - .post(`/api/actions/action/${simulatedActionId}/_execute`) + .post(`/api/actions/connector/${simulatedActionId}/_execute`) .set('kbn-xsrf', 'foo') .send({ params: { 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 61def8b6536ee..d48fb99088d61 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 @@ -54,11 +54,11 @@ export default function webhookTest({ getService }: FtrProviderContext) { }; const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'test') .send({ name: 'A generic Webhook action', - actionTypeId: '.webhook', + connector_type_id: '.webhook', secrets: { user, password, @@ -99,11 +99,11 @@ export default function webhookTest({ getService }: FtrProviderContext) { it('should return 200 when creating a webhook action successfully', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'test') .send({ name: 'A generic Webhook action', - actionTypeId: '.webhook', + connector_type_id: '.webhook', secrets: { user: 'username', password: 'mypassphrase', @@ -116,9 +116,9 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A generic Webhook action', - actionTypeId: '.webhook', + connector_type_id: '.webhook', config: { ...defaultValues, url: webhookSimulatorURL, @@ -128,14 +128,14 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(typeof createdAction.id).to.be('string'); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdAction.id}`) + .get(`/api/actions/connector/${createdAction.id}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A generic Webhook action', - actionTypeId: '.webhook', + connector_type_id: '.webhook', config: { ...defaultValues, url: webhookSimulatorURL, @@ -145,11 +145,11 @@ export default function webhookTest({ getService }: FtrProviderContext) { it('should remove headers when a webhook is updated', async () => { const { body: createdAction } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'test') .send({ name: 'A generic Webhook action', - actionTypeId: '.webhook', + connector_type_id: '.webhook', secrets: { user: 'username', password: 'mypassphrase', @@ -165,9 +165,9 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A generic Webhook action', - actionTypeId: '.webhook', + connector_type_id: '.webhook', config: { ...defaultValues, url: webhookSimulatorURL, @@ -178,7 +178,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { }); await supertest - .put(`/api/actions/action/${createdAction.id}`) + .put(`/api/actions/connector/${createdAction.id}`) .set('kbn-xsrf', 'foo') .send({ name: 'A generic Webhook action', @@ -196,14 +196,14 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); const { body: fetchedAction } = await supertest - .get(`/api/actions/action/${createdAction.id}`) + .get(`/api/actions/connector/${createdAction.id}`) .expect(200); expect(fetchedAction).to.eql({ id: fetchedAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'A generic Webhook action', - actionTypeId: '.webhook', + connector_type_id: '.webhook', config: { ...defaultValues, url: webhookSimulatorURL, @@ -217,7 +217,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { it('should send authentication to the webhook target', async () => { const webhookActionId = await createWebhookAction(webhookSimulatorURL, {}, kibanaURL); const { body: result } = await supertest - .post(`/api/actions/action/${webhookActionId}/_execute`) + .post(`/api/actions/connector/${webhookActionId}/_execute`) .set('kbn-xsrf', 'test') .send({ params: { @@ -236,7 +236,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { kibanaURL ); const { body: result } = await supertest - .post(`/api/actions/action/${webhookActionId}/_execute`) + .post(`/api/actions/connector/${webhookActionId}/_execute`) .set('kbn-xsrf', 'test') .send({ params: { @@ -255,7 +255,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { kibanaURL ); const { body: result } = await supertest - .post(`/api/actions/action/${webhookActionId}/_execute`) + .post(`/api/actions/connector/${webhookActionId}/_execute`) .set('kbn-xsrf', 'test') .send({ params: { @@ -270,11 +270,11 @@ export default function webhookTest({ getService }: FtrProviderContext) { it('should handle target webhooks that are not added to allowedHosts', async () => { const { body: result } = await supertest - .post('/api/actions/action') + .post('/api/actions/connector') .set('kbn-xsrf', 'test') .send({ name: 'A generic Webhook action', - actionTypeId: '.webhook', + connector_type_id: '.webhook', secrets: { user: 'username', password: 'mypassphrase', @@ -296,7 +296,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { kibanaURL ); const { body: result } = await supertest - .post(`/api/actions/action/${webhookActionId}/_execute`) + .post(`/api/actions/connector/${webhookActionId}/_execute`) .set('kbn-xsrf', 'test') .send({ params: { @@ -312,7 +312,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { it('should handle failing webhook targets', async () => { const webhookActionId = await createWebhookAction(webhookSimulatorURL, {}, kibanaURL); const { body: result } = await supertest - .post(`/api/actions/action/${webhookActionId}/_execute`) + .post(`/api/actions/connector/${webhookActionId}/_execute`) .set('kbn-xsrf', 'test') .send({ params: { @@ -323,7 +323,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(result.status).to.eql('error'); expect(result.message).to.match(/error calling webhook, retry later/); - expect(result.serviceMessage).to.eql('[500] Internal Server Error'); + expect(result.service_message).to.eql('[500] Internal Server Error'); }); after(() => { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/list_action_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/connector_types.ts similarity index 96% rename from x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/list_action_types.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/connector_types.ts index 6625af3147450..a76654415c16d 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/list_action_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/connector_types.ts @@ -20,7 +20,7 @@ export default function listActionTypesTests({ getService }: FtrProviderContext) describe(scenario.id, () => { it('should return 200 with list of action types containing defaults', async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/actions/list_action_types`) + .get(`${getUrlPrefix(space.id)}/api/actions/connector_types`) .auth(user.username, user.password); function createActionTypeMatcher(id: string, name: string) { 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 103811ae98c56..5e83ce7821d74 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 @@ -25,12 +25,12 @@ export default function createActionTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle create action request appropriately', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -58,9 +58,9 @@ export default function createActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, response.body.id, 'action', 'actions'); expect(response.body).to.eql({ id: response.body.id, - isPreconfigured: false, + is_preconfigured: false, name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -81,12 +81,12 @@ export default function createActionTests({ getService }: FtrProviderContext) { it(`should handle create action request appropriately when action type isn't registered`, async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ name: 'My action', - actionTypeId: 'test.unregistered-action-type', + connector_type_id: 'test.unregistered-action-type', config: {}, }); @@ -119,7 +119,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { it('should handle create action request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({}); @@ -146,12 +146,12 @@ export default function createActionTests({ getService }: FtrProviderContext) { it(`should handle create action request appropriately when config isn't valid`, async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ name: 'my name', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: 'my unencrypted text', }, @@ -187,12 +187,12 @@ export default function createActionTests({ getService }: FtrProviderContext) { it(`should handle create action requests for action types that are not enabled`, async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ name: 'my name', - actionTypeId: 'test.not-enabled', + connector_type_id: 'test.not-enabled', }); switch (scenario.id) { 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 404e5e21a2d83..a0aae0a1bd64d 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 @@ -26,11 +26,11 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle delete action request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -41,7 +41,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}`) + .delete(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo'); @@ -71,11 +71,11 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { it(`shouldn't delete action from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -87,7 +87,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix('other')}/api/actions/action/${createdAction.id}`) + .delete(`${getUrlPrefix('other')}/api/actions/connector/${createdAction.id}`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo'); @@ -120,7 +120,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { it(`should handle delete request appropriately when action doesn't exist`, async () => { const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/actions/action/2`) + .delete(`${getUrlPrefix(space.id)}/api/actions/connector/2`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -148,7 +148,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { it(`shouldn't delete action from preconfigured list`, async () => { const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/actions/action/my-slack1`) + .delete(`${getUrlPrefix(space.id)}/api/actions/connector/my-slack1`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo'); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts index 5e438eb9506ed..f3c3ee74551c0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts @@ -48,11 +48,11 @@ export default function ({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle execute request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -65,7 +65,7 @@ export default function ({ getService }: FtrProviderContext) { const reference = `actions-execute-1:${user.username}`; const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}/_execute`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}/_execute`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ @@ -117,7 +117,7 @@ export default function ({ getService }: FtrProviderContext) { await validateEventLog({ spaceId: space.id, - actionId: createdAction.id, + connectorId: createdAction.id, outcome: 'success', message: `action executed: test.index-record:${createdAction.id}: My action`, }); @@ -129,11 +129,11 @@ export default function ({ getService }: FtrProviderContext) { it(`shouldn't execute an action from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -146,7 +146,7 @@ export default function ({ getService }: FtrProviderContext) { const reference = `actions-execute-4:${user.username}`; const response = await supertestWithoutAuth - .post(`${getUrlPrefix('other')}/api/actions/action/${createdAction.id}/_execute`) + .post(`${getUrlPrefix('other')}/api/actions/connector/${createdAction.id}/_execute`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ @@ -186,11 +186,11 @@ export default function ({ getService }: FtrProviderContext) { it('should handle execute request appropriately after action is updated', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -202,7 +202,7 @@ export default function ({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); await supertest - .put(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}`) + .put(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}`) .set('kbn-xsrf', 'foo') .send({ name: 'My action updated', @@ -217,7 +217,7 @@ export default function ({ getService }: FtrProviderContext) { const reference = `actions-execute-2:${user.username}`; const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}/_execute`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}/_execute`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ @@ -274,7 +274,7 @@ export default function ({ getService }: FtrProviderContext) { it(`should handle execute request appropriately when action doesn't exist`, async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action/1/_execute`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector/1/_execute`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ @@ -310,7 +310,7 @@ export default function ({ getService }: FtrProviderContext) { it('should handle execute request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action/1/_execute`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector/1/_execute`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({}); @@ -338,11 +338,11 @@ export default function ({ getService }: FtrProviderContext) { it('should handle execute request appropriately after changing config properties', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'test email action', - actionTypeId: '.email', + connector_type_id: '.email', config: { from: 'email-from-1@example.com', // this host is specifically added to allowedHosts in: @@ -359,7 +359,7 @@ export default function ({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); await supertest - .put(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}`) + .put(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}`) .set('kbn-xsrf', 'foo') .send({ name: 'a test email action 2', @@ -375,7 +375,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}/_execute`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}/_execute`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ @@ -413,17 +413,17 @@ export default function ({ getService }: FtrProviderContext) { let searchResult: any; const reference = `actions-execute-3:${user.username}`; const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.authorization', + connector_type_id: 'test.authorization', }) .expect(200); objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}/_execute`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}/_execute`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ @@ -503,21 +503,21 @@ export default function ({ getService }: FtrProviderContext) { interface ValidateEventLogParams { spaceId: string; - actionId: string; + connectorId: string; outcome: string; message: string; errorMessage?: string; } async function validateEventLog(params: ValidateEventLogParams): Promise { - const { spaceId, actionId, outcome, message, errorMessage } = params; + const { spaceId, connectorId, outcome, message, errorMessage } = params; const events: IValidatedEvent[] = await retry.try(async () => { return await getEventLog({ getService, spaceId, type: 'action', - id: actionId, + id: connectorId, provider: 'actions', actions: new Map([['execute', { equal: 1 }]]), filter: 'event.action:(execute)', @@ -550,7 +550,7 @@ export default function ({ getService }: FtrProviderContext) { { rel: 'primary', type: 'action', - id: actionId, + id: connectorId, namespace: spaceId, }, ]); 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 5cbda5f15d839..0a2b2c7520e36 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 @@ -25,11 +25,11 @@ export default function getActionTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle get action request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -41,7 +41,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}`) + .get(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}`) .auth(user.username, user.password); switch (scenario.id) { @@ -62,8 +62,8 @@ 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', + is_preconfigured: false, + connector_type_id: 'test.index-record', name: 'My action', config: { unencrypted: `This value shouldn't get encrypted`, @@ -77,11 +77,11 @@ export default function getActionTests({ getService }: FtrProviderContext) { it(`action shouldn't be acessible from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -93,7 +93,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/actions/action/${createdAction.id}`) + .get(`${getUrlPrefix('other')}/api/actions/connector/${createdAction.id}`) .auth(user.username, user.password); switch (scenario.id) { @@ -125,7 +125,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { it('should handle get preconfigured action request appropriately', async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/actions/action/my-slack1`) + .get(`${getUrlPrefix(space.id)}/api/actions/connector/my-slack1`) .auth(user.username, user.password); switch (scenario.id) { @@ -146,9 +146,9 @@ export default function getActionTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(200); expect(response.body).to.eql({ id: 'my-slack1', - actionTypeId: '.slack', + connector_type_id: '.slack', name: 'Slack#xyz', - isPreconfigured: true, + is_preconfigured: true, }); break; default: 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 1a460dd630e01..2142520894669 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 @@ -25,11 +25,11 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle get all action request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -41,7 +41,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/actions`) + .get(`${getUrlPrefix(space.id)}/api/actions/connectors`) .auth(user.username, user.password); switch (scenario.id) { @@ -63,41 +63,41 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql([ { id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'preconfigured-es-index-action', - isPreconfigured: true, - actionTypeId: '.index', + is_preconfigured: true, + connector_type_id: '.index', name: 'preconfigured_es_index_action', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'my-slack1', - isPreconfigured: true, - actionTypeId: '.slack', + is_preconfigured: true, + connector_type_id: '.slack', name: 'Slack#xyz', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'custom-system-abc-connector', - isPreconfigured: true, - actionTypeId: 'system-abc-action-type', + is_preconfigured: true, + connector_type_id: 'system-abc-action-type', name: 'SystemABC', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'preconfigured.test.index-record', - isPreconfigured: true, - actionTypeId: 'test.index-record', + is_preconfigured: true, + connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', - referencedByCount: 0, + referenced_by_count: 0, }, ]); break; @@ -106,13 +106,13 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { } }); - it('should handle get all request appropriately with proper referencedByCount', async () => { + it('should handle get all request appropriately with proper referenced_by_count', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -148,7 +148,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/actions`) + .get(`${getUrlPrefix(space.id)}/api/actions/connectors`) .auth(user.username, user.password); switch (scenario.id) { @@ -170,41 +170,41 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql([ { id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, - referencedByCount: 1, + referenced_by_count: 1, }, { id: 'preconfigured-es-index-action', - isPreconfigured: true, - actionTypeId: '.index', + is_preconfigured: true, + connector_type_id: '.index', name: 'preconfigured_es_index_action', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'my-slack1', - isPreconfigured: true, - actionTypeId: '.slack', + is_preconfigured: true, + connector_type_id: '.slack', name: 'Slack#xyz', - referencedByCount: 1, + referenced_by_count: 1, }, { id: 'custom-system-abc-connector', - isPreconfigured: true, - actionTypeId: 'system-abc-action-type', + is_preconfigured: true, + connector_type_id: 'system-abc-action-type', name: 'SystemABC', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'preconfigured.test.index-record', - isPreconfigured: true, - actionTypeId: 'test.index-record', + is_preconfigured: true, + connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', - referencedByCount: 0, + referenced_by_count: 0, }, ]); break; @@ -215,11 +215,11 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { it(`shouldn't get actions from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -231,7 +231,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/actions`) + .get(`${getUrlPrefix('other')}/api/actions/connectors`) .auth(user.username, user.password); switch (scenario.id) { @@ -253,31 +253,31 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql([ { id: 'preconfigured-es-index-action', - isPreconfigured: true, - actionTypeId: '.index', + is_preconfigured: true, + connector_type_id: '.index', name: 'preconfigured_es_index_action', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'my-slack1', - isPreconfigured: true, - actionTypeId: '.slack', + is_preconfigured: true, + connector_type_id: '.slack', name: 'Slack#xyz', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'custom-system-abc-connector', - isPreconfigured: true, - actionTypeId: 'system-abc-action-type', + is_preconfigured: true, + connector_type_id: 'system-abc-action-type', name: 'SystemABC', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'preconfigured.test.index-record', - isPreconfigured: true, - actionTypeId: 'test.index-record', + is_preconfigured: true, + connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', - referencedByCount: 0, + referenced_by_count: 0, }, ]); break; 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 9af6c9ad0109c..b5ff287ac58f6 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 @@ -34,7 +34,7 @@ export default function actionsTests({ loadTestFile, getService }: FtrProviderCo loadTestFile(require.resolve('./execute')); loadTestFile(require.resolve('./get_all')); loadTestFile(require.resolve('./get')); - loadTestFile(require.resolve('./list_action_types')); + loadTestFile(require.resolve('./connector_types')); loadTestFile(require.resolve('./update')); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/manual/pr_40694.js b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/manual/pr_40694.js index e6ee275103041..5b422bcd0614d 100755 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/manual/pr_40694.js +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/manual/pr_40694.js @@ -16,8 +16,8 @@ if (require.main === module) main(); async function main() { let response; - response = await httpPost('api/actions/action', { - actionTypeId: '.email', + response = await httpPost('api/actions/connector', { + connector_type_id: '.email', name: 'an email action', config: { from: 'patrick.mueller@elastic.co', @@ -32,12 +32,12 @@ async function main() { }); console.log(`result of create: ${JSON.stringify(response, null, 4)}`); - const actionId = response.id; + const connectorId = response.id; - response = await httpGet(`api/actions/${actionId}`); + response = await httpGet(`api/actions/${connectorId}`); console.log(`action after create: ${JSON.stringify(response, null, 4)}`); - response = await httpPut(`api/actions/action/${actionId}`, { + response = await httpPut(`api/actions/connector/${connectorId}`, { name: 'an email action', config: { from: 'patrick.mueller@elastic.co', @@ -51,10 +51,10 @@ async function main() { console.log(`response from update: ${JSON.stringify(response, null, 4)}`); - response = await httpGet(`api/actions/${actionId}`); + response = await httpGet(`api/actions/${connectorId}`); console.log(`action after update: ${JSON.stringify(response, null, 4)}`); - response = await httpPost(`api/actions/action/${actionId}/_execute`, { + response = await httpPost(`api/actions/connector/${connectorId}/_execute`, { params: { to: ['patrick.mueller@elastic.co'], subject: 'the email subject', 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 ded02f1982be7..15cf409970184 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 @@ -25,11 +25,11 @@ export default function updateActionTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle update action request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -41,7 +41,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}`) + .put(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ @@ -72,8 +72,8 @@ 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', + is_preconfigured: false, + connector_type_id: 'test.index-record', name: 'My action updated', config: { unencrypted: `This value shouldn't get encrypted`, @@ -94,11 +94,11 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it(`shouldn't update action from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -110,7 +110,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix('other')}/api/actions/action/${createdAction.id}`) + .put(`${getUrlPrefix('other')}/api/actions/connector/${createdAction.id}`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ @@ -152,7 +152,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it('should handle update action request appropriately when passing a null config', async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/actions/action/1`) + .put(`${getUrlPrefix(space.id)}/api/actions/connector/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -182,7 +182,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it(`should handle update action request appropriately when action doesn't exist`, async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/actions/action/1`) + .put(`${getUrlPrefix(space.id)}/api/actions/connector/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -224,7 +224,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it('should handle update action request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/actions/action/1`) + .put(`${getUrlPrefix(space.id)}/api/actions/connector/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({}); @@ -252,11 +252,11 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it('should handle update action request appropriately when secrets are not valid', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -268,7 +268,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/actions/action/${createdAction.id}`) + .put(`${getUrlPrefix(space.id)}/api/actions/connector/${createdAction.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -311,7 +311,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it(`shouldn't update action from preconfigured list`, async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/actions/action/custom-system-abc-connector`) + .put(`${getUrlPrefix(space.id)}/api/actions/connector/custom-system-abc-connector`) .auth(user.username, user.password) .set('kbn-xsrf', 'foo') .send({ diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/list_action_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/connector_types.ts similarity index 55% rename from x-pack/test/alerting_api_integration/spaces_only/tests/actions/list_action_types.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/actions/connector_types.ts index c999def5ead27..bb7fbbf0802d2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/list_action_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/connector_types.ts @@ -14,10 +14,10 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function listActionTypesTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - describe('list_action_types', () => { - it('should return 200 with list of action types containing defaults', async () => { + describe('connector_types', () => { + it('should return 200 with list of connector types containing defaults', async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/actions/list_action_types` + `${getUrlPrefix(Spaces.space1.id)}/api/actions/connector_types` ); function createActionTypeMatcher(id: string, name: string) { @@ -33,5 +33,26 @@ export default function listActionTypesTests({ getService }: FtrProviderContext) response.body.some(createActionTypeMatcher('test.index-record', 'Test: Index Record')) ).to.be(true); }); + + describe('legacy', () => { + it('should return 200 with list of action types containing defaults', async () => { + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/actions/list_action_types` + ); + + function createActionTypeMatcher(id: string, name: string) { + return (actionType: { id: string; name: string }) => { + return actionType.id === id && actionType.name === name; + }; + } + + expect(response.status).to.eql(200); + // Check for values explicitly in order to avoid this test failing each time plugins register + // a new action type + expect( + response.body.some(createActionTypeMatcher('test.index-record', 'Test: Index Record')) + ).to.be(true); + }); + }); }); } 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 de06f3fc990b6..c91c05bda606e 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 @@ -19,13 +19,13 @@ export default function createActionTests({ getService }: FtrProviderContext) { after(() => objectRemover.removeAll()); - it('should handle create action request appropriately', async () => { + it('should handle create connector request appropriately', async () => { const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -38,9 +38,9 @@ export default function createActionTests({ getService }: FtrProviderContext) { objectRemover.add(Spaces.space1.id, response.body.id, 'action', 'actions'); expect(response.body).to.eql({ id: response.body.id, - isPreconfigured: false, + is_preconfigured: false, name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -56,6 +56,45 @@ export default function createActionTests({ getService }: FtrProviderContext) { }); }); + describe('legacy', () => { + it('should handle create action request appropriately', async () => { + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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(response.status).to.eql(200); + objectRemover.add(Spaces.space1.id, response.body.id, 'action', 'actions'); + expect(response.body).to.eql({ + id: response.body.id, + isPreconfigured: false, + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + }); + expect(typeof response.body.id).to.be('string'); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: Spaces.space1.id, + type: 'action', + id: response.body.id, + }); + }); + }); + it('should notify feature usage when creating a gold action type', async () => { const testStart = new Date(); const response = await supertest 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 5ba5083bd9245..66ae047e5151b 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 @@ -20,11 +20,11 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { it('should handle delete action request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -35,18 +35,18 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { .expect(200); await supertest - .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}`) + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/${createdAction.id}`) .set('kbn-xsrf', 'foo') .expect(204, ''); }); it(`shouldn't delete action from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -58,7 +58,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); await supertest - .delete(`${getUrlPrefix(Spaces.other.id)}/api/actions/action/${createdAction.id}`) + .delete(`${getUrlPrefix(Spaces.other.id)}/api/actions/connector/${createdAction.id}`) .set('kbn-xsrf', 'foo') .expect(404, { statusCode: 404, @@ -69,7 +69,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { it(`should handle delete request appropriately when action doesn't exist`, async () => { await supertest - .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/2`) + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/2`) .set('kbn-xsrf', 'foo') .expect(404, { statusCode: 404, @@ -80,7 +80,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { it(`shouldn't delete action from preconfigured list`, async () => { await supertest - .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/my-slack1`) + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/my-slack1`) .set('kbn-xsrf', 'foo') .expect(400, { statusCode: 400, @@ -88,5 +88,78 @@ export default function deleteActionTests({ getService }: FtrProviderContext) { message: `Preconfigured action my-slack1 is not allowed to delete.`, }); }); + + describe('legacy', () => { + it('should handle delete action request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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); + + await supertest + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}`) + .set('kbn-xsrf', 'foo') + .expect(204, ''); + }); + + it(`shouldn't delete action from another space`, async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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', 'actions'); + + await supertest + .delete(`${getUrlPrefix(Spaces.other.id)}/api/actions/action/${createdAction.id}`) + .set('kbn-xsrf', 'foo') + .expect(404, { + statusCode: 404, + error: 'Not Found', + message: `Saved object [action/${createdAction.id}] not found`, + }); + }); + + it(`should handle delete request appropriately when action doesn't exist`, async () => { + await supertest + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/2`) + .set('kbn-xsrf', 'foo') + .expect(404, { + statusCode: 404, + error: 'Not Found', + message: 'Saved object [action/2] not found', + }); + }); + + it(`shouldn't delete action from preconfigured list`, async () => { + await supertest + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/my-slack1`) + .set('kbn-xsrf', 'foo') + .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/execute.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts index 0e3e25ad9c99a..fbdde2104dd61 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts @@ -44,11 +44,11 @@ export default function ({ getService }: FtrProviderContext) { it('should handle execute request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -59,9 +59,11 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); - const reference = `actions-execute-1:${Spaces.space1.id}`; + const reference = `actions-execute-1:${Spaces.space1.id}:${createdAction.id}`; const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}/_execute`) + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/${createdAction.id}/_execute` + ) .set('kbn-xsrf', 'foo') .send({ params: { @@ -102,18 +104,20 @@ export default function ({ getService }: FtrProviderContext) { it('should handle failed executions', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'failing action', - actionTypeId: 'test.failing', + connector_type_id: 'test.failing', }) .expect(200); objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); const reference = `actions-failure-1:${Spaces.space1.id}`; const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}/_execute`) + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/${createdAction.id}/_execute` + ) .set('kbn-xsrf', 'foo') .send({ params: { @@ -124,10 +128,10 @@ export default function ({ getService }: FtrProviderContext) { expect(response.status).to.eql(200); expect(response.body).to.eql({ - actionId: createdAction.id, + connector_id: createdAction.id, status: 'error', message: 'an error occurred while running the action executor', - serviceMessage: `expected failure for ${ES_TEST_INDEX_NAME} ${reference}`, + service_message: `expected failure for ${ES_TEST_INDEX_NAME} ${reference}`, retry: false, }); @@ -142,11 +146,11 @@ export default function ({ getService }: FtrProviderContext) { it(`shouldn't execute an action from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -178,17 +182,19 @@ export default function ({ getService }: FtrProviderContext) { it('should handle execute request appropriately and have proper callCluster and savedObjectsClient authorization', async () => { const reference = `actions-execute-3:${Spaces.space1.id}`; const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.authorization', + connector_type_id: 'test.authorization', }) .expect(200); objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}/_execute`) + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/${createdAction.id}/_execute` + ) .set('kbn-xsrf', 'foo') .send({ params: { @@ -220,11 +226,11 @@ export default function ({ getService }: FtrProviderContext) { it('should notify feature usage when executing a gold action type', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'Noop action type', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', secrets: {}, config: {}, }) @@ -233,7 +239,9 @@ export default function ({ getService }: FtrProviderContext) { const executionStart = new Date(); await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}/_execute`) + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/${createdAction.id}/_execute` + ) .set('kbn-xsrf', 'foo') .send({ params: {}, @@ -251,6 +259,72 @@ export default function ({ getService }: FtrProviderContext) { expect(noopFeature.last_used).to.be.a('string'); expect(new Date(noopFeature.last_used).getTime()).to.be.greaterThan(executionStart.getTime()); }); + + describe('legacy', () => { + it('should handle execute request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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', 'actions'); + + const reference = `actions-execute-1:${Spaces.space1.id}:${createdAction.id}`; + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + reference, + index: ES_TEST_INDEX_NAME, + message: 'Testing 123', + }, + }); + + expect(response.status).to.eql(200); + }); + + it('should handle failed executions', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'failing action', + actionTypeId: 'test.failing', + }) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); + + const reference = `actions-failure-1:${Spaces.space1.id}:${createdAction.id}`; + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + reference, + index: ES_TEST_INDEX_NAME, + }, + }); + + expect(response.status).to.eql(200); + expect(response.body).to.eql({ + actionId: createdAction.id, + status: 'error', + message: 'an error occurred while running the action executor', + serviceMessage: `expected failure for ${ES_TEST_INDEX_NAME} ${reference}`, + retry: false, + }); + }); + }); }); interface ValidateEventLogParams { 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 f10755ae31d26..2219e9dda17d6 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 @@ -20,11 +20,11 @@ export default function getActionTests({ getService }: FtrProviderContext) { it('should handle get action request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -36,11 +36,11 @@ export default function getActionTests({ getService }: FtrProviderContext) { objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); await supertest - .get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/${createdAction.id}`) .expect(200, { id: createdAction.id, - isPreconfigured: false, - actionTypeId: 'test.index-record', + is_preconfigured: false, + connector_type_id: 'test.index-record', name: 'My action', config: { unencrypted: `This value shouldn't get encrypted`, @@ -50,11 +50,11 @@ export default function getActionTests({ getService }: FtrProviderContext) { it(`action should't be acessible from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -66,7 +66,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); await supertest - .get(`${getUrlPrefix(Spaces.other.id)}/api/actions/action/${createdAction.id}`) + .get(`${getUrlPrefix(Spaces.other.id)}/api/actions/connector/${createdAction.id}`) .expect(404, { statusCode: 404, error: 'Not Found', @@ -76,13 +76,82 @@ export default function getActionTests({ getService }: FtrProviderContext) { it('should handle get action request from preconfigured list', async () => { await supertest - .get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/my-slack1`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/my-slack1`) .expect(200, { id: 'my-slack1', - isPreconfigured: true, - actionTypeId: '.slack', + is_preconfigured: true, + connector_type_id: '.slack', name: 'Slack#xyz', }); }); + + describe('legacy', () => { + it('should handle get action request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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', 'actions'); + + await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}`) + .expect(200, { + id: createdAction.id, + isPreconfigured: false, + actionTypeId: 'test.index-record', + name: 'My action', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + }); + }); + + it(`action should't be acessible from another space`, async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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', 'actions'); + + await supertest + .get(`${getUrlPrefix(Spaces.other.id)}/api/actions/action/${createdAction.id}`) + .expect(404, { + statusCode: 404, + error: 'Not Found', + 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/actions/action/my-slack1`) + .expect(200, { + id: 'my-slack1', + isPreconfigured: true, + actionTypeId: '.slack', + name: 'Slack#xyz', + }); + }); + }); }); } 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 49befae793445..531df9d4ed19f 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 @@ -35,44 +35,44 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { .expect(200); objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); - await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/actions`).expect(200, [ + await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connectors`).expect(200, [ { id: createdAction.id, - isPreconfigured: false, + is_preconfigured: false, name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'preconfigured-es-index-action', - isPreconfigured: true, - actionTypeId: '.index', + is_preconfigured: true, + connector_type_id: '.index', name: 'preconfigured_es_index_action', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'my-slack1', - isPreconfigured: true, - actionTypeId: '.slack', + is_preconfigured: true, + connector_type_id: '.slack', name: 'Slack#xyz', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'custom-system-abc-connector', - isPreconfigured: true, - actionTypeId: 'system-abc-action-type', + is_preconfigured: true, + connector_type_id: 'system-abc-action-type', name: 'SystemABC', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'preconfigured.test.index-record', - isPreconfigured: true, - actionTypeId: 'test.index-record', + is_preconfigured: true, + connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', - referencedByCount: 0, + referenced_by_count: 0, }, ]); }); @@ -94,36 +94,97 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { .expect(200); objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); - await supertest.get(`${getUrlPrefix(Spaces.other.id)}/api/actions`).expect(200, [ + await supertest.get(`${getUrlPrefix(Spaces.other.id)}/api/actions/connectors`).expect(200, [ { id: 'preconfigured-es-index-action', - isPreconfigured: true, - actionTypeId: '.index', + is_preconfigured: true, + connector_type_id: '.index', name: 'preconfigured_es_index_action', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'my-slack1', - isPreconfigured: true, - actionTypeId: '.slack', + is_preconfigured: true, + connector_type_id: '.slack', name: 'Slack#xyz', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'custom-system-abc-connector', - isPreconfigured: true, - actionTypeId: 'system-abc-action-type', + is_preconfigured: true, + connector_type_id: 'system-abc-action-type', name: 'SystemABC', - referencedByCount: 0, + referenced_by_count: 0, }, { id: 'preconfigured.test.index-record', - isPreconfigured: true, - actionTypeId: 'test.index-record', + is_preconfigured: true, + connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', - referencedByCount: 0, + referenced_by_count: 0, }, ]); }); + + describe('legacy', () => { + it('should handle get all action request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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', 'actions'); + + await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/actions`).expect(200, [ + { + id: createdAction.id, + isPreconfigured: false, + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + referencedByCount: 0, + }, + { + id: 'preconfigured-es-index-action', + isPreconfigured: true, + actionTypeId: '.index', + name: 'preconfigured_es_index_action', + referencedByCount: 0, + }, + { + id: 'my-slack1', + isPreconfigured: true, + actionTypeId: '.slack', + name: 'Slack#xyz', + referencedByCount: 0, + }, + { + id: 'custom-system-abc-connector', + isPreconfigured: true, + actionTypeId: 'system-abc-action-type', + name: 'SystemABC', + referencedByCount: 0, + }, + { + id: 'preconfigured.test.index-record', + isPreconfigured: true, + actionTypeId: 'test.index-record', + name: 'Test:_Preconfigured_Index_Record', + 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 74e4cbb75f01c..d5056508e5de9 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 @@ -18,7 +18,7 @@ export default function actionsTests({ loadTestFile, getService }: FtrProviderCo loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./get_all')); loadTestFile(require.resolve('./get')); - loadTestFile(require.resolve('./list_action_types')); + loadTestFile(require.resolve('./connector_types')); loadTestFile(require.resolve('./update')); loadTestFile(require.resolve('./execute')); loadTestFile(require.resolve('./builtin_action_types/es_index')); 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 80bdba539689d..4e0b4eac8da32 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 @@ -21,11 +21,11 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it('should handle update action request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -37,7 +37,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); await supertest - .put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}`) + .put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/${createdAction.id}`) .set('kbn-xsrf', 'foo') .send({ name: 'My action updated', @@ -50,8 +50,8 @@ export default function updateActionTests({ getService }: FtrProviderContext) { }) .expect(200, { id: createdAction.id, - isPreconfigured: false, - actionTypeId: 'test.index-record', + is_preconfigured: false, + connector_type_id: 'test.index-record', name: 'My action updated', config: { unencrypted: `This value shouldn't get encrypted`, @@ -69,11 +69,11 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it(`shouldn't update action from another space`, async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -105,7 +105,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it(`shouldn't update action from preconfigured list`, async () => { await supertest - .put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/custom-system-abc-connector`) + .put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/custom-system-abc-connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action updated', @@ -125,11 +125,11 @@ export default function updateActionTests({ getService }: FtrProviderContext) { it('should notify feature usage when editing a gold action type', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'Noop action type', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', secrets: {}, config: {}, }) @@ -138,7 +138,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { const updateStart = new Date(); await supertest - .put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}`) + .put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/${createdAction.id}`) .set('kbn-xsrf', 'foo') .send({ name: 'Noop action type updated', @@ -158,5 +158,147 @@ export default function updateActionTests({ getService }: FtrProviderContext) { expect(noopFeature.last_used).to.be.a('string'); expect(new Date(noopFeature.last_used).getTime()).to.be.greaterThan(updateStart.getTime()); }); + + describe('legacy', () => { + it('should handle update action request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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', 'actions'); + + await supertest + .put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}`) + .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(200, { + id: createdAction.id, + isPreconfigured: false, + actionTypeId: 'test.index-record', + name: 'My action updated', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + }); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: Spaces.space1.id, + type: 'action', + id: createdAction.id, + }); + }); + + it(`shouldn't update action from another space`, async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/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', 'actions'); + + await supertest + .put(`${getUrlPrefix(Spaces.other.id)}/api/actions/action/${createdAction.id}`) + .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(404, { + statusCode: 404, + error: 'Not Found', + 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/actions/action/custom-system-abc-connector`) + .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 action custom-system-abc-connector is not allowed to update.`, + }); + }); + + it('should notify feature usage when editing a gold action type', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'Noop action type', + actionTypeId: 'test.noop', + secrets: {}, + config: {}, + }) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); + + const updateStart = new Date(); + await supertest + .put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/${createdAction.id}`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'Noop action type updated', + secrets: {}, + config: {}, + }) + .expect(200); + + const { + body: { features }, + } = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/licensing/feature_usage`); + expect(features).to.be.an(Array); + const noopFeature = features.find( + (feature: { name: string }) => feature.name === 'Connector: Test: Noop' + ); + expect(noopFeature).to.be.ok(); + expect(noopFeature.last_used).to.be.a('string'); + expect(new Date(noopFeature.last_used).getTime()).to.be.greaterThan(updateStart.getTime()); + }); + }); }); }