Skip to content

Commit

Permalink
Add bearer authentication support (#1000)
Browse files Browse the repository at this point in the history
  • Loading branch information
injectives authored Oct 1, 2021
1 parent c11e210 commit 7157b9b
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 6 deletions.
23 changes: 21 additions & 2 deletions driver/src/main/java/org/neo4j/driver/AuthTokens.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
import org.neo4j.driver.internal.security.InternalAuthToken;

import static java.util.Collections.singletonMap;
import static org.neo4j.driver.Values.value;
import static org.neo4j.driver.internal.security.InternalAuthToken.CREDENTIALS_KEY;
import static org.neo4j.driver.internal.security.InternalAuthToken.PARAMETERS_KEY;
import static org.neo4j.driver.internal.security.InternalAuthToken.PRINCIPAL_KEY;
import static org.neo4j.driver.internal.security.InternalAuthToken.REALM_KEY;
import static org.neo4j.driver.internal.security.InternalAuthToken.SCHEME_KEY;
import static org.neo4j.driver.internal.util.Iterables.newHashMapWithSize;
import static org.neo4j.driver.Values.value;

/**
* This is a listing of the various methods of authentication supported by this
Expand Down Expand Up @@ -79,13 +79,32 @@ public static AuthToken basic( String username, String password, String realm )
return new InternalAuthToken( map );
}

/**
* The bearer authentication scheme, using a base64 encoded token.
*
* @param token base64 encoded token
* @return an authentication token that can be used to connect to Neo4j
* @throws NullPointerException when token is {@code null}
* @see GraphDatabase#driver(String, AuthToken)
*/
public static AuthToken bearer( String token )
{
Objects.requireNonNull( token, "Token can't be null" );

Map<String,Value> map = newHashMapWithSize( 2 );
map.put( SCHEME_KEY, value( "bearer" ) );
map.put( CREDENTIALS_KEY, value( token ) );
return new InternalAuthToken( map );
}

/**
* The kerberos authentication scheme, using a base64 encoded ticket
*
* @param base64EncodedTicket a base64 encoded service ticket
* @return an authentication token that can be used to connect to Neo4j
* @throws NullPointerException when ticket is {@code null}
* @see GraphDatabase#driver(String, AuthToken)
* @since 1.3
* @throws NullPointerException when ticket is {@code null}
*/
public static AuthToken kerberos( String base64EncodedTicket )
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) "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.exceptions;

/**
* The provided token has expired.
* <p>
* The current driver instance is considered invalid. It should not be used anymore. The client must create a new driver instance with a valid token.
* <p>
* Error code: Neo.ClientError.Security.TokenExpired
*/
public class TokenExpiredException extends SecurityException
{
public TokenExpiredException( String code, String message )
{
super( code, message );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.driver.exceptions.ResultConsumedException;
import org.neo4j.driver.exceptions.ServiceUnavailableException;
import org.neo4j.driver.exceptions.TokenExpiredException;
import org.neo4j.driver.exceptions.TransientException;

public final class ErrorUtil
Expand Down Expand Up @@ -80,6 +81,10 @@ else if ( code.equalsIgnoreCase( "Neo.ClientError.Security.AuthorizationExpired"
{
return new AuthorizationExpiredException( code, message );
}
else if ( code.equalsIgnoreCase( "Neo.ClientError.Security.TokenExpired" ) )
{
return new TokenExpiredException( code, message );
}
else
{
return new ClientException( code, message );
Expand Down
26 changes: 23 additions & 3 deletions driver/src/test/java/org/neo4j/driver/AuthTokensTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
import java.util.HashMap;
import java.util.Map;

import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Value;
import org.neo4j.driver.internal.security.InternalAuthToken;
import org.neo4j.driver.internal.value.ListValue;
import org.neo4j.driver.internal.value.MapValue;
Expand Down Expand Up @@ -103,6 +100,22 @@ void customAuthParameters()
assertThat( map.get( "parameters" ), equalTo( (Value) new MapValue( expectedParameters ) ) );
}

@Test
void shouldSupportBearerAuth()
{
// GIVEN
String tokenStr = "token";

// WHEN
InternalAuthToken token = (InternalAuthToken) AuthTokens.bearer( tokenStr );

// THEN
Map<String,Value> map = token.toMap();
assertThat( map.size(), equalTo( 2 ) );
assertThat( map.get( "scheme" ), equalTo( new StringValue( "bearer" ) ) );
assertThat( map.get( "credentials" ), equalTo( new StringValue( tokenStr ) ) );
}

@Test
void basicKerberosAuthWithRealm()
{
Expand Down Expand Up @@ -141,6 +154,13 @@ void shouldAllowBasicAuthTokenWithNullRealm()
assertEquals( "password", map.get( "credentials" ).asString() );
}

@Test
void shouldNotAllowBearerAuthTokenWithNullToken()
{
NullPointerException e = assertThrows( NullPointerException.class, () -> AuthTokens.bearer( null ) );
assertEquals( "Token can't be null", e.getMessage() );
}

@Test
void shouldNotAllowKerberosAuthTokenWithNullTicket()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.neo4j.driver.exceptions.DatabaseException;
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.driver.exceptions.ServiceUnavailableException;
import org.neo4j.driver.exceptions.TokenExpiredException;
import org.neo4j.driver.exceptions.TransientException;

import static org.hamcrest.Matchers.containsString;
Expand Down Expand Up @@ -175,4 +176,17 @@ void shouldCreateAuthorizationExpiredException()
assertEquals( code, error.code() );
assertEquals( message, error.getMessage() );
}

@Test
void shouldCreateTokenExpiredException()
{
String code = "Neo.ClientError.Security.TokenExpired";
String message = "message";

Neo4jException error = newNeo4jError( code, message );

assertThat( error, instanceOf( TokenExpiredException.class ) );
assertEquals( code, error.code() );
assertEquals( message, error.getMessage() );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public class GetFeatures implements TestkitRequest
"AuthorizationExpiredTreatment",
"ConfHint:connection.recv_timeout_seconds",
"Temporary:DriverFetchSize",
"Temporary:DriverMaxTxRetryTime"
"Temporary:DriverMaxTxRetryTime",
"Feature:Auth:Bearer"
) );

private static final Set<String> SYNC_FEATURES = new HashSet<>( Arrays.asList(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public TestkitResponse process( TestkitState testkitState )
data.authorizationToken.getTokens().get( "credentials" ),
data.authorizationToken.getTokens().get( "realm" ) );
break;
case "bearer":
authToken = AuthTokens.bearer( data.authorizationToken.getTokens().get( "credentials" ) );
break;
default:
return BackendError.builder()
.data( BackendError
Expand Down

0 comments on commit 7157b9b

Please sign in to comment.