quarkusio / quarkus

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

NPE on oidc-client when quarkus.oidc-client.grant-options.password.password not provided #38504

Closed IanPUiUk closed 8 months ago

IanPUiUk commented 9 months ago

Describe the bug

I have a @RestClient bean that uses @OidcFilter to configure a token to use when making calls and so uses oidc-client.

When starting quarkus if quarkus.oidc-client.grant.type = password and quarkus.oidc-client.grant-options.password.password is not set then an NPE is thrown at startup...

2024-01-31 13:58:52,338 ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application (with profile [dev]): java.lang.RuntimeException: Failed to start quarkus
    at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
    at io.quarkus.runtime.Application.start(Application.java:101)
    at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:111)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:71)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
    at io.quarkus.runner.GeneratedMain.main(Unknown Source)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:104)
    at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:233)
    at io.vertx.core.http.impl.headers.HeadersMultiMap.add(HeadersMultiMap.java:114)
    at io.vertx.core.http.impl.headers.HeadersMultiMap.add(HeadersMultiMap.java:133)
    at io.vertx.core.http.impl.headers.HeadersMultiMap.add(HeadersMultiMap.java:44)
    at io.vertx.mutiny.core.MultiMap.add(MultiMap.java:152)
    at io.quarkus.oidc.client.runtime.OidcClientRecorder$5.apply(OidcClientRecorder.java:170)
    at io.quarkus.oidc.client.runtime.OidcClientRecorder$5.apply(OidcClientRecorder.java:140)
    at io.smallrye.context.impl.wrappers.SlowContextualBiFunction.apply(SlowContextualBiFunction.java:21)
    at io.smallrye.mutiny.operators.uni.UniOnItemOrFailureMap$UniOnItemOrFailureMapProcessor.onItem(UniOnItemOrFailureMap.java:37)
    at io.smallrye.mutiny.operators.uni.UniOnItemTransform$UniOnItemTransformProcessor.onItem(UniOnItemTransform.java:43)
    at io.smallrye.mutiny.operators.uni.UniOperatorProcessor.onItem(UniOperatorProcessor.java:47)
    at io.smallrye.mutiny.operators.uni.builders.UniCreateFromPublisher$PublisherSubscriber.onNext(UniCreateFromPublisher.java:70)
    at io.smallrye.mutiny.helpers.HalfSerializer.onNext(HalfSerializer.java:30)
    at io.smallrye.mutiny.helpers.StrictMultiSubscriber.onItem(StrictMultiSubscriber.java:84)
    at io.smallrye.mutiny.subscription.MultiSubscriber.onNext(MultiSubscriber.java:61)
    at io.smallrye.mutiny.subscription.SerializedSubscriber.onItem(SerializedSubscriber.java:74)
    at io.smallrye.mutiny.operators.multi.MultiRetryWhenOp$RetryWhenOperator.onItem(MultiRetryWhenOp.java:111)
    at io.smallrye.mutiny.subscription.MultiSubscriber.onNext(MultiSubscriber.java:61)
    at io.smallrye.mutiny.converters.uni.UniToMultiPublisher$UniToMultiSubscription.onItem(UniToMultiPublisher.java:92)
    at io.smallrye.mutiny.operators.uni.UniOnItemTransform$UniOnItemTransformProcessor.onItem(UniOnItemTransform.java:43)
    at io.smallrye.mutiny.vertx.AsyncResultUni.lambda$subscribe$1(AsyncResultUni.java:35)
    at io.smallrye.mutiny.vertx.DelegatingHandler.handle(DelegatingHandler.java:25)
    at io.vertx.ext.web.client.impl.HttpContext.handleDispatchResponse(HttpContext.java:397)
    at io.vertx.ext.web.client.impl.HttpContext.execute(HttpContext.java:384)
    at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:362)
    at io.vertx.ext.web.client.impl.HttpContext.fire(HttpContext.java:329)
    at io.vertx.ext.web.client.impl.HttpContext.dispatchResponse(HttpContext.java:291)
    at io.vertx.ext.web.client.impl.HttpContext.lambda$null$7(HttpContext.java:507)
    at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:277)
    at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:259)
    at io.vertx.core.impl.EventLoopContext.lambda$runOnContext$0(EventLoopContext.java:43)
    at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:566)
    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)
    ... 1 more

Expected behavior

The missing config value should be reported

Actual behavior

A NullPointerException is thrown

How to Reproduce?

@Produces(MediaType.APPLICATION_JSON)
@OidcClientFilter
@RegisterRestClient(configKey = "play-api")
@ApplicationScoped
public interface PlayService {
    @Path("/hello")
    @GET
    String getHello();
}
@ApplicationScoped
public class UsesClientResource {

    @RestClient
    PlayService playService;

    @RolesAllowed({"admin"})
    @Override
    public String sayHello(UserDto user) {
        return playService.getHello();
    }
}

application.yaml

quarkus:
  http:
    port: 8484
    root-path: /npe-test
    non-application-root-path: /q
  resteasy:
    path: /api
  package:
    type: uber-jar
  oidc:
    auth-server-url: https://aserver/auth/realms/arealm
  oidc-client:
    auth-server-url: https://aserver/auth/realms/arealm
  rest-client:
    play-api:
      url: https://aserver/auth/admin/realms/arealm

"%dev":
  quarkus:
    http:
      port: 8484
    # Client credentials for play admin actions
    oidc-client:
      client-id: admim-client
      grant:
        type: password
      grant-options:
        password:
          username: admin
# Uncomment line below to remove NPE, comment out to cause it
#        password: missing

    # Base path to play REST API
    rest-client:
      play-api:
        url: https://aserver/api/play

Output of uname -a or ver

windows 11

Output of java -version

21

Quarkus version or git rev

3.2.9.Final

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

maven 3.9.5 through IntelliJ

Additional information

No response

quarkus-bot[bot] commented 9 months ago

/cc @pedroigor (oidc), @sberyozkin (oidc)

IanPUiUk commented 9 months ago

Also it seems like setting an env var for the value isn't getting picked up. If i set QUARKUS_OIDC_CLIENT_GRANT_OPTIONS_PASSWORD_PASSWORD to have a value i the environment I still get the NPE

sberyozkin commented 9 months ago

@IanPUiUk

Also it seems like setting an env var for the value isn't getting picked up. If i set QUARKUS_OIDC_CLIENT_GRANT_OPTIONS_PASSWORD_PASSWORD to have a value i the environment I still get the NPE

AFAIK, due to a prefix in the oidc-client config root, one needs to have a property declared, at least without the value, like password: in your YAML fragment, CC @radcortez

Agreed about enforcing that the password grant password must be set

IanPUiUk commented 9 months ago

Another finding that may help: If I use an exact match env var it works fine:

quarkus.oidc-client.grant-optios.password.password=thepassword