diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java index 7f72b0b8d0..6a2f0b96db 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java @@ -29,6 +29,10 @@ import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.exceptions.Neo4jException; +import static org.neo4j.driver.internal.util.AddressUtil.isLocalHost; +import static org.neo4j.driver.v1.Config.EncryptionLevel.REQUIRED; +import static org.neo4j.driver.v1.Config.EncryptionLevel.REQUIRED_NON_LOCAL; + public class InternalDriver implements Driver { private final ConnectionPool connections; @@ -42,6 +46,15 @@ public InternalDriver( URI url, AuthToken authToken, Config config ) this.config = config; } + @Override + public boolean isEncrypted() + { + + Config.EncryptionLevel encryptionLevel = config.encryptionLevel(); + return encryptionLevel.equals( REQUIRED ) || + ( encryptionLevel.equals( REQUIRED_NON_LOCAL ) && !isLocalHost( url.getHost() ) ); + } + /** * Establish a session * @return a session that could be used to run {@link Session#run(String) a statement} or @@ -63,7 +76,7 @@ public void close() throws Neo4jException { connections.close(); } - catch( Exception e ) + catch ( Exception e ) { throw new ClientException( "Failed to close driver.", e ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketClient.java b/driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketClient.java index cabe03543d..42348057db 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketClient.java +++ b/driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketClient.java @@ -37,6 +37,7 @@ import static java.nio.ByteOrder.BIG_ENDIAN; import static org.neo4j.driver.internal.connector.socket.SocketUtils.blockingRead; import static org.neo4j.driver.internal.connector.socket.SocketUtils.blockingWrite; +import static org.neo4j.driver.internal.util.AddressUtil.isLocalHost; public class SocketClient { @@ -235,6 +236,18 @@ public static ByteChannel create( String host, int port, Config config, Logger l channel = new TLSSocketChannel( host, port, soChannel, logger, config.trustStrategy() ); break; } + case REQUIRED_NON_LOCAL: + { + if ( isLocalHost( host ) ) + { + channel = soChannel; + } + else + { + channel = new TLSSocketChannel( host, port, soChannel, logger, config.trustStrategy() ); + } + break; + } case NONE: { channel = soChannel; diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/AddressUtil.java b/driver/src/main/java/org/neo4j/driver/internal/util/AddressUtil.java new file mode 100644 index 0000000000..8e0e0b0eb2 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/util/AddressUtil.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.util; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class AddressUtil +{ + /** + * Return true if the host provided matches "localhost" or "127.x.x.x". + * + * @param host the host name to test + * @return true if localhost, false otherwise + */ + public static boolean isLocalHost( String host ) + { + try + { + // confirmed to work as desired with both "localhost" and "127.x.x.x" + return InetAddress.getByName( host ).isLoopbackAddress(); + } + catch ( UnknownHostException e ) + { + // if it's unknown, it's not local so we can safely return false + return false; + } + } + +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/Config.java b/driver/src/main/java/org/neo4j/driver/v1/Config.java index 32643f97e0..b84889635f 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Config.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Config.java @@ -67,7 +67,7 @@ private Config( ConfigBuilder builder ) this.connectionPoolSize = builder.connectionPoolSize; this.idleTimeBeforeConnectionTest = builder.idleTimeBeforeConnectionTest; - this.encryptionLevel = builder.encruptionLevel; + this.encryptionLevel = builder.encryptionLevel; this.trustStrategy = builder.trustStrategy; } @@ -140,7 +140,7 @@ public static class ConfigBuilder private Logging logging = new JULogging( Level.INFO ); private int connectionPoolSize = 50; private long idleTimeBeforeConnectionTest = 200; - private EncryptionLevel encruptionLevel = EncryptionLevel.REQUIRED; + private EncryptionLevel encryptionLevel = EncryptionLevel.REQUIRED_NON_LOCAL; private TrustStrategy trustStrategy = trustOnFirstUse( new File( getProperty( "user.home" ), ".neo4j" + File.separator + "known_hosts" ) ); @@ -208,7 +208,7 @@ public ConfigBuilder withSessionLivenessCheckTimeout( long timeout ) */ public ConfigBuilder withEncryptionLevel( EncryptionLevel level ) { - this.encruptionLevel = level; + this.encryptionLevel = level; return this; } @@ -250,6 +250,10 @@ public enum EncryptionLevel /** With this level, the driver will only connect to the server if it can do it without encryption. */ NONE, + /** With this level, the driver will only connect to the server without encryption if local but with + * encryption otherwise. */ + REQUIRED_NON_LOCAL, + /** With this level, the driver will only connect to the server it if can do it with encryption. */ REQUIRED } diff --git a/driver/src/main/java/org/neo4j/driver/v1/Driver.java b/driver/src/main/java/org/neo4j/driver/v1/Driver.java index 5c68de764e..facb6dd9a0 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Driver.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Driver.java @@ -20,6 +20,7 @@ import java.net.URI; +import org.neo4j.driver.v1.Config.EncryptionLevel; import org.neo4j.driver.v1.exceptions.Neo4jException; /** @@ -71,6 +72,11 @@ */ public interface Driver extends AutoCloseable { + /** + * Return a flag to indicate whether or not encryption is used for this driver. + */ + boolean isEncrypted(); + /** * Establish a session * @return a session that could be used to run {@link Session#run(String) a statement} or diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/AddressUtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/util/AddressUtilTest.java new file mode 100644 index 0000000000..2517c58124 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/util/AddressUtilTest.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.util; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.*; +import static org.neo4j.driver.internal.util.AddressUtil.isLocalHost; + +public class AddressUtilTest +{ + @Test + public void shouldWorkForVariantsOfLocalHost() throws Exception + { + assertThat( isLocalHost( "localhost" ), equalTo( true ) ); + assertThat( isLocalHost( "LocalHost" ), equalTo( true ) ); + assertThat( isLocalHost( "LOCALHOST" ), equalTo( true ) ); + assertThat( isLocalHost( "127.0.0.1" ), equalTo( true ) ); + assertThat( isLocalHost( "127.5.6.7" ), equalTo( true ) ); + assertThat( isLocalHost( "x" ), equalTo( false ) ); + } + +} \ No newline at end of file diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/EncryptionIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/EncryptionIT.java new file mode 100644 index 0000000000..cade5ddbcb --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/EncryptionIT.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.integration; + +import org.junit.Rule; +import org.junit.Test; +import org.neo4j.driver.v1.*; +import org.neo4j.driver.v1.util.TestNeo4j; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.neo4j.driver.internal.util.AddressUtil.isLocalHost; +import static org.neo4j.driver.v1.Config.EncryptionLevel.NONE; +import static org.neo4j.driver.v1.Config.EncryptionLevel.REQUIRED; +import static org.neo4j.driver.v1.Config.EncryptionLevel.REQUIRED_NON_LOCAL; + +public class EncryptionIT +{ + @Rule + public TestNeo4j neo4j = new TestNeo4j(); + + @Test + public void shouldOperateWithNoEncryption() throws Exception + { + // Given + Driver driver = GraphDatabase.driver( neo4j.address(), Config.build().withEncryptionLevel( NONE ).toConfig() ); + + // Then + assertThat( driver.isEncrypted(), equalTo( false ) ); + + // When + Session session = driver.session(); + StatementResult result = session.run( "RETURN 1" ); + + // Then + Record record = result.next(); + int value = record.get( 0 ).asInt(); + assertThat( value, equalTo( 1 ) ); + + // Finally + session.close(); + driver.close(); + } + + @Test + public void shouldOperateWithRequiredNonLocalEncryption() throws Exception + { + // Given + Driver driver = GraphDatabase.driver( neo4j.address(), Config.build().withEncryptionLevel( REQUIRED_NON_LOCAL ).toConfig() ); + + // Then + assertThat( driver.isEncrypted(), equalTo( !isLocalHost( neo4j.host() ) ) ); + + // When + Session session = driver.session(); + StatementResult result = session.run( "RETURN 1" ); + + // Then + Record record = result.next(); + int value = record.get( 0 ).asInt(); + assertThat( value, equalTo( 1 ) ); + + // Finally + session.close(); + driver.close(); + } + + @Test + public void shouldOperateWithRequiredEncryption() throws Exception + { + // Given + Driver driver = GraphDatabase.driver( neo4j.address(), Config.build().withEncryptionLevel( REQUIRED ).toConfig() ); + + // Then + assertThat( driver.isEncrypted(), equalTo( true ) ); + + // When + Session session = driver.session(); + StatementResult result = session.run( "RETURN 1" ); + + // Then + Record record = result.next(); + int value = record.get( 0 ).asInt(); + assertThat( value, equalTo( 1 ) ); + + // Finally + session.close(); + driver.close(); + } + +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java b/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java index 278d8b3fc5..0fc4c44ee7 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java @@ -45,7 +45,6 @@ public class Neo4jRunner public static final String NEORUN_START_ARGS = System.getProperty( "neorun.start.args" ); public static final String DEFAULT_URL = "bolt://localhost:7687"; - private static final Config TEST_CONFIG = Config.build().withEncryptionLevel( Config.EncryptionLevel.NONE ).toConfig(); private Driver driver; private Neo4jSettings currentSettings = Neo4jSettings.DEFAULT_SETTINGS; @@ -106,7 +105,7 @@ private void startNeo4j() throws IOException { throw new IOException( "Failed to start neo4j server." ); } - driver = GraphDatabase.driver( DEFAULT_URL, TEST_CONFIG ); + driver = GraphDatabase.driver( DEFAULT_URL /* default encryption REQUIRED_NON_LOCAL */ ); } public synchronized void stopNeo4j() throws IOException diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4j.java b/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4j.java index f1bc0a7abb..6eb56905cc 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4j.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4j.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.net.URI; import java.net.URL; import org.neo4j.driver.v1.Driver; @@ -95,6 +96,11 @@ public String address() return Neo4jRunner.DEFAULT_URL; } + public String host() + { + return URI.create( Neo4jRunner.DEFAULT_URL ).getHost(); + } + static void clearDatabaseContents( Session session, String reason ) { Neo4jRunner.debug( "Clearing database contents for: %s", reason );