diff --git a/driver/clirr-ignored-differences.xml b/driver/clirr-ignored-differences.xml
index a2530156d3..8cc7629950 100644
--- a/driver/clirr-ignored-differences.xml
+++ b/driver/clirr-ignored-differences.xml
@@ -568,4 +568,33 @@
org.neo4j.driver.AuthTokenAndExpiration expiringAt(long)
+
+ org/neo4j/driver/AuthTokenManager
+ 7012
+ boolean handleSecurityException(org.neo4j.driver.AuthToken, org.neo4j.driver.exceptions.SecurityException)
+
+
+
+ org/neo4j/driver/AuthTokenManager
+ 7002
+ void onExpired(org.neo4j.driver.AuthToken)
+
+
+
+ org/neo4j/driver/AuthTokenManagers
+ 7002
+ org.neo4j.driver.AuthTokenManager expirationBased(java.util.function.Supplier)
+
+
+
+ org/neo4j/driver/AuthTokenManagers
+ 7002
+ org.neo4j.driver.AuthTokenManager expirationBasedAsync(java.util.function.Supplier)
+
+
+
+ org/neo4j/driver/exceptions/TokenExpiredRetryableException
+ 8001
+
+
diff --git a/driver/src/main/java/org/neo4j/driver/AuthToken.java b/driver/src/main/java/org/neo4j/driver/AuthToken.java
index 21f18a1f7b..038529e3ba 100644
--- a/driver/src/main/java/org/neo4j/driver/AuthToken.java
+++ b/driver/src/main/java/org/neo4j/driver/AuthToken.java
@@ -36,14 +36,14 @@ public sealed interface AuthToken permits InternalAuthToken {
/**
* Returns a new instance of a type holding both the token and its UTC expiration timestamp.
*
- * This is used by the expiration-based implementation of the {@link AuthTokenManager} supplied by the
+ * This is used by the bearer token implementation of the {@link AuthTokenManager} supplied by the
* {@link AuthTokenManagers}.
*
* @param utcExpirationTimestamp the UTC expiration timestamp
* @return a new instance of a type holding both the token and its UTC expiration timestamp
* @since 5.8
- * @see AuthTokenManagers#expirationBased(Supplier)
- * @see AuthTokenManagers#expirationBasedAsync(Supplier)
+ * @see AuthTokenManagers#bearer(Supplier)
+ * @see AuthTokenManagers#bearerAsync(Supplier)
*/
@Preview(name = "AuthToken rotation and session auth support")
default AuthTokenAndExpiration expiringAt(long utcExpirationTimestamp) {
diff --git a/driver/src/main/java/org/neo4j/driver/AuthTokenAndExpiration.java b/driver/src/main/java/org/neo4j/driver/AuthTokenAndExpiration.java
index ab4fef74df..0fde6bf14e 100644
--- a/driver/src/main/java/org/neo4j/driver/AuthTokenAndExpiration.java
+++ b/driver/src/main/java/org/neo4j/driver/AuthTokenAndExpiration.java
@@ -26,12 +26,12 @@
* A container used by the expiration based {@link AuthTokenManager} implementation provided by the driver, it contains an
* {@link AuthToken} and its UTC expiration timestamp.
*
- * This is used by the expiration-based implementation of the {@link AuthTokenManager} supplied by the
+ * This is used by the bearer token implementation of the {@link AuthTokenManager} supplied by the
* {@link AuthTokenManagers}.
*
* @since 5.8
- * @see AuthTokenManagers#expirationBased(Supplier)
- * @see AuthTokenManagers#expirationBasedAsync(Supplier)
+ * @see AuthTokenManagers#bearer(Supplier)
+ * @see AuthTokenManagers#bearerAsync(Supplier)
*/
@Preview(name = "AuthToken rotation and session auth support")
public sealed interface AuthTokenAndExpiration permits InternalAuthTokenAndExpiration {
diff --git a/driver/src/main/java/org/neo4j/driver/AuthTokenManager.java b/driver/src/main/java/org/neo4j/driver/AuthTokenManager.java
index 1607742552..cf8d621185 100644
--- a/driver/src/main/java/org/neo4j/driver/AuthTokenManager.java
+++ b/driver/src/main/java/org/neo4j/driver/AuthTokenManager.java
@@ -19,6 +19,9 @@
package org.neo4j.driver;
import java.util.concurrent.CompletionStage;
+import org.neo4j.driver.exceptions.AuthTokenManagerExecutionException;
+import org.neo4j.driver.exceptions.SecurityException;
+import org.neo4j.driver.exceptions.SecurityRetryableException;
import org.neo4j.driver.util.Preview;
/**
@@ -49,16 +52,27 @@ public interface AuthTokenManager {
*
* Failures will surface via the driver API, like {@link Session#beginTransaction()} method and others.
* @return a stage for a valid token, must not be {@code null} or complete with {@code null}
- * @see org.neo4j.driver.exceptions.AuthTokenManagerExecutionException
+ * @see AuthTokenManagerExecutionException
*/
CompletionStage getToken();
/**
- * Handles an error notification emitted by the server if the token is expired.
+ * Handles {@link SecurityException} that is created based on the server's security error response by determining if
+ * the given error may be resolved upon next {@link AuthTokenManager#getToken()} invokation.
*
- * This will be called when driver emits the {@link org.neo4j.driver.exceptions.TokenExpiredRetryableException}.
+ * If this method returns {@code true}, the driver wraps the original {@link SecurityException} in
+ * {@link SecurityRetryableException}. The managed transaction API (like
+ * {@link Session#executeRead(TransactionCallback)}, etc.) automatically retries its unit of work if no other
+ * condition is violated, while the other query execution APIs surface this error for external handling.
+ *
+ * If this method returns {@code false}, the original error remains unchanged.
+ *
+ * This method must not throw exceptions.
*
- * @param authToken the expired token
+ * @param authToken the token
+ * @param exception the security exception
+ * @return {@code true} if the exception should be marked as retryable or {@code false} if it should remain unchanged
+ * @since 5.12
*/
- void onExpired(AuthToken authToken);
+ boolean handleSecurityException(AuthToken authToken, SecurityException exception);
}
diff --git a/driver/src/main/java/org/neo4j/driver/AuthTokenManagers.java b/driver/src/main/java/org/neo4j/driver/AuthTokenManagers.java
index e803b0a443..d8e23189e2 100644
--- a/driver/src/main/java/org/neo4j/driver/AuthTokenManagers.java
+++ b/driver/src/main/java/org/neo4j/driver/AuthTokenManagers.java
@@ -18,11 +18,17 @@
*/
package org.neo4j.driver;
+import static java.util.Objects.requireNonNull;
+
import java.time.Clock;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Supplier;
+import org.neo4j.driver.exceptions.AuthenticationException;
+import org.neo4j.driver.exceptions.SecurityException;
+import org.neo4j.driver.exceptions.TokenExpiredException;
import org.neo4j.driver.internal.security.ExpirationBasedAuthTokenManager;
import org.neo4j.driver.util.Preview;
@@ -36,13 +42,55 @@ public final class AuthTokenManagers {
private AuthTokenManagers() {}
/**
- * Returns an {@link AuthTokenManager} that manages {@link AuthToken} instances with UTC expiration timestamp.
+ * Returns an {@link AuthTokenManager} that manages basic {@link AuthToken} instances.
+ *
+ * The implementation will only use the token supplier when it needs a new token instance, which would happen if
+ * the server rejects the current token with {@link AuthenticationException} (see
+ * {@link AuthTokenManager#handleSecurityException(AuthToken, SecurityException)}).
+ * The provided supplier and its completion stages must be non-blocking as documented in the
+ * {@link AuthTokenManager}.
+ *
+ * @param newTokenSupplier a new token stage supplier
+ * @return a new token manager
+ * @since 5.12
+ */
+ public static AuthTokenManager basic(Supplier newTokenSupplier) {
+ requireNonNull(newTokenSupplier, "newTokenSupplier must not be null");
+ return basicAsync(() -> CompletableFuture.supplyAsync(newTokenSupplier));
+ }
+
+ /**
+ * Returns an {@link AuthTokenManager} that manages basic {@link AuthToken} instances.
+ *
+ * The implementation will only use the token supplier when it needs a new token instance, which would happen if
+ * the server rejects the current token with {@link AuthenticationException} (see
+ * {@link AuthTokenManager#handleSecurityException(AuthToken, SecurityException)}).
+ * The provided supplier and its completion stages must be non-blocking as documented in the
+ * {@link AuthTokenManager}.
+ *
+ * @param newTokenStageSupplier a new token stage supplier
+ * @return a new token manager
+ * @since 5.12
+ */
+ public static AuthTokenManager basicAsync(Supplier> newTokenStageSupplier) {
+ requireNonNull(newTokenStageSupplier, "newTokenStageSupplier must not be null");
+ return new ExpirationBasedAuthTokenManager(
+ () -> newTokenStageSupplier.get().thenApply(authToken -> authToken.expiringAt(Long.MAX_VALUE)),
+ Set.of(AuthenticationException.class),
+ Clock.systemUTC());
+ }
+
+ /**
+ * Returns an {@link AuthTokenManager} that manages bearer {@link AuthToken} instances with UTC expiration
+ * timestamp.
*
* The implementation will only use the token supplier when it needs a new token instance. This includes the
* following conditions:
*
* - token's UTC timestamp is expired
- * - server rejects the current token (see {@link AuthTokenManager#onExpired(AuthToken)})
+ * - server rejects the current token with either {@link TokenExpiredException} or
+ * {@link AuthenticationException} (see
+ * {@link AuthTokenManager#handleSecurityException(AuthToken, SecurityException)})
*
*
* The supplier will be called by a task running in the {@link ForkJoinPool#commonPool()} as documented in the
@@ -50,28 +98,38 @@ private AuthTokenManagers() {}
*
* @param newTokenSupplier a new token supplier
* @return a new token manager
+ * @since 5.12
*/
- public static AuthTokenManager expirationBased(Supplier newTokenSupplier) {
- return expirationBasedAsync(() -> CompletableFuture.supplyAsync(newTokenSupplier));
+ public static AuthTokenManager bearer(Supplier newTokenSupplier) {
+ requireNonNull(newTokenSupplier, "newTokenSupplier must not be null");
+ return bearerAsync(() -> CompletableFuture.supplyAsync(newTokenSupplier));
}
/**
- * Returns an {@link AuthTokenManager} that manages {@link AuthToken} instances with UTC expiration timestamp.
+ * Returns an {@link AuthTokenManager} that manages bearer {@link AuthToken} instances with UTC expiration
+ * timestamp.
*
* The implementation will only use the token supplier when it needs a new token instance. This includes the
* following conditions:
*
* - token's UTC timestamp is expired
- * - server rejects the current token (see {@link AuthTokenManager#onExpired(AuthToken)})
+ * - server rejects the current token with either {@link TokenExpiredException} or
+ * {@link AuthenticationException} (see
+ * {@link AuthTokenManager#handleSecurityException(AuthToken, SecurityException)})
*
*
* The provided supplier and its completion stages must be non-blocking as documented in the {@link AuthTokenManager}.
*
* @param newTokenStageSupplier a new token stage supplier
* @return a new token manager
+ * @since 5.12
*/
- public static AuthTokenManager expirationBasedAsync(
+ public static AuthTokenManager bearerAsync(
Supplier> newTokenStageSupplier) {
- return new ExpirationBasedAuthTokenManager(newTokenStageSupplier, Clock.systemUTC());
+ requireNonNull(newTokenStageSupplier, "newTokenStageSupplier must not be null");
+ return new ExpirationBasedAuthTokenManager(
+ newTokenStageSupplier,
+ Set.of(TokenExpiredException.class, AuthenticationException.class),
+ Clock.systemUTC());
}
}
diff --git a/driver/src/main/java/org/neo4j/driver/exceptions/RetryableException.java b/driver/src/main/java/org/neo4j/driver/exceptions/RetryableException.java
index 604431cebd..6bc09529e7 100644
--- a/driver/src/main/java/org/neo4j/driver/exceptions/RetryableException.java
+++ b/driver/src/main/java/org/neo4j/driver/exceptions/RetryableException.java
@@ -22,5 +22,6 @@
* A marker interface for retryable exceptions.
*
* This indicates whether an operation that resulted in retryable exception is worth retrying.
+ * @since 5.0
*/
public interface RetryableException {}
diff --git a/driver/src/main/java/org/neo4j/driver/exceptions/SecurityRetryableException.java b/driver/src/main/java/org/neo4j/driver/exceptions/SecurityRetryableException.java
new file mode 100644
index 0000000000..afbd2b0370
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/exceptions/SecurityRetryableException.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import java.io.Serial;
+import java.util.Objects;
+import org.neo4j.driver.AuthToken;
+import org.neo4j.driver.util.Experimental;
+import org.neo4j.driver.util.Preview;
+
+/**
+ * Indicates that the contained {@link SecurityException} is a {@link RetryableException}, which is determined by the
+ * {@link org.neo4j.driver.AuthTokenManager#handleSecurityException(AuthToken, SecurityException)} method.
+ *
+ * The original {@link java.lang.SecurityException} is always available as a {@link Throwable#getCause()}. The
+ * {@link SecurityRetryableException#code()} and {@link SecurityRetryableException#getMessage()} supply the values from
+ * the original exception.
+ *
+ * @since 5.12
+ */
+@Preview(name = "AuthToken rotation and session auth support")
+public class SecurityRetryableException extends SecurityException implements RetryableException {
+ @Serial
+ private static final long serialVersionUID = 3914900631374208080L;
+
+ /**
+ * The original security exception.
+ */
+ private final SecurityException exception;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param exception the original security exception, must not be {@code null}
+ */
+ public SecurityRetryableException(SecurityException exception) {
+ super(exception.getMessage(), exception);
+ this.exception = Objects.requireNonNull(exception);
+ }
+
+ @Override
+ public String code() {
+ return exception.code();
+ }
+
+ @Override
+ public String getMessage() {
+ return exception.getMessage();
+ }
+
+ /**
+ * Returns the original security exception.
+ *
+ * @return the original security exception
+ */
+ @Experimental
+ public SecurityException securityException() {
+ return exception;
+ }
+}
diff --git a/driver/src/main/java/org/neo4j/driver/exceptions/TokenExpiredRetryableException.java b/driver/src/main/java/org/neo4j/driver/exceptions/TokenExpiredRetryableException.java
deleted file mode 100644
index a2a983656c..0000000000
--- a/driver/src/main/java/org/neo4j/driver/exceptions/TokenExpiredRetryableException.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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;
-
-import java.io.Serial;
-import org.neo4j.driver.AuthTokenManager;
-import org.neo4j.driver.util.Preview;
-
-/**
- * The token provided by the {@link AuthTokenManager} has expired.
- *
- * This is a retryable variant of {@link TokenExpiredException} used when the driver has an explicit
- * {@link AuthTokenManager} that might supply a new token following this failure.
- *
- * Error code: Neo.ClientError.Security.TokenExpired
- * @since 5.8
- * @see TokenExpiredException
- * @see AuthTokenManager
- * @see org.neo4j.driver.GraphDatabase#driver(String, AuthTokenManager)
- */
-@Preview(name = "AuthToken rotation and session auth support")
-public class TokenExpiredRetryableException extends TokenExpiredException implements RetryableException {
- @Serial
- private static final long serialVersionUID = -6672756500436910942L;
-
- /**
- * Constructs a new instance.
- * @param code the code
- * @param message the message
- */
- public TokenExpiredRetryableException(String code, String message) {
- super(code, message);
- }
-}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageDispatcher.java b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageDispatcher.java
index d62418645a..51c57974a4 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageDispatcher.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageDispatcher.java
@@ -36,13 +36,13 @@
import org.neo4j.driver.Value;
import org.neo4j.driver.exceptions.AuthorizationExpiredException;
import org.neo4j.driver.exceptions.ClientException;
+import org.neo4j.driver.exceptions.SecurityException;
+import org.neo4j.driver.exceptions.SecurityRetryableException;
import org.neo4j.driver.exceptions.TokenExpiredException;
-import org.neo4j.driver.exceptions.TokenExpiredRetryableException;
import org.neo4j.driver.internal.handlers.ResetResponseHandler;
import org.neo4j.driver.internal.logging.ChannelActivityLogger;
import org.neo4j.driver.internal.logging.ChannelErrorLogger;
import org.neo4j.driver.internal.messaging.ResponseMessageHandler;
-import org.neo4j.driver.internal.security.StaticAuthTokenManager;
import org.neo4j.driver.internal.spi.ResponseHandler;
import org.neo4j.driver.internal.util.ErrorUtil;
@@ -120,20 +120,26 @@ public void handleFailureMessage(String code, String message) {
}
var currentError = this.currentError;
- if (currentError instanceof AuthorizationExpiredException) {
- authorizationStateListener(channel).onExpired();
- } else if (currentError instanceof TokenExpiredException tokenExpiredException) {
- var authContext = authContext(channel);
- var authTokenProvider = authContext.getAuthTokenManager();
- if (!(authTokenProvider instanceof StaticAuthTokenManager)) {
- currentError = new TokenExpiredRetryableException(
- tokenExpiredException.code(), tokenExpiredException.getMessage());
+ var sendReset = true;
+
+ if (currentError instanceof SecurityException securityException) {
+ if (securityException instanceof AuthorizationExpiredException) {
+ authorizationStateListener(channel).onExpired();
+ sendReset = false;
+ } else if (securityException instanceof TokenExpiredException) {
+ sendReset = false;
}
+ var authContext = authContext(channel);
+ var authTokenManager = authContext.getAuthTokenManager();
var authToken = authContext.getAuthToken();
if (authToken != null && authContext.isManaged()) {
- authTokenProvider.onExpired(authToken);
+ if (authTokenManager.handleSecurityException(authToken, securityException)) {
+ currentError = new SecurityRetryableException(securityException);
+ }
}
- } else {
+ }
+
+ if (sendReset) {
// write a RESET to "acknowledge" the failure
enqueue(new ResetResponseHandler(this));
channel.writeAndFlush(RESET, channel.voidPromise());
diff --git a/driver/src/main/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogic.java b/driver/src/main/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogic.java
index eabca2b381..ab43f3cfb9 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogic.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogic.java
@@ -35,6 +35,7 @@
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.driver.exceptions.RetryableException;
+import org.neo4j.driver.exceptions.SecurityRetryableException;
import org.neo4j.driver.internal.util.Futures;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -163,7 +164,9 @@ protected boolean canRetryOn(Throwable error) {
*/
private static Throwable extractPossibleTerminationCause(Throwable error) {
// Having a dedicated "TerminatedException" inheriting from ClientException might be a good idea.
- if (error instanceof ClientException && error.getCause() != null) {
+ if (!(error instanceof SecurityRetryableException)
+ && error instanceof ClientException
+ && error.getCause() != null) {
return error.getCause();
}
return error;
diff --git a/driver/src/main/java/org/neo4j/driver/internal/security/ExpirationBasedAuthTokenManager.java b/driver/src/main/java/org/neo4j/driver/internal/security/ExpirationBasedAuthTokenManager.java
index cb1d95944e..d706fb8f75 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/security/ExpirationBasedAuthTokenManager.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/security/ExpirationBasedAuthTokenManager.java
@@ -23,6 +23,7 @@
import static org.neo4j.driver.internal.util.LockUtil.executeWithLock;
import java.time.Clock;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -32,20 +33,26 @@
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokenAndExpiration;
import org.neo4j.driver.AuthTokenManager;
+import org.neo4j.driver.exceptions.SecurityException;
public class ExpirationBasedAuthTokenManager implements AuthTokenManager {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Supplier> freshTokenSupplier;
+ private final Set> retryableExceptionClasses;
private final Clock clock;
private CompletableFuture tokenFuture;
private AuthTokenAndExpiration token;
public ExpirationBasedAuthTokenManager(
- Supplier> freshTokenSupplier, Clock clock) {
+ Supplier> freshTokenSupplier,
+ Set> retryableExceptionClasses,
+ Clock clock) {
this.freshTokenSupplier = freshTokenSupplier;
+ this.retryableExceptionClasses = retryableExceptionClasses;
this.clock = clock;
}
+ @Override
public CompletionStage getToken() {
var validTokenFuture = executeWithLock(lock.readLock(), this::getValidTokenFuture);
if (validTokenFuture == null) {
@@ -65,12 +72,18 @@ public CompletionStage getToken() {
return validTokenFuture;
}
- public void onExpired(AuthToken authToken) {
- executeWithLock(lock.writeLock(), () -> {
- if (token != null && token.authToken().equals(authToken)) {
- unsetTokenState();
- }
- });
+ @Override
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ var retryable = retryableExceptionClasses.stream()
+ .anyMatch(retryableExceptionClass -> retryableExceptionClass.isAssignableFrom(exception.getClass()));
+ if (retryable) {
+ executeWithLock(lock.writeLock(), () -> {
+ if (token != null && token.authToken().equals(authToken)) {
+ unsetTokenState();
+ }
+ });
+ }
+ return retryable;
}
private void handleUpstreamResult(AuthTokenAndExpiration authTokenAndExpiration, Throwable throwable) {
diff --git a/driver/src/main/java/org/neo4j/driver/internal/security/StaticAuthTokenManager.java b/driver/src/main/java/org/neo4j/driver/internal/security/StaticAuthTokenManager.java
index ecbbd1c2d2..9eef7d759d 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/security/StaticAuthTokenManager.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/security/StaticAuthTokenManager.java
@@ -22,13 +22,11 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
-import java.util.concurrent.atomic.AtomicBoolean;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokenManager;
-import org.neo4j.driver.exceptions.TokenExpiredException;
+import org.neo4j.driver.exceptions.SecurityException;
public class StaticAuthTokenManager implements AuthTokenManager {
- private final AtomicBoolean expired = new AtomicBoolean();
private final AuthToken authToken;
public StaticAuthTokenManager(AuthToken authToken) {
@@ -38,15 +36,11 @@ public StaticAuthTokenManager(AuthToken authToken) {
@Override
public CompletionStage getToken() {
- return expired.get()
- ? CompletableFuture.failedFuture(new TokenExpiredException(null, "authToken is expired"))
- : CompletableFuture.completedFuture(authToken);
+ return CompletableFuture.completedFuture(authToken);
}
@Override
- public void onExpired(AuthToken authToken) {
- if (authToken.equals(this.authToken)) {
- expired.set(true);
- }
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
}
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/security/ValidatingAuthTokenManager.java b/driver/src/main/java/org/neo4j/driver/internal/security/ValidatingAuthTokenManager.java
index 9ac60a4ce9..a7b5c89435 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/security/ValidatingAuthTokenManager.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/security/ValidatingAuthTokenManager.java
@@ -29,6 +29,7 @@
import org.neo4j.driver.Logger;
import org.neo4j.driver.Logging;
import org.neo4j.driver.exceptions.AuthTokenManagerExecutionException;
+import org.neo4j.driver.exceptions.SecurityException;
public class ValidatingAuthTokenManager implements AuthTokenManager {
private final Logger log;
@@ -68,10 +69,12 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
requireNonNull(authToken, "authToken must not be null");
+ requireNonNull(exception, "exception must not be null");
+ var retryable = false;
try {
- delegate.onExpired(authToken);
+ retryable = delegate.handleSecurityException(authToken, exception);
} catch (Throwable throwable) {
log.warn(String.format(
"%s has been thrown by %s.onExpired method",
@@ -82,5 +85,6 @@ public void onExpired(AuthToken authToken) {
throwable.getClass().getName(), delegate.getClass().getName()),
throwable);
}
+ return retryable;
}
}
diff --git a/driver/src/test/java/org/neo4j/driver/integration/GraphDatabaseAuthClusterIT.java b/driver/src/test/java/org/neo4j/driver/integration/GraphDatabaseAuthClusterIT.java
index a9db6a0dcf..fdc6b00b79 100644
--- a/driver/src/test/java/org/neo4j/driver/integration/GraphDatabaseAuthClusterIT.java
+++ b/driver/src/test/java/org/neo4j/driver/integration/GraphDatabaseAuthClusterIT.java
@@ -35,6 +35,7 @@
import org.neo4j.driver.async.AsyncSession;
import org.neo4j.driver.async.ResultCursor;
import org.neo4j.driver.exceptions.AuthTokenManagerExecutionException;
+import org.neo4j.driver.exceptions.SecurityException;
import org.neo4j.driver.reactive.ReactiveSession;
import org.neo4j.driver.testutil.cc.LocalOrRemoteClusterExtension;
import reactor.core.publisher.Mono;
@@ -54,7 +55,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager);
var session = driver.session()) {
@@ -72,7 +75,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager);
var session = driver.session()) {
@@ -92,7 +97,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager);
var session = driver.session()) {
@@ -113,7 +120,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager);
var session = driver.session()) {
@@ -134,7 +143,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager);
var session = driver.session()) {
@@ -156,7 +167,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager);
var session = driver.session()) {
@@ -177,7 +190,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -198,7 +213,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -224,7 +241,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -254,7 +273,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -278,7 +299,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -307,7 +330,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -338,7 +363,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -358,7 +385,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -383,7 +412,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -412,7 +443,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -435,7 +468,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -463,7 +498,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -493,7 +530,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -513,7 +552,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -538,7 +579,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -567,7 +610,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -590,7 +635,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -618,7 +665,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(clusterRule.getClusterUri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
diff --git a/driver/src/test/java/org/neo4j/driver/integration/GraphDatabaseAuthDirectIT.java b/driver/src/test/java/org/neo4j/driver/integration/GraphDatabaseAuthDirectIT.java
index 13ba46d749..17615efb6f 100644
--- a/driver/src/test/java/org/neo4j/driver/integration/GraphDatabaseAuthDirectIT.java
+++ b/driver/src/test/java/org/neo4j/driver/integration/GraphDatabaseAuthDirectIT.java
@@ -35,6 +35,7 @@
import org.neo4j.driver.async.AsyncSession;
import org.neo4j.driver.async.ResultCursor;
import org.neo4j.driver.exceptions.AuthTokenManagerExecutionException;
+import org.neo4j.driver.exceptions.SecurityException;
import org.neo4j.driver.reactive.ReactiveSession;
import org.neo4j.driver.testutil.DatabaseExtension;
import reactor.core.publisher.Mono;
@@ -53,7 +54,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager);
var session = driver.session()) {
@@ -71,7 +74,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager);
var session = driver.session()) {
@@ -91,7 +96,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager);
var session = driver.session()) {
@@ -112,7 +119,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager);
var session = driver.session()) {
@@ -133,7 +142,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager);
var session = driver.session()) {
@@ -155,7 +166,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager);
var session = driver.session()) {
@@ -176,7 +189,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -197,7 +212,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -223,7 +240,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -253,7 +272,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -277,7 +298,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -306,7 +329,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(AsyncSession.class);
@@ -337,7 +362,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -357,7 +384,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -382,7 +411,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -411,7 +442,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -434,7 +467,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -462,7 +497,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(ReactiveSession.class);
@@ -492,7 +529,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -512,7 +551,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -537,7 +578,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -566,7 +609,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -589,7 +634,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
@@ -617,7 +664,9 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {}
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
+ return false;
+ }
};
try (var driver = GraphDatabase.driver(neo4j.uri(), manager)) {
var session = driver.session(org.neo4j.driver.reactivestreams.ReactiveSession.class);
diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageDispatcherTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageDispatcherTest.java
index 29b70cd6d8..435c45c14a 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageDispatcherTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageDispatcherTest.java
@@ -64,8 +64,8 @@
import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.exceptions.Neo4jException;
+import org.neo4j.driver.exceptions.SecurityException;
import org.neo4j.driver.exceptions.TokenExpiredException;
-import org.neo4j.driver.exceptions.TokenExpiredRetryableException;
import org.neo4j.driver.internal.async.pool.AuthContext;
import org.neo4j.driver.internal.logging.ChannelActivityLogger;
import org.neo4j.driver.internal.logging.ChannelErrorLogger;
@@ -111,7 +111,15 @@ void shouldDequeHandlerOnSuccess() {
@Test
void shouldDequeHandlerOnFailure() {
- var dispatcher = newDispatcher();
+ var channel = new EmbeddedChannel();
+ var authToken = AuthTokens.basic("username", "password");
+ var authTokenManager = spy(new StaticAuthTokenManager(authToken));
+ var authContext = mock(AuthContext.class);
+ given(authContext.isManaged()).willReturn(true);
+ given(authContext.getAuthTokenManager()).willReturn(authTokenManager);
+ given(authContext.getAuthToken()).willReturn(authToken);
+ setAuthContext(channel, authContext);
+ var dispatcher = newDispatcher(channel);
var handler = mock(ResponseHandler.class);
dispatcher.enqueue(handler);
@@ -128,7 +136,14 @@ void shouldDequeHandlerOnFailure() {
@Test
void shouldSendResetOnFailure() {
- var channel = newChannelMock();
+ var channel = spy(new EmbeddedChannel());
+ var authToken = AuthTokens.basic("username", "password");
+ var authTokenManager = spy(new StaticAuthTokenManager(authToken));
+ var authContext = mock(AuthContext.class);
+ given(authContext.isManaged()).willReturn(true);
+ given(authContext.getAuthTokenManager()).willReturn(authTokenManager);
+ given(authContext.getAuthToken()).willReturn(authToken);
+ setAuthContext(channel, authContext);
var dispatcher = newDispatcher(channel);
dispatcher.enqueue(mock(ResponseHandler.class));
@@ -141,7 +156,15 @@ void shouldSendResetOnFailure() {
@Test
void shouldClearFailureOnSuccessOfResetAfterFailure() {
- var dispatcher = newDispatcher();
+ var channel = new EmbeddedChannel();
+ var authToken = AuthTokens.basic("username", "password");
+ var authTokenManager = spy(new StaticAuthTokenManager(authToken));
+ var authContext = mock(AuthContext.class);
+ given(authContext.isManaged()).willReturn(true);
+ given(authContext.getAuthTokenManager()).willReturn(authTokenManager);
+ given(authContext.getAuthToken()).willReturn(authToken);
+ setAuthContext(channel, authContext);
+ var dispatcher = newDispatcher(channel);
dispatcher.enqueue(mock(ResponseHandler.class));
assertEquals(1, dispatcher.queuedHandlersCount());
@@ -239,7 +262,15 @@ void shouldDequeHandlerOnIgnored() {
@Test
void shouldFailHandlerOnIgnoredMessageWithExistingError() {
- var dispatcher = newDispatcher();
+ var channel = new EmbeddedChannel();
+ var authToken = AuthTokens.basic("username", "password");
+ var authTokenManager = spy(new StaticAuthTokenManager(authToken));
+ var authContext = mock(AuthContext.class);
+ given(authContext.isManaged()).willReturn(true);
+ given(authContext.getAuthTokenManager()).willReturn(authTokenManager);
+ given(authContext.getAuthToken()).willReturn(authToken);
+ setAuthContext(channel, authContext);
+ var dispatcher = newDispatcher(channel);
var handler1 = mock(ResponseHandler.class);
var handler2 = mock(ResponseHandler.class);
@@ -267,7 +298,15 @@ void shouldFailHandlerOnIgnoredMessageWhenNoErrorAndNotHandlingReset() {
@Test
void shouldDequeAndFailHandlerOnIgnoredWhenErrorHappened() {
- var dispatcher = newDispatcher();
+ var channel = new EmbeddedChannel();
+ var authToken = AuthTokens.basic("username", "password");
+ var authTokenManager = spy(new StaticAuthTokenManager(authToken));
+ var authContext = mock(AuthContext.class);
+ given(authContext.isManaged()).willReturn(true);
+ given(authContext.getAuthTokenManager()).willReturn(authTokenManager);
+ given(authContext.getAuthToken()).willReturn(authToken);
+ setAuthContext(channel, authContext);
+ var dispatcher = newDispatcher(channel);
var handler1 = mock(ResponseHandler.class);
var handler2 = mock(ResponseHandler.class);
@@ -366,7 +405,14 @@ void shouldReEnableAutoReadWhenAutoReadManagingHandlerIsRemoved() {
@ValueSource(classes = {SuccessMessage.class, FailureMessage.class, RecordMessage.class, IgnoredMessage.class})
void shouldCreateChannelActivityLoggerAndLogDebugMessageOnMessageHandling(Class extends Message> message) {
// GIVEN
- var channel = newChannelMock();
+ var channel = new EmbeddedChannel();
+ var authToken = AuthTokens.basic("username", "password");
+ var authTokenManager = spy(new StaticAuthTokenManager(authToken));
+ var authContext = mock(AuthContext.class);
+ given(authContext.isManaged()).willReturn(true);
+ given(authContext.getAuthTokenManager()).willReturn(authTokenManager);
+ given(authContext.getAuthToken()).willReturn(authToken);
+ setAuthContext(channel, authContext);
var logging = mock(Logging.class);
var logger = mock(Logger.class);
when(logger.isDebugEnabled()).thenReturn(true);
@@ -460,10 +506,11 @@ void shouldEmitTokenExpiredRetryableExceptionAndNotifyAuthTokenManager() {
// then
assertEquals(0, dispatcher.queuedHandlersCount());
- verifyFailure(handler, code, message, TokenExpiredRetryableException.class);
+ verifyFailure(handler, code, message, TokenExpiredException.class);
assertEquals(code, ((Neo4jException) dispatcher.currentError()).code());
assertEquals(message, dispatcher.currentError().getMessage());
- then(authTokenManager).should().onExpired(authToken);
+ then(authTokenManager).should().handleSecurityException(authToken, (SecurityException)
+ dispatcher.currentError());
}
@Test
@@ -491,7 +538,8 @@ void shouldEmitTokenExpiredExceptionAndNotifyAuthTokenManager() {
verifyFailure(handler, code, message, TokenExpiredException.class);
assertEquals(code, ((Neo4jException) dispatcher.currentError()).code());
assertEquals(message, dispatcher.currentError().getMessage());
- then(authTokenManager).should().onExpired(authToken);
+ then(authTokenManager).should().handleSecurityException(authToken, (SecurityException)
+ dispatcher.currentError());
}
private static void verifyFailure(ResponseHandler handler) {
diff --git a/driver/src/test/java/org/neo4j/driver/internal/security/ValidatingAuthTokenManagerTest.java b/driver/src/test/java/org/neo4j/driver/internal/security/ValidatingAuthTokenManagerTest.java
index e03abfb605..51b67616bd 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/security/ValidatingAuthTokenManagerTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/security/ValidatingAuthTokenManagerTest.java
@@ -36,6 +36,7 @@
import org.neo4j.driver.Logger;
import org.neo4j.driver.Logging;
import org.neo4j.driver.exceptions.AuthTokenManagerExecutionException;
+import org.neo4j.driver.exceptions.SecurityException;
class ValidatingAuthTokenManagerTest {
@Test
@@ -103,47 +104,61 @@ void shouldPassOriginalToken() {
}
@Test
- void shouldRejectNullAuthTokenOnExpiration() {
+ void shouldRejectNullAuthTokenOnHandleSecurityException() {
// given
var delegateManager = mock(AuthTokenManager.class);
var manager = new ValidatingAuthTokenManager(delegateManager, Logging.none());
// when & then
- assertThrows(NullPointerException.class, () -> manager.onExpired(null));
+ assertThrows(
+ NullPointerException.class, () -> manager.handleSecurityException(null, mock(SecurityException.class)));
then(delegateManager).shouldHaveNoInteractions();
}
@Test
- void shouldPassOriginalTokenOnExpiration() {
+ void shouldRejectNullExceptionOnHandleSecurityException() {
+ // given
+ var delegateManager = mock(AuthTokenManager.class);
+ var manager = new ValidatingAuthTokenManager(delegateManager, Logging.none());
+
+ // when & then
+ assertThrows(NullPointerException.class, () -> manager.handleSecurityException(AuthTokens.none(), null));
+ then(delegateManager).shouldHaveNoInteractions();
+ }
+
+ @Test
+ void shouldPassOriginalTokenAndExceptionOnHandleSecurityException() {
// given
var delegateManager = mock(AuthTokenManager.class);
var manager = new ValidatingAuthTokenManager(delegateManager, Logging.none());
var token = AuthTokens.none();
+ var exception = mock(SecurityException.class);
// when
- manager.onExpired(token);
+ manager.handleSecurityException(token, exception);
// then
- then(delegateManager).should().onExpired(token);
+ then(delegateManager).should().handleSecurityException(token, exception);
}
@Test
- void shouldLogWhenDelegateOnExpiredFails() {
+ void shouldLogWhenDelegateHandleSecurityExceptionFails() {
// given
var delegateManager = mock(AuthTokenManager.class);
var token = AuthTokens.none();
+ var securityException = mock(SecurityException.class);
var exception = mock(RuntimeException.class);
- willThrow(exception).given(delegateManager).onExpired(token);
+ willThrow(exception).given(delegateManager).handleSecurityException(token, securityException);
var logging = mock(Logging.class);
var log = mock(Logger.class);
given(logging.getLog(ValidatingAuthTokenManager.class)).willReturn(log);
var manager = new ValidatingAuthTokenManager(delegateManager, logging);
// when
- manager.onExpired(token);
+ manager.handleSecurityException(token, securityException);
// then
- then(delegateManager).should().onExpired(token);
+ then(delegateManager).should().handleSecurityException(token, securityException);
then(log).should().warn(anyString());
then(log).should().debug(anyString(), eq(exception));
}
diff --git a/intellij-code-inspection-driver-profile.xml b/intellij-code-inspection-driver-profile.xml
index 2379ccce87..4245680d04 100644
--- a/intellij-code-inspection-driver-profile.xml
+++ b/intellij-code-inspection-driver-profile.xml
@@ -10,6 +10,9 @@
+
+
+
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java
index 535528be2f..e327817ad2 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/channel/handler/TestkitRequestProcessorHandler.java
@@ -38,6 +38,7 @@
import org.neo4j.driver.Logging;
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.driver.exceptions.NoSuchRecordException;
+import org.neo4j.driver.exceptions.RetryableException;
import org.neo4j.driver.exceptions.UntrustedServerException;
import org.neo4j.driver.internal.spi.ConnectionPool;
@@ -116,6 +117,7 @@ private TestkitResponse createErrorResponse(Throwable throwable) {
.errorType(e.getClass().getName())
.code(e.code())
.msg(e.getMessage())
+ .retryable(e instanceof RetryableException)
.build())
.build();
} else if (isConnectionPoolClosedException(throwable)
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/AuthTokenManagerHandleSecurityExceptionCompleted.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/AuthTokenManagerHandleSecurityExceptionCompleted.java
new file mode 100644
index 0000000000..05f3ab0c1c
--- /dev/null
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/AuthTokenManagerHandleSecurityExceptionCompleted.java
@@ -0,0 +1,40 @@
+/*
+ * 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 neo4j.org.testkit.backend.messages.requests;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class AuthTokenManagerHandleSecurityExceptionCompleted implements TestkitCallbackResult {
+ private AuthTokenManagerHandleSecurityExceptionCompletedBody data;
+
+ @Override
+ public String getCallbackId() {
+ return data.getRequestId();
+ }
+
+ @Setter
+ @Getter
+ public static class AuthTokenManagerHandleSecurityExceptionCompletedBody {
+ private String requestId;
+ private boolean handled;
+ }
+}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/AuthTokenManagerOnAuthExpiredCompleted.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/BasicAuthTokenProviderCompleted.java
similarity index 79%
rename from testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/AuthTokenManagerOnAuthExpiredCompleted.java
rename to testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/BasicAuthTokenProviderCompleted.java
index 304eb31f56..10967b9c87 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/AuthTokenManagerOnAuthExpiredCompleted.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/BasicAuthTokenProviderCompleted.java
@@ -23,8 +23,8 @@
@Setter
@Getter
-public class AuthTokenManagerOnAuthExpiredCompleted implements TestkitCallbackResult {
- private AuthTokenManagerOnAuthExpiredCompletedBody data;
+public class BasicAuthTokenProviderCompleted implements TestkitCallbackResult {
+ private BasicAuthTokenProviderCompletedBody data;
@Override
public String getCallbackId() {
@@ -33,7 +33,8 @@ public String getCallbackId() {
@Setter
@Getter
- public static class AuthTokenManagerOnAuthExpiredCompletedBody {
+ public static class BasicAuthTokenProviderCompletedBody {
private String requestId;
+ private AuthorizationToken auth;
}
}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExpirationBasedAuthTokenProviderCompleted.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/BearerAuthTokenProviderCompleted.java
similarity index 81%
rename from testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExpirationBasedAuthTokenProviderCompleted.java
rename to testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/BearerAuthTokenProviderCompleted.java
index 5c1b6533cf..f095acd078 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExpirationBasedAuthTokenProviderCompleted.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/BearerAuthTokenProviderCompleted.java
@@ -23,8 +23,8 @@
@Setter
@Getter
-public class ExpirationBasedAuthTokenProviderCompleted implements TestkitCallbackResult {
- private ExpirationBasedAuthTokenProviderCompletedBody data;
+public class BearerAuthTokenProviderCompleted implements TestkitCallbackResult {
+ private BearerAuthTokenProviderCompletedBody data;
@Override
public String getCallbackId() {
@@ -33,7 +33,7 @@ public String getCallbackId() {
@Setter
@Getter
- public static class ExpirationBasedAuthTokenProviderCompletedBody {
+ public static class BearerAuthTokenProviderCompletedBody {
private String requestId;
private AuthTokenAndExpiration auth;
}
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 1b320b7f00..16ad0764df 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
@@ -68,7 +68,8 @@ public class GetFeatures implements TestkitRequest {
"Backend:MockTime",
"Feature:API:Session:AuthConfig",
"Feature:Auth:Managed",
- "Feature:API:Driver.SupportsSessionAuth"));
+ "Feature:API:Driver.SupportsSessionAuth",
+ "Feature:API:RetryableExceptions"));
private static final Set SYNC_FEATURES = new HashSet<>(Arrays.asList(
"Feature:Bolt:3.0",
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewAuthTokenManager.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewAuthTokenManager.java
index f720faaea9..a939d30a51 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewAuthTokenManager.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewAuthTokenManager.java
@@ -26,11 +26,12 @@
import neo4j.org.testkit.backend.TestkitState;
import neo4j.org.testkit.backend.messages.responses.AuthTokenManager;
import neo4j.org.testkit.backend.messages.responses.AuthTokenManagerGetAuthRequest;
-import neo4j.org.testkit.backend.messages.responses.AuthTokenManagerOnAuthExpiredRequest;
-import neo4j.org.testkit.backend.messages.responses.AuthTokenManagerOnAuthExpiredRequest.AuthTokenManagerOnAuthExpiredRequestBody;
+import neo4j.org.testkit.backend.messages.responses.AuthTokenManagerHandleSecurityExceptionRequest;
+import neo4j.org.testkit.backend.messages.responses.AuthTokenManagerHandleSecurityExceptionRequest.AuthTokenManagerHandleSecurityExceptionRequestBody;
import neo4j.org.testkit.backend.messages.responses.TestkitCallback;
import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
import org.neo4j.driver.AuthToken;
+import org.neo4j.driver.exceptions.SecurityException;
@Setter
@Getter
@@ -74,23 +75,28 @@ public CompletionStage getToken() {
}
@Override
- public void onExpired(AuthToken authToken) {
+ public boolean handleSecurityException(AuthToken authToken, SecurityException exception) {
var callbackId = testkitState.newId();
- var callback = AuthTokenManagerOnAuthExpiredRequest.builder()
- .data(AuthTokenManagerOnAuthExpiredRequestBody.builder()
+ var callback = AuthTokenManagerHandleSecurityExceptionRequest.builder()
+ .data(AuthTokenManagerHandleSecurityExceptionRequestBody.builder()
.id(callbackId)
.authTokenManagerId(authProviderId)
.auth(AuthTokenUtil.parseAuthToken(authToken))
+ .errorCode(exception.code())
.build())
.build();
var callbackStage = dispatchTestkitCallback(testkitState, callback);
try {
- callbackStage.toCompletableFuture().get();
+ var response = callbackStage.toCompletableFuture().get();
+ if (response instanceof AuthTokenManagerHandleSecurityExceptionCompleted authComplete) {
+ return authComplete.getData().isHandled();
+ }
} catch (Exception e) {
throw new RuntimeException("Unexpected failure during Testkit callback", e);
}
+ return false;
}
private CompletionStage dispatchTestkitCallback(
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewBasicAuthTokenManager.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewBasicAuthTokenManager.java
new file mode 100644
index 0000000000..87680e8b14
--- /dev/null
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewBasicAuthTokenManager.java
@@ -0,0 +1,98 @@
+/*
+ * 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 neo4j.org.testkit.backend.messages.requests;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Supplier;
+import lombok.Getter;
+import lombok.Setter;
+import neo4j.org.testkit.backend.AuthTokenUtil;
+import neo4j.org.testkit.backend.TestkitClock;
+import neo4j.org.testkit.backend.TestkitState;
+import neo4j.org.testkit.backend.messages.responses.BasicAuthTokenManager;
+import neo4j.org.testkit.backend.messages.responses.BasicAuthTokenManager.BasicAuthTokenManagerBody;
+import neo4j.org.testkit.backend.messages.responses.BasicAuthTokenProviderRequest;
+import neo4j.org.testkit.backend.messages.responses.BasicAuthTokenProviderRequest.BasicAuthTokenProviderRequestBody;
+import neo4j.org.testkit.backend.messages.responses.TestkitCallback;
+import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
+import org.neo4j.driver.AuthTokenAndExpiration;
+import org.neo4j.driver.exceptions.AuthenticationException;
+import org.neo4j.driver.internal.security.ExpirationBasedAuthTokenManager;
+
+@Setter
+@Getter
+public class NewBasicAuthTokenManager extends AbstractBasicTestkitRequest {
+ private NewBasicAuthTokenManagerBody data;
+
+ @Override
+ protected TestkitResponse processAndCreateResponse(TestkitState testkitState) {
+ var id = testkitState.newId();
+ testkitState.addAuthProvider(
+ id,
+ new ExpirationBasedAuthTokenManager(
+ new TestkitAuthTokenProvider(id, testkitState),
+ Set.of(AuthenticationException.class),
+ TestkitClock.INSTANCE));
+ return BasicAuthTokenManager.builder()
+ .data(BasicAuthTokenManagerBody.builder().id(id).build())
+ .build();
+ }
+
+ private record TestkitAuthTokenProvider(String authProviderId, TestkitState testkitState)
+ implements Supplier> {
+ @Override
+ public CompletionStage get() {
+ var callbackId = testkitState.newId();
+
+ var callback = BasicAuthTokenProviderRequest.builder()
+ .data(BasicAuthTokenProviderRequestBody.builder()
+ .id(callbackId)
+ .basicAuthTokenManagerId(authProviderId)
+ .build())
+ .build();
+
+ var callbackStage = dispatchTestkitCallback(testkitState, callback);
+ BasicAuthTokenProviderCompleted resolutionCompleted;
+ try {
+ resolutionCompleted = (BasicAuthTokenProviderCompleted)
+ callbackStage.toCompletableFuture().get();
+ } catch (Exception e) {
+ throw new RuntimeException("Unexpected failure during Testkit callback", e);
+ }
+
+ var authToken =
+ AuthTokenUtil.parseAuthToken(resolutionCompleted.getData().getAuth());
+ return CompletableFuture.completedFuture(authToken.expiringAt(Long.MAX_VALUE));
+ }
+
+ private CompletionStage dispatchTestkitCallback(
+ TestkitState testkitState, TestkitCallback response) {
+ var future = new CompletableFuture();
+ testkitState.getCallbackIdToFuture().put(response.getCallbackId(), future);
+ testkitState.getResponseWriter().accept(response);
+ return future;
+ }
+ }
+
+ @Setter
+ @Getter
+ public static class NewBasicAuthTokenManagerBody {}
+}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/NewExpirationBasedAuthTokenManager.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewBearerAuthTokenManager.java
similarity index 70%
rename from testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/NewExpirationBasedAuthTokenManager.java
rename to testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewBearerAuthTokenManager.java
index 96e7b20d9b..c93dbcfe84 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/NewExpirationBasedAuthTokenManager.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewBearerAuthTokenManager.java
@@ -16,8 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package neo4j.org.testkit.backend.messages.responses;
+package neo4j.org.testkit.backend.messages.requests;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Supplier;
@@ -26,17 +27,19 @@
import neo4j.org.testkit.backend.AuthTokenUtil;
import neo4j.org.testkit.backend.TestkitClock;
import neo4j.org.testkit.backend.TestkitState;
-import neo4j.org.testkit.backend.messages.requests.AbstractBasicTestkitRequest;
-import neo4j.org.testkit.backend.messages.requests.ExpirationBasedAuthTokenProviderCompleted;
-import neo4j.org.testkit.backend.messages.requests.ExpirationBasedAuthTokenProviderRequest;
-import neo4j.org.testkit.backend.messages.requests.TestkitCallbackResult;
+import neo4j.org.testkit.backend.messages.responses.BearerAuthTokenManager;
+import neo4j.org.testkit.backend.messages.responses.BearerAuthTokenProviderRequest;
+import neo4j.org.testkit.backend.messages.responses.TestkitCallback;
+import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
import org.neo4j.driver.AuthTokenAndExpiration;
+import org.neo4j.driver.exceptions.AuthenticationException;
+import org.neo4j.driver.exceptions.TokenExpiredException;
import org.neo4j.driver.internal.security.ExpirationBasedAuthTokenManager;
@Setter
@Getter
-public class NewExpirationBasedAuthTokenManager extends AbstractBasicTestkitRequest {
- private NewTemporalAuthTokenManagerBody data;
+public class NewBearerAuthTokenManager extends AbstractBasicTestkitRequest {
+ private NewBearerAuthTokenManagerBody data;
@Override
protected TestkitResponse processAndCreateResponse(TestkitState testkitState) {
@@ -44,10 +47,11 @@ protected TestkitResponse processAndCreateResponse(TestkitState testkitState) {
testkitState.addAuthProvider(
id,
new ExpirationBasedAuthTokenManager(
- new TestkitAuthTokenProvider(id, testkitState), TestkitClock.INSTANCE));
- return neo4j.org.testkit.backend.messages.responses.ExpirationBasedAuthTokenManager.builder()
- .data(neo4j.org.testkit.backend.messages.responses.ExpirationBasedAuthTokenManager
- .ExpirationBasedTokenManagerBody.builder()
+ new TestkitAuthTokenProvider(id, testkitState),
+ Set.of(TokenExpiredException.class, AuthenticationException.class),
+ TestkitClock.INSTANCE));
+ return BearerAuthTokenManager.builder()
+ .data(BearerAuthTokenManager.BearerAuthTokenManagerBody.builder()
.id(id)
.build())
.build();
@@ -59,17 +63,17 @@ private record TestkitAuthTokenProvider(String authProviderId, TestkitState test
public CompletionStage get() {
var callbackId = testkitState.newId();
- var callback = ExpirationBasedAuthTokenProviderRequest.builder()
- .data(ExpirationBasedAuthTokenProviderRequest.ExpirationBasedAuthTokenProviderRequestBody.builder()
+ var callback = BearerAuthTokenProviderRequest.builder()
+ .data(BearerAuthTokenProviderRequest.BearerAuthTokenProviderRequestBody.builder()
.id(callbackId)
- .expirationBasedAuthTokenManagerId(authProviderId)
+ .bearerAuthTokenManagerId(authProviderId)
.build())
.build();
var callbackStage = dispatchTestkitCallback(testkitState, callback);
- ExpirationBasedAuthTokenProviderCompleted resolutionCompleted;
+ BearerAuthTokenProviderCompleted resolutionCompleted;
try {
- resolutionCompleted = (ExpirationBasedAuthTokenProviderCompleted)
+ resolutionCompleted = (BearerAuthTokenProviderCompleted)
callbackStage.toCompletableFuture().get();
} catch (Exception e) {
throw new RuntimeException("Unexpected failure during Testkit callback", e);
@@ -94,5 +98,5 @@ private CompletionStage dispatchTestkitCallback(
@Setter
@Getter
- public static class NewTemporalAuthTokenManagerBody {}
+ public static class NewBearerAuthTokenManagerBody {}
}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java
index 3e210f55da..9860102cc8 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java
@@ -159,6 +159,8 @@ public class StartTest implements TestkitRequest {
"^.*\\.TestTxRun\\.test_should_prevent_run_after_tx_termination_on_run$", skipMessage);
REACTIVE_LEGACY_SKIP_PATTERN_TO_REASON.put(
"^.*\\.TestTxRun\\.test_should_prevent_run_after_tx_termination_on_pull$", skipMessage);
+ REACTIVE_LEGACY_SKIP_PATTERN_TO_REASON.put(
+ "^.*\\.TestAuthTokenManager[^.]+\\.test_error_on_run_using_tx_run$", skipMessage);
skipMessage = "Does not support multiple concurrent result streams on session level";
REACTIVE_LEGACY_SKIP_PATTERN_TO_REASON.put("^.*\\.TestSessionRun\\.test_iteration_nested$", skipMessage);
REACTIVE_LEGACY_SKIP_PATTERN_TO_REASON.put("^.*\\.TestSessionRun\\.test_partial_iteration$", skipMessage);
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java
index c8e3237297..61458567e6 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java
@@ -22,7 +22,6 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.util.concurrent.CompletionStage;
import neo4j.org.testkit.backend.TestkitState;
-import neo4j.org.testkit.backend.messages.responses.NewExpirationBasedAuthTokenManager;
import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
import reactor.core.publisher.Mono;
@@ -65,10 +64,12 @@
@JsonSubTypes.Type(BookmarkManagerClose.class),
@JsonSubTypes.Type(ExecuteQuery.class),
@JsonSubTypes.Type(NewAuthTokenManager.class),
- @JsonSubTypes.Type(NewExpirationBasedAuthTokenManager.class),
+ @JsonSubTypes.Type(NewBearerAuthTokenManager.class),
+ @JsonSubTypes.Type(NewBasicAuthTokenManager.class),
@JsonSubTypes.Type(AuthTokenManagerGetAuthCompleted.class),
- @JsonSubTypes.Type(ExpirationBasedAuthTokenProviderCompleted.class),
- @JsonSubTypes.Type(AuthTokenManagerOnAuthExpiredCompleted.class),
+ @JsonSubTypes.Type(BearerAuthTokenProviderCompleted.class),
+ @JsonSubTypes.Type(BasicAuthTokenProviderCompleted.class),
+ @JsonSubTypes.Type(AuthTokenManagerHandleSecurityExceptionCompleted.class),
@JsonSubTypes.Type(AuthTokenManagerClose.class),
@JsonSubTypes.Type(FakeTimeInstall.class),
@JsonSubTypes.Type(FakeTimeTick.class),
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/AuthTokenManagerOnAuthExpiredRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/AuthTokenManagerHandleSecurityExceptionRequest.java
similarity index 81%
rename from testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/AuthTokenManagerOnAuthExpiredRequest.java
rename to testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/AuthTokenManagerHandleSecurityExceptionRequest.java
index 6db6546d00..41421b84ef 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/AuthTokenManagerOnAuthExpiredRequest.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/AuthTokenManagerHandleSecurityExceptionRequest.java
@@ -24,8 +24,8 @@
@Getter
@Builder
-public class AuthTokenManagerOnAuthExpiredRequest implements TestkitCallback {
- private AuthTokenManagerOnAuthExpiredRequestBody data;
+public class AuthTokenManagerHandleSecurityExceptionRequest implements TestkitCallback {
+ private AuthTokenManagerHandleSecurityExceptionRequestBody data;
@Override
public String testkitName() {
@@ -39,9 +39,10 @@ public String getCallbackId() {
@Getter
@Builder
- public static class AuthTokenManagerOnAuthExpiredRequestBody {
+ public static class AuthTokenManagerHandleSecurityExceptionRequestBody {
private String id;
private String authTokenManagerId;
private AuthorizationToken auth;
+ private String errorCode;
}
}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ExpirationBasedAuthTokenManager.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BasicAuthTokenManager.java
similarity index 79%
rename from testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ExpirationBasedAuthTokenManager.java
rename to testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BasicAuthTokenManager.java
index e756b2cb1c..896d87cac7 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ExpirationBasedAuthTokenManager.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BasicAuthTokenManager.java
@@ -23,17 +23,17 @@
@Getter
@Builder
-public class ExpirationBasedAuthTokenManager implements TestkitResponse {
- private ExpirationBasedTokenManagerBody data;
+public class BasicAuthTokenManager implements TestkitResponse {
+ private BasicAuthTokenManagerBody data;
@Override
public String testkitName() {
- return "ExpirationBasedAuthTokenManager";
+ return "BasicAuthTokenManager";
}
@Getter
@Builder
- public static class ExpirationBasedTokenManagerBody {
+ public static class BasicAuthTokenManagerBody {
private String id;
}
}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExpirationBasedAuthTokenProviderRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BasicAuthTokenProviderRequest.java
similarity index 67%
rename from testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExpirationBasedAuthTokenProviderRequest.java
rename to testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BasicAuthTokenProviderRequest.java
index 03f383608d..0ba35f39b6 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExpirationBasedAuthTokenProviderRequest.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BasicAuthTokenProviderRequest.java
@@ -16,16 +16,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package neo4j.org.testkit.backend.messages.requests;
+package neo4j.org.testkit.backend.messages.responses;
import lombok.Builder;
import lombok.Getter;
-import neo4j.org.testkit.backend.messages.responses.TestkitCallback;
@Getter
@Builder
-public class ExpirationBasedAuthTokenProviderRequest implements TestkitCallback {
- private ExpirationBasedAuthTokenProviderRequestBody data;
+public class BasicAuthTokenProviderRequest implements TestkitCallback {
+ private BasicAuthTokenProviderRequestBody data;
@Override
public String getCallbackId() {
@@ -34,13 +33,13 @@ public String getCallbackId() {
@Override
public String testkitName() {
- return "ExpirationBasedAuthTokenProviderRequest";
+ return "BasicAuthTokenProviderRequest";
}
@Getter
@Builder
- public static class ExpirationBasedAuthTokenProviderRequestBody {
+ public static class BasicAuthTokenProviderRequestBody {
private String id;
- private String expirationBasedAuthTokenManagerId;
+ private String basicAuthTokenManagerId;
}
}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BearerAuthTokenManager.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BearerAuthTokenManager.java
new file mode 100644
index 0000000000..09e2453a2a
--- /dev/null
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BearerAuthTokenManager.java
@@ -0,0 +1,39 @@
+/*
+ * 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 neo4j.org.testkit.backend.messages.responses;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class BearerAuthTokenManager implements TestkitResponse {
+ private BearerAuthTokenManagerBody data;
+
+ @Override
+ public String testkitName() {
+ return "BearerAuthTokenManager";
+ }
+
+ @Getter
+ @Builder
+ public static class BearerAuthTokenManagerBody {
+ private String id;
+ }
+}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BearerAuthTokenProviderRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BearerAuthTokenProviderRequest.java
new file mode 100644
index 0000000000..35fcda1e65
--- /dev/null
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/BearerAuthTokenProviderRequest.java
@@ -0,0 +1,45 @@
+/*
+ * 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 neo4j.org.testkit.backend.messages.responses;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class BearerAuthTokenProviderRequest implements TestkitCallback {
+ private BearerAuthTokenProviderRequestBody data;
+
+ @Override
+ public String getCallbackId() {
+ return data.getId();
+ }
+
+ @Override
+ public String testkitName() {
+ return "BearerAuthTokenProviderRequest";
+ }
+
+ @Getter
+ @Builder
+ public static class BearerAuthTokenProviderRequestBody {
+ private String id;
+ private String bearerAuthTokenManagerId;
+ }
+}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java
index 6883ef4c9d..4e7d52e066 100644
--- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java
@@ -41,5 +41,7 @@ public static class DriverErrorBody {
private String code;
private String msg;
+
+ private boolean retryable;
}
}