Skip to content

Commit 8ac8329

Browse files
committed
Merge pull request #21381 from evgeniycheban
* pr/21381: Polish 'Support userInfo in elasticsearch URI' Support userInfo in elasticsearch URI Closes gh-21381
2 parents abaca95 + f8982bd commit 8ac8329

File tree

2 files changed

+141
-9
lines changed

2 files changed

+141
-9
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616

1717
package org.springframework.boot.autoconfigure.elasticsearch;
1818

19+
import java.net.URI;
20+
import java.net.URISyntaxException;
1921
import java.time.Duration;
2022

2123
import org.apache.http.HttpHost;
2224
import org.apache.http.auth.AuthScope;
2325
import org.apache.http.auth.Credentials;
2426
import org.apache.http.auth.UsernamePasswordCredentials;
25-
import org.apache.http.client.CredentialsProvider;
2627
import org.apache.http.client.config.RequestConfig;
2728
import org.apache.http.impl.client.BasicCredentialsProvider;
2829
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
@@ -36,13 +37,15 @@
3637
import org.springframework.boot.context.properties.PropertyMapper;
3738
import org.springframework.context.annotation.Bean;
3839
import org.springframework.context.annotation.Configuration;
40+
import org.springframework.util.StringUtils;
3941

4042
/**
4143
* Elasticsearch rest client infrastructure configurations.
4244
*
4345
* @author Brian Clozel
4446
* @author Stephane Nicoll
4547
* @author Vedran Pavic
48+
* @author Evgeniy Cheban
4649
*/
4750
class ElasticsearchRestClientConfigurations {
4851

@@ -58,7 +61,7 @@ RestClientBuilderCustomizer defaultRestClientBuilderCustomizer(ElasticsearchRest
5861
@Bean
5962
RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchRestClientProperties properties,
6063
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers) {
61-
HttpHost[] hosts = properties.getUris().stream().map(HttpHost::create).toArray(HttpHost[]::new);
64+
HttpHost[] hosts = properties.getUris().stream().map(this::createHttpHost).toArray(HttpHost[]::new);
6265
RestClientBuilder builder = RestClient.builder(hosts);
6366
builder.setHttpClientConfigCallback((httpClientBuilder) -> {
6467
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder));
@@ -72,6 +75,28 @@ RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchRestClientProperti
7275
return builder;
7376
}
7477

78+
private HttpHost createHttpHost(String uri) {
79+
try {
80+
return createHttpHost(URI.create(uri));
81+
}
82+
catch (IllegalArgumentException ex) {
83+
return HttpHost.create(uri);
84+
}
85+
}
86+
87+
private HttpHost createHttpHost(URI uri) {
88+
if (!StringUtils.hasLength(uri.getUserInfo())) {
89+
return HttpHost.create(uri.toString());
90+
}
91+
try {
92+
return HttpHost.create(new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(),
93+
uri.getQuery(), uri.getFragment()).toString());
94+
}
95+
catch (URISyntaxException ex) {
96+
throw new IllegalStateException(ex);
97+
}
98+
}
99+
75100
}
76101

77102
@Configuration(proxyBeanMethods = false)
@@ -124,13 +149,7 @@ public void customize(RestClientBuilder builder) {
124149

125150
@Override
126151
public void customize(HttpAsyncClientBuilder builder) {
127-
map.from(this.properties::getUsername).whenHasText().to((username) -> {
128-
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
129-
Credentials credentials = new UsernamePasswordCredentials(this.properties.getUsername(),
130-
this.properties.getPassword());
131-
credentialsProvider.setCredentials(AuthScope.ANY, credentials);
132-
builder.setDefaultCredentialsProvider(credentialsProvider);
133-
});
152+
builder.setDefaultCredentialsProvider(new PropertiesCredentialsProvider(this.properties));
134153
}
135154

136155
@Override
@@ -143,4 +162,47 @@ public void customize(RequestConfig.Builder builder) {
143162

144163
}
145164

165+
private static class PropertiesCredentialsProvider extends BasicCredentialsProvider {
166+
167+
PropertiesCredentialsProvider(ElasticsearchRestClientProperties properties) {
168+
if (StringUtils.hasText(properties.getUsername())) {
169+
Credentials credentials = new UsernamePasswordCredentials(properties.getUsername(),
170+
properties.getPassword());
171+
setCredentials(AuthScope.ANY, credentials);
172+
}
173+
properties.getUris().stream().map(this::toUri).filter(this::hasUserInfo)
174+
.forEach(this::addUserInfoCredentials);
175+
}
176+
177+
private URI toUri(String uri) {
178+
try {
179+
return URI.create(uri);
180+
}
181+
catch (IllegalArgumentException ex) {
182+
return null;
183+
}
184+
}
185+
186+
private boolean hasUserInfo(URI uri) {
187+
return uri != null && StringUtils.hasLength(uri.getUserInfo());
188+
}
189+
190+
private void addUserInfoCredentials(URI uri) {
191+
AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort());
192+
Credentials credentials = createUserInfoCredentials(uri.getUserInfo());
193+
setCredentials(authScope, credentials);
194+
}
195+
196+
private Credentials createUserInfoCredentials(String userInfo) {
197+
int delimiter = userInfo.indexOf(":");
198+
if (delimiter == -1) {
199+
return new UsernamePasswordCredentials(userInfo, null);
200+
}
201+
String username = userInfo.substring(0, delimiter);
202+
String password = userInfo.substring(delimiter + 1);
203+
return new UsernamePasswordCredentials(username, password);
204+
}
205+
206+
}
207+
146208
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@
2020
import java.util.HashMap;
2121
import java.util.Map;
2222

23+
import org.apache.http.HttpHost;
24+
import org.apache.http.auth.AuthScope;
25+
import org.apache.http.auth.Credentials;
26+
import org.apache.http.client.CredentialsProvider;
2327
import org.apache.http.client.config.RequestConfig;
2428
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
29+
import org.assertj.core.api.InstanceOfAssertFactories;
2530
import org.elasticsearch.action.get.GetRequest;
2631
import org.elasticsearch.action.index.IndexRequest;
32+
import org.elasticsearch.client.Node;
2733
import org.elasticsearch.client.RequestOptions;
2834
import org.elasticsearch.client.RestClient;
2935
import org.elasticsearch.client.RestClientBuilder;
@@ -47,6 +53,7 @@
4753
*
4854
* @author Brian Clozel
4955
* @author Vedran Pavic
56+
* @author Evgeniy Cheban
5057
*/
5158
@Testcontainers(disabledWithoutDocker = true)
5259
class ElasticsearchRestClientAutoConfigurationTests {
@@ -166,6 +173,69 @@ void restClientCanQueryElasticsearchNode() {
166173
});
167174
}
168175

176+
@Test
177+
void configureUriWithUsernameOnly() {
178+
this.contextRunner.withPropertyValues("spring.elasticsearch.rest.uris=http://user@localhost:9200")
179+
.run((context) -> {
180+
RestClient client = context.getBean(RestClient.class);
181+
assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString))
182+
.containsExactly("http://localhost:9200");
183+
assertThat(client).extracting("client")
184+
.extracting("credentialsProvider",
185+
InstanceOfAssertFactories.type(CredentialsProvider.class))
186+
.satisfies((credentialsProvider) -> {
187+
Credentials credentials = credentialsProvider
188+
.getCredentials(new AuthScope("localhost", 9200));
189+
assertThat(credentials.getUserPrincipal().getName()).isEqualTo("user");
190+
assertThat(credentials.getPassword()).isNull();
191+
});
192+
});
193+
}
194+
195+
@Test
196+
void configureUriWithUsernameAndEmptyPassword() {
197+
this.contextRunner.withPropertyValues("spring.elasticsearch.rest.uris=http://user:@localhost:9200")
198+
.run((context) -> {
199+
RestClient client = context.getBean(RestClient.class);
200+
assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString))
201+
.containsExactly("http://localhost:9200");
202+
assertThat(client).extracting("client")
203+
.extracting("credentialsProvider",
204+
InstanceOfAssertFactories.type(CredentialsProvider.class))
205+
.satisfies((credentialsProvider) -> {
206+
Credentials credentials = credentialsProvider
207+
.getCredentials(new AuthScope("localhost", 9200));
208+
assertThat(credentials.getUserPrincipal().getName()).isEqualTo("user");
209+
assertThat(credentials.getPassword()).isEmpty();
210+
});
211+
});
212+
}
213+
214+
@Test
215+
void configureUriWithUsernameAndPasswordWhenUsernameAndPasswordPropertiesSet() {
216+
this.contextRunner
217+
.withPropertyValues("spring.elasticsearch.rest.uris=http://user:password@localhost:9200,localhost:9201",
218+
"spring.elasticsearch.rest.username=admin", "spring.elasticsearch.rest.password=admin")
219+
.run((context) -> {
220+
RestClient client = context.getBean(RestClient.class);
221+
assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString))
222+
.containsExactly("http://localhost:9200", "http://localhost:9201");
223+
assertThat(client).extracting("client")
224+
.extracting("credentialsProvider",
225+
InstanceOfAssertFactories.type(CredentialsProvider.class))
226+
.satisfies((credentialsProvider) -> {
227+
Credentials uriCredentials = credentialsProvider
228+
.getCredentials(new AuthScope("localhost", 9200));
229+
assertThat(uriCredentials.getUserPrincipal().getName()).isEqualTo("user");
230+
assertThat(uriCredentials.getPassword()).isEqualTo("password");
231+
Credentials defaultCredentials = credentialsProvider
232+
.getCredentials(new AuthScope("localhost", 9201));
233+
assertThat(defaultCredentials.getUserPrincipal().getName()).isEqualTo("admin");
234+
assertThat(defaultCredentials.getPassword()).isEqualTo("admin");
235+
});
236+
});
237+
}
238+
169239
@Configuration(proxyBeanMethods = false)
170240
static class CustomRestClientConfiguration {
171241

0 commit comments

Comments
 (0)