Add ConnectionDetail support to Neo4J auto-configuration
Update Neo4J auto-configuration so that `Neo4jConnectionDetails` beans may be optionally used to provide connection details. See gh-34657 Co-Authored-By: Mortitz Halbritter <mkammerer@vmware.com> Co-Authored-By: Phillip Webb <pwebb@vmware.com>
This commit is contained in:
parent
2ef33dc81f
commit
de8fb04814
|
|
@ -36,12 +36,14 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
|||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Authentication;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Pool;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -49,6 +51,9 @@ import org.springframework.util.StringUtils;
|
|||
*
|
||||
* @author Michael J. Simons
|
||||
* @author Stephane Nicoll
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
|
|
@ -56,50 +61,24 @@ import org.springframework.util.StringUtils;
|
|||
@EnableConfigurationProperties(Neo4jProperties.class)
|
||||
public class Neo4jAutoConfiguration {
|
||||
|
||||
private static final URI DEFAULT_SERVER_URI = URI.create("bolt://localhost:7687");
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Driver neo4jDriver(Neo4jProperties properties, Environment environment,
|
||||
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers) {
|
||||
AuthToken authToken = mapAuthToken(properties.getAuthentication(), environment);
|
||||
Config config = mapDriverConfig(properties, configBuilderCustomizers.orderedStream().toList());
|
||||
URI serverUri = determineServerUri(properties, environment);
|
||||
return GraphDatabase.driver(serverUri, authToken, config);
|
||||
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers,
|
||||
ObjectProvider<Neo4jConnectionDetails> connectionDetailsProvider) {
|
||||
Neo4jConnectionDetails connectionDetails = connectionDetailsProvider
|
||||
.getIfAvailable(() -> new PropertiesNeo4jConnectionDetails(properties));
|
||||
AuthToken authToken = connectionDetails.getAuthToken();
|
||||
Config config = mapDriverConfig(properties, connectionDetails,
|
||||
configBuilderCustomizers.orderedStream().toList());
|
||||
return GraphDatabase.driver(connectionDetails.getUri(), authToken, config);
|
||||
}
|
||||
|
||||
URI determineServerUri(Neo4jProperties properties, Environment environment) {
|
||||
URI uri = properties.getUri();
|
||||
return (uri != null) ? uri : DEFAULT_SERVER_URI;
|
||||
}
|
||||
|
||||
AuthToken mapAuthToken(Neo4jProperties.Authentication authentication, Environment environment) {
|
||||
String username = authentication.getUsername();
|
||||
String password = authentication.getPassword();
|
||||
String kerberosTicket = authentication.getKerberosTicket();
|
||||
String realm = authentication.getRealm();
|
||||
|
||||
boolean hasUsername = StringUtils.hasText(username);
|
||||
boolean hasPassword = StringUtils.hasText(password);
|
||||
boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket);
|
||||
|
||||
if (hasUsername && hasKerberosTicket) {
|
||||
throw new IllegalStateException(String
|
||||
.format("Cannot specify both username ('%s') and kerberos ticket ('%s')", username, kerberosTicket));
|
||||
}
|
||||
if (hasUsername && hasPassword) {
|
||||
return AuthTokens.basic(username, password, realm);
|
||||
}
|
||||
if (hasKerberosTicket) {
|
||||
return AuthTokens.kerberos(kerberosTicket);
|
||||
}
|
||||
return AuthTokens.none();
|
||||
}
|
||||
|
||||
Config mapDriverConfig(Neo4jProperties properties, List<ConfigBuilderCustomizer> customizers) {
|
||||
Config mapDriverConfig(Neo4jProperties properties, Neo4jConnectionDetails connectionDetails,
|
||||
List<ConfigBuilderCustomizer> customizers) {
|
||||
Config.ConfigBuilder builder = Config.builder();
|
||||
configurePoolSettings(builder, properties.getPool());
|
||||
URI uri = properties.getUri();
|
||||
URI uri = connectionDetails.getUri();
|
||||
String scheme = (uri != null) ? uri.getScheme() : "bolt";
|
||||
configureDriverSettings(builder, properties, isSimpleScheme(scheme));
|
||||
builder.withLogging(new Neo4jSpringJclLogging());
|
||||
|
|
@ -191,4 +170,43 @@ public class Neo4jAutoConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts {@link Neo4jProperties} to {@link Neo4jConnectionDetails}.
|
||||
*/
|
||||
static class PropertiesNeo4jConnectionDetails implements Neo4jConnectionDetails {
|
||||
|
||||
private final Neo4jProperties properties;
|
||||
|
||||
PropertiesNeo4jConnectionDetails(Neo4jProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
URI uri = this.properties.getUri();
|
||||
return (uri != null) ? uri : Neo4jConnectionDetails.super.getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthToken getAuthToken() {
|
||||
Authentication authentication = this.properties.getAuthentication();
|
||||
String username = authentication.getUsername();
|
||||
String kerberosTicket = authentication.getKerberosTicket();
|
||||
boolean hasUsername = StringUtils.hasText(username);
|
||||
boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket);
|
||||
Assert.state(!(hasUsername && hasKerberosTicket),
|
||||
() -> "Cannot specify both username ('%s') and kerberos ticket ('%s')".formatted(username,
|
||||
kerberosTicket));
|
||||
String password = authentication.getPassword();
|
||||
if (hasUsername && StringUtils.hasText(password)) {
|
||||
return AuthTokens.basic(username, password, authentication.getRealm());
|
||||
}
|
||||
if (hasKerberosTicket) {
|
||||
return AuthTokens.kerberos(kerberosTicket);
|
||||
}
|
||||
return AuthTokens.none();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.neo4j;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.neo4j.driver.AuthToken;
|
||||
import org.neo4j.driver.AuthTokens;
|
||||
|
||||
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
|
||||
|
||||
/**
|
||||
* Details required to establish a connection to a Neo4j service.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface Neo4jConnectionDetails extends ConnectionDetails {
|
||||
|
||||
/**
|
||||
* Returns the URI of the Neo4j server. Defaults to {@code bolt://localhost:7687"}.
|
||||
* @return the Neo4j server URI
|
||||
*/
|
||||
default URI getUri() {
|
||||
return URI.create("bolt://localhost:7687");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token to use for authentication. Defaults to {@link AuthTokens#none()}.
|
||||
* @return the auth token
|
||||
*/
|
||||
default AuthToken getAuthToken() {
|
||||
return AuthTokens.none();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -26,20 +26,19 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.neo4j.driver.AuthToken;
|
||||
import org.neo4j.driver.AuthTokens;
|
||||
import org.neo4j.driver.Config;
|
||||
import org.neo4j.driver.Config.ConfigBuilder;
|
||||
import org.neo4j.driver.Driver;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration.PropertiesNeo4jConnectionDetails;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Authentication;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security.TrustStrategy;
|
||||
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
|
@ -50,6 +49,9 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
|||
*
|
||||
* @author Michael J. Simons
|
||||
* @author Stephane Nicoll
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class Neo4jAutoConfigurationTests {
|
||||
|
||||
|
|
@ -103,6 +105,22 @@ class Neo4jAutoConfigurationTests {
|
|||
.hasMessageContaining("'%s' is not a supported scheme.", invalidScheme));
|
||||
}
|
||||
|
||||
@Bean
|
||||
void usesCustomConnectionDetails() {
|
||||
this.contextRunner.withBean(Neo4jConnectionDetails.class, () -> new Neo4jConnectionDetails() {
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
return URI.create("bolt+ssc://localhost:12345");
|
||||
}
|
||||
|
||||
}).run((context) -> {
|
||||
assertThat(context).hasSingleBean(Driver.class);
|
||||
Driver driver = context.getBean(Driver.class);
|
||||
assertThat(driver.isEncrypted()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionTimeout() {
|
||||
Neo4jProperties properties = new Neo4jProperties();
|
||||
|
|
@ -118,8 +136,8 @@ class Neo4jAutoConfigurationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void determineServerUriShouldDefaultToLocalhost() {
|
||||
assertThat(determineServerUri(new Neo4jProperties(), new MockEnvironment()))
|
||||
void uriShouldDefaultToLocalhost() {
|
||||
assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getUri())
|
||||
.isEqualTo(URI.create("bolt://localhost:7687"));
|
||||
}
|
||||
|
||||
|
|
@ -128,44 +146,52 @@ class Neo4jAutoConfigurationTests {
|
|||
URI customUri = URI.create("bolt://localhost:4242");
|
||||
Neo4jProperties properties = new Neo4jProperties();
|
||||
properties.setUri(customUri);
|
||||
assertThat(determineServerUri(properties, new MockEnvironment())).isEqualTo(customUri);
|
||||
assertThat(new PropertiesNeo4jConnectionDetails(properties).getUri()).isEqualTo(customUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticationShouldDefaultToNone() {
|
||||
assertThat(mapAuthToken(new Authentication())).isEqualTo(AuthTokens.none());
|
||||
assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getAuthToken())
|
||||
.isEqualTo(AuthTokens.none());
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticationWithUsernameShouldEnableBasicAuth() {
|
||||
Authentication authentication = new Authentication();
|
||||
authentication.setUsername("Farin");
|
||||
authentication.setPassword("Urlaub");
|
||||
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub"));
|
||||
Neo4jProperties properties = new Neo4jProperties();
|
||||
properties.getAuthentication().setUsername("Farin");
|
||||
properties.getAuthentication().setPassword("Urlaub");
|
||||
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
|
||||
.isEqualTo(AuthTokens.basic("Farin", "Urlaub"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticationWithUsernameAndRealmShouldEnableBasicAuth() {
|
||||
Authentication authentication = new Authentication();
|
||||
Neo4jProperties properties = new Neo4jProperties();
|
||||
Authentication authentication = properties.getAuthentication();
|
||||
authentication.setUsername("Farin");
|
||||
authentication.setPassword("Urlaub");
|
||||
authentication.setRealm("Test Realm");
|
||||
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm"));
|
||||
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
|
||||
.isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticationWithKerberosTicketShouldEnableKerberos() {
|
||||
Authentication authentication = new Authentication();
|
||||
Neo4jProperties properties = new Neo4jProperties();
|
||||
Authentication authentication = properties.getAuthentication();
|
||||
authentication.setKerberosTicket("AABBCCDDEE");
|
||||
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.kerberos("AABBCCDDEE"));
|
||||
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
|
||||
.isEqualTo(AuthTokens.kerberos("AABBCCDDEE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticationWithBothUsernameAndKerberosShouldNotBeAllowed() {
|
||||
Authentication authentication = new Authentication();
|
||||
Neo4jProperties properties = new Neo4jProperties();
|
||||
Authentication authentication = properties.getAuthentication();
|
||||
authentication.setUsername("Farin");
|
||||
authentication.setKerberosTicket("AABBCCDDEE");
|
||||
assertThatIllegalStateException().isThrownBy(() -> mapAuthToken(authentication))
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
|
||||
.withMessage("Cannot specify both username ('Farin') and kerberos ticket ('AABBCCDDEE')");
|
||||
}
|
||||
|
||||
|
|
@ -279,20 +305,9 @@ class Neo4jAutoConfigurationTests {
|
|||
assertThat(mapDriverConfig(new Neo4jProperties()).logging()).isInstanceOf(Neo4jSpringJclLogging.class);
|
||||
}
|
||||
|
||||
private URI determineServerUri(Neo4jProperties properties, Environment environment) {
|
||||
return new Neo4jAutoConfiguration().determineServerUri(properties, environment);
|
||||
}
|
||||
|
||||
private AuthToken mapAuthToken(Authentication authentication, Environment environment) {
|
||||
return new Neo4jAutoConfiguration().mapAuthToken(authentication, environment);
|
||||
}
|
||||
|
||||
private AuthToken mapAuthToken(Authentication authentication) {
|
||||
return mapAuthToken(authentication, new MockEnvironment());
|
||||
}
|
||||
|
||||
private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) {
|
||||
return new Neo4jAutoConfiguration().mapDriverConfig(properties, Arrays.asList(customizers));
|
||||
return new Neo4jAutoConfiguration().mapDriverConfig(properties,
|
||||
new PropertiesNeo4jConnectionDetails(properties), Arrays.asList(customizers));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue