From a72179aa4f4cf6241770156ea98698183d556cd9 Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Mon, 11 May 2020 03:45:11 +0300 Subject: [PATCH 1/2] Support userInfo in elasticsearch URI See gh-21381 --- ...ElasticsearchRestClientConfigurations.java | 31 +++++++- ...earchRestClientAutoConfigurationTests.java | 71 +++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java index b387ff0e2b9..d7ff03005ba 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.elasticsearch; +import java.net.URI; import java.time.Duration; import org.apache.http.HttpHost; @@ -43,6 +44,7 @@ import org.springframework.context.annotation.Configuration; * @author Brian Clozel * @author Stephane Nicoll * @author Vedran Pavic + * @author Evgeniy Cheban */ class ElasticsearchRestClientConfigurations { @@ -58,7 +60,7 @@ class ElasticsearchRestClientConfigurations { @Bean RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchRestClientProperties properties, ObjectProvider builderCustomizers) { - HttpHost[] hosts = properties.getUris().stream().map(HttpHost::create).toArray(HttpHost[]::new); + HttpHost[] hosts = properties.getUris().stream().map(this::createHttpHost).toArray(HttpHost[]::new); RestClientBuilder builder = RestClient.builder(hosts); builder.setHttpClientConfigCallback((httpClientBuilder) -> { builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder)); @@ -72,6 +74,12 @@ class ElasticsearchRestClientConfigurations { return builder; } + private HttpHost createHttpHost(String uri) { + URI parsedUri = URI.create(uri); + String userInfo = parsedUri.getUserInfo(); + return HttpHost.create((userInfo != null) ? uri.replace(userInfo + "@", "") : uri); + } + } @Configuration(proxyBeanMethods = false) @@ -124,15 +132,32 @@ class ElasticsearchRestClientConfigurations { @Override public void customize(HttpAsyncClientBuilder builder) { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + builder.setDefaultCredentialsProvider(credentialsProvider); + this.properties.getUris().stream().map(URI::create).filter((uri) -> uri.getUserInfo() != null) + .forEach((uri) -> { + AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort()); + Credentials credentials = createCredentials(uri.getUserInfo()); + credentialsProvider.setCredentials(authScope, credentials); + }); map.from(this.properties::getUsername).whenHasText().to((username) -> { - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); Credentials credentials = new UsernamePasswordCredentials(this.properties.getUsername(), this.properties.getPassword()); credentialsProvider.setCredentials(AuthScope.ANY, credentials); - builder.setDefaultCredentialsProvider(credentialsProvider); }); } + private Credentials createCredentials(String usernameAndPassword) { + int delimiter = usernameAndPassword.indexOf(":"); + if (delimiter == -1) { + return new UsernamePasswordCredentials(usernameAndPassword, null); + } + + String username = usernameAndPassword.substring(0, delimiter); + String password = usernameAndPassword.substring(delimiter + 1); + return new UsernamePasswordCredentials(username, password); + } + @Override public void customize(RequestConfig.Builder builder) { map.from(this.properties::getConnectionTimeout).whenNonNull().asInt(Duration::toMillis) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java index 5fdcb32fb89..b2965023f23 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java @@ -20,10 +20,16 @@ import java.time.Duration; import java.util.HashMap; import java.util.Map; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.assertj.core.api.InstanceOfAssertFactories; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.client.Node; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; @@ -47,6 +53,7 @@ import static org.mockito.Mockito.mock; * * @author Brian Clozel * @author Vedran Pavic + * @author Evgeniy Cheban */ @Testcontainers(disabledWithoutDocker = true) class ElasticsearchRestClientAutoConfigurationTests { @@ -166,6 +173,70 @@ class ElasticsearchRestClientAutoConfigurationTests { }); } + @Test + void configureUriWithUsernameOnly() { + this.contextRunner.withPropertyValues("spring.elasticsearch.rest.uris=http://user@localhost:9200") + .run((context) -> { + RestClient client = context.getBean(RestClient.class); + assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString)) + .containsExactly("http://localhost:9200"); + assertThat(client).extracting("client") + .extracting("credentialsProvider", + InstanceOfAssertFactories.type(CredentialsProvider.class)) + .satisfies((credentialsProvider) -> { + Credentials credentials = credentialsProvider + .getCredentials(new AuthScope("localhost", 9200)); + assertThat(credentials.getUserPrincipal().getName()).isEqualTo("user"); + assertThat(credentials.getPassword()).isNull(); + }); + }); + } + + @Test + void configureUriWithUsernameAndEmptyPassword() { + this.contextRunner.withPropertyValues("spring.elasticsearch.rest.uris=http://user:@localhost:9200") + .run((context) -> { + RestClient client = context.getBean(RestClient.class); + assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString)) + .containsExactly("http://localhost:9200"); + assertThat(client).extracting("client") + .extracting("credentialsProvider", + InstanceOfAssertFactories.type(CredentialsProvider.class)) + .satisfies((credentialsProvider) -> { + Credentials credentials = credentialsProvider + .getCredentials(new AuthScope("localhost", 9200)); + assertThat(credentials.getUserPrincipal().getName()).isEqualTo("user"); + assertThat(credentials.getPassword()).isEmpty(); + }); + }); + } + + @Test + void configureUriWithUsernameAndPasswordWhenUsernameAndPasswordPropertiesSet() { + this.contextRunner + .withPropertyValues("spring.elasticsearch.rest.uris=http://user:password@localhost:9200,localhost:9201", + "spring.elasticsearch.rest.username=admin", "spring.elasticsearch.rest.password=admin") + .run((context) -> { + RestClient client = context.getBean(RestClient.class); + assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString)) + .containsExactly("http://localhost:9200", "http://localhost:9201"); + assertThat(client).extracting("client") + .extracting("credentialsProvider", + InstanceOfAssertFactories.type(CredentialsProvider.class)) + .satisfies((credentialsProvider) -> { + Credentials uriCredentials = credentialsProvider + .getCredentials(new AuthScope("localhost", 9200)); + assertThat(uriCredentials.getUserPrincipal().getName()).isEqualTo("user"); + assertThat(uriCredentials.getPassword()).isEqualTo("password"); + + Credentials defaultCredentials = credentialsProvider + .getCredentials(new AuthScope("localhost", 9201)); + assertThat(defaultCredentials.getUserPrincipal().getName()).isEqualTo("admin"); + assertThat(defaultCredentials.getPassword()).isEqualTo("admin"); + }); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomRestClientConfiguration { From f8982bdc14af90b590c8131c09b95b05edebdd79 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sun, 7 Jun 2020 11:56:18 -0700 Subject: [PATCH 2/2] Polish 'Support userInfo in elasticsearch URI' See gh-21381 --- ...ElasticsearchRestClientConfigurations.java | 93 +++++++++++++------ ...earchRestClientAutoConfigurationTests.java | 1 - 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java index d7ff03005ba..b7ab5e9c0b2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java @@ -17,13 +17,13 @@ package org.springframework.boot.autoconfigure.elasticsearch; import java.net.URI; +import java.net.URISyntaxException; import java.time.Duration; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; @@ -37,6 +37,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; /** * Elasticsearch rest client infrastructure configurations. @@ -75,9 +76,25 @@ class ElasticsearchRestClientConfigurations { } private HttpHost createHttpHost(String uri) { - URI parsedUri = URI.create(uri); - String userInfo = parsedUri.getUserInfo(); - return HttpHost.create((userInfo != null) ? uri.replace(userInfo + "@", "") : uri); + try { + return createHttpHost(URI.create(uri)); + } + catch (IllegalArgumentException ex) { + return HttpHost.create(uri); + } + } + + private HttpHost createHttpHost(URI uri) { + if (!StringUtils.hasLength(uri.getUserInfo())) { + return HttpHost.create(uri.toString()); + } + try { + return HttpHost.create(new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), + uri.getQuery(), uri.getFragment()).toString()); + } + catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } } } @@ -132,30 +149,7 @@ class ElasticsearchRestClientConfigurations { @Override public void customize(HttpAsyncClientBuilder builder) { - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - builder.setDefaultCredentialsProvider(credentialsProvider); - this.properties.getUris().stream().map(URI::create).filter((uri) -> uri.getUserInfo() != null) - .forEach((uri) -> { - AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort()); - Credentials credentials = createCredentials(uri.getUserInfo()); - credentialsProvider.setCredentials(authScope, credentials); - }); - map.from(this.properties::getUsername).whenHasText().to((username) -> { - Credentials credentials = new UsernamePasswordCredentials(this.properties.getUsername(), - this.properties.getPassword()); - credentialsProvider.setCredentials(AuthScope.ANY, credentials); - }); - } - - private Credentials createCredentials(String usernameAndPassword) { - int delimiter = usernameAndPassword.indexOf(":"); - if (delimiter == -1) { - return new UsernamePasswordCredentials(usernameAndPassword, null); - } - - String username = usernameAndPassword.substring(0, delimiter); - String password = usernameAndPassword.substring(delimiter + 1); - return new UsernamePasswordCredentials(username, password); + builder.setDefaultCredentialsProvider(new PropertiesCredentialsProvider(this.properties)); } @Override @@ -168,4 +162,47 @@ class ElasticsearchRestClientConfigurations { } + private static class PropertiesCredentialsProvider extends BasicCredentialsProvider { + + PropertiesCredentialsProvider(ElasticsearchRestClientProperties properties) { + if (StringUtils.hasText(properties.getUsername())) { + Credentials credentials = new UsernamePasswordCredentials(properties.getUsername(), + properties.getPassword()); + setCredentials(AuthScope.ANY, credentials); + } + properties.getUris().stream().map(this::toUri).filter(this::hasUserInfo) + .forEach(this::addUserInfoCredentials); + } + + private URI toUri(String uri) { + try { + return URI.create(uri); + } + catch (IllegalArgumentException ex) { + return null; + } + } + + private boolean hasUserInfo(URI uri) { + return uri != null && StringUtils.hasLength(uri.getUserInfo()); + } + + private void addUserInfoCredentials(URI uri) { + AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort()); + Credentials credentials = createUserInfoCredentials(uri.getUserInfo()); + setCredentials(authScope, credentials); + } + + private Credentials createUserInfoCredentials(String userInfo) { + int delimiter = userInfo.indexOf(":"); + if (delimiter == -1) { + return new UsernamePasswordCredentials(userInfo, null); + } + String username = userInfo.substring(0, delimiter); + String password = userInfo.substring(delimiter + 1); + return new UsernamePasswordCredentials(username, password); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java index b2965023f23..09648bf9bc5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java @@ -228,7 +228,6 @@ class ElasticsearchRestClientAutoConfigurationTests { .getCredentials(new AuthScope("localhost", 9200)); assertThat(uriCredentials.getUserPrincipal().getName()).isEqualTo("user"); assertThat(uriCredentials.getPassword()).isEqualTo("password"); - Credentials defaultCredentials = credentialsProvider .getCredentials(new AuthScope("localhost", 9201)); assertThat(defaultCredentials.getUserPrincipal().getName()).isEqualTo("admin");