Skip to content

Commit

Permalink
Security: run blocking tasks on Vert.X duplicated ctx
Browse files Browse the repository at this point in the history
  • Loading branch information
michalvavrik committed Jul 24, 2023
1 parent e763315 commit 58233e2
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 27 deletions.
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());
}
}

0 comments on commit 58233e2

Please sign in to comment.