diff --git a/api/src/main/java/org/neo4j/ogm/config/Configuration.java b/api/src/main/java/org/neo4j/ogm/config/Configuration.java index ad5bab549c..ceb1dfb2db 100644 --- a/api/src/main/java/org/neo4j/ogm/config/Configuration.java +++ b/api/src/main/java/org/neo4j/ogm/config/Configuration.java @@ -555,7 +555,7 @@ public Builder connectionPoolSize(Integer connectionPoolSize) { /** * Required encryption level for the connection to the database. - * See org.neo4j.driver.v1.Config.EncryptionLevel for possible values. + * Possible values are {@literal REQUIRED}, {@literal OPTIONAL}, {@literal DISABLED}. * * @param encryptionLevel required encryption level * @return the changed builder diff --git a/api/src/main/java/org/neo4j/ogm/config/Drivers.java b/api/src/main/java/org/neo4j/ogm/config/Drivers.java index 1497507f12..50838d338f 100644 --- a/api/src/main/java/org/neo4j/ogm/config/Drivers.java +++ b/api/src/main/java/org/neo4j/ogm/config/Drivers.java @@ -24,7 +24,7 @@ enum Drivers { - BOLT("org.neo4j.ogm.drivers.bolt.driver.BoltDriver", "bolt", "bolt+routing", "neo4j"), + BOLT("org.neo4j.ogm.drivers.bolt.driver.BoltDriver", "bolt", "bolt+routing", "bolt+s", "bolt+ssc", "neo4j", "neo4j+s", "neo4j+ssc"), EMBEDDED("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver", "file"), HTTP("org.neo4j.ogm.drivers.http.driver.HttpDriver", "http", "https"); @@ -49,7 +49,7 @@ static Drivers getDriverFor(String scheme) { } } } - throw new RuntimeException(UNSUPPORTED_SCHEME_MESSAGE); + throw new IllegalArgumentException(UNSUPPORTED_SCHEME_MESSAGE); } String driverClassName() { diff --git a/bolt-driver/src/main/java/org/neo4j/ogm/drivers/bolt/driver/BoltDriver.java b/bolt-driver/src/main/java/org/neo4j/ogm/drivers/bolt/driver/BoltDriver.java index 2e76066df2..40141648cb 100644 --- a/bolt-driver/src/main/java/org/neo4j/ogm/drivers/bolt/driver/BoltDriver.java +++ b/bolt-driver/src/main/java/org/neo4j/ogm/drivers/bolt/driver/BoltDriver.java @@ -38,18 +38,10 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.AuthToken; -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Bookmark; -import org.neo4j.driver.Config; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Logging; -import org.neo4j.driver.Session; -import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.*; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.internal.Scheme; import org.neo4j.ogm.config.Configuration; import org.neo4j.ogm.config.Credentials; import org.neo4j.ogm.config.UsernamePasswordCredentials; @@ -149,6 +141,18 @@ private void checkDriverInitialized() { } } + static boolean isSimpleScheme(String scheme) { + + String lowerCaseScheme = scheme.toLowerCase(Locale.ENGLISH); + try { + Scheme.validateScheme(lowerCaseScheme); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException(String.format("'%s' is not a supported scheme.", scheme)); + } + + return lowerCaseScheme.equals("bolt") || lowerCaseScheme.equals("neo4j"); + } + private void initializeDriver() { final String serviceUnavailableMessage = "Could not create driver instance"; @@ -156,7 +160,8 @@ private void initializeDriver() { Driver driver; if (credentials != null) { UsernamePasswordCredentials usernameAndPassword = (UsernamePasswordCredentials) this.credentials; - AuthToken authToken = AuthTokens.basic(usernameAndPassword.getUsername(), usernameAndPassword.getPassword()); + AuthToken authToken = AuthTokens + .basic(usernameAndPassword.getUsername(), usernameAndPassword.getPassword()); driver = createDriver(authToken); } else { LOGGER.debug("Bolt Driver credentials not supplied"); @@ -304,57 +309,32 @@ private Optional getBoltLogging() { } private Config buildDriverConfig() { + + // Done outside the try/catch and explicity catch the illegalargument exception of singleURI + // so that exception semantics are not changed since we introduced that feature. + // + // GraphDatabase.routingDriver asserts `neo4j` scheme for each URI, so our trust settings + // have to be applied in this case. + final boolean shouldApplyEncryptionAndTrustSettings; + if (isRoutingConfig()) { + shouldApplyEncryptionAndTrustSettings = true; + } else { // Otherwise we check if it comes with the scheme or not. + URI singleUri = null; + try { + singleUri = getSingleURI(); + } catch (IllegalArgumentException e) { + } + shouldApplyEncryptionAndTrustSettings = singleUri == null || isSimpleScheme(singleUri.getScheme()); + } + try { Config.ConfigBuilder configBuilder = Config.builder(); configBuilder.withMaxConnectionPoolSize(configuration.getConnectionPoolSize()); - if (configuration.getEncryptionLevel() != null && "REQUIRED" - .equals(configuration.getEncryptionLevel().toUpperCase(Locale.ENGLISH).trim())) { - configBuilder.withEncryption(); - } else { - configBuilder.withoutEncryption(); + if (shouldApplyEncryptionAndTrustSettings) { + applyEncryptionAndTrustSettings(configBuilder); } - Config.TrustStrategy.Strategy trustStrategy; - if (configuration.getTrustStrategy() != null) { - - String configuredTrustStrategy = configuration.getTrustStrategy().toUpperCase(Locale.ENGLISH).trim(); - if (Arrays.asList("TRUST_ON_FIRST_USE", "TRUST_SIGNED_CERTIFICATES") - .contains(configuredTrustStrategy)) { - String validNames = Arrays.stream(Config.TrustStrategy.Strategy.values()).map( - Config.TrustStrategy.Strategy::name).collect(joining(", ")); - throw new IllegalArgumentException( - "Truststrategy " + configuredTrustStrategy + " is no longer supported, please choose one of " - + validNames); - } - - try { - trustStrategy = Config.TrustStrategy.Strategy.valueOf(configuredTrustStrategy); - } catch (IllegalArgumentException iae) { - LOGGER.debug("Invalid configuration for the Bolt Driver Trust Strategy: {}", - configuration.getTrustStrategy()); - throw iae; - } - - switch (trustStrategy) { - case TRUST_ALL_CERTIFICATES: - configBuilder.withTrustStrategy(Config.TrustStrategy.trustAllCertificates()); - break; - case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: - configBuilder.withTrustStrategy(Config.TrustStrategy.trustSystemCertificates()); - break; - case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: - if (configuration.getTrustCertFile() == null) { - throw new IllegalArgumentException( - "Configured trust strategy requires a certificate file."); - } - configBuilder.withTrustStrategy(Config.TrustStrategy - .trustCustomCertificateSignedBy(new File(new URI(configuration.getTrustCertFile())))); - break; - default: - throw new IllegalArgumentException("Unknown strategy." + trustStrategy); - } - } if (configuration.getConnectionLivenessCheckTimeout() != null) { configBuilder.withConnectionLivenessCheckTimeout(configuration.getConnectionLivenessCheckTimeout(), TimeUnit.MILLISECONDS); @@ -368,6 +348,56 @@ private Config buildDriverConfig() { } } + private void applyEncryptionAndTrustSettings(Config.ConfigBuilder configBuilder) { + if (configuration.getEncryptionLevel() != null && "REQUIRED" + .equals(configuration.getEncryptionLevel().toUpperCase(Locale.ENGLISH).trim())) { + configBuilder.withEncryption(); + } else { + configBuilder.withoutEncryption(); + } + + Config.TrustStrategy.Strategy trustStrategy; + if (configuration.getTrustStrategy() != null) { + + String configuredTrustStrategy = configuration.getTrustStrategy().toUpperCase(Locale.ENGLISH).trim(); + if (Arrays.asList("TRUST_ON_FIRST_USE", "TRUST_SIGNED_CERTIFICATES") + .contains(configuredTrustStrategy)) { + String validNames = Arrays.stream(Config.TrustStrategy.Strategy.values()).map( + Config.TrustStrategy.Strategy::name).collect(joining(", ")); + throw new IllegalArgumentException( + "Truststrategy " + configuredTrustStrategy + " is no longer supported, please choose one of " + + validNames); + } + + try { + trustStrategy = Config.TrustStrategy.Strategy.valueOf(configuredTrustStrategy); + } catch (IllegalArgumentException iae) { + LOGGER.debug("Invalid configuration for the Bolt Driver Trust Strategy: {}", + configuration.getTrustStrategy()); + throw iae; + } + + switch (trustStrategy) { + case TRUST_ALL_CERTIFICATES: + configBuilder.withTrustStrategy(Config.TrustStrategy.trustAllCertificates()); + break; + case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: + configBuilder.withTrustStrategy(Config.TrustStrategy.trustSystemCertificates()); + break; + case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: + if (configuration.getTrustCertFile() == null) { + throw new IllegalArgumentException( + "Configured trust strategy requires a certificate file."); + } + configBuilder.withTrustStrategy(Config.TrustStrategy + .trustCustomCertificateSignedBy(new File(URI.create(configuration.getTrustCertFile())))); + break; + default: + throw new IllegalArgumentException("Unknown strategy." + trustStrategy); + } + } + } + @SuppressWarnings("deprecation") static List bookmarksFromStrings(Iterable bookmarks) { return StreamSupport.stream(bookmarks.spliterator(), false) diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/config/ConfigurationTest.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/config/ConfigurationTest.java index da5b8a5aa8..5c3d8594a2 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/config/ConfigurationTest.java +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/config/ConfigurationTest.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*; +import java.util.Arrays; import java.util.Properties; import org.junit.Test; @@ -159,6 +160,49 @@ public void shouldParseEmbeddedUriSchemeCaseInsensitive() { assertThat(configuration.getURI()).isEqualTo("FILE:///somewhere"); } + @Test + public void shouldDetectSimpleBoltSchemes() { + + for (String aSimpleScheme : Arrays.asList("bolt", "Bolt", "neo4j", "Neo4J")) { + String uri = String.format("%s://localhost:7687", aSimpleScheme); + Configuration configuration = new Configuration.Builder() + .uri(uri) + .build(); + + assertThat(configuration.getDriverClassName()).isEqualTo("org.neo4j.ogm.drivers.bolt.driver.BoltDriver"); + assertThat(configuration.getURI()).isEqualTo(uri); + } + } + + @Test + public void shouldDetectAdvancedBoltSchemes() { + + for (String anAdvancedScheme : Arrays.asList("bolt+s", "Bolt+ssc", "neo4j+s", "Neo4J+ssc")) { + String uri = String.format("%s://localhost:7687", anAdvancedScheme); + Configuration configuration = new Configuration.Builder() + .uri(uri) + .build(); + + assertThat(configuration.getDriverClassName()).isEqualTo("org.neo4j.ogm.drivers.bolt.driver.BoltDriver"); + assertThat(configuration.getURI()).isEqualTo(uri); + } + } + + @Test + public void shouldFailEarlyOnInvalidSchemes() { + + for (String invalidScheme : Arrays.asList("bolt+x", "neo4j+wth")) { + String uri = String.format("%s://localhost:7687", invalidScheme); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new Configuration.Builder() + .uri(uri) + .build()) + .withMessage( + "A URI Scheme must be one of: bolt, bolt+routing, bolt+s, bolt+ssc, neo4j, neo4j+s, neo4j+ssc, file, http, https."); + } + } + @Test public void shouldParseBaseBackagesWithEmtpyValue() { Properties properties = new Properties(); diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/drivers/bolt/MultipleURIsBoltDriverTest.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/drivers/bolt/MultipleURIsBoltDriverTest.java deleted file mode 100644 index c30ef46935..0000000000 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/drivers/bolt/MultipleURIsBoltDriverTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 - * - * http://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.neo4j.ogm.drivers.bolt; - -import static org.assertj.core.api.Assertions.*; - -import org.junit.Test; -import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.ogm.config.Configuration; -import org.neo4j.ogm.session.SessionFactory; - -/** - * @author Frantisek Hartman - * @author Michael J. Simons - */ -public class MultipleURIsBoltDriverTest { - - @Test - public void throwCorrectExceptionOnUnavailableCluster() { - - Configuration configuration = new Configuration.Builder() - .uri("neo4j://localhost:1022") - .uris(new String[] { "neo4j://localhost:1023" }) - .verifyConnection(true) - .build(); - - try { - new SessionFactory(configuration, "org.neo4j.ogm.domain.social"); - } catch (Exception e) { - Throwable cause = e.getCause(); - assertThat(cause).isInstanceOf(ServiceUnavailableException.class); - assertThat(cause).hasMessage("Failed to discover an available server"); - } - } -} diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/drivers/bolt/driver/BoltDriverTest.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/drivers/bolt/driver/BoltDriverTest.java new file mode 100644 index 0000000000..a29d1a090b --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/drivers/bolt/driver/BoltDriverTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 + * + * http://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.neo4j.ogm.drivers.bolt.driver; + +import static org.assertj.core.api.Assertions.*; + +import java.lang.reflect.Field; +import java.util.Arrays; + +import org.junit.Test; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.ogm.config.Configuration; +import org.neo4j.ogm.session.SessionFactory; + +/** + * @author Frantisek Hartman + * @author Michael J. Simons + */ +public class BoltDriverTest { + + @Test + public void throwCorrectExceptionOnUnavailableCluster() { + + Configuration configuration = new Configuration.Builder() + .uri("neo4j://localhost:1022") + .uris(new String[] { "neo4j://localhost:1023" }) + .verifyConnection(true) + .build(); + + try { + new SessionFactory(configuration, "org.neo4j.ogm.domain.social"); + } catch (Exception e) { + Throwable cause = e.getCause(); + assertThat(cause).isInstanceOf(ServiceUnavailableException.class); + assertThat(cause).hasMessage("Failed to discover an available server"); + } + } + + @Test + public void shouldDetectSimpleBoltSchemes() { + + for (String aSimpleScheme : Arrays.asList("bolt", "Bolt", "neo4j", "Neo4J")) { + assertThat(BoltDriver.isSimpleScheme(aSimpleScheme)).isTrue(); + } + } + + @Test + public void shouldDetectAdvancedBoltSchemes() { + + for (String anAdvancedScheme : Arrays.asList("bolt+s", "Bolt+ssc", "neo4j+s", "Neo4J+ssc")) { + assertThat(BoltDriver.isSimpleScheme(anAdvancedScheme)).isFalse(); + } + } + + @Test + public void shouldFailEarlyOnInvalidSchemes() { + + // Here `bolt+routing` is invalid, as the Configuration respectively Drivers translates it into neo4j. + for (String invalidScheme : Arrays.asList("bolt+routing", "bolt+x", "neo4j+wth")) { + + assertThatIllegalArgumentException() + .isThrownBy(() -> BoltDriver.isSimpleScheme(invalidScheme)) + .withMessage("'%s' is not a supported scheme.", invalidScheme); + } + } + + @Test + public void schemesShouldBeApplied() throws NoSuchFieldException, IllegalAccessException { + + Field configField = BoltDriver.class.getDeclaredField("driverConfig"); + configField.setAccessible(true); + + for (String scheme : Arrays.asList("bolt+s", "bolt+ssc", "neo4j+s", "neo4j+ssc")) { + + String uri = String.format("%s://localhost:7687", scheme); + Configuration configuration = new Configuration.Builder() + .uri(uri) + .verifyConnection(false) + .build(); + + assertThat(configuration.getURI()).isEqualTo(uri); + + BoltDriver boltDriver = new BoltDriver(); + boltDriver.configure(configuration); + + // Cannot get a Driver from the BoltTransport without opening up a connection + Config config = (Config) configField.get(boltDriver); + + // This would already fail if both the scheme and a programmatic approach would have been applied + Driver driver = GraphDatabase.driver(uri, AuthTokens.none(), config); + // Be on par with the neo4j-java-driver-spring-boot-starter + assertThat(driver.isEncrypted()).isTrue(); + } + } + + @Test + public void advancedSchemesInRoutingScenariosShouldFail() { + + Configuration configuration = new Configuration.Builder() + .uris(new String[] { "neo4j+s://localhost:7687", "neo4j+ssc://localhost:7983" }) + .verifyConnection(true) + .build(); + + BoltDriver boltDriver = new BoltDriver(); + assertThatIllegalArgumentException() + .isThrownBy(() -> boltDriver.configure(configuration)) + .withMessage("Illegal URI scheme, expected 'neo4j' in 'neo4j+s://localhost:7687'"); + } + + @Test + public void configSettingsShouldAlwaysBeAppliedInRoutingScenarios() + throws NoSuchFieldException, IllegalAccessException { + + Field configField = BoltDriver.class.getDeclaredField("driverConfig"); + configField.setAccessible(true); + + Configuration configuration = new Configuration.Builder() + .uris(new String[] { "neo4j+s://localhost:7687", "neo4j+ssc://localhost:7983" }) + .encryptionLevel("REQUIRED") + .build(); + + BoltDriver boltDriver = new BoltDriver(); + boltDriver.configure(configuration); + Config config = (Config) configField.get(boltDriver); + + assertThat(config.encrypted()).isTrue(); + } +} diff --git a/pom.xml b/pom.xml index f41e219df8..8c86924bc6 100644 --- a/pom.xml +++ b/pom.xml @@ -205,7 +205,7 @@ 5.5.5 3.4.17 enterprise - 4.0.0 + 4.0.1 ogm-bolt.properties 3.0.1 3.1.0