diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index df02549b1b..525d230acb 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -30,6 +30,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.internal.SecuritySettings; import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil; @@ -39,7 +40,6 @@ import org.neo4j.driver.util.Immutable; import org.neo4j.driver.util.Resource; -import static org.neo4j.driver.Config.TrustStrategy.trustSystemCertificates; import static org.neo4j.driver.Logging.javaUtilLogging; /** @@ -85,11 +85,7 @@ public class Config private final long maxConnectionLifetimeMillis; private final long connectionAcquisitionTimeoutMillis; - /** Indicator for encrypted traffic */ - private final boolean encrypted; - - /** Strategy for how to trust encryption certificate */ - private final TrustStrategy trustStrategy; + private final SecuritySettings securitySettings; private final int routingFailureLimit; private final long routingRetryDelayMillis; @@ -113,8 +109,8 @@ private Config( ConfigBuilder builder ) this.maxConnectionPoolSize = builder.maxConnectionPoolSize; this.connectionAcquisitionTimeoutMillis = builder.connectionAcquisitionTimeoutMillis; - this.encrypted = builder.encrypted; - this.trustStrategy = builder.trustStrategy; + this.securitySettings = builder.securitySettingsBuilder.build(); + this.routingFailureLimit = builder.routingFailureLimit; this.routingRetryDelayMillis = builder.routingRetryDelayMillis; this.connectionTimeoutMillis = builder.connectionTimeoutMillis; @@ -190,7 +186,7 @@ public long connectionAcquisitionTimeoutMillis() */ public boolean encrypted() { - return encrypted; + return securitySettings.encrypted(); } /** @@ -198,7 +194,7 @@ public boolean encrypted() */ public TrustStrategy trustStrategy() { - return trustStrategy; + return securitySettings.trustStrategy(); } /** @@ -229,6 +225,14 @@ public static Config defaultConfig() return EMPTY; } + /** + * @return the security setting to use when creating connections. + */ + SecuritySettings securitySettings() + { + return securitySettings; + } + RoutingSettings routingSettings() { return new RoutingSettings( routingFailureLimit, routingRetryDelayMillis, routingTablePurgeDelayMillis ); @@ -268,8 +272,7 @@ public static class ConfigBuilder private long idleTimeBeforeConnectionTest = PoolSettings.DEFAULT_IDLE_TIME_BEFORE_CONNECTION_TEST; private long maxConnectionLifetimeMillis = PoolSettings.DEFAULT_MAX_CONNECTION_LIFETIME; private long connectionAcquisitionTimeoutMillis = PoolSettings.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT; - private boolean encrypted = false; - private TrustStrategy trustStrategy = trustSystemCertificates(); + private final SecuritySettings.SecuritySettingsBuilder securitySettingsBuilder = new SecuritySettings.SecuritySettingsBuilder(); private int routingFailureLimit = RoutingSettings.DEFAULT.maxRoutingFailures(); private long routingRetryDelayMillis = RoutingSettings.DEFAULT.retryTimeoutDelay(); private long routingTablePurgeDelayMillis = RoutingSettings.DEFAULT.routingTablePurgeDelayMs(); @@ -443,7 +446,7 @@ public ConfigBuilder withConnectionAcquisitionTimeout( long value, TimeUnit unit */ public ConfigBuilder withEncryption() { - this.encrypted = true; + securitySettingsBuilder.withEncryption(); return this; } @@ -453,7 +456,7 @@ public ConfigBuilder withEncryption() */ public ConfigBuilder withoutEncryption() { - this.encrypted = false; + securitySettingsBuilder.withoutEncryption(); return this; } @@ -473,7 +476,7 @@ public ConfigBuilder withoutEncryption() */ public ConfigBuilder withTrustStrategy( TrustStrategy trustStrategy ) { - this.trustStrategy = trustStrategy; + securitySettingsBuilder.withTrustStrategy( trustStrategy ); return this; } diff --git a/driver/src/main/java/org/neo4j/driver/GraphDatabase.java b/driver/src/main/java/org/neo4j/driver/GraphDatabase.java index 7b69cd7608..d63cc70e53 100644 --- a/driver/src/main/java/org/neo4j/driver/GraphDatabase.java +++ b/driver/src/main/java/org/neo4j/driver/GraphDatabase.java @@ -22,10 +22,13 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.DriverFactory; +import org.neo4j.driver.internal.Scheme; +import org.neo4j.driver.internal.SecuritySettings; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlan; -import static org.neo4j.driver.internal.DriverFactory.BOLT_ROUTING_URI_SCHEME; +import static org.neo4j.driver.internal.Scheme.NEO4J_URI_SCHEME; /** * Creates {@link Driver drivers}, optionally letting you {@link #driver(URI, Config)} to configure them. @@ -132,8 +135,10 @@ public static Driver driver( URI uri, AuthToken authToken, Config config ) config = getOrDefault( config ); RoutingSettings routingSettings = config.routingSettings(); RetrySettings retrySettings = config.retrySettings(); - - return new DriverFactory().newInstance( uri, authToken, routingSettings, retrySettings, config ); + SecuritySettings securitySettings = config.securitySettings(); + Scheme.validateScheme( uri.getScheme() ); + SecurityPlan securityPlan = securitySettings.createSecurityPlan( uri.getScheme() ); + return new DriverFactory().newInstance( uri, authToken, routingSettings, retrySettings, config, securityPlan ); } /** @@ -192,10 +197,10 @@ private static void assertRoutingUris( Iterable uris ) { for ( URI uri : uris ) { - if ( !BOLT_ROUTING_URI_SCHEME.equals( uri.getScheme() ) ) + if ( !NEO4J_URI_SCHEME.equals( uri.getScheme() ) ) { throw new IllegalArgumentException( - "Illegal URI scheme, expected '" + BOLT_ROUTING_URI_SCHEME + "' in '" + uri + "'" ); + "Illegal URI scheme, expected '" + NEO4J_URI_SCHEME + "' in '" + uri + "'" ); } } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java index 78242f2098..bc3640d166 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -23,9 +23,7 @@ import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.internal.logging.InternalLoggerFactory; -import java.io.IOException; import java.net.URI; -import java.security.GeneralSecurityException; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; @@ -50,7 +48,6 @@ import org.neo4j.driver.internal.retry.ExponentialBackoffRetryLogic; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.retry.RetrySettings; -import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -59,24 +56,22 @@ import org.neo4j.driver.net.ServerAddressResolver; import static java.lang.String.format; +import static org.neo4j.driver.internal.Scheme.isRoutingScheme; import static org.neo4j.driver.internal.cluster.IdentityResolver.IDENTITY_RESOLVER; import static org.neo4j.driver.internal.metrics.MetricsProvider.METRICS_DISABLED_PROVIDER; -import static org.neo4j.driver.internal.security.SecurityPlanImpl.insecure; import static org.neo4j.driver.internal.util.ErrorUtil.addSuppressed; public class DriverFactory { - public static final String BOLT_URI_SCHEME = "bolt"; - public static final String BOLT_ROUTING_URI_SCHEME = "neo4j"; public final Driver newInstance( URI uri, AuthToken authToken, RoutingSettings routingSettings, - RetrySettings retrySettings, Config config ) + RetrySettings retrySettings, Config config, SecurityPlan securityPlan ) { - return newInstance( uri, authToken, routingSettings, retrySettings, config, null, null ); + return newInstance( uri, authToken, routingSettings, retrySettings, config, null, securityPlan ); } public final Driver newInstance ( URI uri, AuthToken authToken, RoutingSettings routingSettings, - RetrySettings retrySettings, Config config, EventLoopGroup eventLoopGroup, SecurityPlan customSecurityPlan ) + RetrySettings retrySettings, Config config, EventLoopGroup eventLoopGroup, SecurityPlan securityPlan ) { Bootstrap bootstrap; boolean ownsEventLoopGroup; @@ -96,16 +91,6 @@ public final Driver newInstance ( URI uri, AuthToken authToken, RoutingSettings BoltServerAddress address = new BoltServerAddress( uri ); RoutingSettings newRoutingSettings = routingSettings.withRoutingContext( new RoutingContext( uri ) ); - SecurityPlan securityPlan; - if ( customSecurityPlan != null ) - { - securityPlan = customSecurityPlan; - } - else - { - securityPlan = createSecurityPlan( address, config ); - } - InternalLoggerFactory.setDefaultFactory( new NettyLogging( config.logging() ) ); EventExecutorGroup eventExecutorGroup = bootstrap.config().group(); RetryLogic retryLogic = createRetryLogic( retrySettings, eventExecutorGroup, config.logging() ); @@ -148,20 +133,21 @@ protected ChannelConnector createConnector( ConnectionSettings settings, Securit } private InternalDriver createDriver( URI uri, SecurityPlan securityPlan, BoltServerAddress address, ConnectionPool connectionPool, - EventExecutorGroup eventExecutorGroup, RoutingSettings routingSettings, RetryLogic retryLogic, MetricsProvider metricsProvider, Config config ) + EventExecutorGroup eventExecutorGroup, RoutingSettings routingSettings, RetryLogic retryLogic, + MetricsProvider metricsProvider, Config config ) { try { String scheme = uri.getScheme().toLowerCase(); - switch ( scheme ) + + if ( isRoutingScheme( scheme ) ) + { + return createRoutingDriver( securityPlan, address, connectionPool, eventExecutorGroup, routingSettings, retryLogic, metricsProvider, config ); + } + else { - case BOLT_URI_SCHEME: assertNoRoutingContext( uri, routingSettings ); return createDirectDriver( securityPlan, address, connectionPool, retryLogic, metricsProvider, config ); - case BOLT_ROUTING_URI_SCHEME: - return createRoutingDriver( securityPlan, address, connectionPool, eventExecutorGroup, routingSettings, retryLogic, metricsProvider, config ); - default: - throw new ClientException( format( "Unsupported URI scheme: %s", scheme ) ); } } catch ( Throwable driverError ) @@ -287,47 +273,6 @@ protected Bootstrap createBootstrap( EventLoopGroup eventLoopGroup ) return BootstrapFactory.newBootstrap( eventLoopGroup ); } - private static SecurityPlan createSecurityPlan( BoltServerAddress address, Config config ) - { - try - { - return createSecurityPlanImpl( config ); - } - catch ( GeneralSecurityException | IOException ex ) - { - throw new ClientException( "Unable to establish SSL parameters", ex ); - } - } - - /* - * Establish a complete SecurityPlan based on the details provided for - * driver construction. - */ - private static SecurityPlan createSecurityPlanImpl( Config config ) - throws GeneralSecurityException, IOException - { - if ( config.encrypted() ) - { - Config.TrustStrategy trustStrategy = config.trustStrategy(); - boolean hostnameVerificationEnabled = trustStrategy.isHostnameVerificationEnabled(); - switch ( trustStrategy.strategy() ) - { - case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: - return SecurityPlanImpl.forCustomCASignedCertificates( trustStrategy.certFile(), hostnameVerificationEnabled ); - case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: - return SecurityPlanImpl.forSystemCASignedCertificates( hostnameVerificationEnabled ); - case TRUST_ALL_CERTIFICATES: - return SecurityPlanImpl.forAllCertificates( hostnameVerificationEnabled ); - default: - throw new ClientException( - "Unknown TLS authentication strategy: " + trustStrategy.strategy().name() ); - } - } - else - { - return insecure(); - } - } private static void assertNoRoutingContext( URI uri, RoutingSettings routingSettings ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/Scheme.java b/driver/src/main/java/org/neo4j/driver/internal/Scheme.java new file mode 100644 index 0000000000..e6d071c061 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/Scheme.java @@ -0,0 +1,71 @@ +/* + * 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.driver.internal; + +public class Scheme +{ + public static final String BOLT_URI_SCHEME = "bolt"; + public static final String BOLT_HIGH_TRUST_URI_SCHEME = "bolt+s"; + public static final String BOLT_LOW_TRUST_URI_SCHEME = "bolt+ssc"; + public static final String NEO4J_URI_SCHEME = "neo4j"; + public static final String NEO4J_HIGH_TRUST_URI_SCHEME = "neo4j+s"; + public static final String NEO4J_LOW_TRUST_URI_SCHEME = "neo4j+ssc"; + + public static void validateScheme( String scheme ) + { + if ( scheme == null ) + { + throw new IllegalArgumentException( "Scheme must not be null" ); + } + switch ( scheme ) + { + case BOLT_URI_SCHEME: + case BOLT_LOW_TRUST_URI_SCHEME: + case BOLT_HIGH_TRUST_URI_SCHEME: + case NEO4J_URI_SCHEME: + case NEO4J_LOW_TRUST_URI_SCHEME: + case NEO4J_HIGH_TRUST_URI_SCHEME: + return; + default: + throw new IllegalArgumentException( "Invalid address format " + scheme ); + } + } + + public static boolean isHighTrustScheme( String scheme ) + { + return scheme.equals( BOLT_HIGH_TRUST_URI_SCHEME ) || scheme.equals( NEO4J_HIGH_TRUST_URI_SCHEME ); + } + + public static boolean isLowTrustScheme( String scheme ) + { + return scheme.equals( BOLT_LOW_TRUST_URI_SCHEME ) || scheme.equals( NEO4J_LOW_TRUST_URI_SCHEME ); + } + + public static boolean isSecurityScheme( String scheme ) + { + return scheme.equals( BOLT_LOW_TRUST_URI_SCHEME ) || scheme.equals( NEO4J_LOW_TRUST_URI_SCHEME ) + || scheme.equals( BOLT_HIGH_TRUST_URI_SCHEME ) || scheme.equals( NEO4J_HIGH_TRUST_URI_SCHEME ); + } + + public static boolean isRoutingScheme( String scheme ) + { + return scheme.equals( NEO4J_LOW_TRUST_URI_SCHEME ) || scheme.equals( NEO4J_HIGH_TRUST_URI_SCHEME ) + || scheme.equals( NEO4J_URI_SCHEME ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/SecuritySettings.java b/driver/src/main/java/org/neo4j/driver/internal/SecuritySettings.java new file mode 100644 index 0000000000..5fb1f9bc9e --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/SecuritySettings.java @@ -0,0 +1,163 @@ +/* + * 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.driver.internal; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +import org.neo4j.driver.Config; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.security.SecurityPlan; +import org.neo4j.driver.internal.security.SecurityPlanImpl; + +import static org.neo4j.driver.internal.Scheme.isHighTrustScheme; +import static org.neo4j.driver.internal.Scheme.isSecurityScheme; +import static org.neo4j.driver.internal.security.SecurityPlanImpl.insecure; + +public class SecuritySettings +{ + private static final boolean DEFAULT_ENCRYPTED = false; + private static final Config.TrustStrategy DEFAULT_TRUST_STRATEGY = Config.TrustStrategy.trustSystemCertificates(); + private static final SecuritySettings DEFAULT = new SecuritySettings( DEFAULT_ENCRYPTED, DEFAULT_TRUST_STRATEGY ); + private final boolean encrypted; + private final Config.TrustStrategy trustStrategy; + + public SecuritySettings( boolean encrypted, Config.TrustStrategy trustStrategy ) + { + this.encrypted = encrypted; + this.trustStrategy = trustStrategy == null ? DEFAULT_TRUST_STRATEGY : trustStrategy; + } + + public boolean encrypted() + { + return encrypted; + } + + public Config.TrustStrategy trustStrategy() + { + return trustStrategy; + } + + private boolean isCustomized() + { + return this != DEFAULT; + } + + public SecurityPlan createSecurityPlan( String uriScheme ) + { + try + { + if ( isSecurityScheme( uriScheme ) ) + { + assertSecuritySettingsNotUserConfigured( uriScheme ); + return createSecurityPlanFromScheme( uriScheme ); + } + else + { + return createSecurityPlanImpl( encrypted, trustStrategy ); + } + } + catch ( GeneralSecurityException | IOException ex ) + { + throw new ClientException( "Unable to establish SSL parameters", ex ); + } + } + + private void assertSecuritySettingsNotUserConfigured( String uriScheme ) + { + if ( isCustomized() ) + { + throw new ClientException( String.format( "Scheme %s is not configurable with manual encryption and trust settings", uriScheme ) ); + } + } + + private SecurityPlan createSecurityPlanFromScheme( String scheme ) throws GeneralSecurityException, IOException + { + if ( isHighTrustScheme(scheme) ) + { + return SecurityPlanImpl.forSystemCASignedCertificates( trustStrategy.isHostnameVerificationEnabled() ); + } + else + { + return SecurityPlanImpl.forAllCertificates( trustStrategy.isHostnameVerificationEnabled() ); + } + } + + /* + * Establish a complete SecurityPlan based on the details provided for + * driver construction. + */ + private static SecurityPlan createSecurityPlanImpl( boolean encrypted, Config.TrustStrategy trustStrategy ) + throws GeneralSecurityException, IOException + { + if ( encrypted ) + { + boolean hostnameVerificationEnabled = trustStrategy.isHostnameVerificationEnabled(); + switch ( trustStrategy.strategy() ) + { + case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: + return SecurityPlanImpl.forCustomCASignedCertificates( trustStrategy.certFile(), hostnameVerificationEnabled ); + case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: + return SecurityPlanImpl.forSystemCASignedCertificates( hostnameVerificationEnabled ); + case TRUST_ALL_CERTIFICATES: + return SecurityPlanImpl.forAllCertificates( hostnameVerificationEnabled ); + default: + throw new ClientException( + "Unknown TLS authentication strategy: " + trustStrategy.strategy().name() ); + } + } + else + { + return insecure(); + } + } + + public static class SecuritySettingsBuilder + { + private boolean isCustomized = false; + private boolean encrypted; + private Config.TrustStrategy trustStrategy; + + public SecuritySettingsBuilder withEncryption() + { + encrypted = true; + isCustomized = true; + return this; + } + + public SecuritySettingsBuilder withoutEncryption() + { + encrypted = false; + isCustomized = true; + return this; + } + + public SecuritySettingsBuilder withTrustStrategy( Config.TrustStrategy strategy ) + { + trustStrategy = strategy; + isCustomized = true; + return this; + } + + public SecuritySettings build() + { + return isCustomized ? new SecuritySettings( encrypted, trustStrategy ) : SecuritySettings.DEFAULT; + } + } +} \ No newline at end of file diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java index 7dd914575b..a3e150c149 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java @@ -53,6 +53,7 @@ import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.security.SecurityPlan; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Clock; @@ -98,7 +99,7 @@ void createDriver() AuthToken auth = neo4j.authToken(); RoutingSettings routingSettings = RoutingSettings.DEFAULT; RetrySettings retrySettings = RetrySettings.DEFAULT; - driver = driverFactory.newInstance( neo4j.uri(), auth, routingSettings, retrySettings, defaultConfig() ); + driver = driverFactory.newInstance( neo4j.uri(), auth, routingSettings, retrySettings, defaultConfig(), SecurityPlanImpl.insecure() ); connectionPool = driverFactory.connectionPool; connectionPool.startMemorizing(); // start memorizing connections after driver creation } diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java index f95580b271..edc99d76a3 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java @@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit; import org.neo4j.driver.internal.cluster.RoutingSettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactory; import org.neo4j.driver.internal.util.DriverFactoryWithOneEventLoopThread; import org.neo4j.driver.internal.util.FakeClock; @@ -101,7 +102,7 @@ void shouldDisposeChannelsBasedOnMaxLifetime() throws Exception int maxConnLifetimeHours = 3; Config config = Config.builder().withMaxConnectionLifetime( maxConnLifetimeHours, TimeUnit.HOURS ).build(); - driver = driverFactory.newInstance( neo4j.uri(), neo4j.authToken(), RoutingSettings.DEFAULT, DEFAULT, config ); + driver = driverFactory.newInstance( neo4j.uri(), neo4j.authToken(), RoutingSettings.DEFAULT, DEFAULT, config, SecurityPlanImpl.insecure() ); // force driver create channel and return it to the pool startAndCloseTransactions( driver, 1 ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/DirectDriverIT.java b/driver/src/test/java/org/neo4j/driver/integration/DirectDriverIT.java index f72d260d15..5ef978eebc 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/DirectDriverIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/DirectDriverIT.java @@ -80,7 +80,7 @@ void shouldRejectInvalidAddress() // When & Then IllegalArgumentException e = assertThrows( IllegalArgumentException.class, () -> GraphDatabase.driver( uri, neo4j.authToken() ) ); - assertThat( e.getMessage(), equalTo( "Invalid address format `*`" ) ); + assertThat( e.getMessage(), equalTo( "Scheme must not be null" ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/integration/EncryptionIT.java b/driver/src/test/java/org/neo4j/driver/integration/EncryptionIT.java index 9c44304e1d..2ea91c611c 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/EncryptionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/EncryptionIT.java @@ -21,6 +21,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import java.net.URI; + import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; @@ -28,6 +30,7 @@ import org.neo4j.driver.Result; import org.neo4j.driver.Session; import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.internal.Scheme; import org.neo4j.driver.util.DatabaseExtension; import org.neo4j.driver.util.Neo4jSettings; import org.neo4j.driver.util.Neo4jSettings.BoltTlsLevel; @@ -48,13 +51,13 @@ class EncryptionIT @Test void shouldOperateWithNoEncryptionWhenItIsOptionalInTheDatabase() { - testMatchingEncryption( BoltTlsLevel.OPTIONAL, false ); + testMatchingEncryption( BoltTlsLevel.OPTIONAL, false, neo4j.uri().getScheme() ); } @Test void shouldOperateWithEncryptionWhenItIsOptionalInTheDatabase() { - testMatchingEncryption( BoltTlsLevel.OPTIONAL, true ); + testMatchingEncryption( BoltTlsLevel.OPTIONAL, true, neo4j.uri().getScheme() ); } @Test @@ -66,7 +69,13 @@ void shouldFailWithoutEncryptionWhenItIsRequiredInTheDatabase() @Test void shouldOperateWithEncryptionWhenItIsAlsoRequiredInTheDatabase() { - testMatchingEncryption( BoltTlsLevel.REQUIRED, true ); + testMatchingEncryption( BoltTlsLevel.REQUIRED, true, neo4j.uri().getScheme() ); + } + + @Test + void shouldOperateWithEncryptionWhenConfiguredUsingBoltSscURI() + { + testMatchingEncryption( BoltTlsLevel.REQUIRED, true, "bolt+ssc" ); } @Test @@ -78,15 +87,24 @@ void shouldFailWithEncryptionWhenItIsDisabledInTheDatabase() @Test void shouldOperateWithoutEncryptionWhenItIsAlsoDisabledInTheDatabase() { - testMatchingEncryption( BoltTlsLevel.DISABLED, false ); + testMatchingEncryption( BoltTlsLevel.DISABLED, false, neo4j.uri().getScheme() ); } - private void testMatchingEncryption( BoltTlsLevel tlsLevel, boolean driverEncrypted ) + private void testMatchingEncryption( BoltTlsLevel tlsLevel, boolean driverEncrypted, String scheme ) { neo4j.restartDb( Neo4jSettings.TEST_SETTINGS.updateWith( Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString() ) ); - Config config = newConfig( driverEncrypted ); + Config config; + + if ( scheme.equals( Scheme.BOLT_LOW_TRUST_URI_SCHEME ) ) + { + config = Config.builder().build(); + } else { + config = newConfig( driverEncrypted ); + } + + URI uri = URI.create( String.format( "%s://%s:%s", scheme, neo4j.uri().getHost(), neo4j.uri().getPort()) ); - try ( Driver driver = GraphDatabase.driver( neo4j.uri(), neo4j.authToken(), config ) ) + try ( Driver driver = GraphDatabase.driver( uri, neo4j.authToken(), config ) ) { assertThat( driver.isEncrypted(), equalTo( driverEncrypted ) ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java b/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java index a36f652787..77a56f80b9 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java @@ -45,6 +45,7 @@ import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.messaging.response.FailureMessage; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.FailingMessageFormat; import org.neo4j.driver.internal.util.FakeClock; import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactory; @@ -264,7 +265,7 @@ private Throwable testChannelErrorHandling( Consumer messa Config config = Config.builder().withLogging( DEV_NULL_LOGGING ).build(); Throwable queryError = null; - try ( Driver driver = driverFactory.newInstance( uri, authToken, routingSettings, retrySettings, config ) ) + try ( Driver driver = driverFactory.newInstance( uri, authToken, routingSettings, retrySettings, config, SecurityPlanImpl.insecure() ) ) { driver.verifyConnectivity(); try(Session session = driver.session() ) diff --git a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java index eb434d6b23..c623eadca6 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java @@ -50,6 +50,7 @@ import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.DriverFactoryWithClock; import org.neo4j.driver.internal.util.DriverFactoryWithFixedRetryLogic; import org.neo4j.driver.internal.util.SleeplessClock; @@ -1205,7 +1206,7 @@ private static Driver newDriver( String uriString, DriverFactory driverFactory, URI uri = URI.create( uriString ); RoutingSettings routingConf = new RoutingSettings( 1, 1, 0, null ); AuthToken auth = AuthTokens.none(); - return driverFactory.newInstance( uri, auth, routingConf, RetrySettings.DEFAULT, config ); + return driverFactory.newInstance( uri, auth, routingConf, RetrySettings.DEFAULT, config, SecurityPlanImpl.insecure() ); } private static TransactionWork> queryWork( final String query, final AtomicInteger invocations ) diff --git a/driver/src/test/java/org/neo4j/driver/integration/ServerKilledIT.java b/driver/src/test/java/org/neo4j/driver/integration/ServerKilledIT.java index de06993f6b..a000580e38 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ServerKilledIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ServerKilledIT.java @@ -30,6 +30,7 @@ import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.DriverFactoryWithClock; import org.neo4j.driver.internal.util.FakeClock; @@ -143,6 +144,6 @@ private Driver createDriver( Clock clock, Config config ) DriverFactory factory = new DriverFactoryWithClock( clock ); RoutingSettings routingSettings = RoutingSettings.DEFAULT; RetrySettings retrySettings = RetrySettings.DEFAULT; - return factory.newInstance( neo4j.uri(), DEFAULT_AUTH_TOKEN, routingSettings, retrySettings, config ); + return factory.newInstance( neo4j.uri(), DEFAULT_AUTH_TOKEN, routingSettings, retrySettings, config, SecurityPlanImpl.insecure() ); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java index bcf98a6817..a745a3902d 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java @@ -45,6 +45,7 @@ import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; import org.neo4j.driver.internal.messaging.request.HelloMessage; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.internal.util.MessageRecordingDriverFactory; import org.neo4j.driver.summary.ResultSummary; @@ -283,7 +284,8 @@ void shouldSendGoodbyeWhenClosingDriver() int txCount = 13; MessageRecordingDriverFactory driverFactory = new MessageRecordingDriverFactory(); - try ( Driver otherDriver = driverFactory.newInstance( driver.uri(), driver.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, defaultConfig() ) ) + try ( Driver otherDriver = driverFactory.newInstance( driver.uri(), driver.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, defaultConfig(), + SecurityPlanImpl.insecure() ) ) { List sessions = new ArrayList<>(); List txs = new ArrayList<>(); diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java index 067fde42a4..b20f4ccbf1 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java @@ -55,6 +55,7 @@ import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; import org.neo4j.driver.internal.util.DriverFactoryWithFixedRetryLogic; import org.neo4j.driver.internal.util.DriverFactoryWithOneEventLoopThread; @@ -1413,7 +1414,7 @@ private Driver newDriverWithFixedRetries( int maxRetriesCount ) { DriverFactory driverFactory = new DriverFactoryWithFixedRetryLogic( maxRetriesCount ); AuthToken auth = DEFAULT_AUTH_TOKEN; - return driverFactory.newInstance( neo4j.uri(), auth, RoutingSettings.DEFAULT, RetrySettings.DEFAULT, noLoggingConfig() ); + return driverFactory.newInstance( neo4j.uri(), auth, RoutingSettings.DEFAULT, RetrySettings.DEFAULT, noLoggingConfig(), SecurityPlanImpl.insecure() ); } private Driver newDriverWithLimitedRetries( int maxTxRetryTime, TimeUnit unit ) diff --git a/driver/src/test/java/org/neo4j/driver/integration/SharedEventLoopIT.java b/driver/src/test/java/org/neo4j/driver/integration/SharedEventLoopIT.java index c5aeae20af..f8c313e5da 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SharedEventLoopIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SharedEventLoopIT.java @@ -31,6 +31,7 @@ import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.util.DatabaseExtension; import org.neo4j.driver.util.ParallelizableIT; @@ -94,7 +95,7 @@ void testDriverShouldUseSharedEventLoop() private Driver createDriver( EventLoopGroup eventLoopGroup ) { return driverFactory.newInstance( neo4j.uri(), neo4j.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, Config.defaultConfig(), - eventLoopGroup, null ); + eventLoopGroup, SecurityPlanImpl.insecure() ); } private void testConnection( Driver driver ) diff --git a/driver/src/test/java/org/neo4j/driver/integration/TransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/TransactionIT.java index bcca91f7df..d310cce2cc 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/TransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/TransactionIT.java @@ -36,6 +36,7 @@ import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.cluster.RoutingSettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactory; import org.neo4j.driver.util.ParallelizableIT; @@ -400,7 +401,7 @@ void shouldThrowWhenConnectionKilledDuringTransaction() ChannelTrackingDriverFactory factory = new ChannelTrackingDriverFactory( 1, Clock.SYSTEM ); Config config = Config.builder().withLogging( DEV_NULL_LOGGING ).build(); - try ( Driver driver = factory.newInstance( session.uri(), session.authToken(), RoutingSettings.DEFAULT, DEFAULT, config ) ) + try ( Driver driver = factory.newInstance( session.uri(), session.authToken(), RoutingSettings.DEFAULT, DEFAULT, config, SecurityPlanImpl.insecure() ) ) { ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> { diff --git a/driver/src/test/java/org/neo4j/driver/integration/TrustCustomCertificateIT.java b/driver/src/test/java/org/neo4j/driver/integration/TrustCustomCertificateIT.java index 122c2db7e7..26d17c5330 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/TrustCustomCertificateIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/TrustCustomCertificateIT.java @@ -27,8 +27,8 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Session; import org.neo4j.driver.Result; +import org.neo4j.driver.Session; import org.neo4j.driver.exceptions.SecurityException; import org.neo4j.driver.util.CertificateExtension; import org.neo4j.driver.util.CertificateUtil.CertificateKeyPair; diff --git a/driver/src/test/java/org/neo4j/driver/integration/UnmanagedTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/UnmanagedTransactionIT.java index 76b0cd2643..98bfb49042 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/UnmanagedTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/UnmanagedTransactionIT.java @@ -42,6 +42,7 @@ import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactory; import org.neo4j.driver.util.DatabaseExtension; @@ -221,7 +222,8 @@ private void testCommitAndRollbackFailurePropagation( boolean commit ) ChannelTrackingDriverFactory driverFactory = new ChannelTrackingDriverFactory( 1, Clock.SYSTEM ); Config config = Config.builder().withLogging( DEV_NULL_LOGGING ).build(); - try ( Driver driver = driverFactory.newInstance( neo4j.uri(), neo4j.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config ) ) + try ( Driver driver = driverFactory.newInstance( neo4j.uri(), neo4j.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config, + SecurityPlanImpl.insecure() ) ) { NetworkSession session = ((InternalDriver) driver).newSession( SessionConfig.defaultConfig() ); { diff --git a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java index 0dba2455fd..301cafc0a8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java @@ -47,6 +47,7 @@ import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactory; import org.neo4j.driver.reactive.RxResult; @@ -205,7 +206,8 @@ void shouldCloseChannelWhenResetFails() throws Exception Config config = Config.builder().withLogging( DEV_NULL_LOGGING ).withoutEncryption().build(); ChannelTrackingDriverFactory driverFactory = new ChannelTrackingDriverFactory( 1, Clock.SYSTEM ); - try ( Driver driver = driverFactory.newInstance( uri, AuthTokens.none(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config ) ) + try ( Driver driver = driverFactory.newInstance( uri, AuthTokens.none(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config, + SecurityPlanImpl.insecure() ) ) { try ( Session session = driver.session() ) { diff --git a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java index f3478e90e1..4df791dace 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java @@ -43,6 +43,7 @@ import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.security.SecurityPlan; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -174,7 +175,7 @@ private Driver createDriver( String uri, DriverFactory driverFactory ) private Driver createDriver( String uri, DriverFactory driverFactory, Config config ) { AuthToken auth = AuthTokens.none(); - return driverFactory.newInstance( URI.create( uri ), auth, RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config ); + return driverFactory.newInstance( URI.create( uri ), auth, RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config, SecurityPlanImpl.insecure() ); } private static ConnectionPool connectionPoolMock() diff --git a/driver/src/test/java/org/neo4j/driver/internal/SchemeTest.java b/driver/src/test/java/org/neo4j/driver/internal/SchemeTest.java new file mode 100644 index 0000000000..accfe4b818 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/SchemeTest.java @@ -0,0 +1,112 @@ +/* + * 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.driver.internal; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class SchemeTest +{ + + @ParameterizedTest + @ValueSource( strings = {"neo4j", "neo4j+s", "neo4j+ssc", "bolt", "bolt+s", "bolt+ssc"} ) + void shouldAcceptValidSchemes( String input ) + { + Scheme.validateScheme( input ); + } + + @ParameterizedTest + @ValueSource( strings = {"bob", "grey", "", " ", "blah"} ) + void shouldRejectInvalidSchemes( String input ) + { + IllegalArgumentException ex = + assertThrows( IllegalArgumentException.class, () -> Scheme.validateScheme( input ) ); + assertTrue( ex.getMessage().contains( "Invalid address format " + input ) ); + } + + @ParameterizedTest + @NullSource + void shouldRejectNullScheme( String input ) + { + IllegalArgumentException ex = + assertThrows( IllegalArgumentException.class, () -> Scheme.validateScheme( input ) ); + assertTrue( ex.getMessage().contains( "Scheme must not be null" ) ); + } + + @ParameterizedTest + @ValueSource( strings = {"neo4j+s", "bolt+s"} ) + void shouldAcceptValidHighTrustSchemes( String scheme ) + { + assertTrue( Scheme.isHighTrustScheme( scheme ) ); + } + + @ParameterizedTest + @ValueSource( strings = {"neo4j", "neo4j+ssc", "bolt", "bolt+ssc", "blah"} ) + void shouldRejectInvalidHighTrustSchemes( String scheme ) + { + assertFalse( Scheme.isHighTrustScheme( scheme ) ); + } + + @ParameterizedTest + @ValueSource( strings = {"neo4j+ssc", "bolt+ssc"} ) + void shouldAcceptValidLowTrustSchemes( String scheme ) + { + assertTrue( Scheme.isLowTrustScheme( scheme ) ); + } + + @ParameterizedTest + @ValueSource( strings = {"neo4j", "neo4j+s", "bolt", "bolt+s", "blah"} ) + void shouldRejectInvalidLowTrustSchemes( String scheme ) + { + assertFalse( Scheme.isLowTrustScheme( scheme ) ); + } + + @ParameterizedTest + @ValueSource( strings = {"neo4j+s", "neo4j+ssc", "bolt+s", "bolt+ssc"} ) + void shouldAcceptValidSecuritySchemes( String scheme ) + { + assertTrue( Scheme.isSecurityScheme( scheme ) ); + } + + @ParameterizedTest + @ValueSource( strings = {"neo4j", "bolt", "blah"} ) + void shouldRejectInvalidSecuritySchemes( String scheme ) + { + assertFalse( Scheme.isSecurityScheme( scheme ) ); + } + + @ParameterizedTest + @ValueSource( strings = {"neo4j", "neo4j+s", "neo4j+ssc"} ) + void shouldAcceptValidRoutingSchemes( String scheme ) + { + assertTrue( Scheme.isRoutingScheme( scheme ) ); + } + + @ParameterizedTest + @ValueSource( strings = {"bolt", "bolt+s", "bolt+ssc", "blah"} ) + void shouldRejectInvalidRoutingSchemes( String scheme ) + { + assertFalse( Scheme.isRoutingScheme( scheme ) ); + } +} \ No newline at end of file diff --git a/driver/src/test/java/org/neo4j/driver/internal/SecuritySettingsTest.java b/driver/src/test/java/org/neo4j/driver/internal/SecuritySettingsTest.java new file mode 100644 index 0000000000..5547487850 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/SecuritySettingsTest.java @@ -0,0 +1,171 @@ +/* + * 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.driver.internal; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.security.NoSuchAlgorithmException; +import java.util.stream.Stream; +import javax.net.ssl.SSLContext; + +import org.neo4j.driver.Config; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.security.SecurityPlan; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SecuritySettingsTest +{ + private static Stream selfSignedSchemes() + { + return Stream.of( "bolt+ssc", "neo4j+ssc" ); + } + + private static Stream systemCertSchemes() + { + return Stream.of( "neo4j+s", "bolt+s" ); + } + + private static Stream allSchemes() + { + return Stream.concat( selfSignedSchemes(), systemCertSchemes() ); + } + + @ParameterizedTest + @MethodSource( "allSchemes" ) + void testEncryptionSchemeEnablesEncryption( String scheme ) + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder().build(); + + SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); + + assertTrue( securityPlan.requiresEncryption() ); + } + + @ParameterizedTest + @MethodSource( "systemCertSchemes" ) + void testSystemCertCompatibleConfiguration( String scheme ) throws Exception + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder().build(); + + SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); + + SSLContext defaultContext = SSLContext.getDefault(); + + assertTrue( securityPlan.requiresEncryption() ); + assertEquals( defaultContext, securityPlan.sslContext() ); + } + + @ParameterizedTest + @MethodSource( "allSchemes" ) + void testThrowsOnUserCustomizedEncryption( String scheme ) + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withoutEncryption() + .build(); + + ClientException ex = + assertThrows( ClientException.class, + () -> securitySettings.createSecurityPlan( scheme ) ); + + assertTrue( ex.getMessage().contains( String.format( "Scheme %s is not configurable with manual encryption and trust settings", scheme ) )); + } + + @ParameterizedTest + @MethodSource( "allSchemes" ) + void testThrowsOnUserCustomizedTrustConfiguration( String scheme ) + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withTrustStrategy( Config.TrustStrategy.trustSystemCertificates() ) + .build(); + + ClientException ex = + assertThrows( ClientException.class, + () -> securitySettings.createSecurityPlan( scheme ) ); + + assertTrue( ex.getMessage().contains( String.format( "Scheme %s is not configurable with manual encryption and trust settings", scheme ) )); + } + + @ParameterizedTest + @MethodSource( "allSchemes" ) + void testThrowsOnUserCustomizedTrustConfigurationAndEncryption( String scheme ) + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withTrustStrategy( Config.TrustStrategy.trustSystemCertificates() ) + .withEncryption() + .build(); + + ClientException ex = + assertThrows( ClientException.class, + () -> securitySettings.createSecurityPlan( scheme ) ); + + assertTrue( ex.getMessage().contains( String.format( "Scheme %s is not configurable with manual encryption and trust settings", scheme ) )); + } + + @Test + void testNeo4jSchemeNoEncryption() + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder().build(); + + SecurityPlan securityPlan = securitySettings.createSecurityPlan( "neo4j" ); + + assertFalse( securityPlan.requiresEncryption() ); + } + + @Test + void testBoltSchemeNoEncryption() + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder().build(); + + SecurityPlan securityPlan = securitySettings.createSecurityPlan( "bolt" ); + + assertFalse( securityPlan.requiresEncryption() ); + } + + @Test + void testConfiguredEncryption() + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withEncryption().build(); + + SecurityPlan securityPlan = securitySettings.createSecurityPlan( "neo4j" ); + + assertTrue( securityPlan.requiresEncryption() ); + } + + @Test + void testConfiguredAllCertificates() throws NoSuchAlgorithmException + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withEncryption() + .withTrustStrategy( Config.TrustStrategy.trustAllCertificates() ) + .build(); + + SecurityPlan securityPlan = securitySettings.createSecurityPlan( "neo4j" ); + + assertTrue( securityPlan.requiresEncryption() ); + } + +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/DriverFactoryWithOneEventLoopThread.java b/driver/src/test/java/org/neo4j/driver/internal/util/DriverFactoryWithOneEventLoopThread.java index 6dd4b14779..04eaa89784 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/DriverFactoryWithOneEventLoopThread.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/DriverFactoryWithOneEventLoopThread.java @@ -29,12 +29,13 @@ import org.neo4j.driver.AuthToken; import org.neo4j.driver.Config; import org.neo4j.driver.Driver; +import org.neo4j.driver.internal.security.SecurityPlanImpl; public class DriverFactoryWithOneEventLoopThread extends DriverFactory { public Driver newInstance( URI uri, AuthToken authToken, Config config ) { - return newInstance( uri, authToken, RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config ); + return newInstance( uri, authToken, RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config, SecurityPlanImpl.insecure() ); } @Override diff --git a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java index 40f177a9b9..06767e8c7f 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java @@ -62,6 +62,7 @@ import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.FailingConnectionDriverFactory; import org.neo4j.driver.internal.util.FakeClock; import org.neo4j.driver.internal.util.ServerVersion; @@ -277,7 +278,7 @@ void shouldDropBrokenOldConnections() throws Exception URI routingUri = cluster.leader().getRoutingUri(); AuthToken auth = clusterRule.getDefaultAuthToken(); - try ( Driver driver = driverFactory.newInstance( routingUri, auth, RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config ) ) + try ( Driver driver = driverFactory.newInstance( routingUri, auth, RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config, SecurityPlanImpl.insecure() ) ) { // create nodes in different threads using different sessions and connections createNodesInDifferentThreads( concurrentSessionsCount, driver ); @@ -565,7 +566,7 @@ void shouldAllowExistingTransactionToCompleteAfterDifferentConnectionBreaks() FailingConnectionDriverFactory driverFactory = new FailingConnectionDriverFactory(); try ( Driver driver = driverFactory.newInstance( leader.getRoutingUri(), clusterRule.getDefaultAuthToken(), - RoutingSettings.DEFAULT, RetrySettings.DEFAULT, configWithoutLogging() ) ) + RoutingSettings.DEFAULT, RetrySettings.DEFAULT, configWithoutLogging(), SecurityPlanImpl.insecure() ) ) { Session session1 = driver.session(); Transaction tx1 = session1.beginTransaction(); @@ -607,7 +608,7 @@ void shouldRediscoverWhenConnectionsToAllCoresBreak() ChannelTrackingDriverFactory driverFactory = new ChannelTrackingDriverFactory(); try ( Driver driver = driverFactory.newInstance( leader.getRoutingUri(), clusterRule.getDefaultAuthToken(), - RoutingSettings.DEFAULT, RetrySettings.DEFAULT, configWithoutLogging() ) ) + RoutingSettings.DEFAULT, RetrySettings.DEFAULT, configWithoutLogging(), SecurityPlanImpl.insecure() ) ) { try ( Session session = driver.session() ) { @@ -673,7 +674,7 @@ void shouldKeepOperatingWhenConnectionsBreak() throws Exception .build(); try ( Driver driver = driverFactory.newInstance( cluster.leader().getRoutingUri(), clusterRule.getDefaultAuthToken(), - RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config ) ) + RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config, SecurityPlanImpl.insecure() ) ) { List> results = new ArrayList<>(); diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java b/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java index 3eb43bdc78..3e2b9d94f7 100644 --- a/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java +++ b/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java @@ -23,17 +23,16 @@ import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import java.io.FileNotFoundException; import java.net.URI; -import org.neo4j.driver.internal.util.DriverFactoryWithOneEventLoopThread; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Driver; +import org.neo4j.driver.internal.util.DriverFactoryWithOneEventLoopThread; import org.neo4j.driver.util.TestUtil; -import static org.neo4j.driver.internal.DriverFactory.BOLT_ROUTING_URI_SCHEME; import static org.neo4j.driver.Config.defaultConfig; +import static org.neo4j.driver.internal.Scheme.NEO4J_URI_SCHEME; public class LocalOrRemoteClusterExtension implements BeforeAllCallback, AfterEachCallback, AfterAllCallback { @@ -127,7 +126,7 @@ private static void assertValidSystemPropertiesDefined() "Both cluster uri and 'neo4j' user password system properties should be set. " + "Uri: '" + uri + "', Password: '" + password + "'" ); } - if ( uri != null && !BOLT_ROUTING_URI_SCHEME.equals( uri.getScheme() ) ) + if ( uri != null && !NEO4J_URI_SCHEME.equals( uri.getScheme() ) ) { throw new IllegalStateException( "Cluster uri should have neo4j scheme: '" + uri + "'" ); }