diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoder.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoder.java index 72adff04d78dd..c60cfbeeb6e73 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoder.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoder.java @@ -56,22 +56,22 @@ public BoltResponseHandler responseHandler() public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException { String userAgent = unpacker.unpackString(); - Map authToken = readAuthToken( unpacker ); + Map authToken = readMetaDataMap( unpacker ); return new InitMessage( userAgent, authToken ); } - private static Map readAuthToken( Neo4jPack.Unpacker unpacker ) throws IOException + public static Map readMetaDataMap( Neo4jPack.Unpacker unpacker ) throws IOException { - MapValue authTokenValue = unpacker.unpackMap(); + MapValue metaDataMapValue = unpacker.unpackMap(); PrimitiveOnlyValueWriter writer = new PrimitiveOnlyValueWriter(); - Map tokenMap = new HashMap<>( authTokenValue.size() ); - authTokenValue.foreach( ( key, value ) -> + Map metaDataMap = new HashMap<>( metaDataMapValue.size() ); + metaDataMapValue.foreach( ( key, value ) -> { - Object convertedValue = AuthToken.CREDENTIALS.equals( key ) || AuthToken.NEW_CREDENTIALS.equals( key ) ? + Object convertedValue = AuthToken.containsSensitiveInformation( key ) ? writer.sensitiveValueAsObject( value, key ) : writer.valueAsObject( value ); - tokenMap.put( key, convertedValue ); + metaDataMap.put( key, convertedValue ); } ); - return tokenMap; + return metaDataMap; } } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoder.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoder.java index ef6fb1d50bd6a..24142e042bace 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoder.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoder.java @@ -20,16 +20,14 @@ package org.neo4j.bolt.v3.messaging.decoder; import java.io.IOException; -import java.util.HashMap; import java.util.Map; import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.messaging.RequestMessageDecoder; import org.neo4j.bolt.runtime.BoltResponseHandler; -import org.neo4j.bolt.v1.messaging.decoder.PrimitiveOnlyValueWriter; +import org.neo4j.bolt.v1.messaging.decoder.InitMessageDecoder; import org.neo4j.bolt.v3.messaging.request.HelloMessage; -import org.neo4j.values.virtual.MapValue; public class HelloMessageDecoder implements RequestMessageDecoder { @@ -55,10 +53,7 @@ public BoltResponseHandler responseHandler() @Override public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException { - MapValue helloMeta = unpacker.unpackMap(); - PrimitiveOnlyValueWriter writer = new PrimitiveOnlyValueWriter(); - Map meta = new HashMap<>( helloMeta.size() ); - helloMeta.foreach( ( key, value ) -> meta.put( key, writer.valueAsObject( value ) ) ); + Map meta = InitMessageDecoder.readMetaDataMap( unpacker ); return new HelloMessage( meta ); } } diff --git a/community/bolt/src/test/java/org/neo4j/bolt/security/auth/AuthTokenDecoderTest.java b/community/bolt/src/test/java/org/neo4j/bolt/security/auth/AuthTokenDecoderTest.java new file mode 100644 index 0000000000000..daf291dc79a36 --- /dev/null +++ b/community/bolt/src/test/java/org/neo4j/bolt/security/auth/AuthTokenDecoderTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.security.auth; + +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Map; + +import org.neo4j.kernel.api.security.AuthToken; + +import static org.neo4j.helpers.collection.MapUtil.map; + +public abstract class AuthTokenDecoderTest +{ + protected abstract void testShouldDecodeAuthToken( Map authToken ) throws Exception; + + protected abstract void testShouldFailToDecodeAuthToken( Map authToken, String expectedErrorMessage ) throws Exception; + + @Test + void shouldDecodeAuthTokenWithStringCredentials() throws Exception + { + testShouldDecodeAuthToken( authTokenMapWith( AuthToken.CREDENTIALS, "password" ) ); + } + + @Test + void shouldDecodeAuthTokenWithEmptyStringCredentials() throws Exception + { + testShouldDecodeAuthToken( authTokenMapWith( AuthToken.CREDENTIALS, "" ) ); + } + + @Test + void shouldDecodeAuthTokenWithNullCredentials() throws Exception + { + testShouldDecodeAuthToken( authTokenMapWith( AuthToken.CREDENTIALS, null ) ); + } + + @Test + void shouldDecodeAuthTokenWithStringNewCredentials() throws Exception + { + testShouldDecodeAuthToken( authTokenMapWith( AuthToken.NEW_CREDENTIALS, "password" ) ); + } + + @Test + void shouldDecodeAuthTokenWithEmptyStringNewCredentials() throws Exception + { + testShouldDecodeAuthToken( authTokenMapWith( AuthToken.NEW_CREDENTIALS, "" ) ); + } + + @Test + void shouldDecodeAuthTokenWithNullNewCredentials() throws Exception + { + testShouldDecodeAuthToken( authTokenMapWith( AuthToken.NEW_CREDENTIALS, null ) ); + } + + @Test + void shouldFailToDecodeAuthTokenWithCredentialsOfUnsupportedTypes() throws Exception + { + for ( Object value : valuesWithInvalidTypes ) + { + testShouldFailToDecodeAuthToken( authTokenMapWith( AuthToken.CREDENTIALS, value ), + "INIT message authentication token field '" + AuthToken.CREDENTIALS + "' should be a UTF-8 encoded string" ); + } + } + + @Test + void shouldFailToDecodeAuthTokenWithNewCredentialsOfUnsupportedType() throws Exception + { + for ( Object value : valuesWithInvalidTypes ) + { + testShouldFailToDecodeAuthToken( authTokenMapWith( AuthToken.NEW_CREDENTIALS, value ), + "INIT message authentication token field '" + AuthToken.NEW_CREDENTIALS + "' should be a UTF-8 encoded string" ); + } + } + + private static Map authTokenMapWith( String fieldName, Object fieldValue ) + { + return map( AuthToken.PRINCIPAL, "neo4j", fieldName, fieldValue ); + } + + private static Object[] valuesWithInvalidTypes = { + // This is not an exhaustive list + new char[]{ 'p', 'a', 's', 's' }, + Collections.emptyList(), + Collections.emptyMap() + }; +} diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoderTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoderTest.java index 454f6769d468a..ed4d2db5ce4c8 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoderTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoderTest.java @@ -21,16 +21,16 @@ import org.junit.jupiter.api.Test; -import java.util.Collections; +import java.util.Map; import org.neo4j.bolt.messaging.Neo4jPack.Unpacker; import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.messaging.RequestMessageDecoder; import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.security.auth.AuthTokenDecoderTest; import org.neo4j.bolt.v1.messaging.Neo4jPackV1; import org.neo4j.bolt.v1.messaging.request.InitMessage; import org.neo4j.bolt.v1.packstream.PackedInputArray; -import org.neo4j.kernel.api.security.AuthToken; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -39,7 +39,7 @@ import static org.neo4j.helpers.collection.MapUtil.map; import static org.neo4j.test.AuthTokenUtil.assertAuthTokenMatches; -class InitMessageDecoderTest +class InitMessageDecoderTest extends AuthTokenDecoderTest { private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class ); private final RequestMessageDecoder decoder = new InitMessageDecoder( responseHandler ); @@ -73,73 +73,11 @@ void shouldDecodeAckFailure() throws Exception assertEquals( originalMessage, deserializedMessage ); } - @Test - void shouldDecodeAuthTokenWithStringCredentials() throws Exception - { - testShouldDecodeAuthToken( AuthToken.CREDENTIALS, "password" ); - } - - @Test - void shouldDecodeAuthTokenWithEmptyStringCredentials() throws Exception - { - testShouldDecodeAuthToken( AuthToken.CREDENTIALS, "" ); - } - - @Test - void shouldDecodeAuthTokenWithNullCredentials() throws Exception - { - testShouldDecodeAuthToken( AuthToken.CREDENTIALS, null ); - } - - @Test - void shouldDecodeAuthTokenWithStringNewCredentials() throws Exception - { - testShouldDecodeAuthToken( AuthToken.NEW_CREDENTIALS, "password" ); - } - - @Test - void shouldDecodeAuthTokenWithEmptyStringNewCredentials() throws Exception - { - testShouldDecodeAuthToken( AuthToken.NEW_CREDENTIALS, "" ); - } - - @Test - void shouldDecodeAuthTokenWithNullNewCredentials() throws Exception - { - testShouldDecodeAuthToken( AuthToken.NEW_CREDENTIALS, null ); - } - - @Test - void shouldFailToDecodeAuthTokenWithCredentialsOfUnsupportedTypes() throws Exception - { - for ( Object value : valuesWithInvalidTypes ) - { - testShouldFailToDecodeAuthToken( AuthToken.CREDENTIALS, value, - "INIT message authentication token field '" + AuthToken.CREDENTIALS + "' should be a UTF-8 encoded string" ); - } - } - - @Test - void shouldFailToDecodeAuthTokenWithNewCredentialsOfUnsupportedType() throws Exception - { - for ( Object value : valuesWithInvalidTypes ) - { - testShouldFailToDecodeAuthToken( AuthToken.NEW_CREDENTIALS, value, - "INIT message authentication token field '" + AuthToken.NEW_CREDENTIALS + "' should be a UTF-8 encoded string" ); - } - } - - private static Object[] valuesWithInvalidTypes = { - // This is not an exhaustive list - new char[]{ 'p', 'a', 's', 's' }, - Collections.emptyList(), - Collections.emptyMap() - }; - - private void testShouldDecodeAuthToken( String fieldName, Object fieldValue ) throws Exception + @Override + protected void testShouldDecodeAuthToken( Map authToken ) throws Exception { Neo4jPackV1 neo4jPack = new Neo4jPackV1(); - InitMessage originalMessage = new InitMessage( "My Driver", map( AuthToken.PRINCIPAL, "neo4j", fieldName, fieldValue ) ); + InitMessage originalMessage = new InitMessage( "My Driver", authToken ); PackedInputArray innput = new PackedInputArray( serialize( neo4jPack, originalMessage ) ); Unpacker unpacker = neo4jPack.newUnpacker( innput ); @@ -152,11 +90,11 @@ private void testShouldDecodeAuthToken( String fieldName, Object fieldValue ) th assertInitMessageMatches( originalMessage, deserializedMessage ); } - private void testShouldFailToDecodeAuthToken( String fieldName, Object fieldValue, String expectedErrorMessage ) throws Exception + @Override + protected void testShouldFailToDecodeAuthToken( Map authToken, String expectedErrorMessage ) throws Exception { Neo4jPackV1 neo4jPack = new Neo4jPackV1(); - InitMessage originalMessage = new InitMessage( "My Driver", - map( AuthToken.PRINCIPAL, "neo4j", fieldName, fieldValue ) ); + InitMessage originalMessage = new InitMessage( "My Driver", authToken ); PackedInputArray innput = new PackedInputArray( serialize( neo4jPack, originalMessage ) ); Unpacker unpacker = neo4jPack.newUnpacker( innput ); diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoderTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoderTest.java index 2b88e6bb42fc8..e8a137bc54b29 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoderTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoderTest.java @@ -21,20 +21,25 @@ import org.junit.jupiter.api.Test; +import java.util.Map; + import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.messaging.RequestMessageDecoder; import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.security.auth.AuthTokenDecoderTest; import org.neo4j.bolt.v1.packstream.PackedInputArray; import org.neo4j.bolt.v3.messaging.request.HelloMessage; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.encode; import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.newNeo4jPack; import static org.neo4j.helpers.collection.MapUtil.map; +import static org.neo4j.test.AuthTokenUtil.assertAuthTokenMatches; -class HelloMessageDecoderTest +class HelloMessageDecoderTest extends AuthTokenDecoderTest { private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class ); private final RequestMessageDecoder decoder = new HelloMessageDecoder( responseHandler ); @@ -73,4 +78,52 @@ static void assertOriginalMessageEqualsToDecoded( RequestMessage originalMessage assertEquals( originalMessage, deserializedMessage ); } + @Override + protected void testShouldDecodeAuthToken( Map authToken ) throws Exception + { + Neo4jPack neo4jPack = newNeo4jPack(); + authToken.put( "user_agent", "My Driver" ); + HelloMessage originalMessage = new HelloMessage( authToken ); + + PackedInputArray input = new PackedInputArray( encode( neo4jPack, originalMessage ) ); + Neo4jPack.Unpacker unpacker = neo4jPack.newUnpacker( input ); + + // these two steps are executed before decoding in order to select a correct decoder + unpacker.unpackStructHeader(); + unpacker.unpackStructSignature(); + + RequestMessage deserializedMessage = decoder.decode( unpacker ); + assertHelloMessageMatches( originalMessage, deserializedMessage ); + } + + @Override + protected void testShouldFailToDecodeAuthToken( Map authToken, String expectedErrorMessage ) throws Exception + { + Neo4jPack neo4jPack = newNeo4jPack(); + authToken.put( "user_agent", "My Driver" ); + HelloMessage originalMessage = new HelloMessage( authToken ); + + PackedInputArray input = new PackedInputArray( encode( neo4jPack, originalMessage ) ); + Neo4jPack.Unpacker unpacker = neo4jPack.newUnpacker( input ); + + // these two steps are executed before decoding in order to select a correct decoder + unpacker.unpackStructHeader(); + unpacker.unpackStructSignature(); + + try + { + decoder.decode( unpacker ); + fail( "Expected UnsupportedOperationException" ); + } + catch ( UnsupportedOperationException e ) + { + // Expected + assertEquals( e.getMessage(), expectedErrorMessage ); + } + } + + private static void assertHelloMessageMatches( HelloMessage expected, RequestMessage actual ) + { + assertAuthTokenMatches( expected.meta(), ((HelloMessage) actual).meta() ); + } }