quarkusio / quarkus

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

SecurityIdentity.getPrincipal will fail due to blocking when used with @TestSecurity & @RunOnVertxContext #34074

Closed allertonm closed 1 year ago

allertonm commented 1 year ago

Describe the bug

If a test is annotated with @QuarkusTest, @TestSecurity & @RunOnVertxContext, and injected with SecurityIdentity, calling getPrincipal on the injected identity will fail due to attempting to block in a Vertx context.

Expected behavior

SecurityIdentity.getPrincipal returns a principal

Actual behavior

SecurityIdentity.getPrincipal throws an exception

How to Reproduce?

Run the following test (this test can also be found as part of this project https://github.com/allertonm/quarkus-coroutine-interceptor-example/blob/main/src/test/kotlin/com/example/FooTest.kt)

package com.example

import io.quarkus.security.identity.SecurityIdentity
import io.quarkus.test.junit.QuarkusTest
import io.quarkus.test.security.TestSecurity
import io.quarkus.test.vertx.RunOnVertxContext
import io.quarkus.test.vertx.UniAsserter
import jakarta.inject.Inject
import org.junit.jupiter.api.Test

@QuarkusTest
@RunOnVertxContext
@TestSecurity(user = "helmut")
class FooTest {
    @Inject
    lateinit var securityIdentity: SecurityIdentity

    @Test
    fun test(asserter: UniAsserter) {
        val principal1 = securityIdentity.principal
        println(principal1.name)
        asserter.execute {
            val principal2 = securityIdentity.principal
            println(principal2.name)
        }
        println("Hello")
    }
}

Running this test will throw the following exception

The current thread cannot be blocked: vert.x-eventloop-thread-2
java.lang.IllegalStateException: The current thread cannot be blocked: vert.x-eventloop-thread-2
    at io.smallrye.mutiny.operators.uni.UniBlockingAwait.await(UniBlockingAwait.java:30)
    at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:65)
    at io.smallrye.mutiny.groups.UniAwait.indefinitely(UniAwait.java:46)
    at io.quarkus.security.runtime.SecurityIdentityAssociation.getIdentity(SecurityIdentityAssociation.java:77)
    at io.quarkus.test.security.DelegateSecurityIdentityAssociation_ClientProxy.getIdentity(Unknown Source)
    at io.quarkus.test.security.TestIdentityAssociation.getIdentity(TestIdentityAssociation.java:72)
    at io.quarkus.test.security.TestIdentityAssociation_ClientProxy.getIdentity(Unknown Source)
    at io.quarkus.security.runtime.SecurityIdentityProxy.getPrincipal(SecurityIdentityProxy.java:24)
    at io.quarkus.security.runtime.SecurityIdentityProxy_ClientProxy.getPrincipal(Unknown Source)
    at com.example.FooTest.test(FooTest.kt:20)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at io.quarkus.test.vertx.RunOnVertxContextTestMethodInvoker$RunTestMethodOnContextHandler.doRun(RunOnVertxContextTestMethodInvoker.java:149)
    at io.quarkus.test.vertx.RunOnVertxContextTestMethodInvoker$RunTestMethodOnContextHandler.handle(RunOnVertxContextTestMethodInvoker.java:138)
    at io.quarkus.test.vertx.RunOnVertxContextTestMethodInvoker$RunTestMethodOnContextHandler.handle(RunOnVertxContextTestMethodInvoker.java:109)
    at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:264)
    at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:246)
    at io.vertx.core.impl.EventLoopContext.lambda$runOnContext$0(EventLoopContext.java:43)
    at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569)
    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)

Output of uname -a or ver

Darwin M-L10KJGH6 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:51:50 PDT 2023; root:xnu-8796.121.2~5/RELEASE_X86_64 x86_64

Output of java -version

openjdk version "17.0.7" 2023-04-18 LTS OpenJDK Runtime Environment Corretto-17.0.7.7.1 (build 17.0.7+7-LTS) OpenJDK 64-Bit Server VM Corretto-17.0.7.7.1 (build 17.0.7+7-LTS, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

3.1.1.Final

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

No response

Additional information

No response

quarkus-bot[bot] commented 1 year ago

/cc @evanchooly (kotlin), @geoand (kotlin), @sberyozkin (security)

sberyozkin commented 1 year ago

@allertonm As far as I understand you can't access it outside of the asserter, does val principal2 = securityIdentity.principal work ?

Otherwise, since it is run on the Vert.x context, non-blocking, the advice from https://quarkus.io/guides/security-proactive-authentication-concept should help, io.quarkus.security.identity.CurrentIdentityAssociation and Uni<SecurityIdentity> should be used. CC @michalvavrik

allertonm commented 1 year ago

Neither case works, for what it's worth.

Thanks for the reference, it looks like we were just lucky not to hit this previously.

michalvavrik commented 1 year ago

@allertonm you can't block on IO thread, so as @sberyozkin said when running on IO thread you will need to use io.quarkus.security.identity.CurrentIdentityAssociation#getDeferredIdentity.

allertonm commented 1 year ago

@michalvavrik Understood. I was aware of not being able to block, but the surprise was that access to the SecurityIdentity was blocking. In practice this only broke in tests, which previously were not running on Vertx contexts but have needed to change to match the Quarkus 3 Panache behaviour.

allertonm commented 1 year ago

Closing as resolved due to this being documented behaviour.