Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run authentication request blocking tasks on Vert.x duplicated context #34963

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.quarkus.security.spi.runtime;

import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;

import io.quarkus.security.identity.AuthenticationRequestContext;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.subscription.UniEmitter;

/**
* Blocking executor used for security purposes such {@link AuthenticationRequestContext#runBlocking(Supplier)}.
* Extensions may provide their own implementation if they need a single thread pool.
*/
public interface BlockingSecurityExecutor {

<T> Uni<T> executeBlocking(Supplier<? extends T> supplier);

static BlockingSecurityExecutor createBlockingExecutor(Supplier<Executor> executorSupplier) {
return new BlockingSecurityExecutor() {
@Override
public <T> Uni<T> executeBlocking(Supplier<? extends T> function) {
return Uni.createFrom().emitter(new Consumer<UniEmitter<? super T>>() {
@Override
public void accept(UniEmitter<? super T> uniEmitter) {
executorSupplier.get().execute(new Runnable() {
@Override
public void run() {
try {
uniEmitter.complete(function.get());
} catch (Throwable t) {
uniEmitter.fail(t);
}
}
});
}
});
}
};
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package io.quarkus.security.runtime;

import java.util.concurrent.Executor;
import java.util.function.Supplier;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;

import io.quarkus.arc.DefaultBean;
import io.quarkus.runtime.ExecutorRecorder;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.identity.request.AnonymousAuthenticationRequest;
import io.quarkus.security.spi.runtime.BlockingSecurityExecutor;

/**
* CDI bean than manages the lifecycle of the {@link io.quarkus.security.identity.IdentityProviderManager}
Expand All @@ -25,6 +28,21 @@ public class IdentityProviderManagerCreator {
@Inject
Instance<SecurityIdentityAugmentor> augmentors;

@Inject
BlockingSecurityExecutor blockingExecutor;

@ApplicationScoped
@DefaultBean
@Produces
BlockingSecurityExecutor defaultBlockingExecutor() {
return BlockingSecurityExecutor.createBlockingExecutor(new Supplier<Executor>() {
@Override
public Executor get() {
return ExecutorRecorder.getCurrent();
}
});
}

@Produces
@ApplicationScoped
public IdentityProviderManager ipm() {
Expand All @@ -42,13 +60,7 @@ public IdentityProviderManager ipm() {
for (SecurityIdentityAugmentor i : augmentors) {
builder.addSecurityIdentityAugmentor(i);
}
builder.setBlockingExecutor(new Executor() {
@Override
public void execute(Runnable command) {
//TODO: should we be using vert.x blocking tasks here? We really should only have a single thread pool
ExecutorRecorder.getCurrent().execute(command);
}
});
builder.setBlockingExecutor(blockingExecutor);
return builder.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package io.quarkus.security.runtime;

import static io.quarkus.security.spi.runtime.BlockingSecurityExecutor.createBlockingExecutor;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

Expand All @@ -21,8 +22,8 @@
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.identity.request.AnonymousAuthenticationRequest;
import io.quarkus.security.identity.request.AuthenticationRequest;
import io.quarkus.security.spi.runtime.BlockingSecurityExecutor;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.subscription.UniEmitter;

/**
* A manager that can be used to get a specific type of identity provider.
Expand All @@ -32,7 +33,7 @@ public class QuarkusIdentityProviderManagerImpl implements IdentityProviderManag

private final Map<Class<? extends AuthenticationRequest>, List<IdentityProvider>> providers;
private final List<SecurityIdentityAugmentor> augmenters;
private final Executor blockingExecutor;
private final BlockingSecurityExecutor blockingExecutor;

private final AuthenticationRequestContext blockingRequestContext = new AuthenticationRequestContext() {
@Override
Expand All @@ -48,21 +49,7 @@ public Uni<SecurityIdentity> get() {
return Uni.createFrom().failure(t);
}
} else {
return Uni.createFrom().emitter(new Consumer<UniEmitter<? super SecurityIdentity>>() {
@Override
public void accept(UniEmitter<? super SecurityIdentity> uniEmitter) {
blockingExecutor.execute(new Runnable() {
@Override
public void run() {
try {
uniEmitter.complete(function.get());
} catch (Throwable t) {
uniEmitter.fail(t);
}
}
});
}
});
return blockingExecutor.executeBlocking(function);
}
}
});
Expand Down Expand Up @@ -199,7 +186,7 @@ public static class Builder {

private final Map<Class<? extends AuthenticationRequest>, List<IdentityProvider>> providers = new HashMap<>();
private final List<SecurityIdentityAugmentor> augmentors = new ArrayList<>();
private Executor blockingExecutor;
private BlockingSecurityExecutor blockingExecutor;
private boolean built = false;

/**
Expand Down Expand Up @@ -231,11 +218,20 @@ public Builder addSecurityIdentityAugmentor(SecurityIdentityAugmentor augmentor)
* @param blockingExecutor The executor to use for blocking tasks
* @return this builder
*/
public Builder setBlockingExecutor(Executor blockingExecutor) {
public Builder setBlockingExecutor(BlockingSecurityExecutor blockingExecutor) {
this.blockingExecutor = blockingExecutor;
return this;
}

/**
* @param blockingExecutor The executor to use for blocking tasks
* @return this builder
*/
public Builder setBlockingExecutor(Executor blockingExecutor) {
this.blockingExecutor = createBlockingExecutor(() -> blockingExecutor);
return this;
}

/**
* @return a new {@link QuarkusIdentityProviderManagerImpl}
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.quarkus.jwt.test;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import jakarta.ws.rs.core.Response;

import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.security.AuthenticationFailedException;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;

public class EnabledProactiveAuthFailedExceptionMapperHttp2Test {

private static final String CUSTOMIZED_RESPONSE = "AuthenticationFailedException";
protected static final Class<?>[] classes = { JsonValuejectionEndpoint.class, TokenUtils.class,
AuthFailedExceptionMapper.class };

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(classes)
.addAsResource(new StringAsset("quarkus.http.auth.proactive=true\n" +
"quarkus.smallrye-jwt.blocking-authentication=true\n"), "application.properties"));

@TestHTTPResource
URL url;

@Test
public void testExMapperCustomizedResponse() throws IOException, InterruptedException, URISyntaxException {
var client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();

var response = client.send(
HttpRequest.newBuilder()
.GET()
.header("Authorization", "Bearer 12345")
.uri(url.toURI())
.build(),
HttpResponse.BodyHandlers.ofString());

assertEquals(401, response.statusCode());
}

public static class AuthFailedExceptionMapper {

@ServerExceptionMapper(value = AuthenticationFailedException.class)
public Response unauthorized() {
return Response
.status(401)
.entity(CUSTOMIZED_RESPONSE).build();
}

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.vertx.http.deployment;

import static io.quarkus.arc.processor.DotNames.APPLICATION_SCOPED;
import static org.jboss.jandex.AnnotationTarget.Kind.CLASS;

import java.security.Permission;
Expand Down Expand Up @@ -54,6 +55,7 @@
import io.quarkus.vertx.http.runtime.security.PermitSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.RolesAllowedHttpSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.SupplierImpl;
import io.quarkus.vertx.http.runtime.security.VertxBlockingSecurityExecutor;
import io.vertx.core.http.ClientAuth;
import io.vertx.ext.web.RoutingContext;

Expand Down Expand Up @@ -261,6 +263,9 @@ void setupAuthenticationMechanisms(
}

if (capabilities.isPresent(Capability.SECURITY)) {
beanProducer
.produce(AdditionalBeanBuildItem.builder().setUnremovable()
.addBeanClass(VertxBlockingSecurityExecutor.class).setDefaultScope(APPLICATION_SCOPED).build());
beanProducer
.produce(AdditionalBeanBuildItem.builder().setUnremovable().addBeanClass(HttpAuthenticator.class)
.addBeanClass(HttpAuthorizer.class).build());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.vertx.http.runtime.security;

import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setContextSafe;
import static io.smallrye.common.vertx.VertxContext.getOrCreateDuplicatedContext;

import java.util.function.Supplier;

import jakarta.inject.Inject;

import io.quarkus.security.spi.runtime.BlockingSecurityExecutor;
import io.smallrye.mutiny.Uni;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;

public class VertxBlockingSecurityExecutor implements BlockingSecurityExecutor {

@Inject
Vertx vertx;

@Override
public <T> Uni<T> executeBlocking(Supplier<? extends T> supplier) {
Context local = getOrCreateDuplicatedContext(vertx);
setContextSafe(local, true);
return Uni
.createFrom()
.completionStage(
local
.executeBlocking(new Handler<Promise<T>>() {
@Override
public void handle(Promise<T> promise) {
promise.complete(supplier.get());
}
})
.toCompletionStage());
}
}
Loading