From 77e382ec64c76deab65dd3ec93dd8bc0dc43e714 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 2 Aug 2023 13:20:54 +0100 Subject: [PATCH] Polish "Add support for using an AuthTokenManager with Neo4j" See gh-36650 --- .../neo4j/Neo4jAutoConfiguration.java | 21 +++++-- .../neo4j/Neo4jConnectionDetails.java | 11 ++++ ...eo4jAutoConfigurationIntegrationTests.java | 56 +++++++++++++++++++ .../neo4j/Neo4jAutoConfigurationTests.java | 36 ++++++++---- 4 files changed, 108 insertions(+), 16 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java index 8eaf6569ee..9beb05e518 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java @@ -64,19 +64,20 @@ public class Neo4jAutoConfiguration { @Bean @ConditionalOnMissingBean(Neo4jConnectionDetails.class) - PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properties) { - return new PropertiesNeo4jConnectionDetails(properties); + PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properties, + ObjectProvider authTokenManager) { + return new PropertiesNeo4jConnectionDetails(properties, authTokenManager.getIfUnique()); } @Bean @ConditionalOnMissingBean public Driver neo4jDriver(Neo4jProperties properties, Environment environment, - Neo4jConnectionDetails connectionDetails, ObjectProvider configBuilderCustomizers, - ObjectProvider authTokenManagers) { + Neo4jConnectionDetails connectionDetails, + ObjectProvider configBuilderCustomizers) { Config config = mapDriverConfig(properties, connectionDetails, configBuilderCustomizers.orderedStream().toList()); - AuthTokenManager authTokenManager = authTokenManagers.getIfUnique(); + AuthTokenManager authTokenManager = connectionDetails.getAuthTokenManager(); if (authTokenManager != null) { return GraphDatabase.driver(connectionDetails.getUri(), authTokenManager, config); } @@ -187,8 +188,11 @@ public class Neo4jAutoConfiguration { private final Neo4jProperties properties; - PropertiesNeo4jConnectionDetails(Neo4jProperties properties) { + private final AuthTokenManager authTokenManager; + + PropertiesNeo4jConnectionDetails(Neo4jProperties properties, AuthTokenManager authTokenManager) { this.properties = properties; + this.authTokenManager = authTokenManager; } @Override @@ -217,6 +221,11 @@ public class Neo4jAutoConfiguration { return AuthTokens.none(); } + @Override + public AuthTokenManager getAuthTokenManager() { + return this.authTokenManager; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jConnectionDetails.java index 17a950ebd8..f10a122338 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jConnectionDetails.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jConnectionDetails.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.neo4j; import java.net.URI; import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokenManager; import org.neo4j.driver.AuthTokens; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; @@ -49,4 +50,14 @@ public interface Neo4jConnectionDetails extends ConnectionDetails { return AuthTokens.none(); } + /** + * Returns the {@link AuthTokenManager} to use for authentication. Defaults to + * {@code null} in which case the {@link #getAuthToken() auth token} should be used. + * @return the auth token manager + * @since 3.2.0 + */ + default AuthTokenManager getAuthTokenManager() { + return null; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java index fb97387470..966fc33637 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java @@ -16,10 +16,12 @@ package org.springframework.boot.autoconfigure.neo4j; +import java.net.URI; import java.time.Duration; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokenManager; import org.neo4j.driver.AuthTokenManagers; import org.neo4j.driver.AuthTokens; @@ -124,4 +126,58 @@ class Neo4jAutoConfigurationIntegrationTests { } + @SpringBootTest + @Nested + class DriverWithCustomConnectionDetailsIgnoresAuthTokenManager { + + @DynamicPropertySource + static void neo4jProperties(DynamicPropertyRegistry registry) { + registry.add("spring.neo4j.uri", neo4jServer::getBoltUrl); + registry.add("spring.neo4j.authentication.username", () -> "wrong"); + registry.add("spring.neo4j.authentication.password", () -> "alsowrong"); + } + + @Autowired + private Driver driver; + + @Test + void driverCanHandleRequest() { + try (Session session = this.driver.session(); Transaction tx = session.beginTransaction()) { + Result statementResult = tx.run("MATCH (n:Thing) RETURN n LIMIT 1"); + assertThat(statementResult.hasNext()).isFalse(); + tx.commit(); + } + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(Neo4jAutoConfiguration.class) + static class TestConfiguration { + + @Bean + AuthTokenManager authTokenManager() { + return AuthTokenManagers.expirationBased(() -> AuthTokens.basic("wrongagain", "stillwrong") + .expiringAt(System.currentTimeMillis() + 5_000)); + } + + @Bean + Neo4jConnectionDetails connectionDetails() { + return new Neo4jConnectionDetails() { + + @Override + public URI getUri() { + return URI.create(neo4jServer.getBoltUrl()); + } + + @Override + public AuthToken getAuthToken() { + return AuthTokens.basic("neo4j", neo4jServer.getAdminPassword()); + } + + }; + } + + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java index df6821f2c5..68ca8ed375 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java @@ -26,6 +26,7 @@ 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.AuthTokenManagers; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; import org.neo4j.driver.Config.ConfigBuilder; @@ -143,7 +144,7 @@ class Neo4jAutoConfigurationTests { @Test void uriShouldDefaultToLocalhost() { - assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getUri()) + assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties(), null).getUri()) .isEqualTo(URI.create("bolt://localhost:7687")); } @@ -152,12 +153,12 @@ class Neo4jAutoConfigurationTests { URI customUri = URI.create("bolt://localhost:4242"); Neo4jProperties properties = new Neo4jProperties(); properties.setUri(customUri); - assertThat(new PropertiesNeo4jConnectionDetails(properties).getUri()).isEqualTo(customUri); + assertThat(new PropertiesNeo4jConnectionDetails(properties, null).getUri()).isEqualTo(customUri); } @Test void authenticationShouldDefaultToNone() { - assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getAuthToken()) + assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties(), null).getAuthToken()) .isEqualTo(AuthTokens.none()); } @@ -166,8 +167,9 @@ class Neo4jAutoConfigurationTests { Neo4jProperties properties = new Neo4jProperties(); properties.getAuthentication().setUsername("Farin"); properties.getAuthentication().setPassword("Urlaub"); - assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken()) - .isEqualTo(AuthTokens.basic("Farin", "Urlaub")); + PropertiesNeo4jConnectionDetails connectionDetails = new PropertiesNeo4jConnectionDetails(properties, null); + assertThat(connectionDetails.getAuthToken()).isEqualTo(AuthTokens.basic("Farin", "Urlaub")); + assertThat(connectionDetails.getAuthTokenManager()).isNull(); } @Test @@ -177,8 +179,22 @@ class Neo4jAutoConfigurationTests { authentication.setUsername("Farin"); authentication.setPassword("Urlaub"); authentication.setRealm("Test Realm"); - assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken()) - .isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm")); + PropertiesNeo4jConnectionDetails connectionDetails = new PropertiesNeo4jConnectionDetails(properties, null); + assertThat(connectionDetails.getAuthToken()).isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm")); + assertThat(connectionDetails.getAuthTokenManager()).isNull(); + } + + @Test + void authenticationWithAuthTokenManagerAndUsernameShouldProvideAuthTokenManger() { + Neo4jProperties properties = new Neo4jProperties(); + Authentication authentication = properties.getAuthentication(); + authentication.setUsername("Farin"); + authentication.setPassword("Urlaub"); + authentication.setRealm("Test Realm"); + assertThat(new PropertiesNeo4jConnectionDetails(properties, + AuthTokenManagers.expirationBased( + () -> AuthTokens.basic("username", "password").expiringAt(System.currentTimeMillis() + 5000))) + .getAuthTokenManager()).isNotNull(); } @Test @@ -186,7 +202,7 @@ class Neo4jAutoConfigurationTests { Neo4jProperties properties = new Neo4jProperties(); Authentication authentication = properties.getAuthentication(); authentication.setKerberosTicket("AABBCCDDEE"); - assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken()) + assertThat(new PropertiesNeo4jConnectionDetails(properties, null).getAuthToken()) .isEqualTo(AuthTokens.kerberos("AABBCCDDEE")); } @@ -197,7 +213,7 @@ class Neo4jAutoConfigurationTests { authentication.setUsername("Farin"); authentication.setKerberosTicket("AABBCCDDEE"); assertThatIllegalStateException() - .isThrownBy(() -> new PropertiesNeo4jConnectionDetails(properties).getAuthToken()) + .isThrownBy(() -> new PropertiesNeo4jConnectionDetails(properties, null).getAuthToken()) .withMessage("Cannot specify both username ('Farin') and kerberos ticket ('AABBCCDDEE')"); } @@ -313,7 +329,7 @@ class Neo4jAutoConfigurationTests { private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) { return new Neo4jAutoConfiguration().mapDriverConfig(properties, - new PropertiesNeo4jConnectionDetails(properties), Arrays.asList(customizers)); + new PropertiesNeo4jConnectionDetails(properties, null), Arrays.asList(customizers)); } }