Quarkus: Quarkus Security: @RolesAllowed causes crash on class but works on method when using CompletionStage

Created on 20 Feb 2020  路  17Comments  路  Source: quarkusio/quarkus

Describe the bug
We are using the quarkus-elytron-security-properties-file extension and added the @RolesAllowed annotation to a the GreetingResource from the quarkus kotlin example. At first this works fine. However, as soon as you return a CompletionStage<Response> instead of the response directly, the exception below is thrown.

However, if you attach the @RolesAllowed annotation to the hello() method, the code works just fine.

Exception:

2020-02-20 09:57:00,833 ERROR [org.jbo.res.res.i18n] (executor-thread-2) RESTEASY002020: Unhandled asynchronous exception, sending back 500: javax.enterprise.context.ContextNotActiveException: interface javax.enterprise.context.RequestScoped
        at io.quarkus.security.runtime.SecurityIdentityProxy_ClientProxy.arc$delegate(SecurityIdentityProxy_ClientProxy.zig:368)
        at io.quarkus.security.runtime.SecurityIdentityProxy_ClientProxy.getRoles(SecurityIdentityProxy_ClientProxy.zig:459)
        at io.quarkus.security.runtime.interceptor.check.RolesAllowedCheck.apply(RolesAllowedCheck.java:54)
        at io.quarkus.security.runtime.interceptor.SecurityConstrainer.check(SecurityConstrainer.java:27)
        at io.quarkus.security.runtime.interceptor.SecurityHandler.handle(SecurityHandler.java:23)
        at io.quarkus.security.runtime.interceptor.RolesAllowedInterceptor.intercept(RolesAllowedInterceptor.java:23)
        at io.quarkus.security.runtime.interceptor.RolesAllowedInterceptor_Bean.intercept(RolesAllowedInterceptor_Bean.zig:144)
        at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
        at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
        at org.acme.rest.GreetingResource_Subclass.getService(GreetingResource_Subclass.zig:408)
        at org.acme.rest.GreetingResource$hello$1.get(GreetingResource.kt:26)
        at org.acme.rest.GreetingResource$hello$1.get(GreetingResource.kt:17)
        at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1771)
        at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1763)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1016)
        at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1665)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1598)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)

Expected behavior
When the class is annotated with the @RolesAllowed annotation it should behave as when every method in the class is annotated with this method. Furthermore, it should behave the same with and without using a CompletionStage in the result.

Actual behavior
It crashes when you use @RolesAllowed on the class with CompletionStage results in the methods but works if you annotate the methods or do not use async results.

To Reproduce
Steps to reproduce the behavior:

  1. Download the reproducer project
    rest-security-bug.zip and unzip it.
  2. Start the application by calling ./mvnw compile quarkus:dev in the project`s diretory.
  3. Make a GET request to http://localhost:8080/greeting with basic auth credentials: user: scott, password: test
  4. The request should fail with the exception shown above.
  5. If you comment out line GreetingResource:16 and comment in line GreetingResource:24 and run the request again, the code should work.

Environment (please complete the following information):

  • macOS, Windows 10
  • java 8, java 11
  • Quarkus 1.2.0.Final, 1.2.1.Final, 1.3.0.Alpha2
arekotlin aresecurity kinbug

Most helpful comment

Thanks for checking!

I actually think that at some point we'll need to do a blog post about Quarkus + Kotlin containing the various pitfalls and best practices...

All 17 comments

There is nothing directly we can do here, except enhance the documentation. To make this work you need to:

  • Add quarkus-smallrye-context-propagation
  • Inject a ManagedExecutor
  • use ManagedExecutor.supplyAsync rather than CompletableFuture.supplyAsync so we can propagate the identity.

@stuartwdouglas: I don't understand. The annotation works, when it is placed on the method but it doesn't, when it is placed on the class. I would expect the annotation on the class to be just as good as placing the annotation on every method. Why is this behaving differently?

Kotlin is generating a getService method to access the service field. If you put it on the method then only the method is secured. If you put it on the class then the generated getService field is also secured.

@mkouba I wonder if we should be applying interceptors to synthetic methods?

That's a good question. But I don't have a good answer. From the spec POV it's undefined and I think that there might be use cases where it's desirable (something like panache) and use cases like this ^.

Maybe we could add something kotlin specific. It feels wrong that field access is being intercepted.

I do think that something Kotlin specific is the way to go here...

It feels wrong that field access is being intercepted.

Regulat getters would be intercepted too.

...something Kotlin specific is the way to go...

Is there a way to detect a class compiled by kotlin?

Yes (AFAIK). Kotlin classes always have a kotlin.Metadata class annotation

Ok, let's try it ;-).

So, should we open this issue again to keep track of the progress? And thanks @mkouba for looking into it more!

@stuartwdouglas @geoand So it seems that the getters/setters generated by Kotlin are not synthetic, this is what javap shows:

public org.acme.rest.GreetingService getService();
    descriptor: ()Lorg/acme/rest/GreetingService;
    flags: ACC_PUBLIC

I am pretty sure that is done for (Java) interoperability reasons

@andreas-eberle Unfortunately, I don't see a way to detect the generated getters/setters for kotlin classes and so we're no able to skip such methods when generating the intercepted subclass. Let us know if you're aware of any other way how to detect those methods...

To be honest, I don't really understand the underlying problem here. Are you saying that the code would work if I used a private val initialized via the constructor? Then no getters and setters should be generated, right?

How are other frameworks handling this? E.g. Spring Boot? They have a very good kotlin integration and afaik they allow using the @RolesAllowed annotation as expected. Maybe they found some ways to handle problems like this?

@andreas-eberle Your case should work just fine if you make the field private (you can of course additionally opt for constructor injection).
This should work because Kotlin will not generate a getter for you.

As for the Spring Boot comparison: The reason it works there is because method calls within a class are not intercepted. In this case it's advantageous, but in a lot of other cases it isn't.
So in this case it works by accident for Spring Boot, not by design (unless I am missing something)

OK, I tested using the constructor injection and it actually works. I guess that should then be documented (it then also works without the content propagation). I'm quite busy atm, so I won't be able to do that atm.

Thanks for checking!

I actually think that at some point we'll need to do a blog post about Quarkus + Kotlin containing the various pitfalls and best practices...

Was this page helpful?
0 / 5 - 0 ratings