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]