Skip to content

Commit

Permalink
Fix auth token decoder in Bolt v3 HelloMessage
Browse files Browse the repository at this point in the history
- Share meta data map decoding function between v1 InitMessage
and v3 HelloMessage
- Share auth token decoder unittests
  • Loading branch information
henriknyman committed Oct 3, 2018
1 parent 37d145b commit be6d909
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 87 deletions.
Expand Up @@ -56,22 +56,22 @@ public BoltResponseHandler responseHandler()
public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException
{ {
String userAgent = unpacker.unpackString(); String userAgent = unpacker.unpackString();
Map<String,Object> authToken = readAuthToken( unpacker ); Map<String,Object> authToken = readMetaDataMap( unpacker );
return new InitMessage( userAgent, authToken ); return new InitMessage( userAgent, authToken );
} }


private static Map<String,Object> readAuthToken( Neo4jPack.Unpacker unpacker ) throws IOException public static Map<String,Object> readMetaDataMap( Neo4jPack.Unpacker unpacker ) throws IOException
{ {
MapValue authTokenValue = unpacker.unpackMap(); MapValue metaDataMapValue = unpacker.unpackMap();
PrimitiveOnlyValueWriter writer = new PrimitiveOnlyValueWriter(); PrimitiveOnlyValueWriter writer = new PrimitiveOnlyValueWriter();
Map<String,Object> tokenMap = new HashMap<>( authTokenValue.size() ); Map<String,Object> metaDataMap = new HashMap<>( metaDataMapValue.size() );
authTokenValue.foreach( ( key, value ) -> 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.sensitiveValueAsObject( value, key ) :
writer.valueAsObject( value ); writer.valueAsObject( value );
tokenMap.put( key, convertedValue ); metaDataMap.put( key, convertedValue );
} ); } );
return tokenMap; return metaDataMap;
} }
} }
Expand Up @@ -20,16 +20,14 @@
package org.neo4j.bolt.v3.messaging.decoder; package org.neo4j.bolt.v3.messaging.decoder;


import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map; import java.util.Map;


import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.Neo4jPack;
import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.messaging.RequestMessage;
import org.neo4j.bolt.messaging.RequestMessageDecoder; import org.neo4j.bolt.messaging.RequestMessageDecoder;
import org.neo4j.bolt.runtime.BoltResponseHandler; 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.bolt.v3.messaging.request.HelloMessage;
import org.neo4j.values.virtual.MapValue;


public class HelloMessageDecoder implements RequestMessageDecoder public class HelloMessageDecoder implements RequestMessageDecoder
{ {
Expand All @@ -55,10 +53,7 @@ public BoltResponseHandler responseHandler()
@Override @Override
public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException
{ {
MapValue helloMeta = unpacker.unpackMap(); Map<String,Object> meta = InitMessageDecoder.readMetaDataMap( unpacker );
PrimitiveOnlyValueWriter writer = new PrimitiveOnlyValueWriter();
Map<String,Object> meta = new HashMap<>( helloMeta.size() );
helloMeta.foreach( ( key, value ) -> meta.put( key, writer.valueAsObject( value ) ) );
return new HelloMessage( meta ); return new HelloMessage( meta );
} }
} }
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String,Object> authToken ) throws Exception;

protected abstract void testShouldFailToDecodeAuthToken( Map<String,Object> 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<String,Object> 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()
};
}
Expand Up @@ -21,16 +21,16 @@


import org.junit.jupiter.api.Test; 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.Neo4jPack.Unpacker;
import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.messaging.RequestMessage;
import org.neo4j.bolt.messaging.RequestMessageDecoder; import org.neo4j.bolt.messaging.RequestMessageDecoder;
import org.neo4j.bolt.runtime.BoltResponseHandler; 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.Neo4jPackV1;
import org.neo4j.bolt.v1.messaging.request.InitMessage; import org.neo4j.bolt.v1.messaging.request.InitMessage;
import org.neo4j.bolt.v1.packstream.PackedInputArray; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
Expand All @@ -39,7 +39,7 @@
import static org.neo4j.helpers.collection.MapUtil.map; import static org.neo4j.helpers.collection.MapUtil.map;
import static org.neo4j.test.AuthTokenUtil.assertAuthTokenMatches; import static org.neo4j.test.AuthTokenUtil.assertAuthTokenMatches;


class InitMessageDecoderTest class InitMessageDecoderTest extends AuthTokenDecoderTest
{ {
private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class ); private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class );
private final RequestMessageDecoder decoder = new InitMessageDecoder( responseHandler ); private final RequestMessageDecoder decoder = new InitMessageDecoder( responseHandler );
Expand Down Expand Up @@ -73,73 +73,11 @@ void shouldDecodeAckFailure() throws Exception
assertEquals( originalMessage, deserializedMessage ); assertEquals( originalMessage, deserializedMessage );
} }


@Test @Override
void shouldDecodeAuthTokenWithStringCredentials() throws Exception protected void testShouldDecodeAuthToken( Map<String,Object> authToken ) 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
{ {
Neo4jPackV1 neo4jPack = new Neo4jPackV1(); 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 ) ); PackedInputArray innput = new PackedInputArray( serialize( neo4jPack, originalMessage ) );
Unpacker unpacker = neo4jPack.newUnpacker( innput ); Unpacker unpacker = neo4jPack.newUnpacker( innput );
Expand All @@ -152,11 +90,11 @@ private void testShouldDecodeAuthToken( String fieldName, Object fieldValue ) th
assertInitMessageMatches( originalMessage, deserializedMessage ); assertInitMessageMatches( originalMessage, deserializedMessage );
} }


private void testShouldFailToDecodeAuthToken( String fieldName, Object fieldValue, String expectedErrorMessage ) throws Exception @Override
protected void testShouldFailToDecodeAuthToken( Map<String,Object> authToken, String expectedErrorMessage ) throws Exception
{ {
Neo4jPackV1 neo4jPack = new Neo4jPackV1(); Neo4jPackV1 neo4jPack = new Neo4jPackV1();
InitMessage originalMessage = new InitMessage( "My Driver", InitMessage originalMessage = new InitMessage( "My Driver", authToken );
map( AuthToken.PRINCIPAL, "neo4j", fieldName, fieldValue ) );


PackedInputArray innput = new PackedInputArray( serialize( neo4jPack, originalMessage ) ); PackedInputArray innput = new PackedInputArray( serialize( neo4jPack, originalMessage ) );
Unpacker unpacker = neo4jPack.newUnpacker( innput ); Unpacker unpacker = neo4jPack.newUnpacker( innput );
Expand Down
Expand Up @@ -21,20 +21,25 @@


import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;


import java.util.Map;

import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.Neo4jPack;
import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.messaging.RequestMessage;
import org.neo4j.bolt.messaging.RequestMessageDecoder; import org.neo4j.bolt.messaging.RequestMessageDecoder;
import org.neo4j.bolt.runtime.BoltResponseHandler; import org.neo4j.bolt.runtime.BoltResponseHandler;
import org.neo4j.bolt.security.auth.AuthTokenDecoderTest;
import org.neo4j.bolt.v1.packstream.PackedInputArray; import org.neo4j.bolt.v1.packstream.PackedInputArray;
import org.neo4j.bolt.v3.messaging.request.HelloMessage; import org.neo4j.bolt.v3.messaging.request.HelloMessage;


import static org.junit.jupiter.api.Assertions.assertEquals; 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.mockito.Mockito.mock;
import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.encode; import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.encode;
import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.newNeo4jPack; import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.newNeo4jPack;
import static org.neo4j.helpers.collection.MapUtil.map; 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 BoltResponseHandler responseHandler = mock( BoltResponseHandler.class );
private final RequestMessageDecoder decoder = new HelloMessageDecoder( responseHandler ); private final RequestMessageDecoder decoder = new HelloMessageDecoder( responseHandler );
Expand Down Expand Up @@ -73,4 +78,52 @@ static void assertOriginalMessageEqualsToDecoded( RequestMessage originalMessage
assertEquals( originalMessage, deserializedMessage ); assertEquals( originalMessage, deserializedMessage );
} }


@Override
protected void testShouldDecodeAuthToken( Map<String,Object> 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<String,Object> 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() );
}
} }

0 comments on commit be6d909

Please sign in to comment.