diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/support/http/HttpRequest.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/support/http/HttpRequest.java index 175fba6ddec..22a4ac47f13 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/support/http/HttpRequest.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/support/http/HttpRequest.java @@ -242,6 +242,10 @@ public class HttpRequest implements ToXContent { return new Builder(host, port); } + static Builder builder() { + return new Builder(); + } + public static class Parser { private final HttpAuthRegistry httpAuthRegistry; @@ -443,8 +447,15 @@ public class HttpRequest implements ToXContent { } public Builder fromUrl(String supposedUrl) { + if (Strings.hasLength(supposedUrl) == false) { + throw new ElasticsearchParseException("Configured URL is empty, please configure a valid URL"); + } + try { URI uri = new URI(supposedUrl); + if (Strings.hasLength(uri.getScheme()) == false) { + throw new ElasticsearchParseException("URL [{}] does not contain a scheme", uri); + } scheme = Scheme.parse(uri.getScheme()); port = uri.getPort() > 0 ? uri.getPort() : scheme.defaultPort(); host = uri.getHost(); diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/support/http/HttpRequestTemplate.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/support/http/HttpRequestTemplate.java index b875b0b59cd..9f90661ddee 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/support/http/HttpRequestTemplate.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/support/http/HttpRequestTemplate.java @@ -248,6 +248,10 @@ public class HttpRequestTemplate implements ToXContent { return new Builder(host, port); } + static Builder builder() { + return new Builder(); + } + public static class Parser { private final HttpAuthRegistry httpAuthRegistry; @@ -477,8 +481,15 @@ public class HttpRequestTemplate implements ToXContent { } public Builder fromUrl(String supposedUrl) { + if (Strings.hasLength(supposedUrl) == false) { + throw new ElasticsearchParseException("Configured URL is empty, please configure a valid URL"); + } + try { URI uri = new URI(supposedUrl); + if (Strings.hasLength(uri.getScheme()) == false) { + throw new ElasticsearchParseException("URL [{}] does not contain a scheme", uri); + } scheme = Scheme.parse(uri.getScheme()); port = uri.getPort() > 0 ? uri.getPort() : scheme.defaultPort(); host = uri.getHost(); @@ -495,7 +506,7 @@ public class HttpRequestTemplate implements ToXContent { } } } catch (URISyntaxException e) { - throw new ElasticsearchParseException("Malformed URI [{}]", supposedUrl); + throw new ElasticsearchParseException("Malformed URL [{}]", supposedUrl); } return this; } diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/support/http/HttpRequestTemplateTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/support/http/HttpRequestTemplateTests.java index b54ec6421fd..d1bf3c8d869 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/support/http/HttpRequestTemplateTests.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/support/http/HttpRequestTemplateTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.watcher.support.http; +import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -25,6 +26,7 @@ import java.util.Collections; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; @@ -168,6 +170,33 @@ public class HttpRequestTemplateTests extends ESTestCase { assertThatManualBuilderEqualsParsingFromUrl("http://www.example.org?foo=%20white%20space", builder); } + public void testParsingEmptyUrl() throws Exception { + try { + HttpRequestTemplate.builder().fromUrl(""); + fail("Expected exception due to empty URL"); + } catch (ElasticsearchParseException e) { + assertThat(e.getMessage(), containsString("Configured URL is empty, please configure a valid URL")); + } + } + + public void testInvalidUrlsWithMissingScheme() throws Exception { + try { + HttpRequestTemplate.builder().fromUrl("www.test.de"); + fail("Expected exception due to missing scheme"); + } catch (ElasticsearchParseException e) { + assertThat(e.getMessage(), containsString("URL [www.test.de] does not contain a scheme")); + } + } + + public void testInvalidUrlsWithHost() throws Exception { + try { + HttpRequestTemplate.builder().fromUrl("https://"); + fail("Expected exception due to missing host"); + } catch (ElasticsearchParseException e) { + assertThat(e.getMessage(), containsString("Malformed URL [https://]")); + } + } + private void assertThatManualBuilderEqualsParsingFromUrl(String url, HttpRequestTemplate.Builder builder) throws Exception { XContentBuilder urlContentBuilder = jsonBuilder().startObject().field("url", url).endObject(); XContentParser urlContentParser = JsonXContent.jsonXContent.createParser(urlContentBuilder.bytes()); diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/support/http/HttpRequestTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/support/http/HttpRequestTests.java new file mode 100644 index 00000000000..e539bcf0b57 --- /dev/null +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/support/http/HttpRequestTests.java @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.support.http; + +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +public class HttpRequestTests extends ESTestCase { + + public void testParsingFromUrl() throws Exception { + HttpRequest.Builder builder = HttpRequest.builder("www.example.org", 1234); + builder.path("/foo/bar/org"); + builder.setParam("param", "test"); + builder.scheme(Scheme.HTTPS); + assertThatManualBuilderEqualsParsingFromUrl("https://www.example.org:1234/foo/bar/org?param=test", builder); + + // test without specifying port + builder = HttpRequest.builder("www.example.org", 80); + assertThatManualBuilderEqualsParsingFromUrl("http://www.example.org", builder); + + // encoded values + builder = HttpRequest.builder("www.example.org", 80).setParam("foo", " white space"); + assertThatManualBuilderEqualsParsingFromUrl("http://www.example.org?foo=%20white%20space", builder); + } + + public void testParsingEmptyUrl() throws Exception { + try { + HttpRequest.builder().fromUrl(""); + fail("Expected exception due to empty URL"); + } catch (ElasticsearchParseException e) { + assertThat(e.getMessage(), containsString("Configured URL is empty, please configure a valid URL")); + } + } + + public void testInvalidUrlsWithMissingScheme() throws Exception { + try { + HttpRequest.builder().fromUrl("www.test.de"); + fail("Expected exception due to missing scheme"); + } catch (ElasticsearchParseException e) { + assertThat(e.getMessage(), containsString("URL [www.test.de] does not contain a scheme")); + } + } + + public void testInvalidUrlsWithHost() throws Exception { + try { + HttpRequest.builder().fromUrl("https://"); + fail("Expected exception due to missing host"); + } catch (ElasticsearchParseException e) { + assertThat(e.getMessage(), containsString("Malformed URL [https://]")); + } + } + + private void assertThatManualBuilderEqualsParsingFromUrl(String url, HttpRequest.Builder builder) throws Exception { + XContentBuilder urlContentBuilder = jsonBuilder().startObject().field("url", url).endObject(); + XContentParser urlContentParser = JsonXContent.jsonXContent.createParser(urlContentBuilder.bytes()); + urlContentParser.nextToken(); + + HttpRequest.Parser parser = new HttpRequest.Parser(mock(HttpAuthRegistry.class)); + HttpRequest urlParsedRequest = parser.parse(urlContentParser); + + XContentBuilder xContentBuilder = builder.build().toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS); + XContentParser xContentParser = JsonXContent.jsonXContent.createParser(xContentBuilder.bytes()); + xContentParser.nextToken(); + HttpRequest parsedRequest = parser.parse(xContentParser); + + assertThat(parsedRequest, is(urlParsedRequest)); + } +} diff --git a/elasticsearch/x-pack/watcher/src/test/resources/rest-api-spec/test/watcher/put_watch/50_email_attachment_validation.yaml b/elasticsearch/x-pack/watcher/src/test/resources/rest-api-spec/test/watcher/put_watch/50_email_attachment_validation.yaml new file mode 100644 index 00000000000..dd53e3c81f0 --- /dev/null +++ b/elasticsearch/x-pack/watcher/src/test/resources/rest-api-spec/test/watcher/put_watch/50_email_attachment_validation.yaml @@ -0,0 +1,86 @@ +--- +"Test invalid urls in email attachments reject put watch": + - do: + cluster.health: + wait_for_status: yellow + + - do: + catch: /Configured URL is empty/ + watcher.put_watch: + id: "my_watch" + master_timeout: "40s" + body: > + { + "trigger": { + "schedule": { + "hourly": { + "minute": [ 0, 5 ] + } + } + }, + "input": { + "simple": { + "payload": {} + } + }, + "condition": { + "always": {} + }, + "actions": { + "send_email": { + "email": { + "to": "test.account@elastic.co", + "subject": "Cluster Status Warning", + "body": "hello", + "attachments": { + "my_id": { + "http": { + "request": { "url": "" } + } + } + } + } + } + } + } + + - do: + catch: /Malformed URL/ + watcher.put_watch: + id: "my_watch" + master_timeout: "40s" + body: > + { + "trigger": { + "schedule": { + "hourly": { + "minute": [ 0, 5 ] + } + } + }, + "input": { + "simple": { + "payload": {} + } + }, + "condition": { + "always": {} + }, + "actions": { + "send_email": { + "email": { + "to": "test.account@elastic.co", + "subject": "Cluster Status Warning", + "body": "hello", + "attachments": { + "my_id": { + "http": { + "request": { "url": "https://" } + } + } + } + } + } + } + } +