From e1021eb36dc66559b2bf83094ea59fe77d476f5f Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov Date: Mon, 6 Sep 2021 11:41:59 +0100 Subject: [PATCH] Add bearer authentication support --- .../java/org/neo4j/driver/AuthTokens.java | 23 +++++++++++-- .../exceptions/TokenExpiredException.java | 34 +++++++++++++++++++ .../neo4j/driver/internal/util/ErrorUtil.java | 5 +++ .../java/org/neo4j/driver/AuthTokensTest.java | 26 ++++++++++++-- .../driver/internal/util/ErrorUtilTest.java | 14 ++++++++ .../messages/requests/GetFeatures.java | 3 +- .../backend/messages/requests/NewDriver.java | 3 ++ 7 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 driver/src/main/java/org/neo4j/driver/exceptions/TokenExpiredException.java diff --git a/driver/src/main/java/org/neo4j/driver/AuthTokens.java b/driver/src/main/java/org/neo4j/driver/AuthTokens.java index 30daaa450b..9d49a45784 100644 --- a/driver/src/main/java/org/neo4j/driver/AuthTokens.java +++ b/driver/src/main/java/org/neo4j/driver/AuthTokens.java @@ -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 @@ -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 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 ) { diff --git a/driver/src/main/java/org/neo4j/driver/exceptions/TokenExpiredException.java b/driver/src/main/java/org/neo4j/driver/exceptions/TokenExpiredException.java new file mode 100644 index 0000000000..11ed162d84 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/exceptions/TokenExpiredException.java @@ -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. + *

+ * 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. + *

+ * Error code: Neo.ClientError.Security.TokenExpired + */ +public class TokenExpiredException extends SecurityException +{ + public TokenExpiredException( String code, String message ) + { + super( code, message ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/ErrorUtil.java b/driver/src/main/java/org/neo4j/driver/internal/util/ErrorUtil.java index d4b4fa4df7..ecf4e72bf8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/ErrorUtil.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/ErrorUtil.java @@ -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 @@ -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 ); diff --git a/driver/src/test/java/org/neo4j/driver/AuthTokensTest.java b/driver/src/test/java/org/neo4j/driver/AuthTokensTest.java index fc1051cf2c..f2e0a8fa9f 100644 --- a/driver/src/test/java/org/neo4j/driver/AuthTokensTest.java +++ b/driver/src/test/java/org/neo4j/driver/AuthTokensTest.java @@ -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; @@ -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 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() { @@ -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() { diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ErrorUtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/util/ErrorUtilTest.java index f4b588e0c3..25d0c5caae 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/ErrorUtilTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ErrorUtilTest.java @@ -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; @@ -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() ); + } } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java index 7bf2c7304c..d55be83b22 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java @@ -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 SYNC_FEATURES = new HashSet<>( Arrays.asList( diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java index c516e75b68..4e1d13ceb4 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java @@ -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