Skip to content

Commit

Permalink
GH-775 - Update Neo4j-Java-Driver to 4.0.1 and support advanced schemes.
Browse files Browse the repository at this point in the history
4.0.1 brings the opportunity to configure security (encryption and
trustsettings) via URL schemes. A user should be able to use this with
Neo4j-OGM as well and also be able to rely on the same behaviour across
our stack (in regards of the standalon Spring Boot starter for the
driver).
  • Loading branch information
michael-simons authored Mar 19, 2020
1 parent 935f9f5 commit ecfe28c
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 111 deletions.
2 changes: 1 addition & 1 deletion api/src/main/java/org/neo4j/ogm/config/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions api/src/main/java/org/neo4j/ogm/config/Drivers.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -49,7 +49,7 @@ static Drivers getDriverFor(String scheme) {
}
}
}
throw new RuntimeException(UNSUPPORTED_SCHEME_MESSAGE);
throw new IllegalArgumentException(UNSUPPORTED_SCHEME_MESSAGE);
}

String driverClassName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -149,14 +141,27 @@ 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";
try {
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");
Expand Down Expand Up @@ -304,57 +309,32 @@ private Optional<Logging> 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);
Expand All @@ -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<Bookmark> bookmarksFromStrings(Iterable<String> bookmarks) {
return StreamSupport.stream(bookmarks.spliterator(), false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import static org.assertj.core.api.Assertions.*;

import java.util.Arrays;
import java.util.Properties;

import org.junit.Test;
Expand Down Expand Up @@ -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();
Expand Down

This file was deleted.

Loading

0 comments on commit ecfe28c

Please sign in to comment.