quarkusio / quarkus

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

Keycloak admin client - error response body is unreachable #42577

Open majugurci opened 3 months ago

majugurci commented 3 months ago

Describe the bug

When using Quarkus with dependency quarkus-keycloak-admin-client-reactive and invoking some method which does not return Response how do I get real reason why invocation has failed?

For example, if I invoke a method to update user:

UserResource userResource = fetchUserById(userId); userResource.update(user);

I get the following exception stack trace:

ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /rest/v1/change-username failed, error id: d149a840-21bb-454f-97d9-50af5dc70359-1: org.jboss.resteasy.reactive.ClientWebApplicationException: Received: 'Server response is: 400' when invoking: Rest Client method: 'org.keycloak.admin.client.resource.UserResource#update'
        at org.jboss.resteasy.reactive.client.impl.RestClientRequestContext.unwrapException(RestClientRequestContext.java:195)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.handleException(AbstractResteasyReactiveContext.java:331)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:175)
        at org.jboss.resteasy.reactive.client.impl.RestClientRequestContext$1.lambda$execute$0(RestClientRequestContext.java:314)
        at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:279)
        at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:261)
        at io.vertx.core.impl.ContextInternal.lambda$runOnContext$0(ContextInternal.java:59)
        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)
        at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: org.jboss.resteasy.reactive.client.api.WebClientApplicationException: Server response is: 400
        at org.jboss.resteasy.reactive.client.handlers.ClientSetResponseEntityRestHandler.handle(ClientSetResponseEntityRestHandler.java:32)
        at org.jboss.resteasy.reactive.client.handlers.ClientSetResponseEntityRestHandler.handle(ClientSetResponseEntityRestHandler.java:23)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.invokeHandler(AbstractResteasyReactiveContext.java:231)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
        ... 12 more

Trying to get entity from ClientWebApplicationException or WebApplicationException is giving me null.

But if I turn on the debug log for http request:

quarkus.rest-client.logging.scope=request-response
quarkus.rest-client.logging.body-limit=1024
quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG

I can see that http response has a body attached with a detail why request has failed. It's just that is is not mapped to an exception. For example:

DEBUG [org.jbo.res.rea.cli.log.DefaultClientLogger] (vert.x-eventloop-thread-0) Response: PUT https://keycloak-instance/admin/realms/realmName/users/da71b24c-eb88-4147-85a6-b172a093958f, Status[400 Bad Request], Headers[Date=Wed, 14 Aug 2024 20:35:50 GMT Content-Type=application/json Content-Length=92 Connection=keep-alive Referrer-Policy=no-referrer Strict-Transport-Security=max-age=31536000; includeSubDomains X-Content-Type-Options=nosniff X-Frame-Options=SAMEORIGIN X-XSS-Protection=1; mode=block], Body:
{"field":"username","errorMessage":"error-username-invalid-character","params":["username"]}

But if I create a user:

Response response = keycloak.realm("realmName").users().create(user);

I can read the entity from response which gives me reason why request have failed.

Does anyone know how to get response body when doing the user update?

I have tried implementing custom exception mappers but guess they don't work because it is handled by rest client.

Expected behavior

No response

Actual behavior

No response

How to Reproduce?

No response

Output of uname -a or ver

No response

Output of java -version

No response

Quarkus version or git rev

No response

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

No response

Additional information

No response

quarkus-bot[bot] commented 3 months ago

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

sberyozkin commented 3 months ago

@majugurci May be ClientResponseFilter can help ? In general, as far as I know, the error body is not returned with exceptions to avoid it being leaked by accident back to the client of the Quarkus application which makes a REST client call. Also CC @geoand

majugurci commented 3 months ago

@sberyozkin I have tried with ClientResponseFilter and custom exception mapper but it seems it is not used with keycloak rest client.

If I register ClientResponseFilter or custom exception mapper with @ Provider annotation than it's methods are called for rest clients which I have in my application but not for the keycloak rest client.

I have also tried registering it through application.properties like this but it didn't help:

org.keycloak.admin.client.spi.ResteasyClientProvider/mp-rest/providers=org.filters.MyCustomFilter
or this
io.quarkus.keycloak.admin.client.reactive.runtime.ResteasyClientProvider/mp-rest/providers=org.filters.MyCustomFilter

EDIT: I guess it is related to this issue: https://github.com/quarkusio/quarkus/discussions/33332 Is there any other workaround I could try to get response body?

geoand commented 3 months ago

In general, as far as I know, the error body is not returned with exceptions to avoid it being leaked by accident back to the client of the Quarkus application which makes a REST client call. Also CC @geoand

Exactly correct

majugurci commented 3 months ago

In general, as far as I know, the error body is not returned with exceptions to avoid it being leaked by accident back to the client of the Quarkus application which makes a REST client call. Also CC @geoand

Exactly correct

Could this be made configurable?

We have a use case where we're managing users in our web app through Keycloak admin api. If there is an error while updating a user there is no error explanation so we can only show "Unknown error" in our web application. As you can see that is not really helpful in case of an error like username contains invalid characters or email already in use.

geoand commented 3 months ago

Could this be made configurable?

It's the server that does not return the data containing the error message

majugurci commented 3 months ago

It's the server that does not return the data containing the error message

By the server you mean the Keycloak server? If that is so I can confirm that Keycloak server is returning the error, I have attached debug log in original post.

geoand commented 3 months ago

Ah okay, then I'll leave this to @sberyozkin to explore options on the client part

majugurci commented 3 months ago

@geoand Maybe this issue shouldn't be closed if you are planning to fix it?