From 1788293f74cc9a467b0f75be2102504390ca4b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 5 Jul 2023 20:05:52 +0200 Subject: [PATCH] Grpc: Use eventloop for server calls to avoid hanging --- .../grpc/auth/BlockingHttpSecurityPolicy.java | 32 ++ .../io/quarkus/grpc/auth/GrpcAuthTest.java | 217 +----------- .../quarkus/grpc/auth/GrpcAuthTestBase.java | 315 ++++++++++++++++++ .../auth/GrpcAuthUsingSeparatePortTest.java | 14 + .../grpc/runtime/GrpcServerRecorder.java | 13 + 5 files changed, 376 insertions(+), 215 deletions(-) create mode 100644 extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/BlockingHttpSecurityPolicy.java create mode 100644 extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTestBase.java create mode 100644 extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthUsingSeparatePortTest.java diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/BlockingHttpSecurityPolicy.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/BlockingHttpSecurityPolicy.java new file mode 100644 index 0000000000000..9c2b6585af7d1 --- /dev/null +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/BlockingHttpSecurityPolicy.java @@ -0,0 +1,32 @@ +package io.quarkus.grpc.auth; + +import java.util.function.BiFunction; + +import jakarta.inject.Singleton; + +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.web.RoutingContext; + +@Singleton +public class BlockingHttpSecurityPolicy implements HttpSecurityPolicy { + + final static String BLOCK_REQUEST = "block-request"; + + @Override + public Uni checkPermission(RoutingContext request, Uni identity, + AuthorizationRequestContext requestContext) { + if (request.request().headers().get(BLOCK_REQUEST) != null) { + return requestContext.runBlocking(request, identity, + new BiFunction() { + @Override + public CheckResult apply(RoutingContext routingContext, SecurityIdentity securityIdentity) { + return CheckResult.PERMIT; + } + }); + } else { + return Uni.createFrom().item(CheckResult.PERMIT); + } + } +} diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTest.java index 9ed4beb029ec5..b525a0b05493c 100644 --- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTest.java +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTest.java @@ -1,225 +1,12 @@ package io.quarkus.grpc.auth; -import static com.example.security.Security.ThreadInfo.newBuilder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.inject.Singleton; - -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.StringAsset; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import com.example.security.SecuredService; -import com.example.security.Security; - -import io.grpc.Metadata; -import io.quarkus.grpc.GrpcClient; -import io.quarkus.grpc.GrpcClientUtils; -import io.quarkus.grpc.GrpcService; -import io.quarkus.security.credential.PasswordCredential; -import io.quarkus.security.identity.request.AuthenticationRequest; -import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; import io.quarkus.test.QuarkusUnitTest; -import io.smallrye.common.annotation.Blocking; -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; -import io.vertx.core.Context; -public class GrpcAuthTest { - - public static final Metadata.Key AUTHORIZATION = Metadata.Key.of("Authorization", - Metadata.ASCII_STRING_MARSHALLER); +public class GrpcAuthTest extends GrpcAuthTestBase { @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( - () -> ShrinkWrap.create(JavaArchive.class) - .addClasses(Service.class, BasicGrpcSecurityMechanism.class) - .addPackage(SecuredService.class.getPackage()) - .add(new StringAsset("quarkus.security.users.embedded.enabled=true\n" + - "quarkus.security.users.embedded.users.john=john\n" + - "quarkus.security.users.embedded.roles.john=employees\n" + - "quarkus.security.users.embedded.users.paul=paul\n" + - "quarkus.security.users.embedded.roles.paul=interns\n" + - "quarkus.security.users.embedded.plain-text=true\n" + - "quarkus.http.auth.basic=true"), "application.properties")); - public static final String JOHN_BASIC_CREDS = "am9objpqb2hu"; - public static final String PAUL_BASIC_CREDS = "cGF1bDpwYXVs"; - - @GrpcClient - SecuredService securityClient; - - @Test - void shouldSecureUniEndpoint() { - Metadata headers = new Metadata(); - headers.put(AUTHORIZATION, "Basic " + JOHN_BASIC_CREDS); - SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); - AtomicInteger resultCount = new AtomicInteger(); - client.unaryCall(Security.Container.newBuilder().setText("woo-hoo").build()) - .subscribe().with(e -> { - if (!e.getIsOnEventLoop()) { - Assertions.fail("Secured method should be run on event loop"); - } - resultCount.incrementAndGet(); - }); - - await().atMost(10, TimeUnit.SECONDS) - .until(() -> resultCount.get() == 1); - } - - @Test - void shouldSecureBlockingUniEndpoint() { - Metadata headers = new Metadata(); - headers.put(AUTHORIZATION, "Basic " + JOHN_BASIC_CREDS); - SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); - AtomicInteger resultCount = new AtomicInteger(); - client.unaryCallBlocking(Security.Container.newBuilder().setText("woo-hoo").build()) - .subscribe().with(e -> { - if (e.getIsOnEventLoop()) { - Assertions.fail("Secured method annotated with @Blocking should be executed on worker thread"); - } - resultCount.incrementAndGet(); - }); - - await().atMost(10, TimeUnit.SECONDS) - .until(() -> resultCount.get() == 1); - } - - @Test - void shouldSecureMultiEndpoint() { - Metadata headers = new Metadata(); - headers.put(AUTHORIZATION, "Basic " + PAUL_BASIC_CREDS); - SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); - List results = new CopyOnWriteArrayList<>(); - client.streamCall(Multi.createBy().repeating() - .supplier(() -> (Security.Container.newBuilder().setText("woo-hoo").build())).atMost(4)) - .subscribe().with(e -> results.add(e.getIsOnEventLoop())); - - await().atMost(10, TimeUnit.SECONDS) - .until(() -> results.size() == 5); - - assertThat(results.stream().filter(e -> !e)).isEmpty(); - } - - @Test - void shouldSecureBlockingMultiEndpoint() { - Metadata headers = new Metadata(); - headers.put(AUTHORIZATION, "Basic " + PAUL_BASIC_CREDS); - SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); - List results = new CopyOnWriteArrayList<>(); - client.streamCallBlocking(Multi.createBy().repeating() - .supplier(() -> (Security.Container.newBuilder().setText("woo-hoo").build())).atMost(4)) - .subscribe().with(e -> results.add(e.getIsOnEventLoop())); - - await().atMost(10, TimeUnit.SECONDS) - .until(() -> results.size() == 5); - - assertThat(results.stream().filter(e -> e)).isEmpty(); - } - - @Test - void shouldFailWithInvalidCredentials() { - Metadata headers = new Metadata(); - headers.put(AUTHORIZATION, "Basic invalid creds"); - SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); - - AtomicReference error = new AtomicReference<>(); - - AtomicInteger resultCount = new AtomicInteger(); - client.unaryCall(Security.Container.newBuilder().setText("woo-hoo").build()) - .onFailure().invoke(error::set) - .subscribe().with(e -> resultCount.incrementAndGet()); - - await().atMost(10, TimeUnit.SECONDS) - .until(() -> error.get() != null); - } - - @Test - void shouldFailWithInvalidInsufficientRole() { - Metadata headers = new Metadata(); - headers.put(AUTHORIZATION, PAUL_BASIC_CREDS); - SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); - - AtomicReference error = new AtomicReference<>(); - - AtomicInteger resultCount = new AtomicInteger(); - client.unaryCall(Security.Container.newBuilder().setText("woo-hoo").build()) - .onFailure().invoke(error::set) - .subscribe().with(e -> resultCount.incrementAndGet()); - - await().atMost(10, TimeUnit.SECONDS) - .until(() -> error.get() != null); - } - - @GrpcService - public static class Service implements SecuredService { - @Override - @RolesAllowed("employees") - public Uni unaryCall(Security.Container request) { - return Uni.createFrom() - .item(newBuilder().setIsOnEventLoop(Context.isOnEventLoopThread()).build()); - } - - @Override - @RolesAllowed("interns") - public Multi streamCall(Multi request) { - return Multi.createBy() - .repeating().supplier(() -> newBuilder().setIsOnEventLoop(Context.isOnEventLoopThread()).build()) - .atMost(5); - } - - @Blocking - @Override - @RolesAllowed("employees") - public Uni unaryCallBlocking(Security.Container request) { - return Uni.createFrom() - .item(newBuilder().setIsOnEventLoop(Context.isOnEventLoopThread()).build()); - } - - @Blocking - @Override - @RolesAllowed("interns") - public Multi streamCallBlocking(Multi request) { - return Multi.createBy() - .repeating().supplier(() -> newBuilder().setIsOnEventLoop(Context.isOnEventLoopThread()).build()) - .atMost(5); - } - } - - @Singleton - public static class BasicGrpcSecurityMechanism implements GrpcSecurityMechanism { - @Override - public boolean handles(Metadata metadata) { - String authString = metadata.get(AUTHORIZATION); - return authString != null && authString.startsWith("Basic "); - } + static final QuarkusUnitTest config = createQuarkusUnitTest(null); - @Override - public AuthenticationRequest createAuthenticationRequest(Metadata metadata) { - String authString = metadata.get(AUTHORIZATION); - authString = authString.substring("Basic ".length()); - byte[] decode = Base64.getDecoder().decode(authString); - String plainChallenge = new String(decode, StandardCharsets.UTF_8); - int colonPos; - if ((colonPos = plainChallenge.indexOf(':')) > -1) { - String userName = plainChallenge.substring(0, colonPos); - char[] password = plainChallenge.substring(colonPos + 1).toCharArray(); - return new UsernamePasswordAuthenticationRequest(userName, new PasswordCredential(password)); - } else { - return null; - } - } - } } diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTestBase.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTestBase.java new file mode 100644 index 0000000000000..3ec48c1ca6bc2 --- /dev/null +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTestBase.java @@ -0,0 +1,315 @@ +package io.quarkus.grpc.auth; + +import static com.example.security.Security.ThreadInfo.newBuilder; +import static io.quarkus.grpc.auth.BlockingHttpSecurityPolicy.BLOCK_REQUEST; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Singleton; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.example.security.SecuredService; +import com.example.security.Security; + +import io.grpc.Metadata; +import io.quarkus.grpc.GrpcClient; +import io.quarkus.grpc.GrpcClientUtils; +import io.quarkus.grpc.GrpcService; +import io.quarkus.security.credential.PasswordCredential; +import io.quarkus.security.identity.request.AuthenticationRequest; +import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import io.vertx.core.Context; + +public abstract class GrpcAuthTestBase { + + public static final Metadata.Key AUTHORIZATION = Metadata.Key.of("Authorization", + Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key BLOCK_REQUEST_KEY = Metadata.Key.of(BLOCK_REQUEST, + Metadata.ASCII_STRING_MARSHALLER); + private static final String PROPS = "quarkus.security.users.embedded.enabled=true\n" + + "quarkus.security.users.embedded.users.john=john\n" + + "quarkus.security.users.embedded.roles.john=employees\n" + + "quarkus.security.users.embedded.users.paul=paul\n" + + "quarkus.security.users.embedded.roles.paul=interns\n" + + "quarkus.security.users.embedded.plain-text=true\n" + + "quarkus.http.auth.basic=true\n"; + + protected static QuarkusUnitTest createQuarkusUnitTest(String extraProperty) { + return new QuarkusUnitTest().setArchiveProducer( + () -> { + var props = PROPS; + if (extraProperty != null) { + props += extraProperty; + } + + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Service.class, BasicGrpcSecurityMechanism.class, BlockingHttpSecurityPolicy.class) + .addPackage(SecuredService.class.getPackage()) + .add(new StringAsset(props), "application.properties"); + }); + } + + public static final String JOHN_BASIC_CREDS = "am9objpqb2hu"; + public static final String PAUL_BASIC_CREDS = "cGF1bDpwYXVs"; + + @GrpcClient + SecuredService securityClient; + + @Test + void shouldSecureUniEndpoint() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic " + JOHN_BASIC_CREDS); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + AtomicInteger resultCount = new AtomicInteger(); + client.unaryCall(Security.Container.newBuilder().setText("woo-hoo").build()) + .subscribe().with(e -> { + if (!e.getIsOnEventLoop()) { + Assertions.fail("Secured method should be run on event loop"); + } + resultCount.incrementAndGet(); + }); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> resultCount.get() == 1); + } + + @Test + void shouldSecureBlockingUniEndpoint() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic " + JOHN_BASIC_CREDS); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + AtomicInteger resultCount = new AtomicInteger(); + client.unaryCallBlocking(Security.Container.newBuilder().setText("woo-hoo").build()) + .subscribe().with(e -> { + if (e.getIsOnEventLoop()) { + Assertions.fail("Secured method annotated with @Blocking should be executed on worker thread"); + } + resultCount.incrementAndGet(); + }); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> resultCount.get() == 1); + } + + @Test + void shouldSecureMultiEndpoint() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic " + PAUL_BASIC_CREDS); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + List results = new CopyOnWriteArrayList<>(); + client.streamCall(Multi.createBy().repeating() + .supplier(() -> (Security.Container.newBuilder().setText("woo-hoo").build())).atMost(4)) + .subscribe().with(e -> results.add(e.getIsOnEventLoop())); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> results.size() == 5); + + assertThat(results.stream().filter(e -> !e)).isEmpty(); + } + + @Test + void shouldSecureBlockingMultiEndpoint() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic " + PAUL_BASIC_CREDS); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + List results = new CopyOnWriteArrayList<>(); + client.streamCallBlocking(Multi.createBy().repeating() + .supplier(() -> (Security.Container.newBuilder().setText("woo-hoo").build())).atMost(4)) + .subscribe().with(e -> results.add(e.getIsOnEventLoop())); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> results.size() == 5); + + assertThat(results.stream().filter(e -> e)).isEmpty(); + } + + @Test + void shouldFailWithInvalidCredentials() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic invalid creds"); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + + AtomicReference error = new AtomicReference<>(); + + AtomicInteger resultCount = new AtomicInteger(); + client.unaryCall(Security.Container.newBuilder().setText("woo-hoo").build()) + .onFailure().invoke(error::set) + .subscribe().with(e -> resultCount.incrementAndGet()); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> error.get() != null); + } + + @Test + void shouldFailWithInvalidInsufficientRole() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, PAUL_BASIC_CREDS); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + + AtomicReference error = new AtomicReference<>(); + + AtomicInteger resultCount = new AtomicInteger(); + client.unaryCall(Security.Container.newBuilder().setText("woo-hoo").build()) + .onFailure().invoke(error::set) + .subscribe().with(e -> resultCount.incrementAndGet()); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> error.get() != null); + } + + @Test + void shouldSecureUniEndpointWithBlockingHttpSecurityPolicy() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic " + JOHN_BASIC_CREDS); + addBlockingHeaders(headers); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + AtomicInteger resultCount = new AtomicInteger(); + client.unaryCall(Security.Container.newBuilder().setText("woo-hoo").build()) + .subscribe().with(e -> { + if (!e.getIsOnEventLoop()) { + Assertions.fail("Secured method should be run on event loop"); + } + resultCount.incrementAndGet(); + }); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> resultCount.get() == 1); + } + + @Test + void shouldSecureBlockingUniEndpointWithBlockingHttpSecurityPolicy() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic " + JOHN_BASIC_CREDS); + addBlockingHeaders(headers); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + AtomicInteger resultCount = new AtomicInteger(); + client.unaryCallBlocking(Security.Container.newBuilder().setText("woo-hoo").build()) + .subscribe().with(e -> { + if (e.getIsOnEventLoop()) { + Assertions.fail("Secured method annotated with @Blocking should be executed on worker thread"); + } + resultCount.incrementAndGet(); + }); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> resultCount.get() == 1); + } + + @Test + void shouldSecureMultiEndpointWithBlockingHttpSecurityPolicy() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic " + PAUL_BASIC_CREDS); + addBlockingHeaders(headers); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + List results = new CopyOnWriteArrayList<>(); + client.streamCall(Multi.createBy().repeating() + .supplier(() -> (Security.Container.newBuilder().setText("woo-hoo").build())).atMost(4)) + .subscribe().with(e -> { + results.add(e.getIsOnEventLoop()); + }); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> results.size() == 5); + + assertThat(results.stream().filter(e -> !e)).isEmpty(); + } + + @Test + void shouldSecureBlockingMultiEndpointWithBlockingHttpSecurityPolicy() { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION, "Basic " + PAUL_BASIC_CREDS); + addBlockingHeaders(headers); + SecuredService client = GrpcClientUtils.attachHeaders(securityClient, headers); + List results = new CopyOnWriteArrayList<>(); + client.streamCallBlocking(Multi.createBy().repeating() + .supplier(() -> (Security.Container.newBuilder().setText("woo-hoo").build())).atMost(4)) + .subscribe().with(e -> results.add(e.getIsOnEventLoop())); + + await().atMost(10, TimeUnit.SECONDS) + .until(() -> results.size() == 5); + + assertThat(results.stream().filter(e -> e)).isEmpty(); + } + + private static void addBlockingHeaders(Metadata headers) { + headers.put(BLOCK_REQUEST_KEY, "ignored"); + } + + @GrpcService + public static class Service implements SecuredService { + @Override + @RolesAllowed("employees") + public Uni unaryCall(Security.Container request) { + return Uni.createFrom() + .item(newBuilder().setIsOnEventLoop(Context.isOnEventLoopThread()).build()); + } + + @Override + @RolesAllowed("interns") + public Multi streamCall(Multi request) { + return Multi.createBy() + .repeating().supplier(() -> newBuilder().setIsOnEventLoop(Context.isOnEventLoopThread()).build()) + .atMost(5); + } + + @Blocking + @Override + @RolesAllowed("employees") + public Uni unaryCallBlocking(Security.Container request) { + return Uni.createFrom() + .item(newBuilder().setIsOnEventLoop(Context.isOnEventLoopThread()).build()); + } + + @Blocking + @Override + @RolesAllowed("interns") + public Multi streamCallBlocking(Multi request) { + return Multi.createBy() + .repeating().supplier(() -> newBuilder().setIsOnEventLoop(Context.isOnEventLoopThread()).build()) + .atMost(5); + } + } + + @Singleton + public static class BasicGrpcSecurityMechanism implements GrpcSecurityMechanism { + @Override + public boolean handles(Metadata metadata) { + String authString = metadata.get(AUTHORIZATION); + return authString != null && authString.startsWith("Basic "); + } + + @Override + public AuthenticationRequest createAuthenticationRequest(Metadata metadata) { + String authString = metadata.get(AUTHORIZATION); + authString = authString.substring("Basic ".length()); + byte[] decode = Base64.getDecoder().decode(authString); + String plainChallenge = new String(decode, StandardCharsets.UTF_8); + int colonPos; + if ((colonPos = plainChallenge.indexOf(':')) > -1) { + String userName = plainChallenge.substring(0, colonPos); + char[] password = plainChallenge.substring(colonPos + 1).toCharArray(); + return new UsernamePasswordAuthenticationRequest(userName, new PasswordCredential(password)); + } else { + return null; + } + } + } +} diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthUsingSeparatePortTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthUsingSeparatePortTest.java new file mode 100644 index 0000000000000..f16d44b2da1c7 --- /dev/null +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthUsingSeparatePortTest.java @@ -0,0 +1,14 @@ +package io.quarkus.grpc.auth; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class GrpcAuthUsingSeparatePortTest extends GrpcAuthTestBase { + + @RegisterExtension + static final QuarkusUnitTest config = createQuarkusUnitTest("quarkus.grpc.server.use-separate-server=false\n" + + "quarkus.grpc.clients.securityClient.host=localhost\n" + + "quarkus.grpc.clients.securityClient.port=8081\n"); + +} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java index 154ae53838db7..7217ae4c5fec5 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java @@ -61,6 +61,7 @@ import io.quarkus.vertx.http.runtime.PortSystemProperties; import io.vertx.core.AbstractVerticle; import io.vertx.core.AsyncResult; +import io.vertx.core.Context; import io.vertx.core.DeploymentOptions; import io.vertx.core.Handler; import io.vertx.core.Promise; @@ -183,6 +184,18 @@ private void buildGrpcServer(Vertx vertx, GrpcServerConfiguration configuration, if (!isGrpc(ctx)) { ctx.next(); } else { + if (!Context.isOnEventLoopThread()) { + Context capturedVertxContext = Vertx.currentContext(); + if (capturedVertxContext != null) { + capturedVertxContext.runOnContext(new Handler() { + @Override + public void handle(Void unused) { + server.handle(ctx.request()); + } + }); + return; + } + } server.handle(ctx.request()); } });