diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 78f8e0d1f088e..7dc92938f2cba 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -93,7 +93,7 @@ 2.19.2 1.0.0.Final 3.18.0 - 1.18.0 + 1.19.0 1.7.0 @@ -195,7 +195,7 @@ 2.25.1 1.3.1.Final 1.12.0 - 2.6.12.Final + 2.6.13.Final 0.1.18.Final 1.21.3 3.4.2 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java index 9a4290d017c26..44e2e547975ca 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java @@ -101,8 +101,6 @@ import io.quarkus.runtime.configuration.RuntimeOverrideConfigSourceBuilder; import io.quarkus.runtime.configuration.StaticInitConfigBuilder; import io.smallrye.config.ConfigMappingInterface; -import io.smallrye.config.ConfigMappingLoader; -import io.smallrye.config.ConfigMappingMetadata; import io.smallrye.config.ConfigMappings; import io.smallrye.config.ConfigMappings.ConfigClass; import io.smallrye.config.ConfigSourceFactory; @@ -677,13 +675,6 @@ private static Map generateSharedConfig( clinit.writeStaticField(mappingField, clinit.invokeStaticMethod(CONFIG_CLASS, clinit.load(mapping.getType().getName()), clinit.load(mapping.getPrefix()))); - // Cache implementation types of nested elements - List configMappingsMetadata = ConfigMappingLoader - .getConfigMappingsMetadata(mapping.getType()); - for (ConfigMappingMetadata configMappingMetadata : configMappingsMetadata) { - clinit.invokeStaticMethod(ENSURE_LOADED, clinit.load(configMappingMetadata.getInterfaceType().getName())); - } - fields.put(mapping, mappingField); } diff --git a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc index 8e23abdf0e608..5a41ad288d808 100644 --- a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc +++ b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc @@ -336,7 +336,7 @@ quarkus.oidc.introspection-credentials.secret=introspection-user-secret [[code-flow-oidc-request-filters]] === OIDC request filters -You can filter OIDC requests made by Quarkus to the OIDC provider by registering one or more `OidcRequestFilter` implementations, which can update or add new request headers and can also log requests. +You can filter OIDC requests made by Quarkus to the OIDC provider by registering one or more `OidcRequestFilter` implementations, which can update or add new request headers, customize a request body and can also log requests. For example: @@ -401,6 +401,35 @@ public class OidcDiscoveryRequestCustomizer implements OidcRequestFilter { Currently, you can use a `tenand_id` key to access the OIDC tenant id and a `grant_type` key to access the grant type which the OIDC provider uses to acquire tokens. The `grant_type` can only be set to either `authorization_code` or `refresh_token` grant type, when requests are made to the token endpoint. It is `null` in all other cases. +`OidcRequestFilter` can customize a request body by preparing an instance of `io.vertx.mutiny.core.buffer.Buffer` +and setting it on a request context, for example: + +[source,java] +---- +package io.quarkus.it.keycloak; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.Unremovable; +import io.quarkus.oidc.common.OidcEndpoint; +import io.quarkus.oidc.common.OidcEndpoint.Type; +import io.quarkus.oidc.common.OidcRequestContextProperties; +import io.quarkus.oidc.common.OidcRequestFilter; +import io.vertx.mutiny.core.buffer.Buffer; + +@ApplicationScoped +@Unremovable +@OidcEndpoint(value = Type.TOKEN) +public class TokenRequestFilter implements OidcRequestFilter { + + @Override + public void filter(OidcRequestContext rc) { + // Add more required properties to the token request + rc.requestBody(Buffer.buffer(rc.requestBody().toString() + "&opaque_token_param=opaque_token_value")); + } +} +---- + [[code-flow-oidc-response-filters]] === OIDC response filters @@ -445,6 +474,40 @@ public class TokenEndpointResponseFilter implements OidcResponseFilter { <3> Use `OidcRequestContextProperties` request properties to check only an `authorization_code` token grant response for the `code-flow-user-info-cached-in-idtoken` tenant. <4> Confirm the response JSON contains an `id_token` property. +`OidcResponseFilter` can customize a response body by preparing an instance of `io.vertx.mutiny.core.buffer.Buffer` +and setting it on a response context, for example: + +[source,java] +---- +package io.quarkus.it.keycloak; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.Unremovable; +import io.quarkus.oidc.common.OidcEndpoint; +import io.quarkus.oidc.common.OidcEndpoint.Type; +import io.quarkus.oidc.common.OidcRequestContextProperties; +import io.quarkus.oidc.common.OidcResponseFilter; +import io.vertx.core.json.JsonObject; +import io.vertx.mutiny.core.buffer.Buffer; + +@ApplicationScoped +@Unremovable +@OidcEndpoint(value = Type.TOKEN) +public class TokenResponseFilter implements OidcResponseFilter { + + @Override + public void filter(OidcResponseContext rc) { + JsonObject body = rc.responseBody().toJsonObject(); + // JSON `scope` property has multiple values separated by a comma character. + // It must be replaced with a space character. + String scope = body.getString("scope"); + body.put("scope", scope.replace(",", " ")); + rc.responseBody(Buffer.buffer(body.toString())); + } +} +---- + === Redirecting to and from the OIDC provider When a user is redirected to the OIDC provider to authenticate, the redirect URL includes a `redirect_uri` query parameter, which indicates to the provider where the user has to be redirected to when the authentication is complete. diff --git a/docs/src/main/asciidoc/security-oidc-expanded-configuration.adoc b/docs/src/main/asciidoc/security-oidc-expanded-configuration.adoc index 97740c6bdc63b..151618d9931af 100644 --- a/docs/src/main/asciidoc/security-oidc-expanded-configuration.adoc +++ b/docs/src/main/asciidoc/security-oidc-expanded-configuration.adoc @@ -1235,13 +1235,13 @@ You can also intercept OIDC redirect requests. ==== OIDC requests -You can use OIDC request filters to observe requests, add additional headers, and set context properties for coordinating with OIDC response filters. +You can use OIDC request filters to observe requests, add additional headers, customize a request body, and set context properties for coordinating with OIDC response filters. Use xref:security-oidc-code-flow-authentication.adoc#code-flow-oidc-request-filters[quarkus.oidc.common.OidcRequestFilter] to implement a request filter and if necessary, restrict it to the specific OIDC endpoint or endpoints only with the `quarkus.oidc.common.OidcEndpoint` annotation. ==== OIDC responses -You can use OIDC response filters to observe responses, and use the context properties for coordinating with OIDC request filters. +You can use OIDC response filters to observe responses, customize a response body, and use the context properties for coordinating with OIDC request filters. Use xref:security-oidc-code-flow-authentication.adoc#code-flow-oidc-response-filters[quarkus.oidc.common.OidcResponseFilter] to implement a response filter and if necessary, restrict it to the specific OIDC endpoint or endpoints only with the `quarkus.oidc.common.OidcEndpoint` annotation. diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 93ab52666644f..b2c46ae1014e5 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -1220,7 +1220,7 @@ quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientRecorder".min-lev [[oidc-client-ref-oidc-request-filters]] == OIDC request filters -You can filter OIDC requests made by OIDC client to the OIDC provider by registering one or more `OidcRequestFilter` implementations, which can update or add new request headers, or analyze the request body. +You can filter OIDC requests made by OIDC client to the OIDC provider by registering one or more `OidcRequestFilter` implementations, which can update or add new request headers, customize or analyze the request body. You can have a single filter intercepting requests to all OIDC provider endpoints, or use an `@OidcEndpoint` annotation to apply this filter to requests to specific endpoints only. For example: @@ -1258,6 +1258,35 @@ public class OidcRequestCustomizer implements OidcRequestFilter { `OidcRequestContextProperties` can be used to access request properties. Currently, you can use a `client_id` key to access the client tenant id and a `grant_type` key to access the grant type which the OIDC client uses to acquire tokens. +`OidcRequestFilter` can customize a request body by preparing an instance of `io.vertx.mutiny.core.buffer.Buffer` +and setting it on a request context, for example: + +[source,java] +---- +package io.quarkus.it.keycloak; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.Unremovable; +import io.quarkus.oidc.common.OidcEndpoint; +import io.quarkus.oidc.common.OidcEndpoint.Type; +import io.quarkus.oidc.common.OidcRequestContextProperties; +import io.quarkus.oidc.common.OidcRequestFilter; +import io.vertx.mutiny.core.buffer.Buffer; + +@ApplicationScoped +@Unremovable +@OidcEndpoint(value = Type.TOKEN) +public class TokenRequestFilter implements OidcRequestFilter { + + @Override + public void filter(OidcRequestContext rc) { + // Add more required properties to the token request + rc.requestBody(rc.requestBody().toString() + "&opaque_token_param=opaque_token_value")); + } +} +---- + [[oidc-client-ref-oidc-response-filters]] == OIDC response filters @@ -1302,6 +1331,40 @@ public class TokenEndpointResponseFilter implements OidcResponseFilter { <3> Use `OidcRequestContextProperties` request properties to confirm it is a `refresh_grant` token grant response. <4> Confirm the response JSON contains a `refresh_token` property. +`OidcResponseFilter` can customize a response body by preparing an instance of `io.vertx.mutiny.core.buffer.Buffer` +and setting it as a property on a response context, for example: + +[source,java] +---- +package io.quarkus.it.keycloak; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.Unremovable; +import io.quarkus.oidc.common.OidcEndpoint; +import io.quarkus.oidc.common.OidcEndpoint.Type; +import io.quarkus.oidc.common.OidcRequestContextProperties; +import io.quarkus.oidc.common.OidcResponseFilter; +import io.vertx.core.json.JsonObject; +import io.vertx.mutiny.core.buffer.Buffer; + +@ApplicationScoped +@Unremovable +@OidcEndpoint(value = Type.TOKEN) +public class TokenResponseFilter implements OidcResponseFilter { + + @Override + public void filter(OidcResponseContext rc) { + JsonObject body = rc.responseBody().toJsonObject(); + // JSON `scope` property has multiple values separated by a comma character. + // It must be replaced with a space character. + String scope = body.getString("scope"); + body.put("scope", scope.replace(",", " ")); + rc.responseBody(Buffer.buffer(body.toString())); + } +} +---- + [[token-propagation-rest]] == Token Propagation for Quarkus REST diff --git a/docs/src/main/asciidoc/security-openid-connect-client-registration.adoc b/docs/src/main/asciidoc/security-openid-connect-client-registration.adoc index 1a90ca07d6234..e2cb6786ad7d9 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-registration.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-registration.adoc @@ -505,7 +505,9 @@ You can persist the already registered client's registration URI and registratio [[oidc-client-registration-oidc-request-filters]] == OIDC request filters -You can filter OIDC client registration and registered client requests registering one or more `OidcRequestFilter` implementations, which can update or add new request headers. For example, a filter can analyze the request body and add its digest as a new header value: +You can filter OIDC client registration and registered client requests registering one or more `OidcRequestFilter` implementations, which can update or add new request headers, as well as customize a request body. + +For example, a filter can analyze the request body and add its digest as a new header value: You can have a single filter intercepting all the OIDC registration and registered client requests, or use an `@OidcEndpoint` annotation to apply this filter to either OIDC registration or registered client endpoint responses only. For example: @@ -544,6 +546,40 @@ public class ClientRegistrationRequestFilter implements OidcRequestFilter { `OidcRequestContextProperties` can be used to access request properties. Currently, you can use a `client_id` key to access the client tenant id and a `grant_type` key to access the grant type which the OIDC client uses to acquire tokens. +`OidcRequestFilter` can customize a request body by preparing an instance of `io.vertx.mutiny.core.buffer.Buffer` +and setting it on a request context, for example: + +[source,java] +---- +package io.quarkus.it.keycloak; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.arc.Unremovable; +import io.quarkus.oidc.common.OidcEndpoint; +import io.quarkus.oidc.common.OidcEndpoint.Type; +import io.quarkus.oidc.common.OidcRequestContextProperties; +import io.quarkus.oidc.common.OidcRequestFilter; +import io.vertx.core.json.JsonObject; +import io.vertx.mutiny.core.buffer.Buffer; + +@ApplicationScoped +@Unremovable +@OidcEndpoint(value = Type.CLIENT_REGISTRATION) +public class ClientRegistrationReRequestFilter implements OidcRequestFilter { + + @Override + public void filter(OidcRequestContext rc) { + // Update the client name + JsonObject body = rc.requestBody().toJsonObject(); + if ("Dynamic Tenant Client".equals(body.getString("client_name"))) { + body.put("client_name", "Registered Dynamic Tenant Client"); + rc.requestBody(Buffer.buffer(body.toString())); + } + } +} +---- + [[oidc-client-registration-oidc-response-filters]] == OIDC response filters @@ -625,6 +661,42 @@ public class RegisteredClientResponseFilter implements OidcResponseFilter { <2> Check the response `Content-Type` header. <3> Confirm the client name property was updated. +`OidcResponseFilter` can customize a response body by preparing an instance of `io.vertx.mutiny.core.buffer.Buffer` +and setting it as a property on a response context, for example: + +[source,java] +---- +package io.quarkus.it.keycloak; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.logging.Logger; + +import io.quarkus.arc.Unremovable; +import io.quarkus.oidc.common.OidcEndpoint; +import io.quarkus.oidc.common.OidcEndpoint.Type; +import io.quarkus.oidc.common.OidcRequestContextProperties; +import io.quarkus.oidc.common.OidcResponseFilter; +import io.vertx.core.json.JsonObject; +import io.vertx.mutiny.core.buffer.Buffer; + +@ApplicationScoped +@Unremovable +@OidcEndpoint(value = Type.CLIENT_REGISTRATION) +public class ClientRegistrationResponseFilter implements OidcResponseFilter { + + @Override + public void filter(OidcResponseContext rc) { + // Update the client name + JsonObject body = rc.responseBody().toJsonObject(); + if ("Registered Dynamic Tenant Client".equals(body.getString("client_name"))) { + body.put("client_name", "Registered Dynamically Tenant Client"); + rc.responseContext(Buffer.buffer(body.toString())); + } + } +} +---- + [[configuration-reference]] == Configuration reference diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanGizmo2Adaptor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanGizmo2Adaptor.java new file mode 100644 index 0000000000000..61d3d26009804 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanGizmo2Adaptor.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.deployment; + +import java.util.function.Predicate; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.gizmo2.ClassOutput; +import io.smallrye.common.annotation.Experimental; + +@Experimental("Interim adapter class, to be replaced by an injection-based mechanism") +public class GeneratedBeanGizmo2Adaptor implements ClassOutput { + + private final BuildProducer classOutput; + private final Predicate applicationClassPredicate; + + public GeneratedBeanGizmo2Adaptor(BuildProducer classOutput) { + this(classOutput, new Predicate<>() { + + @Override + public boolean test(String t) { + return true; + } + }); + } + + public GeneratedBeanGizmo2Adaptor(BuildProducer classOutput, + Predicate applicationClassPredicate) { + this.classOutput = classOutput; + this.applicationClassPredicate = applicationClassPredicate; + } + + @Override + public void write(String className, byte[] bytes) { + String classNameWithoutSuffix = className.substring(0, className.length() - 6); + classOutput.produce(new GeneratedBeanBuildItem(classNameWithoutSuffix, bytes, null, + applicationClassPredicate.test(classNameWithoutSuffix))); + } + +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/properties/ConfigPropertiesTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/properties/ConfigPropertiesTest.java index 2d7211ddc7eb2..7a62bdaf973f2 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/properties/ConfigPropertiesTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/properties/ConfigPropertiesTest.java @@ -2,11 +2,14 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; + import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.hibernate.FlushMode; import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; @@ -32,7 +35,9 @@ public class ConfigPropertiesTest { .overrideConfigKey("quarkus.hibernate-orm.\"overrides\".packages", MyEntityForOverridesPU.class.getPackageName()) .overrideConfigKey("quarkus.hibernate-orm.\"overrides\".datasource", "") // Overrides to test that Quarkus configuration properties are taken into account - .overrideConfigKey("quarkus.hibernate-orm.\"overrides\".flush.mode", "always"); + .overrideConfigKey("quarkus.hibernate-orm.\"overrides\".flush.mode", "always") + .overrideConfigKey("quarkus.hibernate-orm.\"overrides\".database.extra-physical-table-types", + "MATERIALIZED VIEW,FOREIGN TABLE"); @Inject Session sessionForDefaultPU; @@ -48,4 +53,19 @@ public void propertiesAffectingSession() { assertThat(sessionForOverridesPU.getHibernateFlushMode()).isEqualTo(FlushMode.ALWAYS); } + @Test + @Transactional + public void extraPhysicalTableTypes() { + Object extraPhysicalTableTypes = sessionForOverridesPU.getProperties() + .get(AvailableSettings.EXTRA_PHYSICAL_TABLE_TYPES); + + assertThat(extraPhysicalTableTypes).isNotNull(); + assertThat(extraPhysicalTableTypes).isInstanceOf(List.class); + + @SuppressWarnings("unchecked") + List tableTypes = (List) extraPhysicalTableTypes; + + assertThat(tableTypes).containsExactly("MATERIALIZED VIEW", "FOREIGN TABLE"); + } + } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index f4faee639a721..0fa5c35e7ebdf 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -443,6 +443,14 @@ private static void injectDataSource(String persistenceUnitName, String dataSour private static void injectRuntimeConfiguration(HibernateOrmRuntimeConfigPersistenceUnit persistenceUnitConfig, Builder runtimeSettingsBuilder) { + + // Pass extraPhysicalTableTypes configuration + List extraPhysicalTableTypes = persistenceUnitConfig.schemaManagement().extraPhysicalTableTypes(); + if (extraPhysicalTableTypes != null && !extraPhysicalTableTypes.isEmpty()) { + String extraTableTypesStr = String.join(",", extraPhysicalTableTypes); + runtimeSettingsBuilder.put(AvailableSettings.EXTRA_PHYSICAL_TABLE_TYPES, extraTableTypesStr); + } + // Database runtimeSettingsBuilder.put(AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, persistenceUnitConfig.database().generation().generation() diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java index 14dae055b0313..141cfa4877660 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java @@ -1,5 +1,6 @@ package io.quarkus.hibernate.orm.runtime; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -161,6 +162,20 @@ interface HibernateOrmConfigPersistenceUnitSchemaManagement { */ @WithDefault("false") boolean haltOnError(); + + /** + * Additional database object types to include in schema management operations. + * + * By default, Hibernate ORM only considers tables and sequences when performing + * schema management operations. + * This setting allows you to specify additional database object types that should be included, + * such as "MATERIALIZED VIEW", "VIEW", or other database-specific object types. + * + * The exact supported values depend on the underlying database and dialect. + * + * @asciidoclet + */ + List<@WithConverter(TrimmedStringConverter.class) String> extraPhysicalTableTypes(); } @ConfigGroup diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java index f0e3d8ef56b69..23dd3ae0c822c 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java @@ -345,6 +345,13 @@ private void registerVertxAndPool(String persistenceUnitName, private static void injectRuntimeConfiguration(HibernateOrmRuntimeConfigPersistenceUnit persistenceUnitConfig, Builder runtimeSettingsBuilder) { + + // Pass extraPhysicalTableTypes configuration + List extraPhysicalTableTypes = persistenceUnitConfig.schemaManagement().extraPhysicalTableTypes(); + if (extraPhysicalTableTypes != null && !extraPhysicalTableTypes.isEmpty()) { + String extraTableTypesStr = String.join(",", extraPhysicalTableTypes); + runtimeSettingsBuilder.put(AvailableSettings.EXTRA_PHYSICAL_TABLE_TYPES, extraTableTypesStr); + } // Database runtimeSettingsBuilder.put(AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, persistenceUnitConfig.database().generation().generation() diff --git a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java index 0406ab7204afb..73eef24c8ea2a 100644 --- a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java +++ b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java @@ -3,6 +3,9 @@ import static org.jboss.jandex.AnnotationTarget.Kind.CLASS; import static org.jboss.jandex.AnnotationTarget.Kind.METHOD; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collection; @@ -41,7 +44,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; -import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; +import io.quarkus.arc.deployment.GeneratedBeanGizmo2Adaptor; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.impl.Reflections; @@ -57,11 +60,15 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo2.ClassOutput; +import io.quarkus.gizmo2.Const; +import io.quarkus.gizmo2.Expr; +import io.quarkus.gizmo2.Gizmo; +import io.quarkus.gizmo2.LocalVar; +import io.quarkus.gizmo2.ParamVar; +import io.quarkus.gizmo2.Reflection2Gizmo; +import io.quarkus.gizmo2.desc.ClassMethodDesc; +import io.quarkus.gizmo2.desc.MethodDesc; import io.quarkus.jackson.JacksonMixin; import io.quarkus.jackson.ObjectMapperCustomizer; import io.quarkus.jackson.runtime.ConfigurationCustomizer; @@ -100,6 +107,9 @@ public class JacksonProcessor { private static final String PARAMETER_NAMES_MODULE = "com.fasterxml.jackson.module.paramnames.ParameterNamesModule"; private static final DotName JACKSON_MIXIN = DotName.createSimple(JacksonMixin.class.getName()); + private static final MethodDesc OBJECT_MAPPER_REGISTER_MODULE_METHOD_DESC = MethodDesc.of(ObjectMapper.class, + "registerModule", ObjectMapper.class, Module.class); + // this list can probably be enriched with more modules private static final List MODULES_NAMES_TO_AUTO_REGISTER = Arrays.asList(TIME_MODULE, JDK8_MODULE, PARAMETER_NAMES_MODULE); @@ -377,79 +387,87 @@ void generateCustomizer(BuildProducer generatedBeans, return; } - ClassOutput classOutput = new GeneratedBeanGizmoAdaptor(generatedBeans); - - try (ClassCreator classCreator = ClassCreator.builder().classOutput(classOutput) - .className("io.quarkus.jackson.customizer.RegisterSerializersAndDeserializersCustomizer") - .interfaces(ObjectMapperCustomizer.class.getName()) - .build()) { - classCreator.addAnnotation(Singleton.class); - - try (MethodCreator customize = classCreator.getMethodCreator("customize", void.class, ObjectMapper.class)) { - ResultHandle objectMapper = customize.getMethodParam(0); - - for (JacksonModuleBuildItem jacksonModule : jacksonModules) { - if (jacksonModule.getItems().isEmpty()) { - continue; - } - - /* - * Create code similar to the following: - * - * SimpleModule module = new SimpleModule("somename"); - * module.addSerializer(Foo.class, new FooSerializer()); - * module.addDeserializer(Foo.class, new FooDeserializer()); - * objectMapper.registerModule(module); - */ - ResultHandle module = customize.newInstance( - MethodDescriptor.ofConstructor(SimpleModule.class, String.class), - customize.load(jacksonModule.getName())); - - for (JacksonModuleBuildItem.Item item : jacksonModule.getItems()) { - ResultHandle targetClass = customize.loadClassFromTCCL(item.getTargetClassName()); - - String serializerClassName = item.getSerializerClassName(); - if (serializerClassName != null && !serializerClassName.isEmpty()) { - ResultHandle serializer = customize.newInstance( - MethodDescriptor.ofConstructor(serializerClassName)); - customize.invokeVirtualMethod( - MethodDescriptor.ofMethod(SimpleModule.class, "addSerializer", SimpleModule.class, - Class.class, JsonSerializer.class), - module, targetClass, serializer); + ClassOutput classOutput = new GeneratedBeanGizmo2Adaptor(generatedBeans); + Gizmo g = Gizmo.create(classOutput); + g.class_("io.quarkus.jackson.customizer.RegisterSerializersAndDeserializersCustomizer", cc -> { + cc.implements_(ObjectMapperCustomizer.class); + cc.defaultConstructor(); + cc.addAnnotation(Singleton.class); + cc.method("customize", mc -> { + ParamVar objectMapperParam = mc.parameter("objectMapper", ObjectMapper.class); + mc.returning(void.class); + mc.body(bc -> { + ClassDesc simpleModuleClassDesc = Reflection2Gizmo.classDescOf(SimpleModule.class); + for (JacksonModuleBuildItem jacksonModule : jacksonModules) { + if (jacksonModule.getItems().isEmpty()) { + continue; } - String deserializerClassName = item.getDeserializerClassName(); - if (deserializerClassName != null && !deserializerClassName.isEmpty()) { - ResultHandle deserializer = customize.newInstance( - MethodDescriptor.ofConstructor(deserializerClassName)); - customize.invokeVirtualMethod( - MethodDescriptor.ofMethod(SimpleModule.class, "addDeserializer", SimpleModule.class, - Class.class, JsonDeserializer.class), - module, targetClass, deserializer); + /* + * Create code similar to the following: + * + * SimpleModule module = new SimpleModule("somename"); + * module.addSerializer(Foo.class, new FooSerializer()); + * module.addDeserializer(Foo.class, new FooDeserializer()); + * objectMapper.registerModule(module); + */ + + LocalVar simpleModuleInstance = bc.localVar("simpleModule", + bc.new_(SimpleModule.class, Const.of(jacksonModule.getName()))); + + for (JacksonModuleBuildItem.Item item : jacksonModule.getItems()) { + + LocalVar targetClass = bc.localVar("targetClass", + Const.of(ClassDesc.of(item.getTargetClassName()))); + String serializerClassName = item.getSerializerClassName(); + if ((serializerClassName != null) && !serializerClassName.isEmpty()) { + ClassDesc serializerClassDesc = ClassDesc.of(serializerClassName); + Expr serializerInstance = bc.new_(serializerClassDesc); + + bc.invokeVirtual( + ClassMethodDesc.of(simpleModuleClassDesc, "addSerializer", + MethodTypeDesc.of(simpleModuleClassDesc, + ConstantDescs.CD_Class, Reflection2Gizmo.classDescOf( + JsonSerializer.class))), + simpleModuleInstance, targetClass, serializerInstance); + + } + + String deserializerClassName = item.getDeserializerClassName(); + if ((deserializerClassName != null) && !deserializerClassName.isEmpty()) { + ClassDesc deserializerClassDesc = ClassDesc.of(deserializerClassName); + Expr deserializerInstance = bc.new_(deserializerClassDesc); + + bc.invokeVirtual( + ClassMethodDesc.of(simpleModuleClassDesc, "addDeserializer", + MethodTypeDesc.of(simpleModuleClassDesc, + ConstantDescs.CD_Class, Reflection2Gizmo.classDescOf( + JsonDeserializer.class))), + simpleModuleInstance, targetClass, deserializerInstance); + + } } - } - customize.invokeVirtualMethod( - MethodDescriptor.ofMethod(ObjectMapper.class, "registerModule", ObjectMapper.class, Module.class), - objectMapper, module); - } + bc.invokeVirtual( + OBJECT_MAPPER_REGISTER_MODULE_METHOD_DESC, + objectMapperParam, simpleModuleInstance); - for (ClassPathJacksonModuleBuildItem classPathJacksonModule : classPathJacksonModules) { - ResultHandle module = customize - .newInstance(MethodDescriptor.ofConstructor(classPathJacksonModule.getModuleClassName())); - customize.invokeVirtualMethod( - MethodDescriptor.ofMethod(ObjectMapper.class, "registerModule", ObjectMapper.class, Module.class), - objectMapper, module); - } + } - customize.returnValue(null); - } + for (ClassPathJacksonModuleBuildItem classPathJacksonModule : classPathJacksonModules) { + bc.invokeVirtual( + OBJECT_MAPPER_REGISTER_MODULE_METHOD_DESC, + objectMapperParam, bc.new_(ClassDesc.of(classPathJacksonModule.getModuleClassName()))); + } - // ensure that the things we auto-register have the lower priority - this ensures that user registered modules take priority - try (MethodCreator priority = classCreator.getMethodCreator("priority", int.class)) { - priority.returnValue(priority.load(ObjectMapperCustomizer.QUARKUS_CUSTOMIZER_PRIORITY)); - } - } + bc.return_(); + }); + }); + cc.method("priority", mc -> { + mc.returning(int.class); + mc.body(bc -> bc.return_(ObjectMapperCustomizer.QUARKUS_CUSTOMIZER_PRIORITY)); + }); + }); } @Record(ExecutionTime.STATIC_INIT) diff --git a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/OidcClientRegistrationImpl.java b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/OidcClientRegistrationImpl.java index 64f3b07ac89ad..2c8cfbc1773e1 100644 --- a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/OidcClientRegistrationImpl.java +++ b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/OidcClientRegistrationImpl.java @@ -162,7 +162,8 @@ static UniOnItem> postRequest(OidcRequestContextProperties } // Retry up to three times with a one-second delay between the retries if the connection is closed Buffer buffer = Buffer.buffer(clientRegJson); - Uni> response = filterHttpRequest(requestProps, request, filters, buffer).sendBuffer(buffer) + Uni> response = filterHttpRequest(requestProps, request, filters, buffer) + .sendBuffer(OidcCommonUtils.getRequestBuffer(requestProps, buffer)) .onFailure(SocketException.class) .retry() .atMost(oidcConfig.connectionRetryCount()) @@ -193,8 +194,8 @@ static private RegisteredClient newRegisteredClient(HttpResponse resp, Map> requestFilters, Map> responseFilters, OidcRequestContextProperties requestProps) { - Buffer buffer = resp.body(); - OidcCommonUtils.filterHttpResponse(requestProps, resp, buffer, responseFilters, OidcEndpoint.Type.CLIENT_REGISTRATION); + Buffer buffer = OidcCommonUtils.filterHttpResponse(requestProps, resp, responseFilters, + OidcEndpoint.Type.CLIENT_REGISTRATION); if (resp.statusCode() == 200 || resp.statusCode() == 201) { JsonObject json = buffer.toJsonObject(); LOG.debugf("Client has been succesfully registered: %s", json.toString()); diff --git a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/RegisteredClientImpl.java b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/RegisteredClientImpl.java index 0d171fceb4a57..8cac4317f6a24 100644 --- a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/RegisteredClientImpl.java +++ b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/RegisteredClientImpl.java @@ -161,7 +161,8 @@ private UniOnItem> makeRequest(OidcRequestContextProperties request.putHeader(AUTHORIZATION_HEADER, OidcConstants.BEARER_SCHEME + " " + registrationToken); } // Retry up to three times with a one-second delay between the retries if the connection is closed - Uni> response = filterHttpRequest(requestProps, request, buffer).sendBuffer(buffer) + Uni> response = filterHttpRequest(requestProps, request, buffer) + .sendBuffer(OidcCommonUtils.getRequestBuffer(requestProps, buffer)) .onFailure(SocketException.class) .retry() .atMost(oidcConfig.connectionRetryCount()) @@ -186,8 +187,8 @@ private HttpRequest filterHttpRequest(OidcRequestContextProperties reque } private RegisteredClient newRegisteredClient(HttpResponse resp, OidcRequestContextProperties requestProps) { - Buffer buffer = resp.body(); - OidcCommonUtils.filterHttpResponse(requestProps, resp, buffer, responseFilters, OidcEndpoint.Type.REGISTERED_CLIENT); + Buffer buffer = OidcCommonUtils.filterHttpResponse(requestProps, resp, responseFilters, + OidcEndpoint.Type.REGISTERED_CLIENT); if (resp.statusCode() >= 200 && resp.statusCode() < 300) { io.vertx.core.json.JsonObject json = buffer.toJsonObject(); LOG.debugf("Client metadata has been succesfully updated: %s", json.toString()); @@ -208,8 +209,8 @@ private RegisteredClient newRegisteredClient(HttpResponse resp, OidcRequ } private Uni deleteResponse(HttpResponse resp, OidcRequestContextProperties requestProps) { - Buffer buffer = resp.body(); - OidcCommonUtils.filterHttpResponse(requestProps, resp, buffer, responseFilters, OidcEndpoint.Type.REGISTERED_CLIENT); + Buffer buffer = OidcCommonUtils.filterHttpResponse(requestProps, resp, responseFilters, + OidcEndpoint.Type.REGISTERED_CLIENT); if (resp.statusCode() == 200) { LOG.debug("Client has been succesfully deleted"); return Uni.createFrom().voidItem(); diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java index d40a66544da8f..28d7512b85ee5 100644 --- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java +++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java @@ -148,8 +148,7 @@ private Boolean toRevokeResponse(OidcRequestContextProperties requestProps, Http // invalid token, https://datatracker.ietf.org/doc/html/rfc7009#section-2.2. // 503 is at least theoretically possible if the OIDC server declines and suggests to Retry-After some period of time. // However this period of time can be set to unpredictable value. - Buffer buffer = resp.body(); - OidcCommonUtils.filterHttpResponse(requestProps, resp, buffer, responseFilters, OidcEndpoint.Type.TOKEN_REVOCATION); + OidcCommonUtils.filterHttpResponse(requestProps, resp, responseFilters, OidcEndpoint.Type.TOKEN_REVOCATION); return resp.statusCode() == 503 ? false : true; } @@ -243,7 +242,8 @@ private UniOnItem> postRequest( } // Retry up to three times with a one-second delay between the retries if the connection is closed Buffer buffer = OidcCommonUtils.encodeForm(body); - Uni> response = filterHttpRequest(requestProps, endpointType, request, buffer).sendBuffer(buffer) + Uni> response = filterHttpRequest(requestProps, endpointType, request, buffer) + .sendBuffer(OidcCommonUtils.getRequestBuffer(requestProps, buffer)) .onFailure(SocketException.class) .retry() .atMost(oidcConfig.connectionRetryCount()) @@ -256,8 +256,7 @@ private UniOnItem> postRequest( } private Tokens emitGrantTokens(OidcRequestContextProperties requestProps, HttpResponse resp, boolean refresh) { - Buffer buffer = resp.body(); - OidcCommonUtils.filterHttpResponse(requestProps, resp, buffer, responseFilters, OidcEndpoint.Type.TOKEN); + Buffer buffer = OidcCommonUtils.filterHttpResponse(requestProps, resp, responseFilters, OidcEndpoint.Type.TOKEN); if (resp.statusCode() == 200) { LOG.debugf("%s OidcClient has %s the tokens", oidcConfig.id().get(), (refresh ? "refreshed" : "acquired")); JsonObject json = buffer.toJsonObject(); diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestContextProperties.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestContextProperties.java index 45c0918cc7bef..8b2fa18615eca 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestContextProperties.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestContextProperties.java @@ -9,6 +9,8 @@ public class OidcRequestContextProperties { public static String TOKEN = "token"; public static String TOKEN_CREDENTIAL = "token_credential"; public static String DISCOVERY_ENDPOINT = "discovery_endpoint"; + public static String REQUEST_BODY = "request_body"; + public static String RESPONSE_BODY = "response_body"; private final Map properties; diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestFilter.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestFilter.java index 2f8aeee5d00bc..1bd9f61f3cf69 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestFilter.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestFilter.java @@ -14,7 +14,35 @@ public interface OidcRequestFilter { /** * OIDC request context which provides access to the HTTP request headers and body, as well as context properties. */ - record OidcRequestContext(HttpRequest request, Buffer requestBody, OidcRequestContextProperties contextProperties) { + class OidcRequestContext { + final HttpRequest request; + final OidcRequestContextProperties contextProperties; + Buffer requestBody; + + public OidcRequestContext(HttpRequest request, Buffer requestBody, + OidcRequestContextProperties contextProperties) { + this.request = request; + this.requestBody = requestBody; + this.contextProperties = contextProperties; + } + + public HttpRequest request() { + return request; + } + + public Buffer requestBody() { + return requestBody; + } + + public OidcRequestContextProperties contextProperties() { + return contextProperties; + } + + public void requestBody(Buffer buffer) { + requestBody = buffer; + contextProperties.put(OidcRequestContextProperties.REQUEST_BODY, buffer); + } + } /** diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcResponseFilter.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcResponseFilter.java index 0fc9bfa933c39..3e2b9e2a80f13 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcResponseFilter.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcResponseFilter.java @@ -13,8 +13,40 @@ public interface OidcResponseFilter { /** * OIDC response context which provides access to the HTTP response status code, headers and body. */ - record OidcResponseContext(OidcRequestContextProperties requestProperties, - int statusCode, MultiMap responseHeaders, Buffer responseBody) { + class OidcResponseContext { + final OidcRequestContextProperties requestProperties; + final int statusCode; + final MultiMap responseHeaders; + Buffer responseBody; + + public OidcResponseContext(OidcRequestContextProperties requestProperties, int statusCode, + MultiMap responseHeaders, Buffer responseBody) { + this.requestProperties = requestProperties; + this.statusCode = statusCode; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + public OidcRequestContextProperties requestProperties() { + return requestProperties; + } + + public int statusCode() { + return statusCode; + } + + public MultiMap responseHeaders() { + return responseHeaders; + } + + public Buffer responseBody() { + return responseBody; + } + + public void responseBody(Buffer buffer) { + responseBody = buffer; + requestProperties.put(OidcRequestContextProperties.RESPONSE_BODY, buffer); + } } /** diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index 91513249ad86d..b347903bbcfe4 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -586,8 +586,7 @@ public static Uni doDiscoverMetadata(WebClient client, } return sendRequest(vertx, request, blockingDnsLookup).onItem().transform(resp -> { - Buffer buffer = resp.body(); - filterHttpResponse(requestProps, resp, buffer, responseFilters, OidcEndpoint.Type.DISCOVERY); + Buffer buffer = filterHttpResponse(requestProps, resp, responseFilters, OidcEndpoint.Type.DISCOVERY); if (resp.statusCode() == 200) { return buffer.toJsonObject(); @@ -622,15 +621,34 @@ private static OidcRequestContextProperties getDiscoveryRequestProps( return new OidcRequestContextProperties(newProperties); } - public static void filterHttpResponse(OidcRequestContextProperties requestProps, - HttpResponse resp, Buffer buffer, - Map> responseFilters, OidcEndpoint.Type type) { + public static Buffer filterHttpResponse(OidcRequestContextProperties requestProps, + HttpResponse resp, Map> responseFilters, OidcEndpoint.Type type) { + Buffer responseBody = resp.body(); if (!responseFilters.isEmpty()) { - OidcResponseContext context = new OidcResponseContext(requestProps, resp.statusCode(), resp.headers(), buffer); + OidcResponseContext context = new OidcResponseContext(requestProps, resp.statusCode(), resp.headers(), + responseBody); for (OidcResponseFilter filter : getMatchingOidcResponseFilters(responseFilters, type)) { filter.filter(context); } + return getResponseBuffer(requestProps, responseBody); } + return responseBody; + } + + public static Buffer getRequestBuffer(OidcRequestContextProperties requestProps, Buffer buffer) { + if (requestProps == null) { + return buffer; + } + Buffer updatedRequestBody = requestProps.get(OidcRequestContextProperties.REQUEST_BODY); + return updatedRequestBody == null ? buffer : updatedRequestBody; + } + + public static Buffer getResponseBuffer(OidcRequestContextProperties requestProps, Buffer buffer) { + if (requestProps == null) { + return buffer; + } + Buffer updatedResponseBody = requestProps.get(OidcRequestContextProperties.RESPONSE_BODY); + return updatedResponseBody == null ? buffer : updatedResponseBody; } public static String getDiscoveryUri(String authServerUrl) { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClientImpl.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClientImpl.java index 46b81dc9aa322..b2b8d7f920a5f 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClientImpl.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClientImpl.java @@ -277,8 +277,7 @@ private Boolean toRevokeResponse(OidcRequestContextProperties requestProps, Http // invalid token, https://datatracker.ietf.org/doc/html/rfc7009#section-2.2. // 503 is at least theoretically possible if the OIDC server declines and suggests to Retry-After some period of time. // However this period of time can be set to unpredictable value. - Buffer buffer = resp.body(); - OidcCommonUtils.filterHttpResponse(requestProps, resp, buffer, responseFilters, OidcEndpoint.Type.TOKEN_REVOCATION); + OidcCommonUtils.filterHttpResponse(requestProps, resp, responseFilters, OidcEndpoint.Type.TOKEN_REVOCATION); return resp.statusCode() == 503 ? false : true; } @@ -345,7 +344,8 @@ private UniOnItem> getHttpResponse(OidcRequestContextProper // Retry up to three times with a one-second delay between the retries if the connection is closed. OidcEndpoint.Type endpoint = introspect ? OidcEndpoint.Type.INTROSPECTION : OidcEndpoint.Type.TOKEN; - Uni> response = filterHttpRequest(requestProps, endpoint, request, buffer).sendBuffer(buffer) + Uni> response = filterHttpRequest(requestProps, endpoint, request, buffer) + .sendBuffer(OidcCommonUtils.getRequestBuffer(requestProps, buffer)) .onFailure(SocketException.class) .retry() .atMost(oidcConfig.connectionRetryCount()).onFailure().transform(Throwable::getCause); @@ -381,8 +381,7 @@ private TokenIntrospection getTokenIntrospection(OidcRequestContextProperties re private JsonObject getJsonObject(OidcRequestContextProperties requestProps, String requestUri, HttpResponse resp, OidcEndpoint.Type endpoint) { - Buffer buffer = resp.body(); - OidcCommonUtils.filterHttpResponse(requestProps, resp, buffer, responseFilters, endpoint); + Buffer buffer = OidcCommonUtils.filterHttpResponse(requestProps, resp, responseFilters, endpoint); if (resp.statusCode() == 200) { LOG.debugf("Request succeeded: %s", resp.bodyAsJsonObject()); return buffer.toJsonObject(); @@ -395,8 +394,7 @@ private JsonObject getJsonObject(OidcRequestContextProperties requestProps, Stri private String getString(final OidcRequestContextProperties requestProps, String requestUri, HttpResponse resp, OidcEndpoint.Type endpoint) { - Buffer buffer = resp.body(); - OidcCommonUtils.filterHttpResponse(requestProps, resp, buffer, responseFilters, endpoint); + Buffer buffer = OidcCommonUtils.filterHttpResponse(requestProps, resp, responseFilters, endpoint); if (resp.statusCode() == 200) { LOG.debugf("Request succeeded: %s", resp.bodyAsString()); return buffer.toString(); diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index aa8826ff9e43e..28b44758ef989 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -53,7 +53,7 @@ 3.0.0 4.1.0 2.0.1 - 1.18.0 + 1.19.0 2.20.0 3.18.0 33.4.8-jre diff --git a/integration-tests/oidc-client-registration/src/main/java/io/quarkus/it/keycloak/ClientRegistrationRequestFilter.java b/integration-tests/oidc-client-registration/src/main/java/io/quarkus/it/keycloak/ClientRegistrationRequestFilter.java index f77b540da9074..0ef1314911f19 100644 --- a/integration-tests/oidc-client-registration/src/main/java/io/quarkus/it/keycloak/ClientRegistrationRequestFilter.java +++ b/integration-tests/oidc-client-registration/src/main/java/io/quarkus/it/keycloak/ClientRegistrationRequestFilter.java @@ -9,6 +9,7 @@ import io.quarkus.oidc.common.OidcEndpoint.Type; import io.quarkus.oidc.common.OidcRequestFilter; import io.vertx.core.json.JsonObject; +import io.vertx.mutiny.core.buffer.Buffer; @ApplicationScoped @Unremovable @@ -21,6 +22,9 @@ public void filter(OidcRequestContext rc) { JsonObject body = rc.requestBody().toJsonObject(); if ("Default Client".equals(body.getString("client_name"))) { LOG.debug("'Default Client' registration request"); + } else if ("Dynamic Tenant Client".equals(body.getString("client_name"))) { + body.put("client_name", "Registered Dynamic Tenant Client"); + rc.requestBody(Buffer.buffer(body.toString())); } } diff --git a/integration-tests/oidc-client-registration/src/main/java/io/quarkus/it/keycloak/ClientRegistrationResponseFilter.java b/integration-tests/oidc-client-registration/src/main/java/io/quarkus/it/keycloak/ClientRegistrationResponseFilter.java index bf5dc72e6fa67..d09ad99f2f810 100644 --- a/integration-tests/oidc-client-registration/src/main/java/io/quarkus/it/keycloak/ClientRegistrationResponseFilter.java +++ b/integration-tests/oidc-client-registration/src/main/java/io/quarkus/it/keycloak/ClientRegistrationResponseFilter.java @@ -9,6 +9,7 @@ import io.quarkus.oidc.common.OidcEndpoint.Type; import io.quarkus.oidc.common.OidcResponseFilter; import io.vertx.core.json.JsonObject; +import io.vertx.mutiny.core.buffer.Buffer; @ApplicationScoped @Unremovable @@ -20,9 +21,13 @@ public class ClientRegistrationResponseFilter implements OidcResponseFilter { public void filter(OidcResponseContext rc) { String contentType = rc.responseHeaders().get("Content-Type"); JsonObject body = rc.responseBody().toJsonObject(); - if (contentType.startsWith("application/json") - && "Default Client".equals(body.getString("client_name"))) { - LOG.debug("'Default Client' has been registered"); + if (contentType.startsWith("application/json")) { + if ("Default Client".equals(body.getString("client_name"))) { + LOG.debug("'Default Client' has been registered"); + } else if ("Registered Dynamic Tenant Client".equals(body.getString("client_name"))) { + body.put("client_name", "Registered Dynamically Tenant Client"); + rc.responseBody(Buffer.buffer(body.toString())); + } } } diff --git a/integration-tests/oidc-client-registration/src/test/java/io/quarkus/it/keycloak/OidcClientRegistrationTest.java b/integration-tests/oidc-client-registration/src/test/java/io/quarkus/it/keycloak/OidcClientRegistrationTest.java index 52ea3210c1bcc..da69c4685ed33 100644 --- a/integration-tests/oidc-client-registration/src/test/java/io/quarkus/it/keycloak/OidcClientRegistrationTest.java +++ b/integration-tests/oidc-client-registration/src/test/java/io/quarkus/it/keycloak/OidcClientRegistrationTest.java @@ -97,7 +97,7 @@ public void testRegisteredClientDynamicTenant() throws IOException { TextPage textPage = loginForm.getButtonByName("login").click(); - assertEquals("registered-client-dynamic-tenant:Dynamic Tenant Client:alice", textPage.getContent()); + assertEquals("registered-client-dynamic-tenant:Registered Dynamically Tenant Client:alice", textPage.getContent()); } } diff --git a/integration-tests/oidc-client-wiremock/src/main/java/io/quarkus/it/keycloak/OidcRequestCustomizer.java b/integration-tests/oidc-client-wiremock/src/main/java/io/quarkus/it/keycloak/OidcRequestResponseCustomizer.java similarity index 54% rename from integration-tests/oidc-client-wiremock/src/main/java/io/quarkus/it/keycloak/OidcRequestCustomizer.java rename to integration-tests/oidc-client-wiremock/src/main/java/io/quarkus/it/keycloak/OidcRequestResponseCustomizer.java index 14b439b510934..47c5aa2a55c5d 100644 --- a/integration-tests/oidc-client-wiremock/src/main/java/io/quarkus/it/keycloak/OidcRequestCustomizer.java +++ b/integration-tests/oidc-client-wiremock/src/main/java/io/quarkus/it/keycloak/OidcRequestResponseCustomizer.java @@ -7,13 +7,15 @@ import io.quarkus.oidc.common.OidcEndpoint.Type; import io.quarkus.oidc.common.OidcRequestContextProperties; import io.quarkus.oidc.common.OidcRequestFilter; +import io.quarkus.oidc.common.OidcResponseFilter; +import io.vertx.core.json.JsonObject; import io.vertx.mutiny.core.buffer.Buffer; import io.vertx.mutiny.ext.web.client.HttpRequest; @ApplicationScoped @Unremovable @OidcEndpoint(value = Type.TOKEN) -public class OidcRequestCustomizer implements OidcRequestFilter { +public class OidcRequestResponseCustomizer implements OidcRequestFilter, OidcResponseFilter { @Override public void filter(HttpRequest request, Buffer buffer, OidcRequestContextProperties contextProps) { @@ -21,6 +23,8 @@ public void filter(HttpRequest request, Buffer buffer, OidcRequestContex if (uri.endsWith("/non-standard-tokens")) { request.putHeader("client-id", contextProps.getString("client-id")); request.putHeader("GrantType", getGrantType(buffer.toString())); + contextProps.put(OidcRequestContextProperties.REQUEST_BODY, + Buffer.buffer(buffer.toString() + "&custom_prop=custom_value")); } } @@ -32,4 +36,17 @@ private String getGrantType(String formString) { } return ""; } + + @Override + public void filter(OidcResponseContext responseContext) { + if (responseContext.statusCode() == 200 + && responseContext.requestProperties().get(OidcRequestContextProperties.REQUEST_BODY) != null) { + // Only the non-standards-tokens request customizes the request body + JsonObject body = responseContext.responseBody().toJsonObject(); + body.put("refreshToken", "refresh_token_non_standard"); + responseContext.requestProperties().put(OidcRequestContextProperties.RESPONSE_BODY, + Buffer.buffer(body.toString())); + } + + } } diff --git a/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java b/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java index df83f97342086..4bd3841cab670 100644 --- a/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java +++ b/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java @@ -109,7 +109,7 @@ public Map start() { .withHeader("GrantType", matching("password")) .withHeader("client-id", containing("non-standard-response")) .withRequestBody(matching( - "grant_type=password&audience=audience1&username=alice&password=alice&extra_param=extra_param_value")) + "grant_type=password&audience=audience1&username=alice&password=alice&extra_param=extra_param_value&custom_prop=custom_value")) .willReturn(WireMock .aResponse() .withHeader("Content-Type", MediaType.APPLICATION_JSON) diff --git a/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java b/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java index aa8f3dafcdb74..1e650d07fac59 100644 --- a/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java +++ b/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java @@ -211,7 +211,7 @@ public void testEchoTokensNonStandardResponse() { RestAssured.when().get("/frontend/echoTokenNonStandardResponse") .then() .statusCode(200) - .body(equalTo("access_token_n refresh_token_n")); + .body(equalTo("access_token_n refresh_token_non_standard")); } @Order(4) diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/TokenRequestResponseFilter.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/TokenRequestResponseFilter.java index e05d38e209081..7a75f4dd7073b 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/TokenRequestResponseFilter.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/TokenRequestResponseFilter.java @@ -10,10 +10,13 @@ import io.quarkus.arc.Unremovable; import io.quarkus.oidc.common.OidcEndpoint; import io.quarkus.oidc.common.OidcEndpoint.Type; +import io.quarkus.oidc.common.OidcRequestContextProperties; import io.quarkus.oidc.common.OidcRequestFilter; import io.quarkus.oidc.common.OidcResponseFilter; import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.oidc.runtime.OidcUtils; +import io.vertx.core.json.JsonObject; +import io.vertx.mutiny.core.buffer.Buffer; @ApplicationScoped @Unremovable @@ -26,8 +29,13 @@ public class TokenRequestResponseFilter implements OidcRequestFilter, OidcRespon @Override public void filter(OidcRequestContext rc) { final Instant now = Instant.now(); - instants.put(rc.contextProperties().get(OidcUtils.TENANT_ID_ATTRIBUTE), now); + String tenantId = rc.contextProperties().get(OidcUtils.TENANT_ID_ATTRIBUTE); + instants.put(tenantId, now); rc.contextProperties().put("instant", now); + if ("code-flow-opaque-access-token".equals(tenantId)) { + rc.contextProperties().put(OidcRequestContextProperties.REQUEST_BODY, + Buffer.buffer(rc.requestBody().toString() + "&opaque_token_param=opaque_token_value")); + } } @Override @@ -44,6 +52,14 @@ public void filter(OidcResponseContext rc) { LOG.debug("Authorization code completed for tenant 'code-flow-user-info-github-cached-in-idtoken' in an instant: " + instantsAreTheSame); } + if (rc.requestProperties().get(OidcRequestContextProperties.REQUEST_BODY) != null) { + // Only the code-flow-opaque-access-token request customizes the request body + JsonObject body = rc.responseBody().toJsonObject(); + String scope = body.getString("scope"); + body.put("scope", scope.replace(",", " ")); + rc.requestProperties().put(OidcRequestContextProperties.RESPONSE_BODY, + Buffer.buffer(body.toString())); + } } } diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java index 3a530639727fd..1d143e3ae0328 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java @@ -872,11 +872,12 @@ private void defineCodeFlowUserInfoCachedInIdTokenStub(String expiredRefreshToke private void defineCodeFlowOpaqueAccessTokenStub() { wireMockServer .stubFor(WireMock.post(urlPathMatching("/auth/realms/quarkus/opaque-access-token")) + .withRequestBody(containing("&opaque_token_param=opaque_token_value")) .willReturn(WireMock.aResponse() .withHeader("Content-Type", "application/json") .withBody("{\n" + " \"access_token\": \"alice\"," - + " \"scope\": \"laptop phone\"," + + " \"scope\": \"laptop,phone\"," + "\"expires_in\": 299}"))); } diff --git a/pom.xml b/pom.xml index be4d18ff59243..af865a2393b21 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ 3.25.5 ${protoc.version} ${protoc.version} - 2.59.1 + 2.59.2 0.27.0