*
* In addition, it also supports the standard JDK log levels.
- *
- * @asciidoclet
*/
@WithDefault("INFO")
@WithConverter(LevelConverter.class)
diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
index 9a636d945e8e2..359b055ed11f3 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
@@ -7,7 +7,6 @@
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -474,7 +473,7 @@ public boolean isSatisfiedBy(Task t) {
// add the code gen sources
final SourceSet generatedSourceSet = sourceSets
.getByName(QuarkusGenerateCode.QUARKUS_GENERATED_SOURCES);
- addCodeGenSourceDirs(compileJava, generatedSourceSet, quarkusExt);
+ addCodeGenSourceDirs(compileJava, generatedSourceSet);
// quarkusGenerateCode is a dependency
compileJava.dependsOn(quarkusGenerateCode);
// quarkusGenerateCodeDev must run before compileJava in case quarkusDev is the target
@@ -495,7 +494,7 @@ public boolean isSatisfiedBy(Task t) {
// add the code gen test sources
final SourceSet generatedSourceSet = sourceSets
.getByName(QuarkusGenerateCode.QUARKUS_TEST_GENERATED_SOURCES);
- addCodeGenSourceDirs(compileTestJava, generatedSourceSet, quarkusExt);
+ addCodeGenSourceDirs(compileTestJava, generatedSourceSet);
compileTestJava.dependsOn(quarkusGenerateCode, quarkusGenerateCodeTests);
if (tasks.contains(new NamedImpl(generatedSourceSet.getCompileJavaTaskName()))) {
compileTestJava.mustRunAfter(tasks.named(generatedSourceSet.getCompileJavaTaskName()));
@@ -511,7 +510,7 @@ public boolean isSatisfiedBy(Task t) {
tasks.named("compileKotlin", task -> {
final SourceSet generatedSourceSet = project.getExtensions().getByType(SourceSetContainer.class)
.getByName(QuarkusGenerateCode.QUARKUS_GENERATED_SOURCES);
- addCodeGenSourceDirs(task, generatedSourceSet, quarkusExt);
+ addCodeGenSourceDirs(task, generatedSourceSet);
task.dependsOn(quarkusGenerateCode);
task.mustRunAfter(quarkusGenerateCodeDev);
if (tasks.contains(new NamedImpl(generatedSourceSet.getCompileJavaTaskName()))) {
@@ -525,7 +524,7 @@ public boolean isSatisfiedBy(Task t) {
tasks.named("compileTestKotlin", task -> {
final SourceSet generatedSourceSet = project.getExtensions().getByType(SourceSetContainer.class)
.getByName(QuarkusGenerateCode.QUARKUS_TEST_GENERATED_SOURCES);
- addCodeGenSourceDirs(task, generatedSourceSet, quarkusExt);
+ addCodeGenSourceDirs(task, generatedSourceSet);
task.dependsOn(quarkusGenerateCodeTests);
if (tasks.contains(new NamedImpl(generatedSourceSet.getCompileJavaTaskName()))) {
task.mustRunAfter(tasks.named(generatedSourceSet.getCompileJavaTaskName()));
@@ -538,22 +537,14 @@ public boolean isSatisfiedBy(Task t) {
});
}
- private static void addCodeGenSourceDirs(JavaCompile compileJava, SourceSet generatedSourceSet,
- QuarkusPluginExtension quarkusExt) {
+ private static void addCodeGenSourceDirs(JavaCompile compileJava, SourceSet generatedSourceSet) {
final File baseDir = generatedSourceSet.getJava().getClassesDirectory().get().getAsFile();
- for (String provider : quarkusExt.getCodeGenerationProviders().get()) {
- compileJava.source(new File(baseDir, provider));
- }
+ compileJava.source(baseDir);
}
- private static void addCodeGenSourceDirs(Task compileKotlin, SourceSet generatedSourceSet,
- QuarkusPluginExtension quarkusExt) {
+ private static void addCodeGenSourceDirs(Task compileKotlin, SourceSet generatedSourceSet) {
final File baseDir = generatedSourceSet.getJava().getClassesDirectory().get().getAsFile();
- final List codeGenProviders = quarkusExt.getCodeGenerationProviders().get();
- final Object[] codeGenDirs = new Object[codeGenProviders.size()];
- for (int i = 0; i < codeGenDirs.length; ++i) {
- codeGenDirs[i] = new File(baseDir, codeGenProviders.get(i));
- }
+ final Object[] codeGenDirs = new Object[] { baseDir };
try {
var sourcesMethod = compileKotlin.getClass().getMethod("source", Object[].class);
sourcesMethod.invoke(compileKotlin, new Object[] { codeGenDirs });
diff --git a/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/lgtm-codestart/java/src/test/java/org/acme/SimpleTest.java b/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/lgtm-codestart/java/src/test/java/org/acme/SimpleTest.java
index 5c62269f9c3a2..c7185c2f0fbd5 100644
--- a/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/lgtm-codestart/java/src/test/java/org/acme/SimpleTest.java
+++ b/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/lgtm-codestart/java/src/test/java/org/acme/SimpleTest.java
@@ -1,6 +1,6 @@
package org.acme;
-import org.eclipse.microprofile.config.inject.ConfigProperty;;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
diff --git a/docs/src/main/asciidoc/cdi.adoc b/docs/src/main/asciidoc/cdi.adoc
index 9a240de0feed1..f2550ee6820fd 100644
--- a/docs/src/main/asciidoc/cdi.adoc
+++ b/docs/src/main/asciidoc/cdi.adoc
@@ -443,6 +443,8 @@ import jakarta.decorator.Delegate;
import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.enterprise.inject.Any;
+import jakarta.enterprise.inject.Decorated;
+import jakarta.enterprise.inject.spi.Bean;
public interface Account {
void withdraw(BigDecimal amount);
@@ -461,7 +463,6 @@ public class LargeTxAccount implements Account { <3>
@Decorated
Bean delegateInfo; <5>
-
@Inject
LogService logService; <6>
diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc
index 78b107fbca2f2..8a3d95935d823 100644
--- a/docs/src/main/asciidoc/rest-client.adoc
+++ b/docs/src/main/asciidoc/rest-client.adoc
@@ -1341,7 +1341,9 @@ public class TestClientRequestFilter implements ResteasyReactiveClientRequestFil
}
----
-== Customizing the ObjectMapper in REST Client Jackson
+== Jackson-specific features
+
+=== Customizing the ObjectMapper in REST Client Jackson
The REST Client supports adding a custom ObjectMapper to be used only the Client using the annotation `@ClientObjectMapper`.
@@ -1369,6 +1371,60 @@ public interface ExtensionsService {
<2> It's must be a static method. Also, the parameter `defaultObjectMapper` will be resolved via CDI. If not found, it will throw an exception at runtime.
<3> In this example, we're creating a copy of the default object mapper. You should *NEVER* modify the default object mapper, but create a copy instead.
+=== @JsonView support
+
+Jakarta REST methods can be annotated with https://fasterxml.github.io/jackson-annotations/javadoc/2.10/com/fasterxml/jackson/annotation/JsonView.html[@JsonView]
+in order to customize the serialization of the returned POJO, on a per method-basis. This is best explained with an example.
+
+A typical use of `@JsonView` is to hide certain fields on certain methods. In that vein, let's define two views:
+
+[source,java]
+----
+public class Views {
+
+ public static class Public {
+ }
+
+ public static class Private extends Public {
+ }
+}
+----
+
+Let's assume we have the `User` POJO on which we want to hide some field during serialization. A simple example of this is:
+
+[source,java]
+----
+public class User {
+
+ @JsonView(Views.Private.class)
+ public int id;
+
+ @JsonView(Views.Public.class)
+ public String name;
+}
+----
+
+The REST Client supports `@JsonView` both for sending content to the REST API and for retrieving data from it:
+
+[source,java]
+----
+ @Path("/users")
+ @RegisterRestClient
+ public interface UserClient {
+ @GET
+ @Path("/{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @JsonView(Views.Public.class)
+ User get(@RestPath String id);
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ Response create(@JsonView(Views.Public.class) User user);
+ }
+----
+
+In the preceding code, the `get` method would return a `User` whose `id` is always `null` while the `create` method would never include `id` in the JSON it sends to the REST API.
+
== Exception handling
The MicroProfile REST Client specification introduces the `org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper` whose purpose is to convert an HTTP response to an exception.
diff --git a/docs/src/main/asciidoc/security-cors.adoc b/docs/src/main/asciidoc/security-cors.adoc
index d5bed31d425c3..0d71d854375a7 100644
--- a/docs/src/main/asciidoc/security-cors.adoc
+++ b/docs/src/main/asciidoc/security-cors.adoc
@@ -34,6 +34,14 @@ The filter then adds CORS headers to the HTTP response, informing browsers about
For preflight requests, the filter returns an HTTP response immediately.
For regular CORS requests, the filter denies access with an HTTP 403 status if the request violates the configured policy; otherwise, the filter forwards the request to the destination if the policy allows it.
+[NOTE]
+====
+Despite its name the CORS filter may also prevent CSRF attacks based on link:https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#using-standard-headers-to-verify-origin[Origin verification].
+Therefore, since an [Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin) header is expected to be set by the browser for cross-origin JavaScript and HTML form requests, you may want to consider using it instead of the xref:security-csrf-prevention.adoc[REST CSRF filter].
+
+You must confirm that the browser does set an `Origin` header for cross-origin requests when accessing your application, especially with HTML forms, before using the CORS filter to prevent CSRF with the link:https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#using-standard-headers-to-verify-origin[Origin verification].
+====
+
For detailed configuration options, see the following Configuration Properties section.
include::{generated-dir}/config/quarkus-vertx-http_quarkus.http.cors.adoc[leveloffset=+1, opts=optional]
diff --git a/docs/src/main/asciidoc/security-csrf-prevention.adoc b/docs/src/main/asciidoc/security-csrf-prevention.adoc
index 77349b67b81e1..a7aea5f3aab6b 100644
--- a/docs/src/main/asciidoc/security-csrf-prevention.adoc
+++ b/docs/src/main/asciidoc/security-csrf-prevention.adoc
@@ -16,6 +16,11 @@ Quarkus Security provides a CSRF prevention feature which implements https://che
`Double Submit Cookie` technique requires that the CSRF token sent as `HTTPOnly`, optionally signed, cookie to the client, and
directly embedded in a hidden form input of server-side rendered HTML forms, or submitted as a request header value.
+[NOTE]
+====
+If you are looking for stateless CSRF prevention that does not involve the server creating a cookie, have a look at the xref:security-cors.adoc#cors-filter[CORS filter]. Despite its name it also offers CSRF prevention by checking whether a request's `Origin` either matches the target `Host` or is in a list of allowed origins server-side.
+====
+
The extension consists of a xref:rest.adoc[Quarkus REST (formerly RESTEasy Reactive)] server filter which creates and verifies CSRF tokens in `application/x-www-form-urlencoded` and `multipart/form-data` forms and a Qute HTML form parameter provider which supports the xref:qute-reference.adoc#injecting-beans-directly-in-templates[injection of CSRF tokens in Qute templates].
The CSRF prevention filter applies to requests using HTTP `POST`, `PUT`, `PATCH`, `DELETE` and other methods that can change the REST application state.
diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc
index 6f3e6d32bcfbf..51766a9895835 100644
--- a/docs/src/main/asciidoc/writing-extensions.adoc
+++ b/docs/src/main/asciidoc/writing-extensions.adoc
@@ -1136,7 +1136,7 @@ public interface LogConfiguration {
* Enable logging to a file.
*/
@WithDefault("true")
- boolean enable();
+ boolean enabled();
/**
* The log format.
@@ -1172,12 +1172,12 @@ public class LoggingProcessor {
----
A configuration property name can be split into segments. For example, a property name like
-`quarkus.log.file.enable` can be split into the following segments:
+`quarkus.log.file.enabled` can be split into the following segments:
* `quarkus` - a namespace claimed by Quarkus which is a prefix for `@ConfigMapping` interfaces,
* `log` - a name segment which corresponds to the prefix set in the interface annotated with `@ConfigMapping`,
* `file` - a name segment which corresponds to the `file` field in this class,
-* `enable` - a name segment which corresponds to `enable` field in `FileConfig`.
+* `enabled` - a name segment which corresponds to `enabled` field in `FileConfig`.
<1> The `@ConfigMapping` annotation indicates that the interface is a configuration mapping, in this case one which
corresponds to a `quarkus.log` segment.
@@ -1189,7 +1189,7 @@ A corresponding `application.properties` for the above example could be:
[source%nowrap,properties]
----
-quarkus.log.file.enable=true
+quarkus.log.file.enabled=true
quarkus.log.file.level=DEBUG
quarkus.log.file.path=/tmp/debug.log
----
diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java
index bc73bc8e36f9b..46e6495d4db52 100644
--- a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java
+++ b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java
@@ -1,5 +1,7 @@
package io.quarkus.amazon.lambda.runtime;
+import java.util.Optional;
+
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
@@ -7,11 +9,20 @@
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
@ConfigMapping(prefix = "quarkus.lambda")
public interface LambdaConfig {
+ /**
+ * The handler name. Handler names are specified on handler classes using the {@link @jakarta.inject.Named} annotation.
+ *
+ * If this name is unspecified and there is exactly one unnamed implementation of
+ * {@link com.amazonaws.services.lambda.runtime.RequestHandler}
+ * then this unnamed handler will be used. If there is only a single named handler and the name is unspecified
+ * then the named handler will be used.
+ *
+ */
+ Optional handler();
/**
* Configuration for the mock event server that is run
* in dev mode and test mode
*/
MockEventServerConfig mockEventServer();
-
}
diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java
deleted file mode 100644
index 0aceb7a4f08ef..0000000000000
--- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package io.quarkus.amazon.lambda.runtime;
-
-import java.util.Optional;
-
-import io.quarkus.runtime.annotations.ConfigPhase;
-import io.quarkus.runtime.annotations.ConfigRoot;
-import io.smallrye.config.ConfigMapping;
-
-@ConfigRoot(phase = ConfigPhase.RUN_TIME)
-@ConfigMapping(prefix = "quarkus.lambda")
-public interface LambdaConfig {
-
- /**
- * The handler name. Handler names are specified on handler classes using the {@link @jakarta.inject.Named} annotation.
- *
- * If this name is unspecified and there is exactly one unnamed implementation of
- * {@link com.amazonaws.services.lambda.runtime.RequestHandler}
- * then this unnamed handler will be used. If there is only a single named handler and the name is unspecified
- * then the named handler will be used.
- *
- */
- Optional handler();
-}
diff --git a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
index f97b43b728aff..d24acb240acc7 100644
--- a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
+++ b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
@@ -30,8 +30,8 @@ public interface ContainerImageConfig {
/**
* The tag of the container image. If not set defaults to the application version
*/
- @WithDefault("${quarkus.application.version:latest}")
- String tag(); //used only by ContainerImageProcessor, use ContainerImageInfoBuildItem instead
+ // keep it Optional as we need the ability to nullify it
+ Optional tag(); //used only by ContainerImageProcessor, use ContainerImageInfoBuildItem instead
/**
* Additional tags of the container image.
diff --git a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageProcessor.java b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageProcessor.java
index e842306621eda..cffa5aa371dd5 100644
--- a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageProcessor.java
+++ b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageProcessor.java
@@ -105,7 +105,7 @@ public void publishImageInfo(ApplicationInfoBuildItem app,
+ group + "' and name '" + effectiveName + "' is invalid");
}
- String effectiveTag = containerImageConfig.tag();
+ String effectiveTag = containerImageConfig.tag().orElse(app.getVersion());
if (effectiveTag.equals(UNSET_VALUE)) {
effectiveTag = DEFAULT_TAG;
}
diff --git a/extensions/devservices/oidc/src/main/java/io/quarkus/devservices/oidc/OidcDevServicesConfig.java b/extensions/devservices/oidc/src/main/java/io/quarkus/devservices/oidc/OidcDevServicesConfig.java
index e1adb8178adb7..3ebc1f306ac17 100644
--- a/extensions/devservices/oidc/src/main/java/io/quarkus/devservices/oidc/OidcDevServicesConfig.java
+++ b/extensions/devservices/oidc/src/main/java/io/quarkus/devservices/oidc/OidcDevServicesConfig.java
@@ -1,5 +1,6 @@
package io.quarkus.devservices.oidc;
+import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -8,6 +9,7 @@
import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
/**
* OpenID Connect Dev Services configuration.
@@ -22,6 +24,18 @@ public interface OidcDevServicesConfig {
@ConfigDocDefault("Enabled when Docker and Podman are not available")
Optional enabled();
+ /**
+ * Relative duration the access token will expire in.
+ */
+ @WithDefault("5M")
+ Duration accessTokenExpiresIn();
+
+ /**
+ * Relative duration the ID token will expire in.
+ */
+ @WithDefault("5M")
+ Duration idTokenExpiresIn();
+
/**
* A map of roles for OIDC identity provider users.
*
diff --git a/extensions/devservices/oidc/src/main/java/io/quarkus/devservices/oidc/OidcDevServicesProcessor.java b/extensions/devservices/oidc/src/main/java/io/quarkus/devservices/oidc/OidcDevServicesProcessor.java
index ed9ac598b1274..3689b80f2224a 100644
--- a/extensions/devservices/oidc/src/main/java/io/quarkus/devservices/oidc/OidcDevServicesProcessor.java
+++ b/extensions/devservices/oidc/src/main/java/io/quarkus/devservices/oidc/OidcDevServicesProcessor.java
@@ -11,7 +11,6 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
-import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@@ -72,6 +71,8 @@ public class OidcDevServicesProcessor {
private static volatile String applicationType;
private static volatile Map configProperties;
private static volatile Map> userToDefaultRoles;
+ private static volatile Long accessTokenExpiresIn;
+ private static volatile Long idTokenExpiresIn;
private static volatile Runnable closeDevServiceTask;
@BuildStep
@@ -84,6 +85,8 @@ DevServicesResultBuildItem startServer(CuratedApplicationShutdownBuildItem close
}
userToDefaultRoles = devServicesConfig.roles();
+ accessTokenExpiresIn = devServicesConfig.accessTokenExpiresIn().toSeconds();
+ idTokenExpiresIn = devServicesConfig.idTokenExpiresIn().toSeconds();
if (closeDevServiceTask == null) {
LOG.info("Starting Dev Services for OIDC");
Vertx vertx = Vertx.vertx();
@@ -571,10 +574,10 @@ private static void passwordTokenEndpoint(RoutingContext rc) {
{
"access_token":"%s",
"token_type":"Bearer",
- "expires_in":3600,
+ "expires_in":%d,
"refresh_token":"%s"
}
- """.formatted(accessToken, refreshToken);
+ """.formatted(accessToken, accessTokenExpiresIn, refreshToken);
rc.response()
.putHeader("Content-Type", "application/json")
.putHeader("Cache-Control", "no-store")
@@ -592,9 +595,9 @@ private static void clientCredentialsTokenEndpoint(RoutingContext rc) {
{
"access_token": "%s",
"token_type": "Bearer",
- "expires_in": 3600
+ "expires_in": %d
}
- """.formatted(accessToken);
+ """.formatted(accessToken, accessTokenExpiresIn);
rc.response()
.putHeader("Content-Type", "application/json")
.putHeader("Cache-Control", "no-store")
@@ -669,9 +672,9 @@ private static void refreshTokenEndpoint(RoutingContext rc) {
"access_token": "%s",
"token_type": "Bearer",
"refresh_token": "%s",
- "expires_in": 3600
+ "expires_in": %d
}
- """.formatted(accessToken, refreshToken);
+ """.formatted(accessToken, refreshToken, accessTokenExpiresIn);
rc.response()
.putHeader("Content-Type", "application/json")
.putHeader("Cache-Control", "no-store")
@@ -689,8 +692,8 @@ private static void refreshTokenEndpoint(RoutingContext rc) {
* {
* "token_type":"Bearer",
* "scope":"openid email profile",
- * "expires_in":3600,
- * "ext_expires_in":3600,
+ * "expires_in":EXPIRES_IN,
+ * "ext_expires_in":EXPIRES_IN,
* "access_token":TOKEN,
* "id_token":JWT
* }
@@ -734,13 +737,14 @@ private static void authorizationCodeFlowTokenEndpoint(RoutingContext rc) {
{
"token_type":"Bearer",
"scope":"openid email profile",
- "expires_in":3600,
- "ext_expires_in":3600,
+ "expires_in":%d,
+ "ext_expires_in":%d,
"access_token":"%s",
"id_token":"%s",
"refresh_token": "%s"
}
- """.formatted(accessToken, idToken, userAndRoles.encode());
+ """.formatted(accessTokenExpiresIn, accessTokenExpiresIn, accessToken, idToken,
+ userAndRoles.encode());
rc.response()
.putHeader("Content-Type", "application/json")
.putHeader("Cache-Control", "no-store")
@@ -761,7 +765,7 @@ private static void invalidTokenResponse(RoutingContext rc) {
private static String createIdToken(String user, Set roles, String clientId) {
return Jwt.claims()
- .expiresIn(Duration.ofDays(1))
+ .expiresIn(idTokenExpiresIn)
.issuedAt(Instant.now())
.issuer(baseURI)
.audience(clientId)
@@ -778,7 +782,7 @@ private static String createIdToken(String user, Set roles, String clien
private static String createAccessToken(String user, Set roles, Set scope) {
return Jwt.claims()
- .expiresIn(Duration.ofDays(1))
+ .expiresIn(accessTokenExpiresIn)
.issuedAt(Instant.now())
.issuer(baseURI)
.subject(user)
diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchProcessor.java b/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchProcessor.java
index ed5306bdb8b0a..3f5357726b665 100644
--- a/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchProcessor.java
+++ b/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchProcessor.java
@@ -29,6 +29,8 @@
import org.jboss.logging.Logger;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
+import io.quarkus.deployment.Capabilities;
+import io.quarkus.deployment.Capability;
import io.quarkus.deployment.IsDevServicesSupportedByLaunchMode;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
@@ -320,10 +322,15 @@ void devServicesDropAndCreateAndDropByDefault(
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep(onlyIf = HibernateSearchManagementEnabled.class)
- void createManagementRoutes(BuildProducer routes,
+ void createManagementRoutes(Capabilities capabilities,
+ BuildProducer routes,
HibernateSearchElasticsearchRecorder recorder,
HibernateSearchElasticsearchBuildTimeConfig hibernateSearchElasticsearchBuildTimeConfig) {
-
+ if (capabilities.isMissing(Capability.VERTX_HTTP)) {
+ throw new IllegalArgumentException(
+ "Enabling Hibernate Search management endpoints, while there are no Vert.x HTTP capabilities " +
+ "in the application, is not allowed.");
+ }
String managementRootPath = hibernateSearchElasticsearchBuildTimeConfig.management().rootPath();
routes.produce(RouteBuildItem.newManagementRoute(
diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationPathHttpRootTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationPathHttpRootTest.java
index 7d9bc1e7c85c1..7f288518bcdcc 100644
--- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationPathHttpRootTest.java
+++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationPathHttpRootTest.java
@@ -26,7 +26,7 @@ public class ApplicationPathHttpRootTest {
@Test
public void testResources() {
- // Note that /foo is added automatically by RestAssuredURLManager
+ // Note that /foo is added automatically by RestAssuredStateManager
RestAssured.when().get("/hello/world").then().body(Matchers.is("hello world"));
RestAssured.when().get("/world").then().statusCode(404);
}
diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/EmptyBeanParamRecordTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/EmptyBeanParamRecordTest.java
new file mode 100644
index 0000000000000..8dcf587c88efe
--- /dev/null
+++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/EmptyBeanParamRecordTest.java
@@ -0,0 +1,45 @@
+package io.quarkus.resteasy.reactive.server.test.beanparam;
+
+import jakarta.ws.rs.BeanParam;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class EmptyBeanParamRecordTest {
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .setArchiveProducer(() -> {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(EmptyBeanParam.class, Resource.class);
+ }).assertException(x -> {
+ x.printStackTrace();
+ Assertions.assertEquals(
+ "Body parameters (or non-annotated fields) are not allowed for records. Make sure to annotate your record components with @Rest* or @*Param or that they can be injected as context objects.",
+ x.getMessage());
+ });
+
+ @Test
+ void empty() {
+ Assertions.fail();
+ }
+
+ public record EmptyBeanParam(String something, Integer other) {
+ }
+
+ @Path("/")
+ public static class Resource {
+
+ @Path("/record")
+ @GET
+ public String beanParamRecord(@BeanParam EmptyBeanParam param) {
+ return "OK";
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/ReallyEmptyBeanParamRecordTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/ReallyEmptyBeanParamRecordTest.java
new file mode 100644
index 0000000000000..facdbda9d0912
--- /dev/null
+++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/ReallyEmptyBeanParamRecordTest.java
@@ -0,0 +1,46 @@
+package io.quarkus.resteasy.reactive.server.test.beanparam;
+
+import jakarta.ws.rs.BeanParam;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class ReallyEmptyBeanParamRecordTest {
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .setArchiveProducer(() -> {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(EmptyBeanParam.class, Resource.class);
+ }).assertException(x -> {
+ x.printStackTrace();
+ Assertions.assertEquals(
+ "No annotations found on fields at 'io.quarkus.resteasy.reactive.server.test.beanparam.ReallyEmptyBeanParamRecordTest$EmptyBeanParam'. Annotations like `@QueryParam` should be used in fields, not in methods.",
+ x.getMessage());
+ });
+
+ @Test
+ void empty() {
+ Assertions.fail();
+ }
+
+ // This one does not even have body params
+ public record EmptyBeanParam() {
+ }
+
+ @Path("/")
+ public static class Resource {
+
+ @Path("/record")
+ @GET
+ public String beanParamRecord(@BeanParam EmptyBeanParam param) {
+ return "OK";
+ }
+ }
+}
diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java
index 9e1eb7a88ccf8..1e2055163f05b 100644
--- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java
+++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java
@@ -54,6 +54,7 @@
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.gizmo.ClassCreator;
@@ -78,6 +79,7 @@
import io.quarkus.smallrye.reactivemessaging.runtime.HealthCenterInterceptor;
import io.quarkus.smallrye.reactivemessaging.runtime.QuarkusMediatorConfiguration;
import io.quarkus.smallrye.reactivemessaging.runtime.QuarkusWorkerPoolRegistry;
+import io.quarkus.smallrye.reactivemessaging.runtime.ReactiveMessagingConfigBuilderCustomizer;
import io.quarkus.smallrye.reactivemessaging.runtime.ReactiveMessagingConfiguration;
import io.quarkus.smallrye.reactivemessaging.runtime.RequestScopedDecorator;
import io.quarkus.smallrye.reactivemessaging.runtime.SmallRyeReactiveMessagingLifecycle;
@@ -546,6 +548,13 @@ void produceCoroutineScope(
}
}
+ @BuildStep
+ void configCustomizer(BuildProducer serviceProvider) {
+ // Config mapping between SmallRye / MP and Quarkus
+ serviceProvider.produce(ServiceProviderBuildItem
+ .allProvidersFromClassPath(ReactiveMessagingConfigBuilderCustomizer.class.getName()));
+ }
+
private void ensureKotlinCoroutinesEnabled(CoroutineConfigurationBuildItem coroutineConfigurationBuildItem,
MethodInfo method) {
if (!coroutineConfigurationBuildItem.isEnabled()) {
diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java
index c1962a6b10523..9379eb24d5ccf 100644
--- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java
+++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java
@@ -81,7 +81,7 @@ static void produceOutgoingChannel(BuildProducer producer, Str
*/
static Optional getManagingConnector(ChannelDirection direction, String channel) {
return ConfigProvider.getConfig().getOptionalValue(
- "mp.messaging." + direction.name().toLowerCase() + "." + normalizeChannelName(channel) + ".connector",
+ "quarkus.messaging." + direction.name().toLowerCase() + "." + normalizeChannelName(channel) + ".connector",
String.class);
}
@@ -95,7 +95,8 @@ static Optional getManagingConnector(ChannelDirection direction, String
static boolean isChannelEnabled(ChannelDirection direction, String channel) {
return ConfigProvider.getConfig()
.getOptionalValue(
- "mp.messaging." + direction.name().toLowerCase() + "." + normalizeChannelName(channel) + ".enabled",
+ "quarkus.messaging." + direction.name().toLowerCase() + "." + normalizeChannelName(channel)
+ + ".enabled",
Boolean.class)
.orElse(true);
}
diff --git a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/ReactiveMessagingConfigBuilderCustomizer.java b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/ReactiveMessagingConfigBuilderCustomizer.java
new file mode 100644
index 0000000000000..41c39764fe4cb
--- /dev/null
+++ b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/ReactiveMessagingConfigBuilderCustomizer.java
@@ -0,0 +1,46 @@
+package io.quarkus.smallrye.reactivemessaging.runtime;
+
+import java.util.function.Function;
+
+import io.smallrye.config.FallbackConfigSourceInterceptor;
+import io.smallrye.config.RelocateConfigSourceInterceptor;
+import io.smallrye.config.SmallRyeConfigBuilder;
+import io.smallrye.config.SmallRyeConfigBuilderCustomizer;
+
+public class ReactiveMessagingConfigBuilderCustomizer implements SmallRyeConfigBuilderCustomizer {
+
+ @Override
+ public void configBuilder(SmallRyeConfigBuilder builder) {
+ builder.withInterceptors(new FallbackConfigSourceInterceptor(new Fallback()));
+ builder.withInterceptors(new RelocateConfigSourceInterceptor(new Relocate()));
+ }
+
+ private static final String REACTIVE_MESSAGING_PREFIX = "quarkus.messaging.";
+ private static final String MP_MESSAGING_PREFIX = "mp.messaging.";
+ private static final String INCOMING = "incoming.";
+ private static final String OUTGOING = "outgoing.";
+
+ private record Fallback() implements Function {
+ @Override
+ public String apply(String name) {
+ if (name.startsWith(REACTIVE_MESSAGING_PREFIX)
+ && (name.regionMatches(18, INCOMING, 0, 9) || name.regionMatches(18, OUTGOING, 0, 9))) {
+ // replaces quarkus with mp
+ return "mp" + name.substring(7);
+ }
+ return name;
+ }
+ }
+
+ private record Relocate() implements Function {
+ @Override
+ public String apply(String name) {
+ if (name.startsWith(MP_MESSAGING_PREFIX)
+ && (name.regionMatches(13, INCOMING, 0, 9) || name.regionMatches(13, OUTGOING, 0, 9))) {
+ // replaces mp with quarkus
+ return "quarkus" + name.substring(2);
+ }
+ return name;
+ }
+ }
+}
diff --git a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/ReactiveMessagingRuntimeConfig.java b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/ReactiveMessagingRuntimeConfig.java
index c90e8b4144e95..8c3a020c1c23a 100644
--- a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/ReactiveMessagingRuntimeConfig.java
+++ b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/ReactiveMessagingRuntimeConfig.java
@@ -1,12 +1,16 @@
package io.quarkus.smallrye.reactivemessaging.runtime;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import io.quarkus.runtime.annotations.ConfigDocIgnore;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
import io.smallrye.config.WithName;
+import io.smallrye.config.WithParentName;
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
@ConfigMapping(prefix = "quarkus.messaging")
@@ -17,4 +21,46 @@ public interface ReactiveMessagingRuntimeConfig {
*/
@WithName("connector-context-propagation")
Optional> connectorContextPropagation();
+
+ /**
+ * Used internally only. Users use mp.messaging.
+ */
+ @ConfigDocIgnore
+ Map incoming();
+
+ /**
+ * Used internally only. Users use mp.messaging.
+ */
+ @ConfigDocIgnore
+ Map outgoing();
+
+ interface ChannelDirection {
+ /**
+ * Used internally only. Users use mp.messaging.
+ */
+ @ConfigDocIgnore
+ Optional connector();
+
+ /**
+ * Used internally only. Users use mp.messaging.
+ */
+ @ConfigDocIgnore
+ @WithDefault("true")
+ boolean enabled();
+
+ /**
+ * Used internally only.
+ */
+ @ConfigDocIgnore
+ @WithParentName
+ Map channelConfig();
+ }
+
+ interface Incoming extends ChannelDirection {
+
+ }
+
+ interface Outgoing extends ChannelDirection {
+
+ }
}
diff --git a/extensions/smallrye-reactive-messaging/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer b/extensions/smallrye-reactive-messaging/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer
new file mode 100644
index 0000000000000..04d34ef7943cd
--- /dev/null
+++ b/extensions/smallrye-reactive-messaging/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer
@@ -0,0 +1 @@
+io.quarkus.smallrye.reactivemessaging.runtime.ReactiveMessagingConfigBuilderCustomizer
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/management/ManagementInterfaceBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/management/ManagementInterfaceBuildTimeConfig.java
index 1e7491ffd4c72..4a3283cd027b5 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/management/ManagementInterfaceBuildTimeConfig.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/management/ManagementInterfaceBuildTimeConfig.java
@@ -18,7 +18,7 @@ public interface ManagementInterfaceBuildTimeConfig {
/**
* Enables / Disables the usage of a separate interface/port to expose the management endpoints.
* If sets to {@code true}, the management endpoints will be exposed to a different HTTP server.
- * This avoids exposing the management endpoints on a y available server(.
+ * This avoids exposing the management endpoints on a publicly available server.
*/
@WithDefault("false")
boolean enabled();
diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java
index a1a12f54d8864..8113cea9f6016 100644
--- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java
+++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java
@@ -395,6 +395,10 @@ protected InjectableBean scanInjectableBean(ClassInfo currentClassInfo, ClassInf
annotations, field.type(), "%s", new Object[] { field }, applyFieldRules, hasRuntimeConverters,
// We don't support annotation-less path params in injectable beans: only annotations
Collections.emptySet(), field.name(), EMPTY_STRING_ARRAY, new HashMap<>());
+ if (currentClassInfo.isRecord() && result.getType() == ParameterType.BODY) {
+ throw new DeploymentException(
+ "Body parameters (or non-annotated fields) are not allowed for records. Make sure to annotate your record components with @Rest* or @*Param or that they can be injected as context objects.");
+ }
if ((result.getType() != null) && (result.getType() != ParameterType.BEAN)) {
//BODY means no annotation, so for fields not injectable
fieldExtractors.put(field, result);
diff --git a/integration-tests/devtools/src/test/resources/__snapshots__/LgtmCodestartTest/testContent/src_test_java_ilove_quark_us_SimpleTest.java b/integration-tests/devtools/src/test/resources/__snapshots__/LgtmCodestartTest/testContent/src_test_java_ilove_quark_us_SimpleTest.java
index 813fd23e2ab58..5bf7be8cced6b 100644
--- a/integration-tests/devtools/src/test/resources/__snapshots__/LgtmCodestartTest/testContent/src_test_java_ilove_quark_us_SimpleTest.java
+++ b/integration-tests/devtools/src/test/resources/__snapshots__/LgtmCodestartTest/testContent/src_test_java_ilove_quark_us_SimpleTest.java
@@ -1,6 +1,6 @@
package ilove.quark.us;
-import org.eclipse.microprofile.config.inject.ConfigProperty;;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
diff --git a/integration-tests/oidc-dev-services/src/main/java/io/quarkus/it/oidc/dev/services/SecuredResource.java b/integration-tests/oidc-dev-services/src/main/java/io/quarkus/it/oidc/dev/services/SecuredResource.java
index 124482eba00aa..03e21c71494fc 100644
--- a/integration-tests/oidc-dev-services/src/main/java/io/quarkus/it/oidc/dev/services/SecuredResource.java
+++ b/integration-tests/oidc-dev-services/src/main/java/io/quarkus/it/oidc/dev/services/SecuredResource.java
@@ -22,6 +22,9 @@ public class SecuredResource {
@IdToken
JsonWebToken idToken;
+ @Inject
+ JsonWebToken accessToken;
+
@Inject
UserInfo userInfo;
@@ -47,6 +50,13 @@ public String getUserOnly() {
return userInfo.getPreferredUserName() + " " + securityIdentity.getRoles() + " " + userInfo.getName();
}
+ @RolesAllowed("user")
+ @GET
+ @Path("expires-in")
+ public String getExpiresIn() {
+ return String.valueOf(accessToken.getExpirationTime() - accessToken.getIssuedAtTime());
+ }
+
@GET
@Path("auth-server-url")
public String getAuthServerUrl() {
diff --git a/integration-tests/oidc-dev-services/src/test/java/io/quarkus/it/oidc/dev/services/BearerAuthenticationOidcDevServicesTest.java b/integration-tests/oidc-dev-services/src/test/java/io/quarkus/it/oidc/dev/services/BearerAuthenticationOidcDevServicesTest.java
index b12d6ac4f9b7c..fe043beb2ca55 100644
--- a/integration-tests/oidc-dev-services/src/test/java/io/quarkus/it/oidc/dev/services/BearerAuthenticationOidcDevServicesTest.java
+++ b/integration-tests/oidc-dev-services/src/test/java/io/quarkus/it/oidc/dev/services/BearerAuthenticationOidcDevServicesTest.java
@@ -91,6 +91,16 @@ void testEmailAndName() {
.body(Matchers.containsString(" Bob"));
}
+ @Test
+ void testTokenExpiration() {
+ RestAssured.given()
+ .auth().oauth2(getAccessToken("bob"))
+ .get("/secured/expires-in")
+ .then()
+ .statusCode(200)
+ .body(Matchers.is(String.valueOf(5 * 60L))); // The default token expiration is 5 minutes
+ }
+
private String getAccessToken(String user) {
return oidcTestClient.getAccessToken(user, user);
}
diff --git a/pom.xml b/pom.xml
index e6219b530d901..19b1bb9821fc1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,15 +71,15 @@
0.8.147.4.05.5.6
- 7.1.10.Final
+ 7.1.11.Final3.2.04.13.21.17.61.0.1
- 3.1.9.Final
+ 3.1.10.Final9.1.0.Final8.1.2.Final
- 7.1.10.Final
+ 7.1.11.Final1.76.0
diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredStateManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredStateManager.java
new file mode 100644
index 0000000000000..0b60b150920b0
--- /dev/null
+++ b/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredStateManager.java
@@ -0,0 +1,178 @@
+package io.quarkus.test.common;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import org.eclipse.microprofile.config.ConfigProvider;
+
+import io.restassured.RestAssured;
+import io.restassured.config.HttpClientConfig;
+import io.restassured.config.RestAssuredConfig;
+import io.restassured.internal.path.json.ConfigurableJsonSlurper;
+import io.restassured.path.json.JsonPath;
+import io.restassured.specification.RequestSpecification;
+
+/**
+ * Utility class that sets the rest assured port to the default test port and meaningful timeouts.
+ *
+ * This class works whether RestAssured is on the classpath or not - if it is not, invoking the methods of the class are
+ * essentially NO-OPs
+ *
+ * TODO: do we actually want this here, or should it be in a different module?
+ */
+public class RestAssuredStateManager {
+
+ private static final int DEFAULT_HTTP_PORT = 8081;
+ private static final int DEFAULT_HTTPS_PORT = 8444;
+
+ private static int oldPort;
+ private static String oldBaseURI;
+ private static String oldBasePath;
+ private static Object oldRestAssuredConfig; // we can't declare the type here as that would prevent this class for being loaded if RestAssured is not present
+ private static Object oldRequestSpecification;
+
+ private static final boolean REST_ASSURED_PRESENT;
+
+ static {
+ boolean present = false;
+ try {
+ Class.forName("io.restassured.RestAssured");
+ present = true;
+ } catch (ClassNotFoundException ignored) {
+ }
+ REST_ASSURED_PRESENT = present;
+ }
+
+ private RestAssuredStateManager() {
+
+ }
+
+ private static int getPortFromConfig(int defaultValue, String... keys) {
+ for (String key : keys) {
+ Optional port = ConfigProvider.getConfig().getOptionalValue(key, Integer.class);
+ if (port.isPresent())
+ return port.get();
+ }
+ return defaultValue;
+ }
+
+ public static void setURL(boolean useSecureConnection) {
+ setURL(useSecureConnection, null, null);
+ }
+
+ public static void setURL(boolean useSecureConnection, String additionalPath) {
+ setURL(useSecureConnection, null, additionalPath);
+ }
+
+ public static void setURL(boolean useSecureConnection, Integer port) {
+ setURL(useSecureConnection, port, null);
+ }
+
+ public static void setURL(boolean useSecureConnection, Integer port, String additionalPath) {
+ if (!REST_ASSURED_PRESENT) {
+ return;
+ }
+
+ oldPort = RestAssured.port;
+ if (port == null) {
+ port = useSecureConnection ? getPortFromConfig(DEFAULT_HTTPS_PORT, "quarkus.http.test-ssl-port")
+ : getPortFromConfig(DEFAULT_HTTP_PORT, "quarkus.lambda.mock-event-server.test-port",
+ "quarkus.http.test-port");
+ }
+ RestAssured.port = port;
+
+ oldBaseURI = RestAssured.baseURI;
+ final String protocol = useSecureConnection ? "https://" : "http://";
+ String host = ConfigProvider.getConfig().getOptionalValue("quarkus.http.host", String.class)
+ .orElse("localhost");
+ if (host.equals("0.0.0.0")) {
+ host = "localhost";
+ }
+ RestAssured.baseURI = protocol + host;
+
+ oldBasePath = RestAssured.basePath;
+ Optional basePath = ConfigProvider.getConfig().getOptionalValue("quarkus.http.root-path",
+ String.class);
+ if (basePath.isPresent() || additionalPath != null) {
+ StringBuilder bp = new StringBuilder();
+ if (basePath.isPresent()) {
+ if (basePath.get().startsWith("/")) {
+ bp.append(basePath.get().substring(1));
+ } else {
+ bp.append(basePath.get());
+ }
+ if (bp.toString().endsWith("/")) {
+ bp.setLength(bp.length() - 1);
+ }
+ }
+ if (additionalPath != null) {
+ if (!additionalPath.startsWith("/")) {
+ bp.append("/");
+ }
+ bp.append(additionalPath);
+ if (bp.toString().endsWith("/")) {
+ bp.setLength(bp.length() - 1);
+ }
+ }
+ RestAssured.basePath = bp.toString();
+ }
+
+ oldRestAssuredConfig = RestAssured.config();
+
+ Duration timeout = ConfigProvider.getConfig()
+ .getOptionalValue("quarkus.http.test-timeout", Duration.class).orElse(Duration.ofSeconds(30));
+ configureTimeouts(timeout);
+
+ oldRequestSpecification = RestAssured.requestSpecification;
+ if (ConfigProvider.getConfig()
+ .getOptionalValue("quarkus.test.rest-assured.enable-logging-on-failure", Boolean.class).orElse(true)) {
+ RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+ }
+ }
+
+ private static void configureTimeouts(Duration d) {
+ RestAssured.config = RestAssured.config().httpClient(new HttpClientConfig()
+ .setParam("http.conn-manager.timeout", d.toMillis()) // this needs to be long
+ .setParam("http.connection.timeout", (int) d.toMillis()) // this needs to be int
+ .setParam("http.socket.timeout", (int) d.toMillis())); // same here
+ }
+
+ public static void clearState() {
+ if (!REST_ASSURED_PRESENT) {
+ return;
+ }
+
+ clearURL();
+
+ JsonPath.config = null;
+ // Reset the numberReturnType ThreadLocal in ConfigurableJsonSlurper as it is causing class loader leaks
+ try {
+ java.lang.reflect.Field numberReturnTypeField = ConfigurableJsonSlurper.class.getDeclaredField("numberReturnType");
+ numberReturnTypeField.setAccessible(true);
+ ThreadLocal> threadLocal = (ThreadLocal>) numberReturnTypeField.get(null);
+ if (threadLocal != null) {
+ threadLocal.remove();
+ }
+ } catch (Exception e) {
+ // Ignore if the field doesn't exist or can't be accessed
+ }
+ }
+
+ /**
+ * @deprecated most probably, you want to call {@link #clearState()}. If it's not the case, please let us know so that we
+ * can reconsider the deprecation.
+ * Note: when removing, please move the code to {@link #clearState()}.
+ */
+ @Deprecated(forRemoval = true, since = "3.31")
+ static void clearURL() {
+ if (!REST_ASSURED_PRESENT) {
+ return;
+ }
+
+ RestAssured.port = oldPort;
+ RestAssured.baseURI = oldBaseURI;
+ RestAssured.basePath = oldBasePath;
+ RestAssured.config = (RestAssuredConfig) oldRestAssuredConfig;
+ RestAssured.requestSpecification = (RequestSpecification) oldRequestSpecification;
+ }
+}
diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredURLManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredURLManager.java
index ec615d334a041..975d325a147e3 100644
--- a/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredURLManager.java
+++ b/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredURLManager.java
@@ -1,59 +1,11 @@
package io.quarkus.test.common;
-import java.time.Duration;
-import java.util.Optional;
-
-import org.eclipse.microprofile.config.ConfigProvider;
-
-import io.restassured.RestAssured;
-import io.restassured.config.HttpClientConfig;
-import io.restassured.config.RestAssuredConfig;
-import io.restassured.specification.RequestSpecification;
-
/**
- * Utility class that sets the rest assured port to the default test port and meaningful timeouts.
- *
- * This class works whether RestAssured is on the classpath or not - if it is not, invoking the methods of the class are
- * essentially NO-OPs
- *
- * TODO: do we actually want this here, or should it be in a different module?
+ * @deprecated use {@link RestAssuredStateManager} instead
*/
+@Deprecated(forRemoval = true, since = "3.31")
public class RestAssuredURLManager {
- private static final int DEFAULT_HTTP_PORT = 8081;
- private static final int DEFAULT_HTTPS_PORT = 8444;
-
- private static int oldPort;
- private static String oldBaseURI;
- private static String oldBasePath;
- private static Object oldRestAssuredConfig; // we can't declare the type here as that would prevent this class for being loaded if RestAssured is not present
- private static Object oldRequestSpecification;
-
- private static final boolean REST_ASSURED_PRESENT;
-
- static {
- boolean present = false;
- try {
- Class.forName("io.restassured.RestAssured");
- present = true;
- } catch (ClassNotFoundException ignored) {
- }
- REST_ASSURED_PRESENT = present;
- }
-
- private RestAssuredURLManager() {
-
- }
-
- private static int getPortFromConfig(int defaultValue, String... keys) {
- for (String key : keys) {
- Optional port = ConfigProvider.getConfig().getOptionalValue(key, Integer.class);
- if (port.isPresent())
- return port.get();
- }
- return defaultValue;
- }
-
public static void setURL(boolean useSecureConnection) {
setURL(useSecureConnection, null, null);
}
@@ -67,83 +19,10 @@ public static void setURL(boolean useSecureConnection, Integer port) {
}
public static void setURL(boolean useSecureConnection, Integer port, String additionalPath) {
- if (!REST_ASSURED_PRESENT) {
- return;
- }
-
- oldPort = RestAssured.port;
- if (port == null) {
- port = useSecureConnection ? getPortFromConfig(DEFAULT_HTTPS_PORT, "quarkus.http.test-ssl-port")
- : getPortFromConfig(DEFAULT_HTTP_PORT, "quarkus.lambda.mock-event-server.test-port",
- "quarkus.http.test-port");
- }
- RestAssured.port = port;
-
- oldBaseURI = RestAssured.baseURI;
- final String protocol = useSecureConnection ? "https://" : "http://";
- String host = ConfigProvider.getConfig().getOptionalValue("quarkus.http.host", String.class)
- .orElse("localhost");
- if (host.equals("0.0.0.0")) {
- host = "localhost";
- }
- RestAssured.baseURI = protocol + host;
-
- oldBasePath = RestAssured.basePath;
- Optional basePath = ConfigProvider.getConfig().getOptionalValue("quarkus.http.root-path",
- String.class);
- if (basePath.isPresent() || additionalPath != null) {
- StringBuilder bp = new StringBuilder();
- if (basePath.isPresent()) {
- if (basePath.get().startsWith("/")) {
- bp.append(basePath.get().substring(1));
- } else {
- bp.append(basePath.get());
- }
- if (bp.toString().endsWith("/")) {
- bp.setLength(bp.length() - 1);
- }
- }
- if (additionalPath != null) {
- if (!additionalPath.startsWith("/")) {
- bp.append("/");
- }
- bp.append(additionalPath);
- if (bp.toString().endsWith("/")) {
- bp.setLength(bp.length() - 1);
- }
- }
- RestAssured.basePath = bp.toString();
- }
-
- oldRestAssuredConfig = RestAssured.config();
-
- Duration timeout = ConfigProvider.getConfig()
- .getOptionalValue("quarkus.http.test-timeout", Duration.class).orElse(Duration.ofSeconds(30));
- configureTimeouts(timeout);
-
- oldRequestSpecification = RestAssured.requestSpecification;
- if (ConfigProvider.getConfig()
- .getOptionalValue("quarkus.test.rest-assured.enable-logging-on-failure", Boolean.class).orElse(true)) {
- RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
- }
- }
-
- private static void configureTimeouts(Duration d) {
- RestAssured.config = RestAssured.config().httpClient(new HttpClientConfig()
- .setParam("http.conn-manager.timeout", d.toMillis()) // this needs to be long
- .setParam("http.connection.timeout", (int) d.toMillis()) // this needs to be int
- .setParam("http.socket.timeout", (int) d.toMillis())); // same here
+ RestAssuredStateManager.setURL(useSecureConnection, port, additionalPath);
}
public static void clearURL() {
- if (!REST_ASSURED_PRESENT) {
- return;
- }
-
- RestAssured.port = oldPort;
- RestAssured.baseURI = oldBaseURI;
- RestAssured.basePath = oldBasePath;
- RestAssured.config = (RestAssuredConfig) oldRestAssuredConfig;
- RestAssured.requestSpecification = (RequestSpecification) oldRequestSpecification;
+ RestAssuredStateManager.clearURL();
}
}
diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/ComponentContainer.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/ComponentContainer.java
index 90a6bbd059bd9..2c5309415f78b 100644
--- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/ComponentContainer.java
+++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/ComponentContainer.java
@@ -59,6 +59,7 @@
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Indexer;
+import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.jandex.Type.Kind;
import org.jboss.logging.Logger;
@@ -357,6 +358,19 @@ public void register(RegistrationContext registrationContext) {
.add(new TypeAndQualifiers(Types.jandexType(param.getParameterizedType()), requiredQualifiers));
}
+ // We need to remove duplicate unsatisfied injection points
+ // More specifically, injection points with the the same required type
+ // and the same qualifiers (with non-binding members ignored)
+ if (!unsatisfiedInjectionPoints.isEmpty()) {
+ Map> qualifierToNonbindingMembers = findQualifierToNonbindingMembers(
+ beanDeployment);
+ if (!qualifierToNonbindingMembers.isEmpty()) {
+ findDuplicateInjectionPoints(beanDeployment, unsatisfiedInjectionPoints,
+ qualifierToNonbindingMembers)
+ .forEach(unsatisfiedInjectionPoints::remove);
+ }
+ }
+
for (TypeAndQualifiers unsatisfied : unsatisfiedInjectionPoints) {
ClassInfo implementationClass = computingIndex.getClassByName(unsatisfied.type.name());
BeanConfigurator