This is an automated email from the ASF dual-hosted git repository.

frankgh pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git


The following commit(s) were added to refs/heads/trunk by this push:
     new e1fe8243 CASSSIDECAR-367: CachedAuthorizationHandler should pause and 
resume request while performing authZ operations (#295)
e1fe8243 is described below

commit e1fe82439d9994e5ae09a300a539ce656d68f9d3
Author: Francisco Guerrero <[email protected]>
AuthorDate: Mon Dec 8 16:58:28 2025 -0800

    CASSSIDECAR-367: CachedAuthorizationHandler should pause and resume request 
while performing authZ operations (#295)
    
    Patch by Francisco Guerrero; reviewed by Yifan Cai for CASSSIDECAR-367
---
 CHANGES.txt                                        |   1 +
 .../RoleBasedAuthorizationIntegrationTest.java     |   2 +-
 .../routes/InvalidateCacheIntegrationTest.java     |  12 +-
 .../authorization/CachedAuthorizationHandler.java  | 206 ++++++++++++++-------
 .../RoleBasedAuthorizationProvider.java            |   2 +-
 .../sidecar/config/SidecarConfiguration.java       |   1 +
 .../sidecar/handlers/InvalidateCacheHandler.java   |  10 +-
 .../sidecar/logging/SidecarLoggerHandler.java      |   1 +
 .../cassandra/sidecar/routes/RouteBuilder.java     |  61 +++---
 .../cassandra/sidecar/utils/CacheFactory.java      |  12 +-
 .../CachedAuthorizationHandlerTest.java            |  68 ++-----
 .../handlers/InvalidateCacheHandlerTest.java       |  33 ++--
 .../cassandra/sidecar/utils/CacheFactoryTest.java  |  10 +-
 .../io/vertx/ext/auth/mtls/impl/MutualTlsUser.java |   8 +
 .../ext/auth/mtls/impl/MutualTlsUserTest.java      |  11 +-
 15 files changed, 257 insertions(+), 181 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 83f6ae67..dc96e0fb 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 0.3.0
 -----
+ * CachedAuthorizationHandler should pause and resume request while performing 
authZ operations (CASSSIDECAR-367)
  * Added system disk information endpoint to Cassandra Sidecar 
(CASSSIDECAR-366)
  * Adding support for quoted tables and keyspaces in snapshot cleanup 
(CASSSIDECAR-388)
  * File descriptor leak after file streamed in Sidecar Client (CASSSIDECAR-386)
diff --git 
a/integration-tests/src/integrationTest/org/apache/cassandra/sidecar/acl/authorization/RoleBasedAuthorizationIntegrationTest.java
 
b/integration-tests/src/integrationTest/org/apache/cassandra/sidecar/acl/authorization/RoleBasedAuthorizationIntegrationTest.java
index b0b1f99c..6c43cf4f 100644
--- 
a/integration-tests/src/integrationTest/org/apache/cassandra/sidecar/acl/authorization/RoleBasedAuthorizationIntegrationTest.java
+++ 
b/integration-tests/src/integrationTest/org/apache/cassandra/sidecar/acl/authorization/RoleBasedAuthorizationIntegrationTest.java
@@ -909,7 +909,7 @@ class RoleBasedAuthorizationIntegrationTest extends 
SharedClusterSidecarIntegrat
     private void invalidateAuthorizationHandlerCaches()
     {
         CacheFactory factory = 
serverWrapper.injector.getInstance(CacheFactory.class);
-        AsyncCache<AuthorizationCacheKey, Boolean> authorizationCache = 
factory.endpointAuthorizationCache();
+        AsyncCache<AuthorizationCacheKey, Future<Boolean>> authorizationCache 
= factory.endpointAuthorizationCache();
         authorizationCache.synchronous().invalidateAll();
     }
 
diff --git 
a/integration-tests/src/integrationTest/org/apache/cassandra/sidecar/routes/InvalidateCacheIntegrationTest.java
 
b/integration-tests/src/integrationTest/org/apache/cassandra/sidecar/routes/InvalidateCacheIntegrationTest.java
index 3b5e6fa0..3ef21325 100644
--- 
a/integration-tests/src/integrationTest/org/apache/cassandra/sidecar/routes/InvalidateCacheIntegrationTest.java
+++ 
b/integration-tests/src/integrationTest/org/apache/cassandra/sidecar/routes/InvalidateCacheIntegrationTest.java
@@ -34,6 +34,7 @@ import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.Future;
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.http.HttpMethod;
 import io.vertx.ext.web.client.HttpResponse;
@@ -287,7 +288,7 @@ class InvalidateCacheIntegrationTest extends 
SharedClusterSidecarIntegrationTest
         // Invalidate cache and verify its empty
         String invalidateCacheRoute = 
String.format(CACHE_INVALIDATE_ROUTE_TEMPLATE, RoleAuthorizationsCache.NAME);
         verifyAccess(HttpMethod.DELETE, invalidateCacheRoute, 
superuserKeystorePath, assertStatus(HttpResponseStatus.OK));
-        loopAssert(3, () -> 
assertThat(roleAuthorizationsCache.getAll().isEmpty()));
+        loopAssert(3, () -> 
assertThat(roleAuthorizationsCache.getAll()).isEmpty());
 
         // Re-populate cache with test user and verify test user is back in 
cache
         verifyAccess(HttpMethod.GET, SCHEMA_ROUTE, testUserKeystorePath, 
assertStatus(HttpResponseStatus.OK));
@@ -347,19 +348,20 @@ class InvalidateCacheIntegrationTest extends 
SharedClusterSidecarIntegrationTest
     void testInvalidateEndpointAuthorizationCache()
     {
         CacheFactory cacheFactory = 
serverWrapper.injector.getInstance(CacheFactory.class);
-        AsyncCache<AuthorizationCacheKey, Boolean> endpointAuthorizationCache 
= cacheFactory.endpointAuthorizationCache();
+        AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
endpointAuthorizationCache = cacheFactory.endpointAuthorizationCache();
 
         // Clear the cache first to ensure clean state
         String clearCacheRoute = 
String.format(CACHE_INVALIDATE_ROUTE_TEMPLATE, 
CacheFactory.ENDPOINT_AUTHORIZATION_CACHE_NAME);
         verifyAccess(HttpMethod.DELETE, clearCacheRoute, 
superuserKeystorePath, assertStatus(HttpResponseStatus.OK));
+        loopAssert(3, () -> 
assertThat(endpointAuthorizationCache.synchronous().asMap()).isEmpty());
 
         verifyAccess(HttpMethod.GET, SCHEMA_ROUTE, testUserKeystorePath, 
assertStatus(HttpResponseStatus.OK));
-        loopAssert(3, () -> 
assertThat(endpointAuthorizationCache.synchronous().asMap().isEmpty()));
+        loopAssert(3, () -> 
assertThat(endpointAuthorizationCache.synchronous().asMap()).isNotEmpty());
 
         // Invalidate cache and verify
         String invalidateCacheRoute = 
String.format(CACHE_INVALIDATE_ROUTE_TEMPLATE, 
CacheFactory.ENDPOINT_AUTHORIZATION_CACHE_NAME);
         verifyAccess(HttpMethod.DELETE, invalidateCacheRoute, 
superuserKeystorePath, assertStatus(HttpResponseStatus.OK));
-        loopAssert(3, () -> 
assertThat(endpointAuthorizationCache.synchronous().asMap().isEmpty()));
+        loopAssert(3, () -> 
assertThat(endpointAuthorizationCache.synchronous().asMap()).isEmpty());
 
         // Re-populate cache with test user and verify test user is back in 
cache
         verifyAccess(HttpMethod.GET, SCHEMA_ROUTE, testUserKeystorePath, 
assertStatus(HttpResponseStatus.OK));
@@ -370,7 +372,7 @@ class InvalidateCacheIntegrationTest extends 
SharedClusterSidecarIntegrationTest
     void testInvalidateEndpointAuthorizationCacheWithKeys()
     {
         CacheFactory cacheFactory = 
serverWrapper.injector.getInstance(CacheFactory.class);
-        AsyncCache<AuthorizationCacheKey, Boolean> endpointAuthorizationCache 
= cacheFactory.endpointAuthorizationCache();
+        AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
endpointAuthorizationCache = cacheFactory.endpointAuthorizationCache();
 
         
verifyKeyBasedInvalidationNotSupported(CacheFactory.ENDPOINT_AUTHORIZATION_CACHE_NAME,
                                               () -> 
endpointAuthorizationCache.synchronous().asMap(),
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/CachedAuthorizationHandler.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/CachedAuthorizationHandler.java
index b61ff280..7ab115ec 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/CachedAuthorizationHandler.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/CachedAuthorizationHandler.java
@@ -18,30 +18,33 @@
 
 package org.apache.cassandra.sidecar.acl.authorization;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.CompletableFuture;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiConsumer;
 
-import com.google.common.annotations.VisibleForTesting;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.github.benmanes.caffeine.cache.AsyncCache;
 import io.vertx.core.Future;
+import io.vertx.core.Promise;
 import io.vertx.ext.auth.User;
 import io.vertx.ext.auth.authorization.Authorization;
 import io.vertx.ext.auth.authorization.AuthorizationContext;
+import io.vertx.ext.auth.authorization.AuthorizationProvider;
 import io.vertx.ext.web.RoutingContext;
 import io.vertx.ext.web.handler.AuthorizationHandler;
 import io.vertx.ext.web.handler.HttpException;
-import io.vertx.ext.web.handler.impl.AuthorizationHandlerImpl;
 import org.apache.cassandra.sidecar.acl.AdminIdentityResolver;
 import org.apache.cassandra.sidecar.config.AccessControlConfiguration;
 import org.apache.cassandra.sidecar.metrics.SidecarMetrics;
 import org.apache.cassandra.sidecar.metrics.server.AuthMetrics;
+import org.jetbrains.annotations.VisibleForTesting;
 
 import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
 import static io.vertx.core.Future.fromCompletionStage;
@@ -50,150 +53,211 @@ import static 
org.apache.cassandra.sidecar.utils.AuthUtils.extractIdentities;
 /**
  * {@link CachedAuthorizationHandler} caches all authorization requests using 
{@link AuthorizationCacheKey}.
  */
-public class CachedAuthorizationHandler extends AuthorizationHandlerImpl
+public class CachedAuthorizationHandler implements AuthorizationHandler
 {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(CachedAuthorizationHandler.class);
 
     // uniquely identities CachedAuthorizationHandler across different routes. 
Having same handlerId can lead
     // to permission bypass across routes.
     private static final AtomicInteger HANDLER_ID_GEN = new AtomicInteger(0);
-    private static final HttpException FORBIDDEN_EXCEPTION = new 
HttpException(403);
     private final int handlerId;
     private final AccessControlConfiguration accessControlConfiguration;
-    private final AuthorizationParameterValidateHandler 
authZParameterValidateHandler;
     private final AdminIdentityResolver adminIdentityResolver;
     private final AuthMetrics authMetrics;
-    private final AsyncCache<AuthorizationCacheKey, Boolean> 
authorizationCache;
+    private final AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
authorizationCache;
 
-    // This is overridden since Vert.x does not expose this
-    private BiConsumer<RoutingContext, AuthorizationContext> variableHandler;
+    protected final Authorization authorization;
+    protected final Collection<AuthorizationProvider> authorizationProviders;
+    protected BiConsumer<RoutingContext, AuthorizationContext> variableHandler;
 
     public CachedAuthorizationHandler(AccessControlConfiguration 
accessControlConfiguration,
-                                      AuthorizationParameterValidateHandler 
authZParameterValidateHandler,
                                       AdminIdentityResolver 
adminIdentityResolver,
                                       Authorization authorization,
                                       SidecarMetrics sidecarMetrics,
-                                      AsyncCache<AuthorizationCacheKey, 
Boolean> authorizationCache)
+                                      AsyncCache<AuthorizationCacheKey, 
Future<Boolean>> authorizationCache)
     {
-        this(HANDLER_ID_GEN.getAndIncrement(), accessControlConfiguration, 
authZParameterValidateHandler,
-             adminIdentityResolver, authorization, sidecarMetrics, 
authorizationCache);
+        this(HANDLER_ID_GEN.getAndIncrement(), accessControlConfiguration, 
adminIdentityResolver,
+             authorization, sidecarMetrics, authorizationCache);
     }
 
     @VisibleForTesting
     public CachedAuthorizationHandler(int handlerId,
                                       AccessControlConfiguration 
accessControlConfiguration,
-                                      AuthorizationParameterValidateHandler 
authZParameterValidateHandler,
                                       AdminIdentityResolver 
adminIdentityResolver,
                                       Authorization authorization,
                                       SidecarMetrics sidecarMetrics,
-                                      AsyncCache<AuthorizationCacheKey, 
Boolean> authorizationCache)
+                                      AsyncCache<AuthorizationCacheKey, 
Future<Boolean>> authorizationCache)
     {
-        super(authorization);
+        this.authorization = Objects.requireNonNull(authorization, 
"authorization cannot be null");
+        this.authorizationProviders = new ArrayList<>();
         this.handlerId = handlerId;
         this.accessControlConfiguration = accessControlConfiguration;
-        this.authZParameterValidateHandler = authZParameterValidateHandler;
         this.adminIdentityResolver = adminIdentityResolver;
         this.authMetrics = sidecarMetrics.server().auth();
         this.authorizationCache = authorizationCache;
     }
 
     @Override
-    public void handle(RoutingContext ctx)
+    public void handle(RoutingContext context)
     {
         long startTimeNanos = System.nanoTime();
-        authZParameterValidateHandler.handle(ctx);
-        if (ctx.failed()) // failed due to validation
+        User user = context.user();
+        if (user == null)
         {
+            context.fail(FORBIDDEN.code(), new 
HttpException(FORBIDDEN.code()));
             return;
         }
 
-        AtomicBoolean ctxNextCalled = new AtomicBoolean(false);
-        Future<Boolean> authorizationFuture
-        = fromCompletionStage(checkAuthorization(ctx, ctxNextCalled, 
startTimeNanos));
+        try
+        {
+            // this handler can perform asynchronous operations
+            if (!context.request().isEnded())
+            {
+                context.request().pause();
+            }
 
-        authorizationFuture
-        .onSuccess(authorized -> {
-            // We avoid calling ctx.next() and ctx.fail() when it is already 
done during cache value computation
-            if (Boolean.TRUE.equals(authorized))
+            // create the authorization context
+            AuthorizationContext authorizationContext = 
AuthorizationContext.create(user);
+            if (variableHandler != null)
             {
-                if (!ctxNextCalled.get())
+                variableHandler.accept(context, authorizationContext);
+            }
+
+            checkAuthorization(authorizationContext, startTimeNanos)
+            .onSuccess(ignored -> {
+                if (!context.request().isEnded())
                 {
-                    ctx.next();
+                    context.request().resume();
                 }
-            }
-            else
-            {
-                if (!ctx.failed())
+                context.next();
+            })
+            .onFailure(cause -> {
+                LOGGER.error("Authorization failed for user='{}'", user, 
cause);
+                // resume as the error handler may allow this request to 
become valid again
+                if (!context.request().isEnded())
                 {
-                    ctx.fail(FORBIDDEN.code(), FORBIDDEN_EXCEPTION);
+                    context.request().resume();
                 }
-            }
-        })
-        .onFailure(cause -> {
-            LOGGER.error("Error encountered during authorization cache 
computation", cause);
-            if (!ctx.failed())
+                context.fail(FORBIDDEN.code(), cause);
+            });
+        }
+        catch (Throwable e)
+        {
+            // resume as the error handler may allow this request to become 
valid again
+            if (!context.request().isEnded())
             {
-                ctx.fail(FORBIDDEN.code(), FORBIDDEN_EXCEPTION);
+                context.request().resume();
             }
-        });
+            context.fail(e);
+        }
     }
 
     @Override
     public AuthorizationHandler variableConsumer(BiConsumer<RoutingContext, 
AuthorizationContext> handler)
     {
-        this.variableHandler = handler;
-        super.variableConsumer(handler);
+        variableHandler = handler;
         return this;
     }
 
-    private CompletableFuture<Boolean> checkAuthorization(RoutingContext ctx, 
AtomicBoolean ctxNextCalled,
-                                                          long startTimeNanos)
+    @Override
+    public AuthorizationHandler addAuthorizationProvider(AuthorizationProvider 
authorizationProvider)
     {
-        if 
(!this.accessControlConfiguration.permissionCacheConfiguration().enabled())
-        {
-            // We perform authorization checks everytime if caching is disabled
-            return CompletableFuture.completedFuture(isUserAuthorized(ctx, 
ctxNextCalled, startTimeNanos));
-        }
-
-        AuthorizationCacheKey key = createAuthorizationKey(ctx);
-        return authorizationCache.get(key, k -> isUserAuthorized(ctx, 
ctxNextCalled, startTimeNanos));
+        
authorizationProviders.add(Objects.requireNonNull(authorizationProvider,
+                                                          
"authorizationProvider cannot be null"));
+        return this;
     }
 
-    private AuthorizationCacheKey createAuthorizationKey(RoutingContext ctx)
+    protected Future<Boolean> checkAuthorization(AuthorizationContext 
authorizationContext, long startTimeNanos)
     {
-        User user = ctx.user();
-        AuthorizationContext authorizationContext = 
AuthorizationContext.create(user);
-        if (this.variableHandler != null)
+        if 
(!accessControlConfiguration.permissionCacheConfiguration().enabled())
         {
-            this.variableHandler.accept(ctx, authorizationContext);
+            // We perform authorization checks everytime if caching is disabled
+            return authorizeUser(authorizationContext, startTimeNanos);
         }
-        return AuthorizationCacheKey.create(handlerId, authorizationContext);
+
+        AuthorizationCacheKey key = AuthorizationCacheKey.create(handlerId, 
authorizationContext);
+        return fromCompletionStage(authorizationCache.get(key, k -> 
authorizeUser(authorizationContext, startTimeNanos)))
+               .compose(future -> future);
     }
 
-    private boolean isUserAuthorized(RoutingContext ctx, AtomicBoolean 
ctxNextCalled, long startTimeNanos)
+    protected Future<Boolean> authorizeUser(AuthorizationContext 
authorizationContext, long startTimeNanos)
     {
-        User user = ctx.user();
-        List<String> identities = extractIdentities(user);
+        List<String> identities = 
extractIdentities(authorizationContext.user());
 
         // Admin identities bypass route specific authorization checks
         if (isAdmin(identities))
         {
-            return true;
+            return Future.succeededFuture(true);
         }
 
-        super.handle(ctx);
-        if (!ctx.failed())
-        {
-            ctxNextCalled.set(true);
+        Promise<Boolean> promise = Promise.promise();
+        // check or fetch authorizations
+        checkOrFetchAuthorizations(promise, authorizationContext, 
authorizationProviders.iterator());
+
+        Future<Boolean> future = promise.future();
+        future.onSuccess(ignored -> {
             long durationNanos = System.nanoTime() - startTimeNanos;
             // authorization time recorded here is only taking into account 
authorizations that are not cached
+            // and only successful authorizations are recorded
             authMetrics.authorizationTime.metric.update(durationNanos, 
TimeUnit.NANOSECONDS);
-            return true;
+        });
+        return future;
+    }
+
+    /**
+     * this method checks that the specified authorization match the current 
content.
+     * It doesn't fetch all providers at once in order to do early-out, but 
rather tries to be smart and fetch authorizations one provider at a time
+     *
+     * @param promise              the promise
+     * @param authorizationContext the current authorization context
+     * @param providers            the providers iterator
+     */
+    protected void checkOrFetchAuthorizations(Promise<Boolean> promise, 
AuthorizationContext authorizationContext, Iterator<AuthorizationProvider> 
providers)
+    {
+        if (authorization.match(authorizationContext))
+        {
+            promise.complete(true); // Authorization succeeds
+            return;
         }
-        return false;
+
+        User user = authorizationContext.user();
+
+        // there was no match, in this case we do the following:
+        // 1) contact the next provider we haven't contacted yet
+        // 2) if there is a match, get out right away otherwise repeat 1)
+        while (providers.hasNext())
+        {
+            AuthorizationProvider provider = providers.next();
+            // we haven't fetched authorization from this provider yet
+            if 
(!user.authorizations().getProviderIds().contains(provider.getId()))
+            {
+                try
+                {
+                    provider.getAuthorizations(user, authorizationResult -> {
+                        if (authorizationResult.failed())
+                        {
+                            LOGGER.warn("An error occurred getting 
authorization - providerId='{}'", provider.getId(), 
authorizationResult.cause());
+                            // note that we don't 'record' the fact that we 
tried to fetch the authorization provider. therefore, it will be re-fetched 
later-on
+                        }
+                        checkOrFetchAuthorizations(promise, 
authorizationContext, providers);
+                    });
+                }
+                catch (Exception e)
+                {
+                    // if getAuthorizations throws we want to handle this case
+                    // otherwise the promise will be lost
+                    LOGGER.error("Exception thrown while retrieving 
authorizations - providerId='{}'", provider.getId(), e);
+                    checkOrFetchAuthorizations(promise, authorizationContext, 
providers);
+                }
+                // get out right now as the callback will decide what to do 
next
+                return;
+            }
+        }
+        // exhausted all authorization providers
+        promise.fail(new HttpException(FORBIDDEN.code()));
     }
 
-    private boolean isAdmin(List<String> identities)
+    protected boolean isAdmin(List<String> identities)
     {
         for (String identity : identities)
         {
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/RoleBasedAuthorizationProvider.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/RoleBasedAuthorizationProvider.java
index d4d665ad..4628fd68 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/RoleBasedAuthorizationProvider.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/RoleBasedAuthorizationProvider.java
@@ -73,11 +73,11 @@ public class RoleBasedAuthorizationProvider implements 
AuthorizationProvider
                 continue;
             }
 
-            String authorizationId = getId();
             Set<Authorization> authorizations = 
roleAuthorizationsCache.getAuthorizations(role);
             // when entries in cache are not found, null is returned. We can 
not add null in user.authorizations()
             if (authorizations != null)
             {
+                String authorizationId = getId();
                 user.authorizations().add(authorizationId, authorizations);
             }
         }
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/config/SidecarConfiguration.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/config/SidecarConfiguration.java
index 917c3b55..c2355a91 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/config/SidecarConfiguration.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/config/SidecarConfiguration.java
@@ -32,6 +32,7 @@ public interface SidecarConfiguration
      * @return a single configured cassandra instance
      * @deprecated in favor of configuring multiple instances in the yaml 
under cassandra_instances
      */
+    @Deprecated
     InstanceConfiguration cassandra();
 
     /**
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/InvalidateCacheHandler.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/InvalidateCacheHandler.java
index aa71ee5d..5efb14d7 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/InvalidateCacheHandler.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/InvalidateCacheHandler.java
@@ -25,6 +25,7 @@ import com.github.benmanes.caffeine.cache.AsyncCache;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.Future;
 import io.vertx.core.http.HttpServerRequest;
 import io.vertx.core.net.SocketAddress;
 import io.vertx.ext.auth.authorization.Authorization;
@@ -62,7 +63,7 @@ public class InvalidateCacheHandler extends 
AbstractHandler<InvalidateCacheHandl
     private final IdentityToRoleCache identityToRoleCache;
     private final RoleAuthorizationsCache roleAuthorizationsCache;
     private final SuperUserCache superUserCache;
-    private final AsyncCache<AuthorizationCacheKey, Boolean> 
endpointAuthorizationCache;
+    private final AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
endpointAuthorizationCache;
 
     @Inject
     public InvalidateCacheHandler(InstanceMetadataFetcher metadataFetcher,
@@ -123,13 +124,13 @@ public class InvalidateCacheHandler extends 
AbstractHandler<InvalidateCacheHandl
                 if (!invalidateAll)
                 {
                     throw wrapHttpException(HttpResponseStatus.BAD_REQUEST,
-                                                   
"endpoint_authorization_cache does not support selective key invalidation");
+                                            "endpoint_authorization_cache does 
not support selective key invalidation");
                 }
                 endpointAuthorizationCache.synchronous().invalidateAll();
                 break;
             default:
                 throw wrapHttpException(HttpResponseStatus.NOT_FOUND,
-                                               "Unknown cache: " + cacheName);
+                                        "Unknown cache: " + cacheName);
         }
 
         logger.info("Cache {} invalidated successfully. Keys: {}", cacheName,
@@ -143,7 +144,7 @@ public class InvalidateCacheHandler extends 
AbstractHandler<InvalidateCacheHandl
      * @param cache                         AuthCache to invalidate
      * @param params                        params containing cache name and 
keys
      * @param supportsSelectiveInvalidation whether cache supports selective 
key invalidation
-     * @param <V> the cache value type
+     * @param <V>                           the cache value type
      */
     private <V> void invalidateAuthCache(AuthCache<String, V> cache, Params 
params, boolean supportsSelectiveInvalidation)
     {
@@ -182,5 +183,4 @@ public class InvalidateCacheHandler extends 
AbstractHandler<InvalidateCacheHandl
             return keys == null || keys.isEmpty();
         }
     }
-
 }
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/logging/SidecarLoggerHandler.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/logging/SidecarLoggerHandler.java
index ef0fe726..83358a38 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/logging/SidecarLoggerHandler.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/logging/SidecarLoggerHandler.java
@@ -79,6 +79,7 @@ public class SidecarLoggerHandler implements LoggerHandler
      * @deprecated Superseded by {@link #customFormatter(LoggerFormatter)}
      */
     @Override
+    @Deprecated
     public LoggerHandler customFormatter(Function<HttpServerRequest, String> 
formatter)
     {
         return loggerHandler.customFormatter(formatter);
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/routes/RouteBuilder.java 
b/server/src/main/java/org/apache/cassandra/sidecar/routes/RouteBuilder.java
index cd25cfe6..3b2b31f0 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/routes/RouteBuilder.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/routes/RouteBuilder.java
@@ -29,6 +29,7 @@ import java.util.stream.Collectors;
 import com.google.common.annotations.VisibleForTesting;
 
 import com.github.benmanes.caffeine.cache.AsyncCache;
+import io.vertx.core.Future;
 import io.vertx.core.Handler;
 import io.vertx.core.http.HttpMethod;
 import io.vertx.ext.auth.authorization.AndAuthorization;
@@ -40,6 +41,7 @@ import io.vertx.ext.web.Router;
 import io.vertx.ext.web.RoutingContext;
 import io.vertx.ext.web.handler.AuthorizationHandler;
 import io.vertx.ext.web.handler.BodyHandler;
+import io.vertx.ext.web.handler.InputTrustHandler;
 import org.apache.cassandra.sidecar.acl.AdminIdentityResolver;
 import org.apache.cassandra.sidecar.acl.authorization.AuthorizationCacheKey;
 import 
org.apache.cassandra.sidecar.acl.authorization.AuthorizationParameterValidateHandler;
@@ -60,12 +62,13 @@ import static 
org.apache.cassandra.sidecar.routes.RoutingContextUtils.SC_QUALIFI
  */
 public class RouteBuilder
 {
+    private static final RouteGenericVariableConsumer 
ROUTE_GENERIC_VARIABLE_CONSUMER = new RouteGenericVariableConsumer();
     private final AccessControlConfiguration accessControlConfiguration;
     private final AuthorizationProvider authorizationProvider;
     private final AdminIdentityResolver adminIdentityResolver;
     private final AuthorizationParameterValidateHandler 
authZParameterValidateHandler;
     private final SidecarMetrics sidecarMetrics;
-    private final AsyncCache<AuthorizationCacheKey, Boolean> 
authorizationCache;
+    private final AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
authorizationCache;
     private boolean setBodyHandler;
     private boolean accessProtected = true;
     private final List<Handler<RoutingContext>> handlers = new ArrayList<>();
@@ -107,6 +110,7 @@ public class RouteBuilder
     /**
      * Indicate the route does not require authorization.
      * <p>Note that the method is to be accessed by {@link Factory} only
+     *
      * @return a reference to {@link RouteBuilder} for chaining
      */
     RouteBuilder setUnauthorized()
@@ -144,14 +148,16 @@ public class RouteBuilder
                 if (accessControlConfiguration.enabled())
                 {
                     // authorization handler added before route specific 
handler chain
-                    AuthorizationHandler authorizationHandler =
-                    new CachedAuthorizationHandler(accessControlConfiguration, 
authZParameterValidateHandler,
-                                                   adminIdentityResolver, 
requiredAuthorization(), sidecarMetrics,
-                                                   authorizationCache);
+                    AuthorizationHandler authorizationHandler = new 
CachedAuthorizationHandler(accessControlConfiguration,
+                                                                               
                adminIdentityResolver,
+                                                                               
                requiredAuthorization(),
+                                                                               
                sidecarMetrics,
+                                                                               
                authorizationCache);
                     
authorizationHandler.addAuthorizationProvider(authorizationProvider);
                     
authorizationHandler.variableConsumer(routeGenericVariableConsumer());
 
-                    route.handler(authorizationHandler);
+                    
route.handler(wrapInputTrustHandler(authZParameterValidateHandler))
+                         .handler(authorizationHandler);
                 }
 
                 handlers.forEach(route::handler);
@@ -184,26 +190,39 @@ public class RouteBuilder
     @VisibleForTesting
     public BiConsumer<RoutingContext, AuthorizationContext> 
routeGenericVariableConsumer()
     {
-        return (routingCtx, authZContext) -> {
-            Optional<QualifiedTableName> optional = 
RoutingContextUtils.getAsOptional(routingCtx, SC_QUALIFIED_TABLE_NAME);
-            String keyspace = null;
-            String table = null;
-            if (optional.isPresent())
-            {
-                QualifiedTableName qualifiedTableName = optional.get();
-                keyspace = qualifiedTableName.keyspace();
-                table = qualifiedTableName.tableName();
-            }
+        return ROUTE_GENERIC_VARIABLE_CONSUMER;
+    }
+
+    private InputTrustHandler wrapInputTrustHandler(Handler<RoutingContext> 
handler)
+    {
+        return event -> {
+            handler.handle(event);
+            event.next();
+        };
+    }
+
+    static class RouteGenericVariableConsumer implements 
BiConsumer<RoutingContext, AuthorizationContext>
+    {
+        @Override
+        public void accept(RoutingContext routingContext, AuthorizationContext 
authorizationContext)
+        {
+            Optional<QualifiedTableName> optional = 
RoutingContextUtils.getAsOptional(routingContext, SC_QUALIFIED_TABLE_NAME);
+            if (optional.isEmpty())
+                return;
+
+            QualifiedTableName qualifiedTableName = optional.get();
+            String keyspace = qualifiedTableName.keyspace();
+            String table = qualifiedTableName.tableName();
 
             if (keyspace != null)
             {
-                authZContext.variables().add(KEYSPACE, keyspace);
+                authorizationContext.variables().add(KEYSPACE, keyspace);
             }
             if (table != null)
             {
-                authZContext.variables().add(TABLE, table);
+                authorizationContext.variables().add(TABLE, table);
             }
-        };
+        }
     }
 
     /**
@@ -216,14 +235,14 @@ public class RouteBuilder
         private final AdminIdentityResolver adminIdentityResolver;
         private final AuthorizationParameterValidateHandler 
authZParameterValidateHandler;
         private final SidecarMetrics sidecarMetrics;
-        private final AsyncCache<AuthorizationCacheKey, Boolean> 
authorizationCache;
+        private final AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
authorizationCache;
 
         public Factory(AccessControlConfiguration accessControlConfiguration,
                        AuthorizationProvider authorizationProvider,
                        AdminIdentityResolver adminIdentityResolver,
                        AuthorizationParameterValidateHandler 
authZParameterValidateHandler,
                        SidecarMetrics sidecarMetrics,
-                       AsyncCache<AuthorizationCacheKey, Boolean> 
authorizationCache)
+                       AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
authorizationCache)
         {
             this.accessControlConfiguration = accessControlConfiguration;
             this.authorizationProvider = authorizationProvider;
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/utils/CacheFactory.java 
b/server/src/main/java/org/apache/cassandra/sidecar/utils/CacheFactory.java
index 2174ac57..410c45a4 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/utils/CacheFactory.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/utils/CacheFactory.java
@@ -48,7 +48,7 @@ public class CacheFactory
     public static final String ENDPOINT_AUTHORIZATION_CACHE_NAME = 
"endpoint_authorization_cache";
 
     private final Cache<SSTableImporter.ImportOptions, Future<Void>> 
ssTableImportCache;
-    private final AsyncCache<AuthorizationCacheKey, Boolean> 
endpointAuthorizationCache;
+    private final AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
endpointAuthorizationCache;
 
     @Inject
     public CacheFactory(SidecarConfiguration configuration,
@@ -80,7 +80,7 @@ public class CacheFactory
     /**
      * @return the cache used for authorization requests
      */
-    public AsyncCache<AuthorizationCacheKey, Boolean> 
endpointAuthorizationCache()
+    public AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
endpointAuthorizationCache()
     {
         return endpointAuthorizationCache;
     }
@@ -122,11 +122,12 @@ public class CacheFactory
      *
      * @param sidecarConfiguration the Sidecar configuration
      * @param sidecarMetrics       the Sidecar metrics registry
+     * @param ticker               the ticker for the cache
      * @return instance of {@link AsyncCache} for caching authorization 
requests
      */
-    private AsyncCache<AuthorizationCacheKey, Boolean> 
initEndpointAuthorizationCache(SidecarConfiguration sidecarConfiguration,
-                                                                               
       SidecarMetrics sidecarMetrics,
-                                                                               
       Ticker ticker)
+    private AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
initEndpointAuthorizationCache(SidecarConfiguration sidecarConfiguration,
+                                                                               
               SidecarMetrics sidecarMetrics,
+                                                                               
               Ticker ticker)
     {
         if (!sidecarConfiguration.accessControlConfiguration().enabled()
             || 
!sidecarConfiguration.accessControlConfiguration().permissionCacheConfiguration().enabled())
@@ -143,6 +144,7 @@ public class CacheFactory
                     permissionCacheConfig.expireAfterAccess(), 
permissionCacheConfig.maximumSize());
         return Caffeine.newBuilder()
                        .ticker(ticker)
+                       .executor(MoreExecutors.directExecutor())
                        
.expireAfterAccess(permissionCacheConfig.expireAfterAccess().quantity(),
                                           
permissionCacheConfig.expireAfterAccess().unit())
                        .maximumSize(permissionCacheConfig.maximumSize())
diff --git 
a/server/src/test/java/org/apache/cassandra/sidecar/acl/authorization/CachedAuthorizationHandlerTest.java
 
b/server/src/test/java/org/apache/cassandra/sidecar/acl/authorization/CachedAuthorizationHandlerTest.java
index 6fe4b3ea..99c1c482 100644
--- 
a/server/src/test/java/org/apache/cassandra/sidecar/acl/authorization/CachedAuthorizationHandlerTest.java
+++ 
b/server/src/test/java/org/apache/cassandra/sidecar/acl/authorization/CachedAuthorizationHandlerTest.java
@@ -68,7 +68,6 @@ class CachedAuthorizationHandlerTest
 {
     private SidecarConfiguration sidecarConfiguration;
     private AccessControlConfiguration mockAccessControlConfig;
-    private AuthorizationParameterValidateHandler mockValidateHandler;
     private AdminIdentityResolver mockAdminIdentityResolver;
     private CacheConfiguration mockCacheConfig;
     private SidecarMetrics metrics;
@@ -80,7 +79,7 @@ class CachedAuthorizationHandlerTest
     void setUp()
     {
         mockAccessControlConfig = mock(AccessControlConfiguration.class);
-        mockValidateHandler = 
mock(AuthorizationParameterValidateHandler.class);
+        AuthorizationParameterValidateHandler mockValidateHandler = 
mock(AuthorizationParameterValidateHandler.class);
         mockAdminIdentityResolver = mock(AdminIdentityResolver.class);
         mockCacheConfig = mock(CacheConfiguration.class);
 
@@ -113,8 +112,7 @@ class CachedAuthorizationHandlerTest
 
         // Take baseline before first call. A snapshot call refreshes cache 
miss and cache hits. But it does not reset
         // load success count or load failure count
-        CacheStats baseline = 
metrics.server().cache().authorizationCacheMetrics.snapshot();
-
+        metrics.server().cache().authorizationCacheMetrics.snapshot();
     }
 
     @AfterEach
@@ -132,7 +130,7 @@ class CachedAuthorizationHandlerTest
         Authorization expected = PermissionBasedAuthorization.create("CREATE");
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(1, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(1, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          expected, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext = createMockContext("admin-user", 
"admin-identity1", "admin-role1");
@@ -144,7 +142,7 @@ class CachedAuthorizationHandlerTest
     {
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(2, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(2, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext
@@ -160,7 +158,7 @@ class CachedAuthorizationHandlerTest
     {
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(3, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(3, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext = createMockContext("user2", "identity2", 
"role2");
@@ -175,7 +173,7 @@ class CachedAuthorizationHandlerTest
         Authorization expected = PermissionBasedAuthorization.create("CREATE");
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(4, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(4, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          expected, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext = createMockContext("user3", "identity3", 
"role3");
@@ -189,7 +187,7 @@ class CachedAuthorizationHandlerTest
     {
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(5, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(5, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext1 = createMockContext("user4", "identity4", 
"role4");
@@ -216,7 +214,7 @@ class CachedAuthorizationHandlerTest
     {
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(6, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(6, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext1 = createMockContext("user5", "identity5", 
"role5");
@@ -240,7 +238,7 @@ class CachedAuthorizationHandlerTest
     {
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(7, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(7, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         
handler.variableConsumer(routeBuilderFactory.builderForRoute().routeGenericVariableConsumer());
@@ -267,7 +265,7 @@ class CachedAuthorizationHandlerTest
     {
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(8, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(8, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         
handler.variableConsumer(routeBuilderFactory.builderForRoute().routeGenericVariableConsumer());
@@ -292,40 +290,12 @@ class CachedAuthorizationHandlerTest
         assertThat(sameUserDifferentResourceCallStats.hitCount()).isEqualTo(0);
     }
 
-    @Test
-    void testValidationFailure()
-    {
-        CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
-        CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(9, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
-                                         testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
-
-        RoutingContext mockContext = createMockContext("user8", "identity9", 
"role9");
-
-        // Mock validation handler to fail the context
-        doAnswer(invocation -> {
-            RoutingContext ctx = invocation.getArgument(0);
-            when(ctx.failed()).thenReturn(true);
-            return null;
-        }).when(mockValidateHandler).handle(any(RoutingContext.class));
-
-        handler.handle(mockContext);
-
-        // Should not proceed to authorization or call next()
-        verify(mockContext, times(0)).next();
-
-        // Verify no cache operations when validation fails
-        CacheStats firstCallStats = 
metrics.server().cache().authorizationCacheMetrics.snapshot();
-        assertThat(firstCallStats.missCount()).isEqualTo(0);
-        assertThat(firstCallStats.hitCount()).isEqualTo(0);
-    }
-
     @Test
     void testEmptyIdentities()
     {
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handlerWithModifyPermission
-        = new CachedAuthorizationHandler(10, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(10, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext1 = createMockContext("user9", List.of(), 
List.of());
@@ -335,7 +305,7 @@ class CachedAuthorizationHandlerTest
 
         Authorization expected = PermissionBasedAuthorization.create("CREATE");
         CachedAuthorizationHandler handlerWithCreatePermission
-        = new CachedAuthorizationHandler(11, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(11, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          expected, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext2 = createMockContext("user10", List.of(), 
List.of());
@@ -353,7 +323,7 @@ class CachedAuthorizationHandlerTest
 
         CacheFactory cacheFactory = new CacheFactory(sidecarConfiguration, 
sstableImporter, metrics);
         CachedAuthorizationHandler handler
-        = new CachedAuthorizationHandler(12, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(12, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         RoutingContext mockContext1 = createMockContext("user11", 
"identity11", "role11");
@@ -391,14 +361,14 @@ class CachedAuthorizationHandlerTest
 
         // Handler 1 requires MODIFY permission
         CachedAuthorizationHandler handler1
-        = new CachedAuthorizationHandler(100, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(100, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          testAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         // Handler 2 requires CREATE permission
         Authorization createAuthorization = AndAuthorization.create()
                                                             
.addAuthorization(PermissionBasedAuthorization.create("CREATE"));
         CachedAuthorizationHandler handler2
-        = new CachedAuthorizationHandler(200, mockAccessControlConfig, 
mockValidateHandler, mockAdminIdentityResolver,
+        = new CachedAuthorizationHandler(200, mockAccessControlConfig, 
mockAdminIdentityResolver,
                                          createAuthorization, metrics, 
cacheFactory.endpointAuthorizationCache());
 
         // Same user accessing both routes
@@ -439,12 +409,12 @@ class CachedAuthorizationHandlerTest
 
         // Create two handler instances with the SAME handlerId
         CachedAuthorizationHandler handler1
-        = new CachedAuthorizationHandler(sharedHandlerId, 
mockAccessControlConfig, mockValidateHandler,
+        = new CachedAuthorizationHandler(sharedHandlerId, 
mockAccessControlConfig,
                                          mockAdminIdentityResolver, 
testAuthorization, metrics,
                                          
cacheFactory.endpointAuthorizationCache());
 
         CachedAuthorizationHandler handler2
-        = new CachedAuthorizationHandler(sharedHandlerId, 
mockAccessControlConfig, mockValidateHandler,
+        = new CachedAuthorizationHandler(sharedHandlerId, 
mockAccessControlConfig,
                                          mockAdminIdentityResolver, 
testAuthorization, metrics,
                                          
cacheFactory.endpointAuthorizationCache());
 
@@ -478,7 +448,7 @@ class CachedAuthorizationHandlerTest
 
         // Create two handler instances with the SAME handlerId
         CachedAuthorizationHandler handler1
-        = new CachedAuthorizationHandler(sharedHandlerId, 
mockAccessControlConfig, mockValidateHandler,
+        = new CachedAuthorizationHandler(sharedHandlerId, 
mockAccessControlConfig,
                                          mockAdminIdentityResolver, 
testAuthorization, metrics,
                                          
cacheFactory.endpointAuthorizationCache());
 
@@ -487,7 +457,7 @@ class CachedAuthorizationHandlerTest
                                                             
.addAuthorization(PermissionBasedAuthorization.create("CREATE"));
 
         CachedAuthorizationHandler handler2
-        = new CachedAuthorizationHandler(sharedHandlerId, 
mockAccessControlConfig, mockValidateHandler,
+        = new CachedAuthorizationHandler(sharedHandlerId, 
mockAccessControlConfig,
                                          mockAdminIdentityResolver, 
createAuthorization, metrics,
                                          
cacheFactory.endpointAuthorizationCache());
 
diff --git 
a/server/src/test/java/org/apache/cassandra/sidecar/handlers/InvalidateCacheHandlerTest.java
 
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/InvalidateCacheHandlerTest.java
index 465b447e..b386232e 100644
--- 
a/server/src/test/java/org/apache/cassandra/sidecar/handlers/InvalidateCacheHandlerTest.java
+++ 
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/InvalidateCacheHandlerTest.java
@@ -40,6 +40,7 @@ import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import com.google.inject.util.Modules;
 import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.Future;
 import io.vertx.core.Vertx;
 import io.vertx.ext.web.client.WebClient;
 import io.vertx.ext.web.client.predicate.ResponsePredicate;
@@ -78,8 +79,8 @@ public class InvalidateCacheHandlerTest
     IdentityToRoleCache mockIdentityToRoleCache = 
mock(IdentityToRoleCache.class);
     RoleAuthorizationsCache mockRoleAuthorizationsCache = 
mock(RoleAuthorizationsCache.class);
     SuperUserCache mockSuperUserCache = mock(SuperUserCache.class);
-    AsyncCache<AuthorizationCacheKey, Boolean> mockEndpointAuthorizationCache 
= mock(AsyncCache.class);
-    Cache<AuthorizationCacheKey, Boolean> mockSyncCache = mock(Cache.class);
+    AsyncCache<AuthorizationCacheKey, Future<Boolean>> 
mockEndpointAuthorizationCache = mock(AsyncCache.class);
+    Cache<AuthorizationCacheKey, Future<Boolean>> mockSyncCache = 
mock(Cache.class);
 
     @BeforeEach
     void before() throws InterruptedException
@@ -151,9 +152,9 @@ public class InvalidateCacheHandlerTest
     void testInvalidateRoleAuthorizationsCacheWithKeys(VertxTestContext 
context)
     {
         verifyInvalidateCache(context, RoleAuthorizationsCache.NAME,
-                             Arrays.asList("key1"),
-                             BAD_REQUEST,
-                             null, mockIdentityToRoleCache, 
mockRoleAuthorizationsCache, mockSuperUserCache);
+                              Arrays.asList("key1"),
+                              BAD_REQUEST,
+                              null, mockIdentityToRoleCache, 
mockRoleAuthorizationsCache, mockSuperUserCache);
     }
 
     // SuperUserCache tests
@@ -173,9 +174,9 @@ public class InvalidateCacheHandlerTest
     void testInvalidateSuperUserCacheWithKeys(VertxTestContext context)
     {
         verifyInvalidateCache(context, SuperUserCache.NAME,
-                             Arrays.asList("user1", "user2", "user3"),
-                             OK,
-                             mockSuperUserCache, mockIdentityToRoleCache, 
mockRoleAuthorizationsCache);
+                              Arrays.asList("user1", "user2", "user3"),
+                              OK,
+                              mockSuperUserCache, mockIdentityToRoleCache, 
mockRoleAuthorizationsCache);
     }
 
     // EndpointAuthorizationCache tests
@@ -227,9 +228,9 @@ public class InvalidateCacheHandlerTest
     void testInvalidateEndpointAuthorizationCacheWithKeys(VertxTestContext 
context)
     {
         verifyInvalidateCache(context, 
CacheFactory.ENDPOINT_AUTHORIZATION_CACHE_NAME,
-                             Arrays.asList("key1"),
-                             BAD_REQUEST,
-                             null, mockIdentityToRoleCache, 
mockRoleAuthorizationsCache, mockSuperUserCache);
+                              Arrays.asList("key1"),
+                              BAD_REQUEST,
+                              null, mockIdentityToRoleCache, 
mockRoleAuthorizationsCache, mockSuperUserCache);
     }
 
     // Error case tests
@@ -258,11 +259,11 @@ public class InvalidateCacheHandlerTest
      * Helper method to test cache invalidation
      * Verifies that the specified cache is invalidated (or returns the 
expected error) and other caches are not touched.
      *
-     * @param context the test context
-     * @param cacheName the name of the cache to invalidate (sent in the HTTP 
request)
-     * @param keys the specific keys to invalidate, or null to invalidate all 
keys
-     * @param expectedStatus the expected HTTP response status (OK, 
BAD_REQUEST, NOT_FOUND, etc.)
-     * @param cacheToInvalidate the mock cache that should be invalidated 
(null if expecting an error)
+     * @param context             the test context
+     * @param cacheName           the name of the cache to invalidate (sent in 
the HTTP request)
+     * @param keys                the specific keys to invalidate, or null to 
invalidate all keys
+     * @param expectedStatus      the expected HTTP response status (OK, 
BAD_REQUEST, NOT_FOUND, etc.)
+     * @param cacheToInvalidate   the mock cache that should be invalidated 
(null if expecting an error)
      * @param cachesToNotInteract other mock caches that should not be touched
      */
     @SuppressWarnings("rawtypes")
diff --git 
a/server/src/test/java/org/apache/cassandra/sidecar/utils/CacheFactoryTest.java 
b/server/src/test/java/org/apache/cassandra/sidecar/utils/CacheFactoryTest.java
index 1a93206d..f6450300 100644
--- 
a/server/src/test/java/org/apache/cassandra/sidecar/utils/CacheFactoryTest.java
+++ 
b/server/src/test/java/org/apache/cassandra/sidecar/utils/CacheFactoryTest.java
@@ -217,7 +217,7 @@ class CacheFactoryTest
     @Test
     void testEndpointAuthorizationCacheExpiration() throws ExecutionException, 
InterruptedException
     {
-        AsyncCache<AuthorizationCacheKey, Boolean> cache = 
cacheFactory.endpointAuthorizationCache();
+        AsyncCache<AuthorizationCacheKey, Future<Boolean>> cache = 
cacheFactory.endpointAuthorizationCache();
         AuthorizationCacheKey key1
         = createAuthorizationCacheKey(1, "user1", List.of("role1"), "ks1", 
"tbl1");
         AuthorizationCacheKey key2
@@ -260,7 +260,7 @@ class CacheFactoryTest
     @Test
     void testEndpointAuthorizationCacheDifferentKeys() throws 
ExecutionException, InterruptedException
     {
-        AsyncCache<AuthorizationCacheKey, Boolean> cache = 
cacheFactory.endpointAuthorizationCache();
+        AsyncCache<AuthorizationCacheKey, Future<Boolean>> cache = 
cacheFactory.endpointAuthorizationCache();
 
         // Different handler IDs
         AuthorizationCacheKey key1
@@ -343,12 +343,12 @@ class CacheFactoryTest
         return AuthorizationCacheKey.create(handlerId, authContext);
     }
 
-    private Boolean authorizationCacheEntry(AsyncCache<AuthorizationCacheKey, 
Boolean> cache,
+    private Boolean authorizationCacheEntry(AsyncCache<AuthorizationCacheKey, 
Future<Boolean>> cache,
                                             AuthorizationCacheKey key,
                                             Boolean value) throws 
ExecutionException, InterruptedException
     {
-        CompletableFuture<Boolean> future = cache.get(key, k -> value);
+        CompletableFuture<Future<Boolean>> future = cache.get(key, k -> 
Future.succeededFuture(value));
         assertThat(future).isNotNull();
-        return future.get();
+        return future.get().toCompletionStage().toCompletableFuture().get();
     }
 }
diff --git 
a/vertx-auth-mtls/src/main/java/io/vertx/ext/auth/mtls/impl/MutualTlsUser.java 
b/vertx-auth-mtls/src/main/java/io/vertx/ext/auth/mtls/impl/MutualTlsUser.java
index 33cf0cd9..6b0ea291 100644
--- 
a/vertx-auth-mtls/src/main/java/io/vertx/ext/auth/mtls/impl/MutualTlsUser.java
+++ 
b/vertx-auth-mtls/src/main/java/io/vertx/ext/auth/mtls/impl/MutualTlsUser.java
@@ -45,6 +45,14 @@ public class MutualTlsUser extends UserImpl
         return identities;
     }
 
+    @Override
+    public String toString()
+    {
+        return "MutualTlsUser{" +
+               "identities=" + identities +
+               '}';
+    }
+
     @Override
     public boolean equals(Object o)
     {
diff --git 
a/vertx-auth-mtls/src/test/java/io/vertx/ext/auth/mtls/impl/MutualTlsUserTest.java
 
b/vertx-auth-mtls/src/test/java/io/vertx/ext/auth/mtls/impl/MutualTlsUserTest.java
index a373b969..bc25a4fa 100644
--- 
a/vertx-auth-mtls/src/test/java/io/vertx/ext/auth/mtls/impl/MutualTlsUserTest.java
+++ 
b/vertx-auth-mtls/src/test/java/io/vertx/ext/auth/mtls/impl/MutualTlsUserTest.java
@@ -27,14 +27,21 @@ import static org.assertj.core.api.Assertions.assertThat;
 /**
  * Test for {@link MutualTlsUser}
  */
-public class MutualTlsUserTest
+class MutualTlsUserTest
 {
     @Test
-    public void testPrincipal()
+    void testPrincipal()
     {
         MutualTlsUser mutualTlsUser = 
MutualTlsUser.fromIdentities(Arrays.asList("identity1", "identity2"));
         assertThat(mutualTlsUser.identities().size()).isEqualTo(2);
         
assertThat(mutualTlsUser.principal().containsKey("identities")).isTrue();
         
assertThat(mutualTlsUser.principal().getString("identities")).isEqualTo("identity1,identity2");
     }
+
+    @Test
+    void testToString()
+    {
+        MutualTlsUser user = 
MutualTlsUser.fromIdentities(Arrays.asList("identity1", "identity2"));
+        
assertThat(user.toString()).isEqualTo("MutualTlsUser{identities=[identity1, 
identity2]}");
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to