quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.58k stars 2.63k forks source link

BlockingOperationNotAllowedException When using quarkus-test-security + quarkus.security.jaxrs.deny-unannotated-endpoints = true #28791

Closed davidfrickert closed 1 year ago

davidfrickert commented 1 year ago

Describe the bug

It seems that quarkus.security.jaxrs.deny-unannotated-endpoints = true has some problems with quarkus-test-security. This only seems to be a problem on Resources with exception mappers inside the class.

Stacktrace:

Suppressed: io.quarkus.runtime.BlockingOperationNotAllowedException: Blocking security check attempted in code running on the event loop. Make the secured method return an async type, i.e. Uni, Multi or CompletionStage, or use an authentication mechanism that sets the SecurityIdentity in a blocking manner prior to delegating the call
    at io.quarkus.security.runtime.interceptor.SecurityConstrainer.check(SecurityConstrainer.java:34)
    at io.quarkus.security.runtime.interceptor.SecurityHandler.handle(SecurityHandler.java:46)
    at io.quarkus.security.runtime.interceptor.DenyAllInterceptor.intercept(DenyAllInterceptor.java:30)
    at io.quarkus.security.runtime.interceptor.DenyAllInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:42)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:33)
    at com.dfrickert.rest.resources.func.RestService_Subclass.authenticationFailedHandler(Unknown Source)
    at com.dfrickert.rest.resources.func.RestService$GeneratedExceptionHandlerFor$UnauthorizedException$OfMethod$authenticationFailedHandler.toResponse(Unknown Source)
    at com.dfrickert.rest.resources.func.RestService$GeneratedExceptionHandlerFor$UnauthorizedException$OfMethod$authenticationFailedHandler.toResponse(Unknown Source)
    at org.jboss.resteasy.reactive.server.core.RuntimeExceptionMapper.mapException(RuntimeExceptionMapper.java:93)
    at org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext.mapExceptionIfPresent(ResteasyReactiveRequestContext.java:342)
    at org.jboss.resteasy.reactive.server.handlers.ExceptionHandler.handle(ExceptionHandler.java:15)
    at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:124)
    at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:142)
    at org.jboss.resteasy.reactive.server.handlers.RestInitialHandler.beginProcessing(RestInitialHandler.java:51)
    at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:18)
    at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:8)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
    at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:84)
    at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:71)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$6.handle(VertxHttpRecorder.java:430)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$6.handle(VertxHttpRecorder.java:408)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
    at io.quarkus.vertx.http.runtime.security.HttpAuthorizer.doPermissionCheck(HttpAuthorizer.java:117)
    at io.quarkus.vertx.http.runtime.security.HttpAuthorizer.checkPermission(HttpAuthorizer.java:100)
    at io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder$3.handle(HttpSecurityRecorder.java:233)
    at io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder$3.handle(HttpSecurityRecorder.java:225)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
    at io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder$2.handle(HttpSecurityRecorder.java:206)
    at io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder$2.handle(HttpSecurityRecorder.java:60)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:173)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
    at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:68)
    at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:37)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$15.handle(VertxHttpRecorder.java:606)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$15.handle(VertxHttpRecorder.java:589)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$1.handle(VertxHttpRecorder.java:185)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$1.handle(VertxHttpRecorder.java:160)
    at io.vertx.core.http.impl.Http1xServerRequestHandler.handle(Http1xServerRequestHandler.java:67)
    at io.vertx.core.http.impl.Http1xServerRequestHandler.handle(Http1xServerRequestHandler.java:30)
    at io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:55)
    at io.vertx.core.impl.DuplicatedContext.emit(DuplicatedContext.java:158)
    at io.vertx.core.http.impl.Http1xServerConnection.handleMessage(Http1xServerConnection.java:145)
    at io.vertx.core.net.impl.ConnectionBase.read(ConnectionBase.java:157)
    at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:153)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
    at io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler.channelRead(WebSocketServerExtensionHandler.java:99)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.vertx.core.http.impl.Http1xUpgradeToH2CHandler.channelRead(Http1xUpgradeToH2CHandler.java:116)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:336)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:444)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:280)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.vertx.core.http.impl.Http1xOrH2CHandler.end(Http1xOrH2CHandler.java:61)
    at io.vertx.core.http.impl.Http1xOrH2CHandler.channelRead(Http1xOrH2CHandler.java:38)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:833)
    Caused by: io.quarkus.runtime.BlockingOperationNotAllowedException: Cannot call getIdentity() from the IO thread when lazy authentication is in use, as resolving the identity may block the thread. Instead you should inject the CurrentIdentityAssociation, call CurrentIdentityAssociation#getDeferredIdentity() and subscribe to the Uni.
    at io.quarkus.security.runtime.SecurityIdentityAssociation.getIdentity(SecurityIdentityAssociation.java:68)
    at io.quarkus.test.security.DelegateSecurityIdentityAssociation_ClientProxy.getIdentity(Unknown Source)
    at io.quarkus.test.security.TestIdentityAssociation.getIdentity(TestIdentityAssociation.java:71)
    at io.quarkus.test.security.TestIdentityAssociation_ClientProxy.getIdentity(Unknown Source)
    at io.quarkus.security.runtime.interceptor.SecurityConstrainer.check(SecurityConstrainer.java:32)
        ... 100 more

Expected behavior

Test returns appropriate status code depending on the test case (401/403)

Actual behavior

Test fails with HTTP 500

How to Reproduce?

https://github.com/davidfrickert/quarkus-issue-reproducer/tree/security-test-resource-exception-mappers-auth

Run test GreetingResourceImplTest. Expected 403, actually is 500 with the error on the issue description.

Output of uname -a or ver

Linux dfrickert 5.15.74-4-MANJARO #1 SMP PREEMPT Sat Oct 15 18:49:48 UTC 2022 x86_64 GNU/Linux

Output of java -version

openjdk version "17.0.5" 2022-10-18 OpenJDK Runtime Environment (build 17.0.5+1) OpenJDK 64-Bit Server VM (build 17.0.5+1, mixed mode)

GraalVM version (if different from Java)

N/A

Quarkus version or git rev

2.13.3.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63) Maven home: /opt/maven Java version: 17.0.5, vendor: N/A, runtime: /usr/lib/jvm/java-17-openjdk Default locale: en_US, platform encoding: UTF-8 OS name: "linux", version: "5.15.74-4-manjaro", arch: "amd64", family: "unix"

Additional information

No response

quarkus-bot[bot] commented 1 year ago

/cc @sberyozkin

davidfrickert commented 1 year ago

(Altough the reproducer doesn't have one, I have no problem testing a resource that has no custom exception mappers in it)

sberyozkin commented 1 year ago

@michalvavrik Hey Michal, you are an expert now in this area :-), have a look when you get a chance please

michalvavrik commented 1 year ago

@sberyozkin :-D I'll look today or tomorrow (when time allows).

michalvavrik commented 1 year ago

io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#setUpDenyAllJaxRs adds DenyAll (and RolesAllowed for default-roles-allowed) around exception mappers methods and judging by ServerExceptionMapper annotation Javadoc, it's completely valid to have them in the resource. I'll rewrite the way we identify resource methods there.

UPDATE: looks like it also affect classic one and any resource method that can be intercepted, not just exception mappers.

sberyozkin commented 1 year ago

@michalvavrik Hi Michal, please take your time, IMHO it is not an urgent issue

plevart commented 11 months ago

Hi, I'm experiencing the same exception in Quarkus 3.2.4. I do use custom exception mapper in the reactive REST endpoint (via the annotated method in the endpoint class), but I don't do anything special in this method - I don't access SecurityIdentity in it or such - just plain java logic without calls to any services or similar.

This exception is triggered by a client invoking an @Authenticated endpoint, but not providing Bearer token (using OIDC authentication in service mode). The correct behaviour would be UNAUTHORIZED or FORBIDDEN, but I get INTERNAL SERVER ERROR due to this problem. Correctly authenticated requests have no problem though.

This started happening after some changes in the app, namely:

I have a feeling that this last change is the culprit.

michalvavrik commented 11 months ago

@plevart I'll need reproducer, can you open issue and ping me there, please? creating one based on your feedback would require a lot of guessing, therefore more time.

michalvavrik commented 11 months ago

I have a feeling that this last change is the culprit.

absolutely, it shouldn't be necessary unless I'm something missing