diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index 6daf15208f4d9..53b17f58d6e18 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -114,6 +114,17 @@ describe('config validation', () => { }); }); + test('config validation failed when a url is invalid', () => { + const config: Record = { + url: 'example.com/do-something', + }; + expect(() => { + validateConfig(actionType, config); + }).toThrowErrorMatchingInlineSnapshot( + '"error validating action type config: error configuring webhook action: unable to parse url: TypeError: Invalid URL: example.com/do-something"' + ); + }); + test('config validation passes when valid headers are provided', () => { // any for testing // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts index 4a34fea762164..0b8b27b278928 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -85,8 +85,20 @@ function validateActionTypeConfig( configurationUtilities: ActionsConfigurationUtilities, configObject: ActionTypeConfigType ) { + let url: URL; try { - configurationUtilities.ensureWhitelistedUri(configObject.url); + url = new URL(configObject.url); + } catch (err) { + return i18n.translate('xpack.actions.builtin.webhook.webhookConfigurationErrorNoHostname', { + defaultMessage: 'error configuring webhook action: unable to parse url: {err}', + values: { + err, + }, + }); + } + + try { + configurationUtilities.ensureWhitelistedUri(url.toString()); } catch (whitelistError) { return i18n.translate('xpack.actions.builtin.webhook.webhookConfigurationError', { defaultMessage: 'error configuring webhook action: {message}', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx index 3413465d70d93..337c1f0f18a93 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx @@ -40,7 +40,7 @@ describe('webhook connector validation', () => { isPreconfigured: false, config: { method: 'PUT', - url: 'http:\\test', + url: 'http://test.com', headers: { 'content-type': 'text' }, }, } as WebhookActionConnector; @@ -77,6 +77,31 @@ describe('webhook connector validation', () => { }, }); }); + + test('connector validation fails when url in config is not valid', () => { + const actionConnector = { + secrets: { + user: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.webhook', + name: 'webhook', + config: { + method: 'PUT', + url: 'invalid.url', + }, + } as WebhookActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + url: ['URL is invalid.'], + method: [], + user: [], + password: [], + }, + }); + }); }); describe('webhook action params validation', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.tsx index 9f33e4491233a..2c51b21d70034 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.tsx @@ -7,6 +7,7 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; import { ActionTypeModel, ValidationResult } from '../../../../types'; import { WebhookActionParams, WebhookActionConnector } from '../types'; +import { isValidUrl } from '../../../lib/value_validators'; export function getActionType(): ActionTypeModel { return { @@ -43,6 +44,17 @@ export function getActionType(): ActionTypeModel 0 && url !== undefined} fullWidth value={url || ''} + placeholder="https:// or http://" data-test-subj="webhookUrlText" onChange={(e) => { editActionConfig('url', e.target.value); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts index 9d628adc1db6b..e954fb5c7617b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { throwIfAbsent, throwIfIsntContained } from './value_validators'; +import { throwIfAbsent, throwIfIsntContained, isValidUrl } from './value_validators'; import uuid from 'uuid'; describe('throwIfAbsent', () => { @@ -79,3 +79,17 @@ describe('throwIfIsntContained', () => { ).toEqual(values); }); }); + +describe('isValidUrl', () => { + test('verifies invalid url', () => { + expect(isValidUrl('this is not a url')).toBeFalsy(); + }); + + test('verifies valid url any protocol', () => { + expect(isValidUrl('https://www.elastic.co/')).toBeTruthy(); + }); + + test('verifies valid url with specific protocol', () => { + expect(isValidUrl('https://www.elastic.co/', 'https:')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts index 7ee7359086406..4942e6328097d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts @@ -31,3 +31,15 @@ export function throwIfIsntContained( return values; }; } + +export const isValidUrl = (urlString: string, protocol?: string) => { + try { + const urlObject = new URL(urlString); + if (protocol === undefined || urlObject.protocol === protocol) { + return true; + } + return false; + } catch (err) { + return false; + } +};