diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index a38220001f0d0..21758802c8383 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -23,7 +23,7 @@
3.1.71.3.21
- 1.1.5
+ 1.1.62.1.5.Final3.1.2.Final6.2.7.Final
@@ -34,7 +34,7 @@
1.32.01.32.0-alpha1.21.0-alpha
- 5.2.1.Final
+ 5.2.2.Final1.12.42.1.120.22.0
@@ -55,7 +55,7 @@
4.1.04.0.03.10.0
- 2.8.2
+ 2.8.36.3.04.5.12.1.0
@@ -85,7 +85,7 @@
4.0.24.0.59.7
- 2.16.0
+ 2.16.116.0.0.Final3.0-alpha-22.1.0
@@ -95,19 +95,19 @@
2.17.01.0.0.Final3.14.0
- 1.16.1
+ 1.17.01.7.0
- 6.4.4.Final
+ 6.4.7.Final1.14.116.0.6.Final2.2.2.Final8.0.1.Final
- 7.1.0.Final
+ 7.1.1.Final7.0.1.Final2.38.0.0.Final
@@ -116,7 +116,6 @@
2.2.5.Final2.2.2.Final2.2.1.Final
- 2.0.62.0.0.Final1.7.0.Final1.0.1.Final
@@ -140,8 +139,8 @@
1.2.62.25.10.2
- 15.0.1.Final
- 5.0.2.Final
+ 15.0.2.Final
+ 5.0.3.Final3.1.54.1.108.Final1.16.0
@@ -155,7 +154,7 @@
2.13.131.2.3
- 3.11.4
+ 3.11.52.15.23.1.01.0.0
@@ -166,7 +165,7 @@
4.1.23.2.04.2.1
- 3.0.4.Final
+ 3.0.6.Final10.10.03.0.3
@@ -5064,11 +5063,6 @@
asm-util${asm.version}
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
- org.jboss.slf4jslf4j-jboss-logmanager
diff --git a/bom/dev-ui/pom.xml b/bom/dev-ui/pom.xml
index c461772d70c91..a8c23cc5f4a11 100644
--- a/bom/dev-ui/pom.xml
+++ b/bom/dev-ui/pom.xml
@@ -28,7 +28,7 @@
1.7.51.7.05.5.0
- 1.0.12
+ 1.0.131.8.32.4.0
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java
index 62ef5f74e3043..f6c5b9344a991 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java
@@ -622,7 +622,13 @@ ReadResult run() {
// it's not managed by us; record it
ConfigValue configValue = withoutExpansion(() -> runtimeConfig.getConfigValue(propertyName));
if (configValue.getValue() != null) {
- runTimeValues.put(configValue.getNameProfiled(), configValue.getValue());
+ String configName = configValue.getNameProfiled();
+ // record the profile parent in the original form; if recorded in the active profile it may mess the profile ordering
+ if (configName.equals("quarkus.config.profile.parent")) {
+ runTimeValues.put(propertyName, configValue.getValue());
+ } else {
+ runTimeValues.put(configName, configValue.getValue());
+ }
}
// in the case the user defined compound keys in YAML (or similar config source, that quotes the name)
@@ -1045,6 +1051,7 @@ private Converter> getConverter(SmallRyeConfig config, Field field, ConverterT
* want to record properties set by the compiling JVM (or other properties that are only related to the build).
*/
private Set getAllProperties(final Set registeredRoots) {
+ // Collects all properties from allowed sources
Set sourcesProperties = new HashSet<>();
for (ConfigSource configSource : config.getConfigSources()) {
if (configSource instanceof SysPropConfigSource || configSource instanceof EnvConfigSource
@@ -1120,7 +1127,16 @@ public Set getPropertyNames() {
String[] profiles = config.getProfiles().toArray(String[]::new);
for (String property : builder.build().getPropertyNames()) {
- properties.add(ProfileConfigSourceInterceptor.activeName(property, profiles));
+ String activeProperty = ProfileConfigSourceInterceptor.activeName(property, profiles);
+ // keep the profile parent in the original form; if we use the active profile it may mess the profile ordering
+ if (activeProperty.equals("quarkus.config.profile.parent")) {
+ if (!activeProperty.equals(property)) {
+ properties.remove(activeProperty);
+ properties.add(property);
+ continue;
+ }
+ }
+ properties.add(activeProperty);
}
return properties;
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigCompatibility.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigCompatibility.java
index 20b1853ae0c17..8710a0211c4cb 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigCompatibility.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigCompatibility.java
@@ -55,12 +55,18 @@ public final class ConfigCompatibility {
ConfigCompatibility::quarkusPackageIncludedOptionalDependencies),
entry(List.of("quarkus", "package", "include-dependency-list"),
ConfigCompatibility::quarkusPackageIncludeDependencyList),
+ entry(List.of("quarkus", "package", "decompiler", "version"),
+ ConfigCompatibility::quarkusPackageDecompilerVersion),
+ entry(List.of("quarkus", "package", "decompiler", "enabled"),
+ ConfigCompatibility::quarkusPackageDecompilerEnabled),
+ entry(List.of("quarkus", "package", "decompiler", "jar-directory"),
+ ConfigCompatibility::quarkusPackageDecompilerJarDirectory),
entry(List.of("quarkus", "package", "vineflower", "version"),
- ConfigCompatibility::quarkusPackageVineflowerVersion),
+ ConfigCompatibility::quarkusPackageDecompilerVersion),
entry(List.of("quarkus", "package", "vineflower", "enabled"),
- ConfigCompatibility::quarkusPackageVineflowerEnabled),
+ ConfigCompatibility::quarkusPackageDecompilerEnabled),
entry(List.of("quarkus", "package", "vineflower", "jar-directory"),
- ConfigCompatibility::quarkusPackageVineflowerJarDirectory),
+ ConfigCompatibility::quarkusPackageDecompilerJarDirectory),
entry(List.of("quarkus", "package", "manifest", "attributes", "*"),
ConfigCompatibility::quarkusPackageManifestAttributes),
entry(List.of("quarkus", "package", "manifest", "sections", "*", "*"),
@@ -102,9 +108,10 @@ public final class ConfigCompatibility {
ConfigCompatibility::quarkusPackageJarManifestSections),
entry(List.of("quarkus", "package", "jar", "manifest", "add-implementation-entries"),
ConfigCompatibility::quarkusPackageJarManifestAddImplementationEntries),
- entry(List.of("quarkus", "package", "decompiler", "enabled"), ConfigCompatibility::quarkusPackageDecompilerEnabled),
- entry(List.of("quarkus", "package", "decompiler", "jar-directory"),
- ConfigCompatibility::quarkusPackageDecompilerJarDirectory));
+ entry(List.of("quarkus", "package", "jar", "decompiler", "enabled"),
+ ConfigCompatibility::quarkusPackageJarDecompilerEnabled),
+ entry(List.of("quarkus", "package", "jar", "decompiler", "jar-directory"),
+ ConfigCompatibility::quarkusPackageJarDecompilerJarDirectory));
/**
* The interceptor at the front of the chain which handles hiding deprecated properties from the iterator.
@@ -271,17 +278,17 @@ private static List quarkusPackageIncludedOptionalDependencies(ConfigSou
return List.of("quarkus.package.jar.included-optional-dependencies");
}
- private static List quarkusPackageVineflowerVersion(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
+ private static List quarkusPackageDecompilerVersion(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
// always hide this ignored property
return List.of();
}
- private static List quarkusPackageVineflowerEnabled(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
+ private static List quarkusPackageDecompilerEnabled(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
// simple mapping to a new name
return List.of("quarkus.package.decompiler.enabled");
}
- private static List quarkusPackageVineflowerJarDirectory(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
+ private static List quarkusPackageDecompilerJarDirectory(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
// simple mapping to a new name
return List.of("quarkus.package.decompiler.jar-directory");
}
@@ -500,26 +507,30 @@ private static ConfigValue quarkusPackageJarManifestAddImplementationEntries(Con
}
}
- private static ConfigValue quarkusPackageDecompilerEnabled(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
- ConfigValue oldVal = ctxt.restart("quarkus.package.vineflower.enabled");
+ private static ConfigValue quarkusPackageJarDecompilerEnabled(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
+ ConfigValue oldVal = ctxt.restart("quarkus.package.decompiler.enabled");
if (oldVal == null) {
- // on to the default value
- return ctxt.proceed(ni.getName());
- } else {
- // map old name to new name
- return oldVal.withName(ni.getName());
+ oldVal = ctxt.restart("quarkus.package.vineflower.enabled");
+ if (oldVal == null) {
+ // on to the default value
+ return ctxt.proceed(ni.getName());
+ }
}
+ // map old name to new name
+ return oldVal.withName(ni.getName());
}
- private static ConfigValue quarkusPackageDecompilerJarDirectory(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
- ConfigValue oldVal = ctxt.restart("quarkus.package.vineflower.jar-directory");
+ private static ConfigValue quarkusPackageJarDecompilerJarDirectory(ConfigSourceInterceptorContext ctxt, NameIterator ni) {
+ ConfigValue oldVal = ctxt.restart("quarkus.package.decompiler.jar-directory");
if (oldVal == null) {
- // on to the default value
- return ctxt.proceed(ni.getName());
- } else {
- // map old name to new name
- return oldVal.withName(ni.getName());
+ oldVal = ctxt.restart("quarkus.package.vineflower.jar-directory");
+ if (oldVal == null) {
+ // on to the default value
+ return ctxt.proceed(ni.getName());
+ }
}
+ // map old name to new name
+ return oldVal.withName(ni.getName());
}
// utilities
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java b/core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java
index 6037be04cd1da..1dff627633358 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java
@@ -55,6 +55,7 @@ public void accept(Path path) {
final ApplicationModel existingModel = appModel.getAppModel(appRoot);
System.setProperty("quarkus.package.jar.type", "mutable-jar");
+ System.setProperty("quarkus.native.enabled", "false");
try (CuratedApplication bootstrap = QuarkusBootstrap.builder()
.setAppArtifact(existingModel.getAppArtifact())
.setExistingModel(existingModel)
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassPathSystemPropBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassPathSystemPropBuildStep.java
index feda25cbade14..dccb19214baec 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassPathSystemPropBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassPathSystemPropBuildStep.java
@@ -4,7 +4,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.quarkus.deployment.annotations.BuildProducer;
@@ -46,8 +45,8 @@ public void set(List setCPItems,
}
}
- String classPathValue = Stream.concat(parentFirst.stream(), regular.stream()).map(p -> p.toAbsolutePath().toString())
- .collect(Collectors.joining(":"));
- recorder.set(classPathValue);
+ List allJarPaths = Stream.concat(parentFirst.stream(), regular.stream()).map(p -> p.toAbsolutePath().toString())
+ .toList();
+ recorder.set(allJarPaths);
}
}
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 a84ccdd1a0fda..130d382c3718b 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
@@ -759,6 +759,12 @@ private static Set staticSafeServices(Set services) {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
Set staticSafe = new HashSet<>();
for (String service : services) {
+ // SmallRye Config services are always safe, but they cannot be annotated with @StaticInitSafe
+ if (service.startsWith("io.smallrye.config.")) {
+ staticSafe.add(service);
+ continue;
+ }
+
try {
Class> serviceClass = classloader.loadClass(service);
if (serviceClass.isAnnotationPresent(StaticInitSafe.class)) {
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java
index 4c7ed149e1737..a410463dba571 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java
@@ -183,7 +183,8 @@ public static void run(Application application, Class extends QuarkusApplicati
} else {
for (Integer port : ports) {
applicationLogger
- .warnf("Use 'netstat -anop | grep %d' to identify the process occupying the port.", port);
+ .warnf("Use 'ss -anop | grep %d' or 'netstat -anop | grep %d' to identify the process occupying the port.",
+ port);
}
applicationLogger.warn("You can try to kill it with 'kill -9 '.");
}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ClassPathSystemPropertyRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/ClassPathSystemPropertyRecorder.java
index fdca4fcb0cb65..c5f17f4b8d0f6 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/ClassPathSystemPropertyRecorder.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/ClassPathSystemPropertyRecorder.java
@@ -1,11 +1,13 @@
package io.quarkus.runtime;
+import java.util.List;
+
import io.quarkus.runtime.annotations.Recorder;
@Recorder
public class ClassPathSystemPropertyRecorder {
- public void set(String value) {
- System.setProperty("java.class.path", value);
+ public void set(List allJarPaths) {
+ System.setProperty("java.class.path", String.join(":", allJarPaths));
}
}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigBuilderCustomizer.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigBuilderCustomizer.java
index 6d9e82852aa30..3edbd84569bf5 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigBuilderCustomizer.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigBuilderCustomizer.java
@@ -7,6 +7,7 @@
import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE_PARENT;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.OptionalInt;
import java.util.function.Function;
@@ -70,7 +71,12 @@ public String apply(final String name) {
return name;
}
- });
+ }) {
+ @Override
+ public Iterator iterateNames(final ConfigSourceInterceptorContext context) {
+ return context.iterateNames();
+ }
+ };
}
@Override
@@ -89,7 +95,12 @@ public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorConte
fallbacks.put("quarkus.config.profile.parent", SMALLRYE_CONFIG_PROFILE_PARENT);
fallbacks.put("quarkus.config.mapping.validate-unknown", SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN);
fallbacks.put("quarkus.config.log.values", SMALLRYE_CONFIG_LOG_VALUES);
- return new FallbackConfigSourceInterceptor(fallbacks);
+ return new FallbackConfigSourceInterceptor(fallbacks) {
+ @Override
+ public Iterator iterateNames(final ConfigSourceInterceptorContext context) {
+ return context.iterateNames();
+ }
+ };
}
@Override
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/DisableLoggingFeature.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/DisableLoggingFeature.java
index 955884c00e8ac..2b5565c8015ef 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/graal/DisableLoggingFeature.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/DisableLoggingFeature.java
@@ -31,10 +31,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
public void afterAnalysis(AfterAnalysisAccess access) {
for (String category : CATEGORIES) {
Level level = categoryMap.remove(category);
- if (level != null) {
- Logger logger = Logger.getLogger(category);
- logger.setLevel(level);
- }
+ Logger logger = Logger.getLogger(category);
+ logger.setLevel(level);
}
}
diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc
index e4f0065489314..a56046cb64efd 100644
--- a/docs/src/main/asciidoc/datasource.adoc
+++ b/docs/src/main/asciidoc/datasource.adoc
@@ -177,6 +177,7 @@ Until now, the configuration has been the same regardless of whether you are usi
When you have defined the database kind and the credentials, the rest depends on what type of driver you are using.
It is possible to use JDBC and a reactive driver simultaneously.
+[[jdbc-datasource]]
==== JDBC datasource
JDBC is the most common database connection pattern, typically needed when used in combination with non-reactive Hibernate ORM.
@@ -294,6 +295,7 @@ AgroalDataSource defaultDataSource;
In the above example, the type is `AgroalDataSource`, a `javax.sql.DataSource` subtype.
Because of this, you can also use `javax.sql.DataSource` as the injected type.
+[[reactive-datasource]]
==== Reactive datasource
Quarkus offers several reactive clients for use with a reactive datasource.
@@ -325,10 +327,22 @@ Be aware that setting the pool size too low might cause some requests to time ou
For more information about pool size adjustment properties, see the <> section.
+[[jdbc-and-reactive-datasources-simultaneously]]
==== JDBC and reactive datasources simultaneously
When a JDBC extension - along with Agroal - and a reactive datasource extension handling the given database kind are included, they will both be created by default.
+If you want to use them both,
+make sure to set both <> and <> configuration,
+for example:
+
+[source,properties]
+----
+%prod.quarkus.datasource.reactive.url=postgresql:///your_database
+%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test
+----
+
+If you do not want to have both a JDBC datasource and a reactive datasource created, use the following configuration.
* To disable the JDBC datasource explicitly:
+
[source, properties]
diff --git a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc
index 523793c9373bc..8dc1c5784ed1b 100644
--- a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc
+++ b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc
@@ -401,7 +401,7 @@ quarkus.kubernetes.annotations."app.quarkus/id"=42
----
[[env-vars]]
-==== Environment variables
+=== Environment variables
Kubernetes provides multiple ways of defining environment variables:
@@ -410,7 +410,7 @@ Kubernetes provides multiple ways of defining environment variables:
- interpolate a single value identified by a given field in a Secret or ConfigMap
- interpolate a value from a field within the same resource
-===== Environment variables from key/value pairs
+==== Environment variables from key/value pairs
To add a key/value pair as an environment variable in the generated resources:
@@ -423,7 +423,7 @@ The command above will add `MY_ENV_VAR=foobar` as an environment variable.
Please note that the key `my-env-var` will be converted to uppercase and dashes will be replaced by underscores resulting in `MY_ENV_VAR`.
[[secret-mapping]]
-===== Environment variables from Secret
+==== Environment variables from Secret
To add all key/value pairs of `Secret` as environment variables just apply the following configuration, separating each `Secret`
to be used as source by a comma (`,`):
@@ -467,7 +467,7 @@ This would generate the following in the `env` section of your container:
optional: false
----
-===== Environment variables from ConfigMap
+==== Environment variables from ConfigMap
To add all key/value pairs from `ConfigMap` as environment variables just apply the following configuration, separating each
`ConfigMap` to be used as source by a comma (`,`):
@@ -512,7 +512,7 @@ This would generate the following in the `env` section of your container:
optional: false
----
-===== Environment variables from fields
+==== Environment variables from fields
It's also possible to use the value from another field to add a new environment variable by specifying the path of the field to be used as a source, as follows:
@@ -532,7 +532,7 @@ quarkus.openshift.env.fields.foo=metadata.name
----
====
-===== Validation
+==== Validation
A conflict between two definitions, e.g. mistakenly assigning both a value and specifying that a variable is derived from a field, will result in an error being thrown at build time so that you get the opportunity to fix the issue before you deploy your application to your cluster where it might be more difficult to diagnose the source of the issue.
@@ -561,7 +561,7 @@ and a warning will be issued to alert you of the problem.For example, if you def
`quarkus.kubernetes.env-vars.my-env-var.value=foobar` and `quarkus.kubernetes.env.vars.my-env-var=newValue`, the extension will
only generate an environment variable `MY_ENV_VAR=newValue` and issue a warning.
-==== Mounting volumes
+=== Mounting volumes
The Kubernetes extension allows the user to configure both volumes and mounts for the application.
Any volume can be mounted with a simple configuration:
@@ -574,14 +574,14 @@ quarkus.kubernetes.mounts.my-volume.path=/where/to/mount
This will add a mount to the pod for volume `my-volume` to path `/where/to/mount`.
The volumes themselves can be configured as shown in the sections below.
-===== Secret volumes
+==== Secret volumes
[source,properties]
----
quarkus.kubernetes.secret-volumes.my-volume.secret-name=my-secret
----
-===== ConfigMap volumes
+==== ConfigMap volumes
[source,properties]
----
@@ -616,7 +616,7 @@ The application config volumes will be created using path: `/mnt/app-secret` and
Note: Users may use both properties at the same time.
-=== Changing the number of replicas:
+=== Changing the number of replicas
To change the number of replicas from 1 to 3:
diff --git a/docs/src/main/asciidoc/dev-ui.adoc b/docs/src/main/asciidoc/dev-ui.adoc
index b4365b2ef1aec..e27cb0910d8a1 100644
--- a/docs/src/main/asciidoc/dev-ui.adoc
+++ b/docs/src/main/asciidoc/dev-ui.adoc
@@ -697,7 +697,7 @@ import '@quarkus-webcomponents/codeblock';
;
----
-https://github.com/quarkusio/quarkus/blob/e03a97845738436c69443a591ec4ce88ed04ac91/extensions/kubernetes/vanilla/deployment/src/main/resources/dev-ui/qwc-kubernetes-manifest.js#L99[Example code]
+https://github.com/quarkusio/quarkus/blob/05800d2a74601247a465f91f50d18c4075fb7fe6/extensions/kubernetes/vanilla/deployment/src/main/resources/dev-ui/qwc-kubernetes-manifest.js#L102[Example code]
Or fetching the contents from a URL:
@@ -711,7 +711,35 @@ Or fetching the contents from a URL:
----
-https://github.com/quarkusio/quarkus/blob/95c54fa46a6b6f31d69477234486d9359a2a3a4a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js#L116[Example code]
+https://github.com/quarkusio/quarkus/blob/05800d2a74601247a465f91f50d18c4075fb7fe6/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js#L118[Example code]
+
+To make sure that the code block adopt the correct code-mirror theme (based on the current one in Dev UI), you can do the following:
+
+[source,javascript]
+----
+import { observeState } from 'lit-element-state';
+import { themeState } from 'theme-state';
+----
+
+Then change the `extends` to observe state:
+
+[source,javascript]
+----
+extends observeState(LitElement) {
+----
+
+Now you can get the current theme, so add the `theme` property to your code block, example:
+
+[source,html]
+----
+
+
+
+
+----
====== IDE link
diff --git a/docs/src/main/asciidoc/extension-codestart.adoc b/docs/src/main/asciidoc/extension-codestart.adoc
index 891a6e61da066..3d5dba084ee1d 100644
--- a/docs/src/main/asciidoc/extension-codestart.adoc
+++ b/docs/src/main/asciidoc/extension-codestart.adoc
@@ -13,7 +13,7 @@ This guide explains how to create and configure a Quarkus Codestart for an exten
== Description
-"Extension Codestarts" is the name we give to our Quarkus extension quickstart code generation system. It aims to provide a personalized getting started experience with Quarkus.
+"Extension Codestarts" is the name we give to our Quarkus extension "getting started" code generation system. It aims to provide a personalized getting started experience with Quarkus.
A Quarkus extension is able to provide one or more well-defined codestarts which will contain the resources and code required/recommended to start using that particular extension.
Extension codestarts are applied by default when using the Quarkus tooling (if the chosen extensions contain any):
diff --git a/docs/src/main/asciidoc/flyway.adoc b/docs/src/main/asciidoc/flyway.adoc
index 3544d4361e457..ecdee7ee18944 100644
--- a/docs/src/main/asciidoc/flyway.adoc
+++ b/docs/src/main/asciidoc/flyway.adoc
@@ -285,6 +285,23 @@ When using Flyway together with Hibernate ORM, you can use the Dev UI to generat
You can find more information about this feature in the xref:hibernate-orm.adoc#flyway[Hibernate ORM guide].
+[[reactive-datasources]]
+== Flyway and Reactive datasources
+
+Flyway internally relies on a JDBC datasource,
+whereas reactive use cases will rely on xref:reactive-sql-clients.adoc[reactive SQL clients],
+either directly or through xref:hibernate-reactive.adoc[Hibernate Reactive].
+This is not a problem in Quarkus,
+because xref:datasource.adoc#jdbc-and-reactive-datasources-simultaneously[a single configured datasource can be made available both through reactive clients and JDBC].
+
+To use Flyway on a datasource you otherwise access reactively,
+simply make sure to configure that datasource
+both as xref:datasource.adoc#jdbc-datasource[JDBC]
+and xref:datasource.adoc#reactive-datasource[reactive].
+This involves in particular adding dependencies to Quarkus extensions
+for both the JDBC driver and the reactive client,
+for instance `quarkus-jdbc-postgresql` *and* `quarkus-reactive-pg-client`.
+
== Flyway on Kubernetes
Sometimes, it's helpful not to execute Flyway initialization on each application startup. One such example is when deploying
diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc
index 3923a512e4327..def7a4f8bf355 100644
--- a/docs/src/main/asciidoc/hibernate-orm.adoc
+++ b/docs/src/main/asciidoc/hibernate-orm.adoc
@@ -741,8 +741,10 @@ Add the following in your properties file.
[[flyway]]
== Automatically transitioning to Flyway to Manage Schemas
-If you have the xref:flyway.adoc[Flyway extension] installed when running in development mode, Quarkus provides a simple way to turn
-your Hibernate ORM auto generated schema into a Flyway migration file. This is intended to make is easy to move from
+If you have the xref:flyway.adoc[Flyway extension] installed when running in development mode,
+Quarkus provides a simple way to initialize your Flyway configuration
+using the schema generated automatically by Hibernate ORM.
+This is intended to ease the move from
the early development phase, where Hibernate can be used to quickly set up the schema, to the production phase, where
Flyway is used to manage schema changes.
diff --git a/docs/src/main/asciidoc/hibernate-reactive.adoc b/docs/src/main/asciidoc/hibernate-reactive.adoc
index 6a3efe3a2b594..52d2f412e0d66 100644
--- a/docs/src/main/asciidoc/hibernate-reactive.adoc
+++ b/docs/src/main/asciidoc/hibernate-reactive.adoc
@@ -224,6 +224,22 @@ This will inject the `Mutiny.SessionFactory` of the default persistence unit.
NOTE: Prior to Quarkus 3.0 it was also possible to inject a `@RequestScoped` bean for `Mutiny.Session`. However, the lifecycle of a reactive session does not fit the lifecycle of the CDI request context. Therefore, this bean is removed in Quarkus 3.0.
+[[flyway]]
+== Automatically transitioning to Flyway to Manage Schemas
+
+Hibernate Reactive can be used in the same application as Flyway.
+See xref:flyway.adoc#reactive-datasources[this section of the Flyway extension documentation]
+for details regarding configuration of Flyway in a reactive application.
+
+[TIP]
+====
+If you have the xref:flyway.adoc[Flyway extension] installed when running in development mode,
+Quarkus provides a simple way to initialize your Flyway configuration
+using the schema generated automatically by Hibernate Reactive.
+
+See xref:hibernate-orm.adoc#flyway[the Hibernate ORM guide] for more details.
+====
+
[[testing]]
=== Testing
diff --git a/docs/src/main/asciidoc/http-reference.adoc b/docs/src/main/asciidoc/http-reference.adoc
index 5201ebbc4df5e..d92ef3e2de8af 100644
--- a/docs/src/main/asciidoc/http-reference.adoc
+++ b/docs/src/main/asciidoc/http-reference.adoc
@@ -518,7 +518,7 @@ import jakarta.inject.Singleton;
import io.quarkus.vertx.http.HttpServerOptionsCustomizer;
@Singleton <1>
-public static class MyCustomizer implements HttpServerOptionsCustomizer {
+public class MyCustomizer implements HttpServerOptionsCustomizer {
@Override
public void customizeHttpServer(HttpServerOptions options) { <2>
diff --git a/docs/src/main/asciidoc/init-tasks.adoc b/docs/src/main/asciidoc/init-tasks.adoc
index 939ae65a79c29..6c750cc087955 100644
--- a/docs/src/main/asciidoc/init-tasks.adoc
+++ b/docs/src/main/asciidoc/init-tasks.adoc
@@ -62,7 +62,7 @@ For Liquibase:
[source,properties]
----
-quarkus.kubernets.init-tasks.liquibase.enabled=false
+quarkus.kubernetes.init-tasks.liquibase.enabled=false
----
For Liquibase Mongodb:
diff --git a/docs/src/main/asciidoc/rest.adoc b/docs/src/main/asciidoc/rest.adoc
index 968213774a87b..4d1fac46866e8 100644
--- a/docs/src/main/asciidoc/rest.adoc
+++ b/docs/src/main/asciidoc/rest.adoc
@@ -1458,8 +1458,12 @@ A very simple Jakarta REST Resource that uses `Person` could be:
----
package org.acme.rest;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Response;
@Path("person")
public class Person {
@@ -1469,8 +1473,25 @@ public class Person {
public Person getPerson(Long id) {
return new Person(id, "foo", "bar", "Brick Lane");
}
+
+ @Produces(APPLICATION_JSON) <1>
+ @Path("/friend/{id}")
+ @GET
+ public Response getPersonFriend(Long id) {
+ var person = new Person(id, "foo", "bar", "Brick Lane");
+ return Response.ok(person).build();
+ }
}
----
+<1> The `@SecureField` annotation is only effective when Quarkus recognizes that produced content type is the 'application/json' type.
+
+WARNING: Currently you cannot use the `@SecureField` annotation to secure your data returned from resource methods returning the `io.smallrye.mutiny.Multi` reactive type.
+
+[IMPORTANT]
+====
+All resource methods returning data secured with the `@SecureField` annotation should be tested.
+Please make sure data are secured as you intended.
+====
Assuming security has been set up for the application (see our xref:security-overview.adoc[guide] for more details), when a user with the `admin` role
performs an HTTP GET on `/person/1` they will receive:
@@ -2350,7 +2371,7 @@ class Filters {
@ServerRequestFilter
public Optional> getFilter(ContainerRequestContext ctx) {
// only allow GET methods for now
- if(ctx.getMethod().equals(HttpMethod.GET)) {
+ if(!ctx.getMethod().equals(HttpMethod.GET)) {
return Optional.of(RestResponse.status(Response.Status.METHOD_NOT_ALLOWED));
}
return Optional.empty();
diff --git a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc
index 4d764c915a9e9..b1c4bd9180b8f 100644
--- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc
+++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc
@@ -95,6 +95,12 @@ If you must request a UserInfo JSON object from the OIDC `UserInfo` endpoint, se
A request is sent to the OIDC provider `UserInfo` endpoint, and an `io.quarkus.oidc.UserInfo` (a simple `javax.json.JsonObject` wrapper) object is created.
`io.quarkus.oidc.UserInfo` can be injected or accessed as a `SecurityIdentity` `userinfo` attribute.
+`quarkus.oidc.authentication.user-info-required` is automatically enabled if one of these conditions is met:
+
+- if `quarkus.oidc.roles.source` is set to `userinfo` or `quarkus.oidc.token.verify-access-token-with-user-info` is set to `true` or `quarkus.oidc.authentication.id-token-required` is set to `false`, the current OIDC tenant must support a UserInfo endpoint in these cases.
+
+- if `io.quarkus.oidc.UserInfo` injection point is detected but only if the current OIDC tenant supports a UserInfo endpoint.
+
[[config-metadata]]
=== Configuration metadata
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 034cf992124ee..e3f1a15a6f686 100644
--- a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
+++ b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
@@ -483,6 +483,11 @@ public class ProtectedResource {
}
----
+[NOTE]
+====
+When an authorization code flow access token is injected as `JsonWebToken`, its verification is automatically enabled, in addition to the mandatory ID token verification. If really needed, you can disable this code flow access token verification with `quarkus.oidc.authentication.verify-access-token=false`.
+====
+
[NOTE]
====
`AccessTokenCredential` is used if the access token issued to the Quarkus `web-app` application is opaque (binary) and cannot be parsed to a `JsonWebToken` or if the inner content is necessary for the application.
@@ -501,6 +506,12 @@ Set the `quarkus.oidc.authentication.user-info-required=true` property to reques
A request is sent to the OIDC provider `UserInfo` endpoint by using the access token returned with the authorization code grant response, and an `io.quarkus.oidc.UserInfo` (a simple `jakarta.json.JsonObject` wrapper) object is created.
`io.quarkus.oidc.UserInfo` can be injected or accessed as a SecurityIdentity `userinfo` attribute.
+`quarkus.oidc.authentication.user-info-required` is automatically enabled if one of these conditions is met:
+
+- if `quarkus.oidc.roles.source` is set to `userinfo` or `quarkus.oidc.token.verify-access-token-with-user-info` is set to `true` or `quarkus.oidc.authentication.id-token-required` is set to `false`, the current OIDC tenant must support a UserInfo endpoint in these cases.
+
+- if `io.quarkus.oidc.UserInfo` injection point is detected but only if the current OIDC tenant supports a UserInfo endpoint.
+
[[config-metadata]]
==== Accessing the OIDC configuration information
diff --git a/docs/src/main/asciidoc/websockets-next-reference.adoc b/docs/src/main/asciidoc/websockets-next-reference.adoc
index 5387561323b5f..95bdad02b9fd5 100644
--- a/docs/src/main/asciidoc/websockets-next-reference.adoc
+++ b/docs/src/main/asciidoc/websockets-next-reference.adoc
@@ -12,9 +12,9 @@ include::_attributes.adoc[]
:categories: web
:topics: web,websockets
:extensions: io.quarkus:quarkus-websockets-next
+:extension-status: experimental
-The `websockets-next` extension provides an experimental API to define _WebSocket_ endpoints declaratively.
-The proposed API may change in future releases.
+include::{includes}/extension-status.adoc[]
== The WebSocket protocol
diff --git a/docs/src/main/asciidoc/websockets-next-tutorial.adoc b/docs/src/main/asciidoc/websockets-next-tutorial.adoc
index fb5f2ad82e9a2..91a67eda53e3a 100644
--- a/docs/src/main/asciidoc/websockets-next-tutorial.adoc
+++ b/docs/src/main/asciidoc/websockets-next-tutorial.adoc
@@ -10,11 +10,13 @@ include::_attributes.adoc[]
:summary: This guide explains how your Quarkus application can utilize web sockets to create interactive web applications. This guide uses the WebSockets Next extension
:topics: web,websockets
:extensions: io.quarkus:quarkus-websockets-next
+:extension-status: experimental
This guide explains how your Quarkus application can utilize web sockets to create interactive web applications.
In this guide, we will develop a very simple chat application using web sockets to receive and send messages to the other connected users.
-IMPORTANT: The `websockets-next` extension is experimental. The proposal API may change in future releases.
+include::{includes}/extension-status.adoc[]
+
== Prerequisites
diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc
index 7bf884802e9b4..b9d0f647a2530 100644
--- a/docs/src/main/asciidoc/writing-extensions.adoc
+++ b/docs/src/main/asciidoc/writing-extensions.adoc
@@ -823,8 +823,8 @@ They represent build items with validation errors that make the build fail. Thes
----
@BuildStep
void checkCompatibility(Capabilities capabilities, BuildProducer validationErrors) {
- if (capabilities.isMissing(Capability.RESTEASY_REACTIVE)
- && capabilities.isMissing(Capability.RESTEASY_CLASSIC)) {
+ if (capabilities.isPresent(Capability.RESTEASY_REACTIVE)
+ && capabilities.isPresent(Capability.RESTEASY)) {
validationErrors.produce(new ValidationErrorBuildItem(
new ConfigurationException("Cannot use both RESTEasy Classic and Reactive extensions at the same time")));
}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
index bbb55c3e1ec94..d09507d72b2de 100644
--- a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
+++ b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
@@ -16,7 +16,7 @@ void testImageWithJava17() {
Path path = getPath("openjdk-17-runtime");
var result = sut.determine(path);
assertThat(result).hasValueSatisfying(v -> {
- assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.18");
+ assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.19");
assertThat(v.getJavaVersion()).isEqualTo(17);
});
}
@@ -26,7 +26,7 @@ void testImageWithJava21() {
Path path = getPath("openjdk-21-runtime");
var result = sut.determine(path);
assertThat(result).hasValueSatisfying(v -> {
- assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-21-runtime:1.18");
+ assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-21-runtime:1.19");
assertThat(v.getJavaVersion()).isEqualTo(21);
});
}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
index 9bc56f98c9d33..a06add4a4733e 100644
--- a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
+++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
@@ -1,4 +1,4 @@
-FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.18
+FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.19
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime
index 8d11343e7b78e..0a470b183b8da 100644
--- a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime
+++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime
@@ -1,5 +1,5 @@
-# Use Java 17 base image
-FROM registry.access.redhat.com/ubi8/openjdk-21-runtime:1.18
+# Use Java 21 base image
+FROM registry.access.redhat.com/ubi8/openjdk-21-runtime:1.19
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java
index a245688b8f7ea..a19391b993afd 100644
--- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java
+++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java
@@ -16,9 +16,9 @@ public class ContainerImageJibConfig {
/**
* The base image to be used when a container image is being produced for the jar build.
*
- * When the application is built against Java 21 or higher, {@code registry.access.redhat.com/ubi8/openjdk-21-runtime:1.18}
+ * When the application is built against Java 21 or higher, {@code registry.access.redhat.com/ubi8/openjdk-21-runtime:1.19}
* is used as the default.
- * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-17-runtime:1.18} is used as the default.
+ * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-17-runtime:1.19} is used as the default.
*/
@ConfigItem
public Optional baseJvmImage;
diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java
index c8ad868835b6d..df20d64f6c596 100644
--- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java
+++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java
@@ -15,8 +15,8 @@
@ConfigRoot(name = "openshift", phase = ConfigPhase.BUILD_TIME)
public class ContainerImageOpenshiftConfig {
- public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.18";
- public static final String DEFAULT_BASE_JVM_JDK21_IMAGE = "registry.access.redhat.com/ubi8/openjdk-21:1.18";
+ public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.19";
+ public static final String DEFAULT_BASE_JVM_JDK21_IMAGE = "registry.access.redhat.com/ubi8/openjdk-21:1.19";
public static final String DEFAULT_BASE_NATIVE_IMAGE = "quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0";
public static final String DEFAULT_NATIVE_TARGET_FILENAME = "application";
@@ -47,9 +47,9 @@ public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion
* The value of this property is used to create an ImageStream for the builder image used in the Openshift build.
* When it references images already available in the internal Openshift registry, the corresponding streams are used
* instead.
- * When the application is built against Java 21 or higher, {@code registry.access.redhat.com/ubi8/openjdk-21:1.18}
+ * When the application is built against Java 21 or higher, {@code registry.access.redhat.com/ubi8/openjdk-21:1.19}
* is used as the default.
- * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-17:1.18} is used as the default.
+ * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-17:1.19} is used as the default.
*/
@ConfigItem
public Optional baseJvmImage;
diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java
index defde810dc8a7..675519cd28f9a 100644
--- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java
+++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java
@@ -12,8 +12,8 @@
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public class S2iConfig {
- public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.18";
- public static final String DEFAULT_BASE_JVM_JDK21_IMAGE = "registry.access.redhat.com/ubi8/openjdk-21:1.18";
+ public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.19";
+ public static final String DEFAULT_BASE_JVM_JDK21_IMAGE = "registry.access.redhat.com/ubi8/openjdk-21:1.19";
public static final String DEFAULT_BASE_NATIVE_IMAGE = "quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0";
public static final String DEFAULT_NATIVE_TARGET_FILENAME = "application";
@@ -41,9 +41,9 @@ public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion
/**
* The base image to be used when a container image is being produced for the jar build.
*
- * When the application is built against Java 21 or higher, {@code registry.access.redhat.com/ubi8/openjdk-21:1.18}
+ * When the application is built against Java 21 or higher, {@code registry.access.redhat.com/ubi8/openjdk-21:1.19}
* is used as the default.
- * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-17:1.18} is used as the default.
+ * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-17:1.19} is used as the default.
*/
@ConfigItem
public Optional baseJvmImage;
diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/SQLServerDatabaseTypeSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/SQLServerDatabaseTypeSubstitutions.java
deleted file mode 100644
index 3e2d0e156cb6f..0000000000000
--- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/SQLServerDatabaseTypeSubstitutions.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.quarkus.flyway.runtime.graal;
-
-import java.util.function.BooleanSupplier;
-
-import org.flywaydb.core.api.configuration.Configuration;
-import org.flywaydb.core.internal.jdbc.JdbcConnectionFactory;
-import org.flywaydb.core.internal.jdbc.StatementInterceptor;
-import org.flywaydb.core.internal.util.ClassUtils;
-
-import com.oracle.svm.core.annotate.Alias;
-import com.oracle.svm.core.annotate.Substitute;
-import com.oracle.svm.core.annotate.TargetClass;
-
-@TargetClass(className = "org.flywaydb.database.sqlserver.SQLServerDatabaseType", onlyWith = SQLServerDatabaseTypeSubstitutions.SQLServerAvailable.class)
-public final class SQLServerDatabaseTypeSubstitutions {
-
- @Substitute
- public Object createDatabase(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory,
- StatementInterceptor statementInterceptor) {
- return new SQLServerDatabaseSubstitution(configuration, jdbcConnectionFactory, statementInterceptor);
- }
-
- @TargetClass(className = "org.flywaydb.database.sqlserver.SQLServerDatabase", onlyWith = SQLServerAvailable.class)
- public static final class SQLServerDatabaseSubstitution {
-
- @Alias
- public SQLServerDatabaseSubstitution(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory,
- StatementInterceptor statementInterceptor) {
- }
- }
-
- public static final class SQLServerAvailable implements BooleanSupplier {
- @Override
- public boolean getAsBoolean() {
- return ClassUtils.isPresent("org.flywaydb.database.sqlserver.SQLServerDatabaseType",
- Thread.currentThread().getContextClassLoader());
- }
- }
-
-}
diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java
index 54e83ead17432..322cf5eb18db7 100644
--- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java
+++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java
@@ -68,6 +68,8 @@
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.HttpVersion;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
@@ -216,7 +218,13 @@ public void handle(Void unused) {
// TODO -- handle Avro, plain text ... when supported / needed
private static boolean isGrpc(RoutingContext rc) {
- String header = rc.request().getHeader("content-type");
+ HttpServerRequest request = rc.request();
+ HttpVersion version = request.version();
+ if (HttpVersion.HTTP_1_0.equals(version) || HttpVersion.HTTP_1_1.equals(version)) {
+ LOGGER.debugf("Expecting %s, received %s - not a gRPC request", HttpVersion.HTTP_2, version);
+ return false;
+ }
+ String header = request.getHeader("content-type");
return header != null && GRPC_CONTENT_TYPE.matcher(header.toLowerCase(Locale.ROOT)).matches();
}
diff --git a/extensions/hibernate-envers/runtime/src/main/java/io/quarkus/hibernate/envers/runtime/graal/DisableLoggingFeature.java b/extensions/hibernate-envers/runtime/src/main/java/io/quarkus/hibernate/envers/runtime/graal/DisableLoggingFeature.java
index de6d22b94c1a6..481398edb364d 100644
--- a/extensions/hibernate-envers/runtime/src/main/java/io/quarkus/hibernate/envers/runtime/graal/DisableLoggingFeature.java
+++ b/extensions/hibernate-envers/runtime/src/main/java/io/quarkus/hibernate/envers/runtime/graal/DisableLoggingFeature.java
@@ -32,10 +32,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
public void afterAnalysis(AfterAnalysisAccess access) {
for (String category : CATEGORIES) {
Level level = categoryMap.remove(category);
- if (level != null) {
- Logger logger = Logger.getLogger(category);
- logger.setLevel(level);
- }
+ Logger logger = Logger.getLogger(category);
+ logger.setLevel(level);
}
}
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/graal/DisableLoggingFeature.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/graal/DisableLoggingFeature.java
index 50acd745f0063..ccd4be97371f3 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/graal/DisableLoggingFeature.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/graal/DisableLoggingFeature.java
@@ -34,10 +34,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
public void afterAnalysis(AfterAnalysisAccess access) {
for (String category : CATEGORIES) {
Level level = categoryMap.remove(category);
- if (level != null) {
- Logger logger = Logger.getLogger(category);
- logger.setLevel(level);
- }
+ Logger logger = Logger.getLogger(category);
+ logger.setLevel(level);
}
}
diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/graal/DisableLoggingFeature.java b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/graal/DisableLoggingFeature.java
index 245bc72fca92d..e3666fda2aed3 100644
--- a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/graal/DisableLoggingFeature.java
+++ b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/graal/DisableLoggingFeature.java
@@ -31,10 +31,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
public void afterAnalysis(AfterAnalysisAccess access) {
for (String category : CATEGORIES) {
Level level = categoryMap.remove(category);
- if (level != null) {
- Logger logger = Logger.getLogger(category);
- logger.setLevel(level);
- }
+ Logger logger = Logger.getLogger(category);
+ logger.setLevel(level);
}
}
diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java
new file mode 100644
index 0000000000000..a9dd211d283b9
--- /dev/null
+++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java
@@ -0,0 +1,63 @@
+package io.quarkus.hibernate.validator.test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.AbstractCollection;
+
+import jakarta.inject.Inject;
+import jakarta.validation.Valid;
+import jakarta.validation.Validator;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.ByteArrayAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+
+public class ClassHierarchyTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(() -> {
+ JavaArchive javaArchive = ShrinkWrap.create(JavaArchive.class)
+ .addClass(Dto.class);
+ // Create an inner class with an incomplete hierarchy
+ try (DynamicType.Unloaded> superClass = new ByteBuddy()
+ .subclass(Object.class)
+ .name("SuperClass")
+ .make();
+ DynamicType.Unloaded> outerClass = new ByteBuddy()
+ .subclass(superClass.getTypeDescription())
+ .name("OuterClass")
+ .make();
+ DynamicType.Unloaded> innerClass = new ByteBuddy()
+ .subclass(AbstractCollection.class)
+ .innerTypeOf(outerClass.getTypeDescription())
+ .name("InnerClass")
+ .make();
+ DynamicType.Loaded> innerLoad = innerClass.load(Thread.currentThread().getContextClassLoader())) {
+ javaArchive.add(new ByteArrayAsset(innerLoad.getBytes()), "InnerClass.class");
+ }
+ return javaArchive;
+ });
+
+ @Inject
+ Validator validator;
+
+ @Test
+ public void doNotFailWhenLoadingIncompleteClassHierarchy() {
+ assertThat(validator).isNotNull();
+ }
+
+ @Valid
+ public static class Dto {
+ String name;
+
+ // InnerClass is a subclass with an incomplete hierarchy
+ @Valid
+ AbstractCollection items;
+ }
+}
diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/DisableLoggingFeature.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/DisableLoggingFeature.java
index 6d65ddf89463f..64f01c14cfb25 100644
--- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/DisableLoggingFeature.java
+++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/DisableLoggingFeature.java
@@ -32,10 +32,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
public void afterAnalysis(AfterAnalysisAccess access) {
for (String category : CATEGORIES) {
Level level = categoryMap.remove(category);
- if (level != null) {
- Logger logger = Logger.getLogger(category);
- logger.setLevel(level);
- }
+ Logger logger = Logger.getLogger(category);
+ logger.setLevel(level);
}
}
diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java
index 88b808fd14142..40429cc069436 100644
--- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java
+++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java
@@ -2,6 +2,7 @@
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -76,6 +77,9 @@ public void created(BeanContainer container) {
configuration.localeResolver(localeResolver);
}
+ // Filter out classes with incomplete hierarchy
+ filterIncompleteClasses(classesToBeValidated);
+
configuration.builtinConstraints(detectedBuiltinConstraints)
.initializeBeanMetaData(classesToBeValidated)
// Locales, Locale ROOT means all locales in this setting.
@@ -188,6 +192,22 @@ public void run() {
}
});
}
+
+ /**
+ * Filter out classes with incomplete hierarchy
+ */
+ private void filterIncompleteClasses(Set> classesToBeValidated) {
+ Iterator> iterator = classesToBeValidated.iterator();
+ while (iterator.hasNext()) {
+ Class> clazz = iterator.next();
+ try {
+ // This should trigger a NoClassDefFoundError if the class has an incomplete hierarchy
+ clazz.getCanonicalName();
+ } catch (NoClassDefFoundError e) {
+ iterator.remove();
+ }
+ }
+ }
};
return beanContainerListener;
diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/graal/DisableLoggingFeature.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/graal/DisableLoggingFeature.java
index edb073b1e6320..178a83bc3f6fd 100644
--- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/graal/DisableLoggingFeature.java
+++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/graal/DisableLoggingFeature.java
@@ -31,10 +31,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
public void afterAnalysis(AfterAnalysisAccess access) {
for (String category : CATEGORIES) {
Level level = categoryMap.remove(category);
- if (level != null) {
- Logger logger = Logger.getLogger(category);
- logger.setLevel(level);
- }
+ Logger logger = Logger.getLogger(category);
+ logger.setLevel(level);
}
}
diff --git a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java
index c8882b07d9f1d..9e62736b27be6 100644
--- a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java
+++ b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java
@@ -13,7 +13,7 @@
import java.util.TimeZone;
import java.util.stream.Collectors;
-import jakarta.inject.Singleton;
+import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -122,7 +122,7 @@ void gitInfo(InfoBuildTimeConfig config,
valuesProducer.produce(new InfoBuildTimeValuesBuildItem("git", data));
beanProducer.produce(SyntheticBeanBuildItem.configure(GitInfo.class)
.supplier(recorder.gitInfoSupplier(branch, latestCommitId, latestCommitTime))
- .scope(Singleton.class)
+ .scope(ApplicationScoped.class)
.setRuntimeInit()
.done());
} catch (Exception e) {
@@ -229,7 +229,7 @@ void buildInfo(CurateOutcomeBuildItem curateOutcomeBuildItem,
valuesProducer.produce(new InfoBuildTimeValuesBuildItem("build", data));
beanProducer.produce(SyntheticBeanBuildItem.configure(BuildInfo.class)
.supplier(recorder.buildInfoSupplier(group, artifact, version, time, quarkusVersion))
- .scope(Singleton.class)
+ .scope(ApplicationScoped.class)
.setRuntimeInit()
.done());
}
@@ -251,7 +251,7 @@ void osInfo(InfoRecorder recorder,
valuesProducer.produce(new InfoBuildTimeContributorBuildItem(recorder.osInfoContributor()));
beanProducer.produce(SyntheticBeanBuildItem.configure(OsInfo.class)
.supplier(recorder.osInfoSupplier())
- .scope(Singleton.class)
+ .scope(ApplicationScoped.class)
.setRuntimeInit()
.done());
}
@@ -264,7 +264,7 @@ void javaInfo(InfoRecorder recorder,
valuesProducer.produce(new InfoBuildTimeContributorBuildItem(recorder.javaInfoContributor()));
beanProducer.produce(SyntheticBeanBuildItem.configure(JavaInfo.class)
.supplier(recorder.javaInfoSupplier())
- .scope(Singleton.class)
+ .scope(ApplicationScoped.class)
.setRuntimeInit()
.done());
}
diff --git a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java
index 4c2741d2092ed..361033561c391 100644
--- a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java
+++ b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java
@@ -171,14 +171,8 @@ NativeImageConfigBuildItem build(
if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.buffer.UnpooledByteBufAllocator")) {
builder.addRuntimeReinitializedClass("io.netty.buffer.UnpooledByteBufAllocator")
.addRuntimeReinitializedClass("io.netty.buffer.Unpooled")
- .addRuntimeReinitializedClass("io.vertx.core.http.impl.Http1xServerResponse")
.addRuntimeReinitializedClass("io.netty.handler.codec.http.HttpObjectAggregator")
- .addRuntimeReinitializedClass("io.netty.handler.codec.ReplayingDecoderByteBuf")
- .addRuntimeReinitializedClass("io.vertx.core.parsetools.impl.RecordParserImpl");
-
- if (QuarkusClassLoader.isClassPresentAtRuntime("io.vertx.ext.web.client.impl.MultipartFormUpload")) {
- builder.addRuntimeReinitializedClass("io.vertx.ext.web.client.impl.MultipartFormUpload");
- }
+ .addRuntimeReinitializedClass("io.netty.handler.codec.ReplayingDecoderByteBuf");
if (QuarkusClassLoader
.isClassPresentAtRuntime("org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartFormUpload")) {
diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java
index 9bc2cae847574..a7e4e41dfbd2f 100644
--- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java
+++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java
@@ -218,6 +218,7 @@ private static boolean isTenantIdentityProviderType(InjectionPointInfo ip) {
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
public SyntheticBeanBuildItem setup(
+ BeanRegistrationPhaseBuildItem beanRegistration,
OidcConfig config,
OidcRecorder recorder,
CoreVertxBuildItem vertxBuildItem,
@@ -225,7 +226,8 @@ public SyntheticBeanBuildItem setup(
// this is required for setup ordering: we need CP set up
ContextPropagationInitializedBuildItem cpInitializedBuildItem) {
return SyntheticBeanBuildItem.configure(TenantConfigBean.class).unremovable().types(TenantConfigBean.class)
- .supplier(recorder.setup(config, vertxBuildItem.getVertx(), tlsConfig))
+ .supplier(
+ recorder.setup(config, vertxBuildItem.getVertx(), tlsConfig, detectUserInfoRequired(beanRegistration)))
.destroyer(TenantConfigBean.Destroyer.class)
.scope(Singleton.class) // this should have been @ApplicationScoped but fails for some reason
.setRuntimeInit()
@@ -252,15 +254,8 @@ public void registerTenantResolverInterceptor(Capabilities capabilities, OidcRec
}
}
- @BuildStep
- void detectUserInfoRequired(BeanRegistrationPhaseBuildItem beanRegistrationPhaseBuildItem,
- BuildProducer runtimeConfigDefaultProducer) {
- if (isInjected(beanRegistrationPhaseBuildItem, USER_INFO_NAME, null)) {
- runtimeConfigDefaultProducer.produce(
- new RunTimeConfigurationDefaultBuildItem("quarkus.oidc.authentication.user-info-required", "true"));
- runtimeConfigDefaultProducer.produce(
- new RunTimeConfigurationDefaultBuildItem("quarkus.oidc.*.authentication.user-info-required", "true"));
- }
+ private static boolean detectUserInfoRequired(BeanRegistrationPhaseBuildItem beanRegistrationPhaseBuildItem) {
+ return isInjected(beanRegistrationPhaseBuildItem, USER_INFO_NAME, null);
}
@BuildStep
diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowVerifyInjectedAccessTokenDisabledTest.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowVerifyInjectedAccessTokenDisabledTest.java
new file mode 100644
index 0000000000000..6d1aaf041f503
--- /dev/null
+++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowVerifyInjectedAccessTokenDisabledTest.java
@@ -0,0 +1,54 @@
+package io.quarkus.oidc.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
+
+@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
+public class CodeFlowVerifyInjectedAccessTokenDisabledTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest test = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(ProtectedResourceWithJwtAccessToken.class)
+ .addAsResource("application-verify-injected-access-token-disabled.properties", "application.properties"));
+
+ @Test
+ public void testVerifyAccessTokenDisabled() throws IOException, InterruptedException {
+ try (final WebClient webClient = createWebClient()) {
+
+ HtmlPage page = webClient.getPage("http://localhost:8081/protected");
+
+ assertEquals("Sign in to quarkus", page.getTitleText());
+
+ HtmlForm loginForm = page.getForms().get(0);
+
+ loginForm.getInputByName("username").setValueAttribute("alice");
+ loginForm.getInputByName("password").setValueAttribute("alice");
+
+ page = loginForm.getInputByName("login").click();
+
+ assertEquals("alice:false", page.getBody().asNormalizedText());
+
+ webClient.getCookieManager().clearCookies();
+ }
+ }
+
+ private WebClient createWebClient() {
+ WebClient webClient = new WebClient();
+ webClient.setCssErrorHandler(new SilentCssErrorHandler());
+ return webClient;
+ }
+}
diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/ProtectedResourceWithJwtAccessToken.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/ProtectedResourceWithJwtAccessToken.java
new file mode 100644
index 0000000000000..dc82b0c8ce510
--- /dev/null
+++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/ProtectedResourceWithJwtAccessToken.java
@@ -0,0 +1,31 @@
+package io.quarkus.oidc.test;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import io.quarkus.oidc.IdToken;
+import io.quarkus.oidc.runtime.OidcConfig;
+import io.quarkus.security.Authenticated;
+
+@Path("/protected")
+@Authenticated
+public class ProtectedResourceWithJwtAccessToken {
+
+ @Inject
+ @IdToken
+ JsonWebToken idToken;
+
+ @Inject
+ JsonWebToken accessToken;
+
+ @Inject
+ OidcConfig config;
+
+ @GET
+ public String getName() {
+ return idToken.getName() + ":" + config.defaultTenant.authentication.verifyAccessToken;
+ }
+}
diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/UserInfoRequiredDetectionTest.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/UserInfoRequiredDetectionTest.java
index 10c69b0587693..73633f3bbb4a1 100644
--- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/UserInfoRequiredDetectionTest.java
+++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/UserInfoRequiredDetectionTest.java
@@ -37,6 +37,16 @@ public class UserInfoRequiredDetectionTest {
quarkus.oidc.named.auth-server-url=${quarkus.oidc.auth-server-url}
quarkus.oidc.named.tenant-paths=/user-info/named-tenant
quarkus.oidc.named.user-info-path=http://${quarkus.http.host}:${quarkus.http.port}/user-info-endpoint
+ quarkus.oidc.named-2.auth-server-url=${quarkus.oidc.auth-server-url}
+ quarkus.oidc.named-2.tenant-paths=/user-info/named-tenant-2
+ quarkus.oidc.named-2.discovery-enabled=false
+ quarkus.oidc.named-2.jwks-path=protocol/openid-connect/certs
+ quarkus.oidc.named-3.auth-server-url=${quarkus.oidc.auth-server-url}
+ quarkus.oidc.named-3.tenant-paths=/user-info/named-tenant-3
+ quarkus.oidc.named-3.discovery-enabled=false
+ quarkus.oidc.named-3.jwks-path=protocol/openid-connect/certs
+ quarkus.oidc.named-3.user-info-path=http://${quarkus.http.host}:${quarkus.http.port}/user-info-endpoint
+ quarkus.oidc.named-3.authentication.user-info-required=false
quarkus.http.auth.proactive=false
"""),
"application.properties"));
@@ -53,6 +63,18 @@ public void testNamedTenant() {
.body(Matchers.is("alice"));
}
+ @Test
+ public void testUserInfoNotRequiredWhenMissingUserInfoEndpoint() {
+ RestAssured.given().auth().oauth2(getAccessToken()).get("/user-info/named-tenant-2").then().statusCode(200)
+ .body(Matchers.is("false"));
+ }
+
+ @Test
+ public void testUserInfoNotRequiredIfDisabledWhenUserInfoEndpointIsPresent() {
+ RestAssured.given().auth().oauth2(getAccessToken()).get("/user-info/named-tenant-3").then().statusCode(200)
+ .body(Matchers.is("false"));
+ }
+
private static String getAccessToken() {
return new KeycloakTestClient().getAccessToken("alice", "alice", "quarkus-service-app", "secret", List.of("openid"));
}
@@ -94,6 +116,20 @@ public String getNamedTenantName() {
}
return userInfo.getPreferredUserName();
}
+
+ @PermissionsAllowed("openid")
+ @Path("named-tenant-2")
+ @GET
+ public boolean getNamed2TenantUserInfoRequired() {
+ return config.namedTenants.get("named-2").authentication.userInfoRequired.orElse(false);
+ }
+
+ @PermissionsAllowed("openid")
+ @Path("named-tenant-3")
+ @GET
+ public boolean getNamed3TenantUserInfoRequired() {
+ return config.namedTenants.get("named-3").authentication.userInfoRequired.orElse(false);
+ }
}
}
diff --git a/extensions/oidc/deployment/src/test/resources/application-verify-injected-access-token-disabled.properties b/extensions/oidc/deployment/src/test/resources/application-verify-injected-access-token-disabled.properties
new file mode 100644
index 0000000000000..5f262bf4b7779
--- /dev/null
+++ b/extensions/oidc/deployment/src/test/resources/application-verify-injected-access-token-disabled.properties
@@ -0,0 +1,5 @@
+quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus
+quarkus.oidc.client-id=quarkus-web-app
+quarkus.oidc.credentials.secret=secret
+quarkus.oidc.application-type=web-app
+quarkus.oidc.authentication.verify-access-token=false
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java
index 301293de4d47a..8698275cb67af 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java
@@ -982,16 +982,18 @@ public enum ResponseMode {
/**
* Both ID and access tokens are fetched from the OIDC provider as part of the authorization code flow.
+ *
* ID token is always verified on every user request as the primary token which is used
* to represent the principal and extract the roles.
- * Access token is not verified by default since it is meant to be propagated to the downstream services.
- * The verification of the access token should be enabled if it is injected as a JWT token.
- *
- * Access tokens obtained as part of the code flow are always verified if `quarkus.oidc.roles.source`
- * property is set to `accesstoken` which means the authorization decision is based on the roles extracted from the
- * access token.
- *
- * Bearer access tokens are always verified.
+ *
+ * Authorization code flow access token is meant to be propagated to downstream services
+ * and is not verified by default unless `quarkus.oidc.roles.source` property is set to `accesstoken`
+ * which means the authorization decision is based on the roles extracted from the access token.
+ *
+ * Authorization code flow access token verification is also enabled if this token is injected as JsonWebToken.
+ * Set this property to `false` if it is not required.
+ *
+ * Bearer access token is always verified.
*/
@ConfigItem(defaultValueDocumentation = "true when access token is injected as the JsonWebToken bean, false otherwise")
public boolean verifyAccessToken;
@@ -1129,10 +1131,14 @@ public enum ResponseMode {
/**
* If this property is set to `true`, an OIDC UserInfo endpoint is called.
- * This property is enabled if `quarkus.oidc.roles.source` is `userinfo`.
- * or `quarkus.oidc.token.verify-access-token-with-user-info` is `true`
+ *
+ * This property is enabled automatically if `quarkus.oidc.roles.source` is set to `userinfo`
+ * or `quarkus.oidc.token.verify-access-token-with-user-info` is set to `true`
* or `quarkus.oidc.authentication.id-token-required` is set to `false`,
- * you do not need to enable this property manually in these cases.
+ * the current OIDC tenant must support a UserInfo endpoint in these cases.
+ *
+ * It is also enabled automatically if `io.quarkus.oidc.UserInfo` injection point is detected but only
+ * if the current OIDC tenant supports a UserInfo endpoint.
*/
@ConfigItem(defaultValueDocumentation = "true when UserInfo bean is injected, false otherwise")
public Optional userInfoRequired = Optional.empty();
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
index 161fbcd67e884..b23fd118c454c 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
@@ -338,7 +338,7 @@ public Uni extends SecurityIdentity> apply(Throwable t) {
.hasErrorCode(ErrorCodes.EXPIRED);
if (!expired) {
- LOG.errorf("ID token verification failure: %s", errorMessage(t));
+ logAuthenticationError(context, t);
return removeSessionCookie(context, configContext.oidcConfig)
.replaceWith(Uni.createFrom()
.failure(t
@@ -837,7 +837,7 @@ public Throwable apply(Throwable tInner) {
return tInner;
}
- LOG.errorf("ID token verification has failed: %s", errorMessage(tInner));
+ logAuthenticationError(context, tInner);
return new AuthenticationCompletionException(tInner);
}
});
@@ -846,6 +846,17 @@ public Throwable apply(Throwable tInner) {
});
}
+ private static void logAuthenticationError(RoutingContext context, Throwable t) {
+ final String errorMessage = errorMessage(t);
+ final boolean accessTokenFailure = context.get(OidcConstants.ACCESS_TOKEN_VALUE) != null
+ && context.get(OidcUtils.CODE_ACCESS_TOKEN_RESULT) == null;
+ if (accessTokenFailure) {
+ LOG.errorf("Access token verification has failed: %s. ID token has not been verified yet", errorMessage);
+ } else {
+ LOG.errorf("ID token verification has failed: %s", errorMessage);
+ }
+ }
+
private static boolean prepareNonceForVerification(RoutingContext context, OidcTenantConfig oidcConfig,
CodeAuthenticationStateBean stateBean, String idToken) {
if (oidcConfig.authentication.nonceRequired) {
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
index f67d7f851f41d..9cf4b4177e88d 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
@@ -44,6 +44,7 @@
import io.quarkus.runtime.TlsConfig;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigurationException;
+import io.quarkus.security.AuthenticationFailedException;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.TokenAuthenticationRequest;
@@ -67,6 +68,7 @@ public class OidcRecorder {
private static final Map dynamicTenantsConfig = new ConcurrentHashMap<>();
private static final Set tenantsExpectingServerAvailableEvents = ConcurrentHashMap.newKeySet();
+ private static volatile boolean userInfoInjectionPointDetected = false;
public Supplier setupTokenCache(OidcConfig config, Supplier vertx) {
return new Supplier() {
@@ -77,7 +79,9 @@ public DefaultTokenIntrospectionUserInfoCache get() {
};
}
- public Supplier setup(OidcConfig config, Supplier vertx, TlsConfig tlsConfig) {
+ public Supplier setup(OidcConfig config, Supplier vertx, TlsConfig tlsConfig,
+ boolean userInfoInjectionPointDetected) {
+ OidcRecorder.userInfoInjectionPointDetected = userInfoInjectionPointDetected;
final Vertx vertxValue = vertx.get();
String defaultTenantId = config.defaultTenant.getTenantId().orElse(DEFAULT_TENANT_ID);
@@ -540,6 +544,9 @@ public Uni apply(OidcConfigurationMetadata metadata, Throwab
"The application supports RP-Initiated Logout but the OpenID Provider does not advertise the end_session_endpoint"));
}
}
+ if (userInfoInjectionPointDetected && metadata.getUserInfoUri() != null) {
+ enableUserInfo(oidcConfig);
+ }
if (oidcConfig.authentication.userInfoRequired.orElse(false) && metadata.getUserInfoUri() == null) {
client.close();
return Uni.createFrom().failure(new ConfigurationException(
@@ -600,6 +607,30 @@ public Consumer apply(String tenantId) {
return new Consumer() {
@Override
public void accept(RoutingContext routingContext) {
+ OidcTenantConfig tenantConfig = routingContext.get(OidcTenantConfig.class.getName());
+ if (tenantConfig != null) {
+ // authentication has happened before @Tenant annotation was matched with the HTTP request
+ String tenantUsedForAuth = tenantConfig.tenantId.orElse(null);
+ if (tenantId.equals(tenantUsedForAuth)) {
+ // @Tenant selects the same tenant as already selected
+ return;
+ } else {
+ // @Tenant selects the different tenant than already selected
+ throw new AuthenticationFailedException(
+ """
+ The '%1$s' selected with the @Tenant annotation must be used to authenticate
+ the request but it was already authenticated with the '%2$s' tenant. It
+ can happen if the '%1$s' is selected with an annotation but '%2$s' is
+ resolved during authentication required by the HTTP Security Policy which
+ is enforced before the JAX-RS chain is run. In such cases, please set the
+ 'quarkus.http.auth.permission."permissions".applies-to=JAXRS' to all HTTP
+ Security Policies which secure the same REST endpoints as the ones
+ where the '%1$s' tenant is resolved by the '@Tenant' annotation.
+ """
+ .formatted(tenantId, tenantUsedForAuth));
+ }
+ }
+
LOG.debugf("@Tenant annotation set a '%s' tenant id on the %s request path", tenantId,
routingContext.request().path());
routingContext.put(OidcUtils.TENANT_ID_SET_BY_ANNOTATION, tenantId);
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
index f7430ed6b1733..6483c7e8163f5 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
@@ -14,7 +14,6 @@
import java.io.UncheckedIOException;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
-import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -2170,7 +2169,7 @@ private void scanPathTree(PathTree pathTree, TemplateRootsBuildItem templateRoot
// if template root is found in this tree then walk over its subtree
scanTemplateRootSubtree(
new FilteredPathTree(pathTree, PathFilter.forIncludes(List.of(templateRoot + "/**"))),
- visit.getPath(), watchedPaths, templatePaths, nativeImageResources, config);
+ visit.getRelativePath(), watchedPaths, templatePaths, nativeImageResources, config);
}
});
}
@@ -3383,7 +3382,7 @@ private static void produceTemplateBuildItems(BuildProducer watchedPaths,
BuildProducer templatePaths,
BuildProducer nativeImageResources,
@@ -3391,30 +3390,19 @@ private void scanTemplateRootSubtree(PathTree pathTree, Path templateRoot,
pathTree.walk(visit -> {
if (Files.isRegularFile(visit.getPath())) {
LOGGER.debugf("Found template: %s", visit.getPath());
- String templatePath = toOsAgnosticPath(templateRoot.relativize(visit.getPath()));
+ // remove templateRoot + /
+ final String relativePath = visit.getRelativePath();
+ String templatePath = relativePath.substring(templateRoot.length() + 1);
if (config.templatePathExclude.matcher(templatePath).matches()) {
LOGGER.debugf("Template file excluded: %s", visit.getPath());
return;
}
produceTemplateBuildItems(templatePaths, watchedPaths, nativeImageResources,
- visit.getRelativePath("/"),
- templatePath, visit.getPath(), config);
+ relativePath, templatePath, visit.getPath(), config);
}
});
}
- private static String toOsAgnosticPath(String path, FileSystem fs) {
- String separator = fs.getSeparator();
- if (!separator.equals("/")) {
- path = path.replace(separator, "/");
- }
- return path;
- }
-
- private static String toOsAgnosticPath(Path path) {
- return toOsAgnosticPath(path.toString(), path.getFileSystem());
- }
-
private static boolean isExcluded(TypeCheck check, Iterable> excludes) {
for (Predicate exclude : excludes) {
if (exclude.test(check)) {
diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/JakartaRestResourceHttpPermissionTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/JakartaRestResourceHttpPermissionTest.java
index a8324f72cfc2c..0eb468a1bcdc0 100644
--- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/JakartaRestResourceHttpPermissionTest.java
+++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/JakartaRestResourceHttpPermissionTest.java
@@ -12,10 +12,12 @@
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.test.utils.TestIdentityController;
import io.quarkus.security.test.utils.TestIdentityProvider;
import io.quarkus.test.QuarkusUnitTest;
@@ -40,18 +42,23 @@ public class JakartaRestResourceHttpPermissionTest {
"quarkus.http.auth.permission.root.paths=/\n" +
"quarkus.http.auth.permission.root.policy=authenticated\n" +
"quarkus.http.auth.permission.dot.paths=dot,dot/\n" +
- "quarkus.http.auth.permission.dot.policy=authenticated\n";
+ "quarkus.http.auth.permission.dot.policy=authenticated\n" +
+ "quarkus.http.auth.permission.jax-rs.paths=jax-rs\n" +
+ "quarkus.http.auth.permission.jax-rs.policy=admin-role\n" +
+ "quarkus.http.auth.policy.admin-role.roles-allowed=admin";
@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(TestIdentityProvider.class, TestIdentityController.class, ApiResource.class,
- RootResource.class, PublicResource.class)
+ RootResource.class, PublicResource.class, JaxRsResource.class)
.addAsResource(new StringAsset(APP_PROPS), "application.properties"));
@BeforeAll
public static void setup() {
- TestIdentityController.resetRoles().add("test", "test", "test");
+ TestIdentityController.resetRoles()
+ .add("admin", "admin", "admin")
+ .add("test", "test", "test");
}
@TestHTTPResource
@@ -97,6 +104,15 @@ public void testSecuredNotFound(String path) {
assurePathAuthenticated(path, 404);
}
+ @Test
+ public void testJaxRsRolesHttpSecurityPolicy() {
+ // insufficient role, expected admin
+ assurePath("/jax-rs", 401);
+ assurePath("///jax-rs///", 401);
+
+ assurePath("/jax-rs", 200, "admin", true, "admin:admin");
+ }
+
private static String getLastNonEmptySegmentContent(String path) {
while (path.endsWith("/") || path.endsWith(".")) {
path = path.substring(0, path.length() - 1);
@@ -104,6 +120,18 @@ private static String getLastNonEmptySegmentContent(String path) {
return path.substring(path.lastIndexOf('/') + 1);
}
+ @Path("jax-rs")
+ public static class JaxRsResource {
+
+ @Inject
+ SecurityIdentity identity;
+
+ @GET
+ public String getPrincipalName() {
+ return identity.getPrincipal().getName();
+ }
+ }
+
@Path("/api")
public static class ApiResource {
@@ -201,13 +229,17 @@ private void assurePathAuthenticated(String path, String body) {
}
private void assurePath(String path, int expectedStatusCode, String body, boolean auth) {
+ assurePath(path, expectedStatusCode, body, auth, "test:test");
+ }
+
+ private void assurePath(String path, int expectedStatusCode, String body, boolean auth, String credentials) {
var httpClient = vertx.createHttpClient();
try {
httpClient
.request(HttpMethod.GET, url.getPort(), url.getHost(), path)
.map(r -> {
if (auth) {
- r.putHeader("Authorization", "Basic " + encodeBase64URLSafeString("test:test".getBytes()));
+ r.putHeader("Authorization", "Basic " + encodeBase64URLSafeString(credentials.getBytes()));
}
return r;
})
diff --git a/extensions/resteasy-reactive/rest-client-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/ClientObjectMapper.java b/extensions/resteasy-reactive/rest-client-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/ClientObjectMapper.java
index a5e92124e6f0c..c4030926ecf50 100644
--- a/extensions/resteasy-reactive/rest-client-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/ClientObjectMapper.java
+++ b/extensions/resteasy-reactive/rest-client-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/ClientObjectMapper.java
@@ -17,7 +17,7 @@
*
*
* {@code
- * @ClientObjectMapper
+ * @ClientObjectMapper
* static ObjectMapper objectMapper() {
* return new ObjectMapper();
* }
diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java b/extensions/resteasy-reactive/rest-client-jaxrs/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java
index 45c8aef084b2c..d7cad534f9c4e 100644
--- a/extensions/resteasy-reactive/rest-client-jaxrs/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java
+++ b/extensions/resteasy-reactive/rest-client-jaxrs/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java
@@ -144,7 +144,7 @@ public Object convertParam(T value, Class type, Type genericType, Annotat
} else {
// FIXME: cheating, we should generate a converter for this enum
if (value instanceof Enum) {
- return ((Enum) value).name();
+ return value.toString();
}
return value;
}
diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanParamTest.java b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanParamTest.java
index 02facb4a25e22..606a79768edd6 100644
--- a/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanParamTest.java
+++ b/extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanParamTest.java
@@ -37,23 +37,23 @@ void shouldPassPathParamFromBeanParam() {
assertThat(client.beanParamWithFields(new MyBeanParamWithFields()))
.isEqualTo("restPathDefault/restPathOverridden/pathParam"
+ "/restHeaderDefault/restHeaderOverridden/headerParam"
- + "/restFormDefault/restFormOverridden/formParam"
+ + "/restFormDefault/test/restFormOverridden/formParam"
+ "/restCookieDefault/restCookieOverridden/cookieParam"
+ "/restQueryDefault/restQueryOverridden/queryParam");
assertThat(client.regularParameters(
"restPathDefault", "restPathOverridden", "pathParam",
"restHeaderDefault", "restHeaderOverridden", "headerParam",
"restCookieDefault", "restCookieOverridden", "cookieParam",
- "restFormDefault", "restFormOverridden", "formParam",
+ "restFormDefault", SomeEnum.TEST, "restFormOverridden", "formParam",
"restQueryDefault", "restQueryOverridden", "queryParam"))
.isEqualTo("restPathDefault/restPathOverridden/pathParam"
+ "/restHeaderDefault/restHeaderOverridden/headerParam"
- + "/restFormDefault/restFormOverridden/formParam"
+ + "/restFormDefault/test/restFormOverridden/formParam"
+ "/restCookieDefault/restCookieOverridden/cookieParam"
+ "/restQueryDefault/restQueryOverridden/queryParam");
assertThat(client.beanParamWithProperties(new MyBeanParamWithProperties())).isEqualTo("null/null/pathParam"
+ "/null/null/headerParam"
- + "/null/null/formParam"
+ + "/null/null/null/formParam"
+ "/null/null/cookieParam"
+ "/null/null/queryParam");
}
@@ -78,6 +78,7 @@ String regularParameters(@RestPath String restPathDefault,
@CookieParam("cookieParam") String cookieParam,
@RestForm String restFormDefault,
+ @RestForm SomeEnum someEnum,
@RestForm("restForm_Overridden") String restFormOverridden,
@FormParam("formParam") String formParam,
@@ -114,6 +115,8 @@ public static class MyBeanParamWithFields {
@RestForm
private String restFormDefault = "restFormDefault";
+ @RestForm
+ private SomeEnum someEnum = SomeEnum.TEST;
@RestForm("restForm_Overridden")
private String restFormOverridden = "restFormOverridden";
@FormParam("formParam")
@@ -210,6 +213,7 @@ public String beanParamWithFields(@RestPath String restPathDefault,
@RestHeader("restHeader_Overridden") String restHeader_Overridden,
@RestHeader("headerParam") String headerParam,
@RestForm String restFormDefault,
+ @RestForm String someEnum,
@RestForm String restForm_Overridden,
@RestForm String formParam,
@RestCookie String restCookieDefault,
@@ -220,7 +224,7 @@ public String beanParamWithFields(@RestPath String restPathDefault,
@RestQuery String queryParam) {
return restPathDefault + "/" + restPath_Overridden + "/" + pathParam
+ "/" + restHeaderDefault + "/" + restHeader_Overridden + "/" + headerParam
- + "/" + restFormDefault + "/" + restForm_Overridden + "/" + formParam
+ + "/" + restFormDefault + "/" + someEnum + "/" + restForm_Overridden + "/" + formParam
+ "/" + restCookieDefault + "/" + restCookie_Overridden + "/" + cookieParam
+ "/" + restQueryDefault + "/" + restQuery_Overridden + "/" + queryParam;
}
@@ -234,6 +238,7 @@ public String beanParamWithProperties(@RestPath String restPathDefault,
@RestHeader("restHeader_Overridden") String restHeader_Overridden,
@RestHeader("headerParam") String headerParam,
@RestForm String restFormDefault,
+ @RestForm String someEnum,
@RestForm String restForm_Overridden,
@RestForm String formParam,
@RestCookie String restCookieDefault,
@@ -244,9 +249,24 @@ public String beanParamWithProperties(@RestPath String restPathDefault,
@RestQuery String queryParam) {
return restPathDefault + "/" + restPath_Overridden + "/" + pathParam
+ "/" + restHeaderDefault + "/" + restHeader_Overridden + "/" + headerParam
- + "/" + restFormDefault + "/" + restForm_Overridden + "/" + formParam
+ + "/" + restFormDefault + "/" + someEnum + "/" + restForm_Overridden + "/" + formParam
+ "/" + restCookieDefault + "/" + restCookie_Overridden + "/" + cookieParam
+ "/" + restQueryDefault + "/" + restQuery_Overridden + "/" + queryParam;
}
}
+
+ public enum SomeEnum {
+ TEST("test");
+
+ private final String value;
+
+ SomeEnum(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
}
diff --git a/extensions/resteasy-reactive/rest-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ArcThreadSetupAction.java b/extensions/resteasy-reactive/rest-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ArcThreadSetupAction.java
index 4d13a0db03c38..6ff9c39989490 100644
--- a/extensions/resteasy-reactive/rest-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ArcThreadSetupAction.java
+++ b/extensions/resteasy-reactive/rest-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ArcThreadSetupAction.java
@@ -41,4 +41,9 @@ public void deactivate() {
public ThreadState currentState() {
return toThreadState(managedContext.getState());
}
+
+ @Override
+ public boolean isRequestContextActive() {
+ return managedContext.isActive();
+ }
}
diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java
index 84282af70af8f..38f5ee6411377 100644
--- a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java
+++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java
@@ -1,5 +1,6 @@
package io.quarkus.resteasy.reactive.jackson.deployment.processor;
+import static io.quarkus.resteasy.reactive.common.deployment.QuarkusResteasyReactiveDotNames.JSON_IGNORE;
import static io.quarkus.security.spi.RolesAllowedConfigExpResolverBuildItem.isSecurityConfigExpressionCandidate;
import static org.jboss.resteasy.reactive.common.util.RestMediaType.APPLICATION_NDJSON;
import static org.jboss.resteasy.reactive.common.util.RestMediaType.APPLICATION_STREAM_JSON;
@@ -15,6 +16,7 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
+import java.util.function.Predicate;
import java.util.function.Supplier;
import jakarta.inject.Singleton;
@@ -59,6 +61,7 @@
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.JaxRsResourceIndexBuildItem;
+import io.quarkus.resteasy.reactive.common.deployment.QuarkusResteasyReactiveDotNames;
import io.quarkus.resteasy.reactive.common.deployment.ResourceScanningResultBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem;
import io.quarkus.resteasy.reactive.jackson.CustomDeserialization;
@@ -372,7 +375,12 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
JaxRsResourceIndexBuildItem index,
BuildProducer producer) {
IndexView indexView = index.getIndexView();
- Map typeToHasSecureField = new HashMap<>();
+ boolean noSecureFieldDetected = indexView.getAnnotations(SECURE_FIELD).isEmpty();
+ if (noSecureFieldDetected) {
+ return;
+ }
+
+ Map typeToHasSecureField = new HashMap<>(getTypesWithSecureField());
List result = new ArrayList<>();
for (ResteasyReactiveResourceMethodEntriesBuildItem.Entry entry : resourceMethodEntries.getEntries()) {
MethodInfo methodInfo = entry.getMethodInfo();
@@ -425,7 +433,7 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
}
ClassInfo effectiveReturnClassInfo = indexView.getClassByName(effectiveReturnType.name());
- if ((effectiveReturnClassInfo == null) || effectiveReturnClassInfo.name().equals(ResteasyReactiveDotNames.OBJECT)) {
+ if (effectiveReturnClassInfo == null) {
continue;
}
AtomicBoolean needToDeleteCache = new AtomicBoolean(false);
@@ -443,6 +451,7 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
}
if (needToDeleteCache.get()) {
typeToHasSecureField.clear();
+ typeToHasSecureField.putAll(getTypesWithSecureField());
}
}
if (!result.isEmpty()) {
@@ -452,6 +461,13 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
}
}
+ private static Map getTypesWithSecureField() {
+ // if any of following types is detected as an endpoint return type or a field of endpoint return type,
+ // we always need to apply security serialization as any type can be represented with them
+ return Map.of(ResteasyReactiveDotNames.OBJECT.toString(), Boolean.TRUE, ResteasyReactiveDotNames.RESPONSE.toString(),
+ Boolean.TRUE);
+ }
+
private static boolean hasSecureFields(IndexView indexView, ClassInfo currentClassInfo,
Map typeToHasSecureField, AtomicBoolean needToDeleteCache) {
// use cached result if there is any
@@ -479,10 +495,20 @@ private static boolean hasSecureFields(IndexView indexView, ClassInfo currentCla
.anyMatch(ci -> hasSecureFields(indexView, ci, typeToHasSecureField, needToDeleteCache));
} else {
// figure if any field or parent / subclass field is secured
- hasSecureFields = hasSecureFields(currentClassInfo)
- || anyFieldHasSecureFields(indexView, currentClassInfo, typeToHasSecureField, needToDeleteCache)
- || anySubclassHasSecureFields(indexView, currentClassInfo, typeToHasSecureField, needToDeleteCache)
- || anyParentClassHasSecureFields(indexView, currentClassInfo, typeToHasSecureField, needToDeleteCache);
+ if (hasSecureFields(currentClassInfo)) {
+ hasSecureFields = true;
+ } else {
+ Predicate ignoredTypesPredicate = QuarkusResteasyReactiveDotNames.IGNORE_TYPE_FOR_REFLECTION_PREDICATE;
+ if (ignoredTypesPredicate.test(currentClassInfo.name())) {
+ hasSecureFields = false;
+ } else {
+ hasSecureFields = anyFieldHasSecureFields(indexView, currentClassInfo, typeToHasSecureField,
+ needToDeleteCache)
+ || anySubclassHasSecureFields(indexView, currentClassInfo, typeToHasSecureField, needToDeleteCache)
+ || anyParentClassHasSecureFields(indexView, currentClassInfo, typeToHasSecureField,
+ needToDeleteCache);
+ }
+ }
}
typeToHasSecureField.put(className, hasSecureFields);
return hasSecureFields;
@@ -513,6 +539,7 @@ private static boolean anyFieldHasSecureFields(IndexView indexView, ClassInfo cu
return currentClassInfo
.fields()
.stream()
+ .filter(fieldInfo -> !fieldInfo.hasAnnotation(JSON_IGNORE))
.map(FieldInfo::type)
.anyMatch(fieldType -> fieldTypeHasSecureFields(fieldType, indexView, typeToHasSecureField, needToDeleteCache));
}
diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java
index 6fd1e3c3553f8..3b8f1cf12509e 100644
--- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java
+++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java
@@ -17,11 +17,13 @@
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;
+import io.quarkus.resteasy.reactive.jackson.DisableSecureSerialization;
import io.smallrye.common.annotation.Blocking;
@Path("/multipart")
public class MultipartResource {
+ @DisableSecureSerialization // Person has @SecureField but we want to inspect all data
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@@ -45,6 +47,7 @@ public Map greeting(@Valid @BeanParam FormData formData) {
return result;
}
+ @DisableSecureSerialization // Person has @SecureField but we want to inspect all data
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ResponseType.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ResponseType.java
new file mode 100644
index 0000000000000..dc0235c8464a1
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ResponseType.java
@@ -0,0 +1,57 @@
+package io.quarkus.resteasy.reactive.jackson.deployment.test;
+
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.resteasy.reactive.RestResponse;
+
+import io.quarkus.resteasy.reactive.jackson.SecureField;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.Uni;
+
+public enum ResponseType {
+ /**
+ * Returns DTOs directly.
+ */
+ PLAIN(true, "plain"),
+ /**
+ * Returns {@link Multi} with DTOs.
+ */
+ // TODO: enable when https://github.com/quarkusio/quarkus/issues/40447 gets fixed
+ //MULTI(true, "multi"),
+ /**
+ * Returns {@link Uni} with DTOs.
+ */
+ UNI(true, "uni"),
+ /**
+ * Returns {@link Object} that is either DTO with a {@link SecureField} or not.
+ */
+ OBJECT(false, "object"), // we must always assume it can contain SecureField
+ /**
+ * Returns {@link Response} that is either DTO with a {@link SecureField} or not.
+ */
+ RESPONSE(false, "response"), // we must always assume it can contain SecureField
+ /**
+ * Returns {@link RestResponse} with DTOs.
+ */
+ REST_RESPONSE(true, "rest-response"),
+ /**
+ * Returns {@link RestResponse} with DTOs.
+ */
+ COLLECTION(true, "collection");
+
+ private final boolean secureFieldDetectable;
+ private final String resourceSubPath;
+
+ ResponseType(boolean secureFieldDetectable, String resourceSubPath) {
+ this.secureFieldDetectable = secureFieldDetectable;
+ this.resourceSubPath = resourceSubPath;
+ }
+
+ boolean isSecureFieldDetectable() {
+ return secureFieldDetectable;
+ }
+
+ String getResourceSubPath() {
+ return resourceSubPath;
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SecureFieldDetectionTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SecureFieldDetectionTest.java
new file mode 100644
index 0000000000000..4c93d912b230a
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SecureFieldDetectionTest.java
@@ -0,0 +1,399 @@
+package io.quarkus.resteasy.reactive.jackson.deployment.test;
+
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.MethodInfo;
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.common.model.ResourceClass;
+import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
+import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer;
+import org.jboss.resteasy.reactive.server.model.ServerResourceMethod;
+import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner;
+import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import io.quarkus.resteasy.reactive.jackson.SecureField;
+import io.quarkus.resteasy.reactive.jackson.runtime.ResteasyReactiveServerJacksonRecorder;
+import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
+import io.quarkus.security.test.utils.TestIdentityController;
+import io.quarkus.security.test.utils.TestIdentityProvider;
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.Uni;
+import io.vertx.ext.web.RoutingContext;
+
+public class SecureFieldDetectionTest {
+
+ private static final String SECURITY_SERIALIZATION = "security_serialization";
+
+ @RegisterExtension
+ static QuarkusUnitTest test = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(MultiResource.class, UniResource.class, ObjectResource.class, ResponseResource.class,
+ PlainResource.class, TestIdentityProvider.class, TestIdentityController.class,
+ CollectionResource.class, NoSecureField.class, WithSecureField.class, WithNestedSecureField.class,
+ ResponseType.class, DetectSecuritySerializationHandler.class, JsonIgnoreDto.class))
+ .addBuildChainCustomizer(buildChainBuilder -> buildChainBuilder.addBuildStep(context -> context.produce(
+ new MethodScannerBuildItem(new MethodScanner() {
+ @Override
+ public List scan(MethodInfo method, ClassInfo actualEndpointClass,
+ Map methodContext) {
+ return List.of(new DetectSecuritySerializationHandler());
+ }
+ }))).produces(MethodScannerBuildItem.class).build());
+
+ @BeforeEach
+ public void setupSecurity() {
+ TestIdentityController.resetRoles().add("Georgios", "Andrianakis", "admin");
+ }
+
+ private static Stream responseTypes() {
+ return EnumSet.allOf(ResponseType.class).stream().map(Enum::toString).map(Arguments::of);
+ }
+
+ @ParameterizedTest
+ @MethodSource("responseTypes")
+ public void testSecureFieldDetection(String responseTypeStr) {
+ var responseType = ResponseType.valueOf(responseTypeStr);
+
+ // with auth
+ RestAssured
+ .given()
+ .auth().preemptive().basic("Georgios", "Andrianakis")
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/secure-field")
+ .then()
+ .statusCode(200)
+ .body(containsString("hush hush"));
+ RestAssured
+ .given()
+ .auth().preemptive().basic("Georgios", "Andrianakis")
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/no-secure-field")
+ .then()
+ .statusCode(200)
+ .body(containsString("public"));
+
+ // no auth
+ RestAssured
+ .given()
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/secure-field")
+ .then()
+ .statusCode(200)
+ .header(SECURITY_SERIALIZATION, is("true"))
+ .body(not(containsString("hush hush")));
+
+ // if endpoint returns for example Object or Response we can't really tell during the build time
+ // therefore we add custom security serialization and let decision be made dynamically based on present annotation
+ boolean isSecureSerializationApplied = !responseType.isSecureFieldDetectable();
+ RestAssured
+ .given()
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/no-secure-field")
+ .then()
+ .statusCode(200)
+ .header(SECURITY_SERIALIZATION, is(Boolean.toString(isSecureSerializationApplied)))
+ .body(containsString("public"));
+
+ RestAssured
+ .given()
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/json-ignore")
+ .then()
+ .statusCode(200)
+ .header(SECURITY_SERIALIZATION, is(Boolean.toString(isSecureSerializationApplied)))
+ .body(containsString("other"))
+ .body(not(containsString("ignored")));
+ }
+
+ @Path("plain")
+ public static class PlainResource {
+
+ @Path("secure-field")
+ @GET
+ public WithNestedSecureField secureField() {
+ return createEntityWithSecureField();
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public NoSecureField noSecureField() {
+ return createEntityWithoutSecureField();
+ }
+
+ @Path("json-ignore")
+ @GET
+ public JsonIgnoreDto jsonIgnore() {
+ return createEntityWithSecureFieldInIgnored();
+ }
+
+ }
+
+ @Path("multi")
+ public static class MultiResource {
+
+ @Path("secure-field")
+ @GET
+ public Multi secureField() {
+ return Multi.createFrom().item(createEntityWithSecureField());
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Multi noSecureField() {
+ return Multi.createFrom().item(createEntityWithoutSecureField());
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Multi jsonIgnore() {
+ return Multi.createFrom().item(createEntityWithSecureFieldInIgnored());
+ }
+
+ }
+
+ @Path("collection")
+ public static class CollectionResource {
+
+ @Path("secure-field")
+ @GET
+ public Collection secureField() {
+ return List.of(createEntityWithSecureField());
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Collection noSecureField() {
+ return Set.of(createEntityWithoutSecureField());
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Collection jsonIgnore() {
+ return Set.of(createEntityWithSecureFieldInIgnored());
+ }
+
+ }
+
+ @Path("uni")
+ public static class UniResource {
+
+ @Path("secure-field")
+ @GET
+ public Uni secureField() {
+ return Uni.createFrom().item(createEntityWithSecureField());
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Uni noSecureField() {
+ return Uni.createFrom().item(createEntityWithoutSecureField());
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Uni jsonIgnore() {
+ return Uni.createFrom().item(createEntityWithSecureFieldInIgnored());
+ }
+
+ }
+
+ @Produces(APPLICATION_JSON)
+ @Path("object")
+ public static class ObjectResource {
+
+ @Path("secure-field")
+ @GET
+ public Object secureField() {
+ return createEntityWithSecureField();
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Object noSecureField() {
+ return createEntityWithoutSecureField();
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Object jsonIgnore() {
+ return createEntityWithSecureFieldInIgnored();
+ }
+
+ }
+
+ @Produces(APPLICATION_JSON)
+ @Path("response")
+ public static class ResponseResource {
+
+ @Path("secure-field")
+ @GET
+ public Response secureField() {
+ return Response.ok(createEntityWithSecureField()).build();
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Response noSecureField() {
+ return Response.ok(createEntityWithoutSecureField()).build();
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Response jsonIgnore() {
+ return Response.ok(createEntityWithSecureFieldInIgnored()).build();
+ }
+
+ }
+
+ @Path("rest-response")
+ public static class RestResponseResource {
+
+ @Path("secure-field")
+ @GET
+ public RestResponse secureField() {
+ return RestResponse.ok(createEntityWithSecureField());
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public RestResponse noSecureField() {
+ return RestResponse.ok(createEntityWithoutSecureField());
+ }
+
+ @Path("json-ignore")
+ @GET
+ public RestResponse jsonIgnore() {
+ return RestResponse.ok(createEntityWithSecureFieldInIgnored());
+ }
+
+ }
+
+ private static NoSecureField createEntityWithoutSecureField() {
+ var resp = new NoSecureField();
+ resp.setNotSecured("public");
+ return resp;
+ }
+
+ private static JsonIgnoreDto createEntityWithSecureFieldInIgnored() {
+ var resp = new JsonIgnoreDto();
+ resp.setOtherField("other");
+ var nested = new WithSecureField();
+ nested.setSecured("ignored");
+ resp.setWithSecureField(nested);
+ return resp;
+ }
+
+ private static WithNestedSecureField createEntityWithSecureField() {
+ var resp = new WithNestedSecureField();
+ var nested = new WithSecureField();
+ nested.setSecured("hush hush");
+ resp.setWithSecureField(nested);
+ return resp;
+ }
+
+ public static class JsonIgnoreDto {
+
+ @JsonIgnore
+ private WithSecureField withSecureField;
+
+ private String otherField;
+
+ public WithSecureField getWithSecureField() {
+ return withSecureField;
+ }
+
+ public void setWithSecureField(WithSecureField withSecureField) {
+ this.withSecureField = withSecureField;
+ }
+
+ public String getOtherField() {
+ return otherField;
+ }
+
+ public void setOtherField(String otherField) {
+ this.otherField = otherField;
+ }
+ }
+
+ public static class NoSecureField {
+
+ private String notSecured;
+
+ public String getNotSecured() {
+ return notSecured;
+ }
+
+ public void setNotSecured(String notSecured) {
+ this.notSecured = notSecured;
+ }
+ }
+
+ public static class WithNestedSecureField {
+
+ private WithSecureField withSecureField;
+
+ public WithSecureField getWithSecureField() {
+ return withSecureField;
+ }
+
+ public void setWithSecureField(WithSecureField withSecureField) {
+ this.withSecureField = withSecureField;
+ }
+ }
+
+ public static class WithSecureField {
+
+ @SecureField(rolesAllowed = "admin")
+ private String secured;
+
+ public String getSecured() {
+ return secured;
+ }
+
+ public void setSecured(String secured) {
+ this.secured = secured;
+ }
+ }
+
+ public static class DetectSecuritySerializationHandler implements ServerRestHandler, HandlerChainCustomizer {
+
+ @Override
+ public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
+ var methodId = requestContext.getResteasyReactiveResourceInfo().getMethodId();
+ var customSerialization = ResteasyReactiveServerJacksonRecorder.customSerializationForMethod(methodId);
+ var customSerializationDetected = Boolean.toString(customSerialization != null);
+ requestContext.unwrap(RoutingContext.class).response().putHeader(SECURITY_SERIALIZATION,
+ customSerializationDetected);
+ }
+
+ @Override
+ public List handlers(Phase phase, ResourceClass resourceClass, ServerResourceMethod resourceMethod) {
+ return List.of(new DetectSecuritySerializationHandler());
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ObservabilityIntegrationBuildItem.java b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ObservabilityIntegrationBuildItem.java
new file mode 100644
index 0000000000000..6db8506c6f418
--- /dev/null
+++ b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ObservabilityIntegrationBuildItem.java
@@ -0,0 +1,9 @@
+package io.quarkus.resteasy.reactive.server.deployment;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+
+/**
+ * A marker build item signifying that observability features have been integrated
+ */
+public final class ObservabilityIntegrationBuildItem extends SimpleBuildItem {
+}
diff --git a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ObservabilityProcessor.java b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ObservabilityProcessor.java
index 54a8626b5fbea..c6625226dcd1b 100644
--- a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ObservabilityProcessor.java
+++ b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ObservabilityProcessor.java
@@ -12,6 +12,7 @@
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
+import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
@@ -42,17 +43,20 @@ public List scan(MethodInfo method, ClassInfo actualEndp
@BuildStep
@Record(value = ExecutionTime.STATIC_INIT)
- FilterBuildItem preAuthFailureFilter(Capabilities capabilities,
+ void preAuthFailureFilter(Capabilities capabilities,
Optional metricsCapability,
ObservabilityIntegrationRecorder recorder,
- ResteasyReactiveDeploymentBuildItem deployment) {
+ ResteasyReactiveDeploymentBuildItem deployment,
+ BuildProducer filterProducer,
+ BuildProducer observabilityIntegrationProducer) {
boolean integrationNeeded = integrationNeeded(capabilities, metricsCapability);
if (!integrationNeeded) {
- return null;
+ return;
}
- return FilterBuildItem.ofPreAuthenticationFailureHandler(
- recorder.preAuthFailureHandler(deployment.getDeployment()));
+ filterProducer.produce(FilterBuildItem.ofPreAuthenticationFailureHandler(
+ recorder.preAuthFailureHandler(deployment.getDeployment())));
+ observabilityIntegrationProducer.produce(new ObservabilityIntegrationBuildItem());
}
private boolean integrationNeeded(Capabilities capabilities,
diff --git a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
index 8fbaa8e3f20db..a6e846be18697 100644
--- a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
+++ b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
@@ -1365,9 +1365,13 @@ private static boolean notFoundCustomExMapper(String builtInExSignature, String
@BuildStep
@Record(value = ExecutionTime.STATIC_INIT)
- public FilterBuildItem addDefaultAuthFailureHandler(ResteasyReactiveRecorder recorder) {
+ public FilterBuildItem addDefaultAuthFailureHandler(ResteasyReactiveRecorder recorder,
+ ResteasyReactiveDeploymentBuildItem deployment,
+ Optional observabilityIntegrationBuildItem) {
// replace default auth failure handler added by vertx-http so that our exception mappers can customize response
- return new FilterBuildItem(recorder.defaultAuthFailureHandler(), FilterBuildItem.AUTHENTICATION - 1);
+ return new FilterBuildItem(
+ recorder.defaultAuthFailureHandler(deployment.getDeployment(), observabilityIntegrationBuildItem.isPresent()),
+ FilterBuildItem.AUTHENTICATION - 1);
}
private void checkForDuplicateEndpoint(ResteasyReactiveConfig config, Map> allMethods) {
diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/JakartaRestResourceHttpPermissionTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/JakartaRestResourceHttpPermissionTest.java
index 220102abd6348..e6ff7c5e18e7b 100644
--- a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/JakartaRestResourceHttpPermissionTest.java
+++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/JakartaRestResourceHttpPermissionTest.java
@@ -14,10 +14,12 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.test.utils.TestIdentityController;
import io.quarkus.security.test.utils.TestIdentityProvider;
import io.quarkus.test.QuarkusUnitTest;
@@ -40,19 +42,24 @@ public class JakartaRestResourceHttpPermissionTest {
"quarkus.http.auth.permission.root.paths=/\n" +
"quarkus.http.auth.permission.root.policy=authenticated\n" +
"quarkus.http.auth.permission.fragment.paths=/#stuff,/#stuff/\n" +
- "quarkus.http.auth.permission.fragment.policy=authenticated\n";
+ "quarkus.http.auth.permission.fragment.policy=authenticated\n" +
+ "quarkus.http.auth.permission.jax-rs.paths=jax-rs\n" +
+ "quarkus.http.auth.permission.jax-rs.policy=admin-role\n" +
+ "quarkus.http.auth.policy.admin-role.roles-allowed=admin";
private static WebClient client;
@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(TestIdentityProvider.class, TestIdentityController.class, ApiResource.class,
- RootResource.class, PublicResource.class)
+ RootResource.class, PublicResource.class, JaxRsResource.class)
.addAsResource(new StringAsset(APP_PROPS), "application.properties"));
@BeforeAll
public static void setup() {
- TestIdentityController.resetRoles().add("test", "test", "test");
+ TestIdentityController.resetRoles()
+ .add("admin", "admin", "admin")
+ .add("test", "test", "test");
}
@AfterAll
@@ -106,6 +113,15 @@ public void testNotSecuredPaths(String path) {
assurePathAuthenticated(path);
}
+ @Test
+ public void testJaxRsRolesHttpSecurityPolicy() {
+ // insufficient role, expected admin
+ assurePath("/jax-rs", 401);
+ assurePath("///jax-rs///", 401);
+
+ assurePath("/jax-rs", 200, "admin", true, "admin");
+ }
+
private static String getLastNonEmptySegmentContent(String path) {
while (path.endsWith("/") || path.endsWith(".")) {
path = path.substring(0, path.length() - 1);
@@ -113,6 +129,18 @@ private static String getLastNonEmptySegmentContent(String path) {
return path.substring(path.lastIndexOf('/') + 1);
}
+ @Path("jax-rs")
+ public static class JaxRsResource {
+
+ @Inject
+ SecurityIdentity identity;
+
+ @GET
+ public String getPrincipalName() {
+ return identity.getPrincipal().getName();
+ }
+ }
+
@Path("/api")
public static class ApiResource {
@@ -212,9 +240,13 @@ private void assurePathAuthenticated(String path, String body) {
}
private void assurePath(String path, int expectedStatusCode, String body, boolean auth) {
+ assurePath(path, expectedStatusCode, body, auth, "test");
+ }
+
+ private void assurePath(String path, int expectedStatusCode, String body, boolean auth, String username) {
var req = getClient().get(url.getPort(), url.getHost(), path);
if (auth) {
- req.basicAuthentication("test", "test");
+ req.basicAuthentication(username, username);
}
var result = req.send();
await().atMost(REQUEST_TIMEOUT).until(result::isComplete);
diff --git a/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java b/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java
index e54d35e3229ec..7d0a6f1231ae1 100644
--- a/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java
+++ b/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java
@@ -53,6 +53,7 @@
import io.quarkus.resteasy.reactive.common.runtime.ArcBeanFactory;
import io.quarkus.resteasy.reactive.common.runtime.ArcThreadSetupAction;
import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveCommonRecorder;
+import io.quarkus.resteasy.reactive.server.runtime.observability.ObservabilityIntegrationRecorder;
import io.quarkus.runtime.BlockingOperationControl;
import io.quarkus.runtime.ExecutorRecorder;
import io.quarkus.runtime.LaunchMode;
@@ -341,11 +342,16 @@ public ServerSerialisers createServerSerialisers() {
return new ServerSerialisers();
}
- public Handler defaultAuthFailureHandler() {
+ public Handler defaultAuthFailureHandler(
+ RuntimeValue deployment, boolean setTemplatePath) {
return new Handler() {
@Override
public void handle(RoutingContext event) {
if (event.get(QuarkusHttpUser.AUTH_FAILURE_HANDLER) instanceof DefaultAuthFailureHandler) {
+ if (setTemplatePath) {
+ ObservabilityIntegrationRecorder.setTemplatePath(event, deployment.getValue());
+ }
+
// fail event rather than end it, so it's handled by abort handlers (see #addFailureHandler method)
event.put(QuarkusHttpUser.AUTH_FAILURE_HANDLER, new FailingDefaultAuthFailureHandler());
}
diff --git a/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/observability/ObservabilityIntegrationRecorder.java b/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/observability/ObservabilityIntegrationRecorder.java
index 72acb0831b1e6..3e550fa61c9e4 100644
--- a/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/observability/ObservabilityIntegrationRecorder.java
+++ b/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/observability/ObservabilityIntegrationRecorder.java
@@ -47,82 +47,85 @@ private boolean shouldHandle(RoutingContext event) {
|| event.failure() instanceof ForbiddenException
|| event.failure() instanceof UnauthorizedException;
}
+ };
+ }
- private void setTemplatePath(RoutingContext rc, Deployment deployment) {
- // do what RestInitialHandler does
- var initMappers = new RequestMapper<>(deployment.getClassMappers());
- var requestMatch = initMappers.map(getPathWithoutPrefix(rc, deployment));
- var remaining = requestMatch.remaining.isEmpty() ? "/" : requestMatch.remaining;
-
- var serverRestHandlers = requestMatch.value.handlers;
- if (serverRestHandlers == null || serverRestHandlers.length < 1) {
- // nothing we can do
- return;
- }
- var firstHandler = serverRestHandlers[0];
- if (!(firstHandler instanceof ClassRoutingHandler)) {
- // nothing we can do
- return;
- }
-
- var classRoutingHandler = (ClassRoutingHandler) firstHandler;
- var mappers = classRoutingHandler.getMappers();
-
- var requestMethod = rc.request().method().name();
-
- // do what ClassRoutingHandler does
- var mapper = mappers.get(requestMethod);
- if (mapper == null) {
- if (requestMethod.equals(HttpMethod.HEAD) || requestMethod.equals(HttpMethod.OPTIONS)) {
- mapper = mappers.get(HttpMethod.GET);
- }
- if (mapper == null) {
- mapper = mappers.get(null);
- }
- if (mapper == null) {
- // can't match the path
- return;
- }
+ public static void setTemplatePath(RoutingContext rc, Deployment deployment) {
+ // do what RestInitialHandler does
+ var initMappers = new RequestMapper<>(deployment.getClassMappers());
+ var requestMatch = initMappers.map(getPathWithoutPrefix(rc, deployment));
+ if (requestMatch == null) {
+ return;
+ }
+ var remaining = requestMatch.remaining.isEmpty() ? "/" : requestMatch.remaining;
+
+ var serverRestHandlers = requestMatch.value.handlers;
+ if (serverRestHandlers == null || serverRestHandlers.length < 1) {
+ // nothing we can do
+ return;
+ }
+ var firstHandler = serverRestHandlers[0];
+ if (!(firstHandler instanceof ClassRoutingHandler)) {
+ // nothing we can do
+ return;
+ }
+
+ var classRoutingHandler = (ClassRoutingHandler) firstHandler;
+ var mappers = classRoutingHandler.getMappers();
+
+ var requestMethod = rc.request().method().name();
+
+ // do what ClassRoutingHandler does
+ var mapper = mappers.get(requestMethod);
+ if (mapper == null) {
+ if (requestMethod.equals(HttpMethod.HEAD) || requestMethod.equals(HttpMethod.OPTIONS)) {
+ mapper = mappers.get(HttpMethod.GET);
+ }
+ if (mapper == null) {
+ mapper = mappers.get(null);
+ }
+ if (mapper == null) {
+ // can't match the path
+ return;
+ }
+ }
+ var target = mapper.map(remaining);
+ if (target == null) {
+ if (requestMethod.equals(HttpMethod.HEAD)) {
+ mapper = mappers.get(HttpMethod.GET);
+ if (mapper != null) {
+ target = mapper.map(remaining);
}
- var target = mapper.map(remaining);
- if (target == null) {
- if (requestMethod.equals(HttpMethod.HEAD)) {
- mapper = mappers.get(HttpMethod.GET);
- if (mapper != null) {
- target = mapper.map(remaining);
- }
- }
+ }
- if (target == null) {
- // can't match the path
- return;
- }
- }
+ if (target == null) {
+ // can't match the path
+ return;
+ }
+ }
- var templatePath = requestMatch.template.template + target.template.template;
- if (templatePath.endsWith("/")) {
- templatePath = templatePath.substring(0, templatePath.length() - 1);
- }
+ var templatePath = requestMatch.template.template + target.template.template;
+ if (templatePath.endsWith("/")) {
+ templatePath = templatePath.substring(0, templatePath.length() - 1);
+ }
- setUrlPathTemplate(rc, templatePath);
- }
+ setUrlPathTemplate(rc, templatePath);
+ }
- public String getPath(RoutingContext rc) {
- return rc.normalizedPath();
- }
+ private static String getPath(RoutingContext rc) {
+ return rc.normalizedPath();
+ }
- public String getPathWithoutPrefix(RoutingContext rc, Deployment deployment) {
- String path = getPath(rc);
- if (path != null) {
- String prefix = deployment.getPrefix();
- if (!prefix.isEmpty()) {
- if (path.startsWith(prefix)) {
- return path.substring(prefix.length());
- }
- }
+ private static String getPathWithoutPrefix(RoutingContext rc, Deployment deployment) {
+ String path = getPath(rc);
+ if (path != null) {
+ String prefix = deployment.getPrefix();
+ if (!prefix.isEmpty()) {
+ if (path.startsWith(prefix)) {
+ return path.substring(prefix.length());
}
- return path;
}
- };
+ }
+ return path;
}
}
diff --git a/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java b/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java
index f29bc1f0c6f4e..bb63fb075ba5b 100644
--- a/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java
+++ b/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java
@@ -12,6 +12,7 @@
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
+import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -47,7 +48,7 @@ public class RolesAllowedExpressionTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(RolesAllowedBean.class, IdentityMock.class,
- AuthData.class, SecurityTestUtils.class)
+ AuthData.class, SecurityTestUtils.class, SecuredUtils.class)
.addAsResource(new StringAsset(APP_PROPS), "application.properties"));
@Inject
@@ -97,6 +98,12 @@ public void shouldRestrictAccessToSpecificRole() {
new AuthData(Set.of("cn=Administrator,ou=Software,dc=Tester,dc=User"), false, "ldap"));
}
+ @Test
+ public void testStaticSecuredMethod() {
+ assertSuccess(SecuredUtils::staticSecuredMethod, "admin", ADMIN);
+ assertFailureFor(SecuredUtils::staticSecuredMethod, ForbiddenException.class, USER);
+ }
+
@Singleton
public static class RolesAllowedBean {
@@ -153,4 +160,17 @@ public final String ldap() {
}
+ public static class SecuredUtils {
+
+ private SecuredUtils() {
+ // UTIL CLASS
+ }
+
+ @RolesAllowed("${sudo}")
+ public static String staticSecuredMethod() {
+ return ConfigProvider.getConfig().getValue("sudo", String.class);
+ }
+
+ }
+
}
diff --git a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityEventHelper.java b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityEventHelper.java
index e9a70973b5661..8907c4e541894 100644
--- a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityEventHelper.java
+++ b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityEventHelper.java
@@ -5,6 +5,8 @@
import jakarta.enterprise.event.Event;
import jakarta.enterprise.inject.spi.BeanManager;
+import org.eclipse.microprofile.config.ConfigProvider;
+
import io.quarkus.security.identity.SecurityIdentity;
public class SecurityEventHelper {
@@ -90,4 +92,61 @@ public Map getEventProperties() {
return false;
}
}
+
+ /**
+ * Creates {@link SecurityEventHelper} initialized on first request.
+ * This method should only be used when there is a risk the helper will be initialized during the static init phase.
+ * During the runtime init phase, prefer the constructor.
+ */
+ public static SecurityEventHelper lazilyOf(Event successEvent,
+ Event failureEvent, S successInstance, F failureInstance, BeanManager beanManager) {
+ return new SecurityEventHelper<>(successEvent, failureEvent, successInstance, failureInstance, beanManager, true) {
+
+ private volatile Boolean eventsDisabled = null;
+
+ private boolean areEventsDisabled() {
+ if (eventsDisabled == null) {
+ synchronized (this) {
+ if (eventsDisabled == null) {
+ this.eventsDisabled = !ConfigProvider.getConfig().getValue("quarkus.security.events.enabled",
+ Boolean.class);
+ }
+ }
+ }
+ return eventsDisabled;
+ }
+
+ @Override
+ public void fireSuccessEvent(S successInstance) {
+ if (areEventsDisabled()) {
+ return;
+ }
+ super.fireSuccessEvent(successInstance);
+ }
+
+ @Override
+ public void fireFailureEvent(F failureInstance) {
+ if (areEventsDisabled()) {
+ return;
+ }
+ super.fireFailureEvent(failureInstance);
+ }
+
+ @Override
+ public boolean fireEventOnSuccess() {
+ if (areEventsDisabled()) {
+ return false;
+ }
+ return super.fireEventOnSuccess();
+ }
+
+ @Override
+ public boolean fireEventOnFailure() {
+ if (areEventsDisabled()) {
+ return false;
+ }
+ return super.fireEventOnFailure();
+ }
+ };
+ }
}
diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java
index 3d1f357ad9b77..a8d68b3c23e48 100644
--- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java
+++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java
@@ -14,7 +14,6 @@
import io.quarkus.runtime.BlockingOperationNotAllowedException;
import io.quarkus.security.identity.SecurityIdentity;
-import io.quarkus.security.runtime.SecurityConfig;
import io.quarkus.security.runtime.SecurityIdentityAssociation;
import io.quarkus.security.spi.runtime.AuthorizationFailureEvent;
import io.quarkus.security.spi.runtime.AuthorizationSuccessEvent;
@@ -36,11 +35,12 @@ public class SecurityConstrainer {
@Inject
SecurityIdentityAssociation identityAssociation;
- SecurityConstrainer(SecurityCheckStorage storage, BeanManager beanManager, SecurityConfig securityConfig,
+ SecurityConstrainer(SecurityCheckStorage storage, BeanManager beanManager,
Event authZFailureEvent, Event authZSuccessEvent) {
this.storage = storage;
- this.securityEventHelper = new SecurityEventHelper<>(authZSuccessEvent, authZFailureEvent, AUTHORIZATION_SUCCESS,
- AUTHORIZATION_FAILURE, beanManager, securityConfig.events().enabled());
+ // static interceptors are initialized during the static init, therefore we need to initialize the helper lazily
+ this.securityEventHelper = SecurityEventHelper.lazilyOf(authZSuccessEvent, authZFailureEvent,
+ AUTHORIZATION_SUCCESS, AUTHORIZATION_FAILURE, beanManager);
}
public void check(Method method, Object[] parameters) {
diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
index 53f3f86b0fe17..2502f2c710442 100644
--- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
+++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
@@ -12,7 +12,6 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
@@ -939,14 +938,19 @@ private void storeGeneratedSchema(SmallRyeOpenApiConfig openApiConfig, OutputTar
Path outputDirectory = out.getOutputDirectory();
if (!directory.isAbsolute() && outputDirectory != null) {
- directory = Paths.get(outputDirectory.getParent().toString(), directory.toString());
+ var baseDir = outputDirectory.getParent();
+ // check if outputDirectory is the root of the filesystem
+ if (baseDir == null) {
+ baseDir = outputDirectory;
+ }
+ directory = baseDir.resolve(directory);
}
if (!Files.exists(directory)) {
Files.createDirectories(directory);
}
- Path file = Paths.get(directory.toString(), "openapi." + format.toString().toLowerCase());
+ Path file = directory.resolve("openapi." + format.toString().toLowerCase());
if (!Files.exists(file)) {
Files.createFile(file);
}
diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java
index 415204b0d4fc2..98a1a8dbc3d2d 100644
--- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java
+++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java
@@ -61,6 +61,7 @@
import org.jboss.metadata.web.spec.CookieConfigMetaData;
import org.jboss.metadata.web.spec.DispatcherType;
import org.jboss.metadata.web.spec.EmptyRoleSemanticType;
+import org.jboss.metadata.web.spec.ErrorPageMetaData;
import org.jboss.metadata.web.spec.FilterMappingMetaData;
import org.jboss.metadata.web.spec.FilterMetaData;
import org.jboss.metadata.web.spec.FiltersMetaData;
@@ -651,6 +652,16 @@ public ServletDeploymentManagerBuildItem build(List servlets,
recorder.addServletContainerInitializer(deployment,
(Class extends ServletContainerInitializer>) context.classProxy(sci.sciClass), handlesTypes);
}
+ if (webMetaData.getErrorPages() != null) {
+ for (ErrorPageMetaData errorPage : webMetaData.getErrorPages()) {
+ if (errorPage.getErrorCode() != null && !errorPage.getErrorCode().isBlank()) {
+ recorder.addErrorPage(deployment, errorPage.getLocation(), Integer.parseInt(errorPage.getErrorCode()));
+ } else if (errorPage.getExceptionType() != null && !errorPage.getExceptionType().isBlank()) {
+ recorder.addErrorPage(deployment, errorPage.getLocation(),
+ (Class extends Throwable>) context.classProxy(errorPage.getExceptionType()));
+ }
+ }
+ }
SessionConfigMetaData sessionConfig = webMetaData.getSessionConfig();
if (sessionConfig != null) {
if (sessionConfig.getSessionTimeoutSet()) {
diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletWebXmlTestCase.java b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletWebXmlTestCase.java
index 30b0e7c7122d6..64ca9d88894fc 100644
--- a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletWebXmlTestCase.java
+++ b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletWebXmlTestCase.java
@@ -33,7 +33,11 @@ public class ServletWebXmlTestCase {
" \n" +
" wasm\n" +
" application/wasm\n" +
- " " +
+ " \n" +
+ " \n" +
+ " 404\n" +
+ " /mapped\n" +
+ " \n" +
"";
@RegisterExtension
@@ -56,4 +60,11 @@ public void testMimeMapping() {
.statusCode(200)
.contentType(is("application/wasm"));
}
+
+ @Test
+ public void test404Mapping() {
+ RestAssured.when().get("/missing").then()
+ .statusCode(404)
+ .body(is("web xml servlet"));
+ }
}
diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java
index 082431efc6a9e..a0ccfe2a2d83d 100644
--- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java
+++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java
@@ -747,6 +747,16 @@ public void setSessionCookieConfig(ServletSessionConfig config, String name, Str
}
}
+ public void addErrorPage(RuntimeValue deployment, String location, int errorCode) {
+ deployment.getValue().addErrorPage(new ErrorPage(location, errorCode));
+
+ }
+
+ public void addErrorPage(RuntimeValue deployment, String location,
+ Class extends Throwable> exceptionType) {
+ deployment.getValue().addErrorPage(new ErrorPage(location, exceptionType));
+ }
+
/**
* we can't have SecureRandom in the native image heap, so we need to lazy init
*/
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpHostConfigSource.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpHostConfigSource.java
deleted file mode 100644
index 0b60d975d26f0..0000000000000
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpHostConfigSource.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package io.quarkus.vertx.http.runtime;
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.microprofile.config.spi.ConfigSource;
-
-import io.quarkus.runtime.LaunchMode;
-
-/**
- * Sets the default host config value, depending on the launch mode.
- *
- * This can't be done with a normal default, is it changes based on the mode,
- * so instead it is provided as a low priority config source.
- */
-public class HttpHostConfigSource implements ConfigSource, Serializable {
-
- public static final String QUARKUS_HTTP_HOST = "quarkus.http.host";
- private static final String ALL_INTERFACES = "0.0.0.0";
-
- @Override
- public Map getProperties() {
- return Collections.singletonMap(QUARKUS_HTTP_HOST, getValue(QUARKUS_HTTP_HOST));
- }
-
- @Override
- public Set getPropertyNames() {
- return Collections.singleton(QUARKUS_HTTP_HOST);
- }
-
- @Override
- public int getOrdinal() {
- return Integer.MIN_VALUE;
- }
-
- @Override
- public String getValue(String propertyName) {
- if (propertyName.equals(QUARKUS_HTTP_HOST)) {
- if (LaunchMode.isRemoteDev()) {
- // in remote-dev mode we need to listen on all interfaces
- return ALL_INTERFACES;
- }
- // In dev-mode we want to only listen on localhost so others on the network cannot connect to the application.
- // However, in WSL this would result in the application not being accessible,
- // so in that case, we launch it on all interfaces.
- return (LaunchMode.current().isDevOrTest() && !isWSL()) ? "localhost" : ALL_INTERFACES;
- }
- return null;
- }
-
- /**
- * @return {@code true} if the application is running in a WSL (Windows Subsystem for Linux) environment
- */
- private boolean isWSL() {
- var sysEnv = System.getenv();
- return sysEnv.containsKey("IS_WSL") || sysEnv.containsKey("WSL_DISTRO_NAME");
- }
-
- @Override
- public String getName() {
- return "Quarkus HTTP Host Default Value";
- }
-}
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxConfigBuilder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxConfigBuilder.java
index 98db8159eeb00..cef3f4b079945 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxConfigBuilder.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxConfigBuilder.java
@@ -1,11 +1,37 @@
package io.quarkus.vertx.http.runtime;
+import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigBuilder;
import io.smallrye.config.SmallRyeConfigBuilder;
public class VertxConfigBuilder implements ConfigBuilder {
+ private static final String QUARKUS_HTTP_HOST = "quarkus.http.host";
+ private static final String ALL_INTERFACES = "0.0.0.0";
+
@Override
public SmallRyeConfigBuilder configBuilder(final SmallRyeConfigBuilder builder) {
- return builder.withSources(new HttpHostConfigSource());
+ // It may have been recorded, so only set if it not available in the defaults
+ if (builder.getDefaultValues().get(QUARKUS_HTTP_HOST) == null) {
+ // Sets the default host config value, depending on the launch mode
+ if (LaunchMode.isRemoteDev()) {
+ // in remote-dev mode we need to listen on all interfaces
+ builder.withDefaultValue(QUARKUS_HTTP_HOST, ALL_INTERFACES);
+ } else {
+ // In dev-mode we want to only listen on localhost so others on the network cannot connect to the application.
+ // However, in WSL this would result in the application not being accessible,
+ // so in that case, we launch it on all interfaces.
+ builder.withDefaultValue(QUARKUS_HTTP_HOST,
+ (LaunchMode.current().isDevOrTest() && !isWSL()) ? "localhost" : ALL_INTERFACES);
+ }
+ }
+ return builder;
+ }
+
+ /**
+ * @return {@code true} if the application is running in a WSL (Windows Subsystem for Linux) environment
+ */
+ private boolean isWSL() {
+ var sysEnv = System.getenv();
+ return sysEnv.containsKey("IS_WSL") || sysEnv.containsKey("WSL_DISTRO_NAME");
}
}
diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java
index 2d69d4a6d7be9..9bd62eba808f2 100644
--- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java
+++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java
@@ -28,6 +28,7 @@
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.BuiltinScope;
+import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.Feature;
@@ -44,6 +45,7 @@
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.gizmo.ClassOutput;
@@ -196,6 +198,23 @@ void faultToleranceIntegration(Capabilities capabilities, BuildProducer tryLoad(String name, ClassLoader tccl) {
try {
return tccl.loadClass(name);
diff --git a/extensions/websockets/client/runtime/src/main/java/io/quarkus/websockets/client/runtime/DisableLoggingFeature.java b/extensions/websockets/client/runtime/src/main/java/io/quarkus/websockets/client/runtime/DisableLoggingFeature.java
index 022507620961e..b8e723d990eb4 100644
--- a/extensions/websockets/client/runtime/src/main/java/io/quarkus/websockets/client/runtime/DisableLoggingFeature.java
+++ b/extensions/websockets/client/runtime/src/main/java/io/quarkus/websockets/client/runtime/DisableLoggingFeature.java
@@ -31,10 +31,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
public void afterAnalysis(AfterAnalysisAccess access) {
for (String category : CATEGORIES) {
Level level = categoryMap.remove(category);
- if (level != null) {
- Logger logger = Logger.getLogger(category);
- logger.setLevel(level);
- }
+ Logger logger = Logger.getLogger(category);
+ logger.setLevel(level);
}
}
diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java
index e9e04ef605da8..1225141452304 100644
--- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java
+++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java
@@ -18,7 +18,7 @@ abstract class AbstractInstanceHandle implements InstanceHandle {
private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER = AtomicIntegerFieldUpdater
.newUpdater(AbstractInstanceHandle.class, "destroyed");
- private final InjectableBean bean;
+ protected final InjectableBean bean;
private final CreationalContext creationalContext;
private final CreationalContext> parentCreationalContext;
private final Consumer destroyLogic;
@@ -56,7 +56,7 @@ public void destroy() {
if (isInstanceCreated() && DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {
if (destroyLogic != null) {
destroyLogic.accept(instanceInternal());
- } else {
+ } else if (bean != null) {
if (bean.getScope().equals(Dependent.class)) {
destroyInternal();
} else {
diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EagerInstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EagerInstanceHandle.java
index 1ba2cf08159a6..df2430fcf5505 100644
--- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EagerInstanceHandle.java
+++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EagerInstanceHandle.java
@@ -8,8 +8,10 @@
import io.quarkus.arc.InstanceHandle;
/**
+ * Instance handle that is initialized eagerly when created.
*
* @param
+ * @see LazyInstanceHandle
*/
class EagerInstanceHandle extends AbstractInstanceHandle {
@@ -34,7 +36,7 @@ public static final InstanceHandle unavailable() {
@Override
protected boolean isInstanceCreated() {
- return true;
+ return instance != null;
}
@Override
diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LazyInstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LazyInstanceHandle.java
index 891a1c8397257..4be2d86d85e36 100644
--- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LazyInstanceHandle.java
+++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LazyInstanceHandle.java
@@ -8,8 +8,10 @@
import io.quarkus.arc.InjectableBean;
/**
+ * Instance handle that is initialized lazily when first used.
*
* @param
+ * @see EagerInstanceHandle
*/
class LazyInstanceHandle extends AbstractInstanceHandle {
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java
index a2349c3024766..47bde335702f4 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java
@@ -87,6 +87,11 @@ public void accept(String relativePath, Consumer func) {
delegate.accept(relativePath, func);
}
+ @Override
+ public void acceptAll(String relativePath, Consumer func) {
+ delegate.acceptAll(relativePath, func);
+ }
+
@Override
public boolean contains(String relativePath) {
final LinkedHashMap snapshot = walkSnapshot;
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java
index 11e125a04fbf1..8265ef59771f1 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java
@@ -54,6 +54,15 @@ public void accept(String relativePath, Consumer consumer) {
}
}
+ @Override
+ public void acceptAll(String relativePath, Consumer consumer) {
+ if (!PathFilter.isVisible(filter, relativePath)) {
+ consumer.accept(null);
+ } else {
+ original.acceptAll(relativePath, consumer);
+ }
+ }
+
@Override
public boolean contains(String relativePath) {
return PathFilter.isVisible(filter, relativePath) && original.contains(relativePath);
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java
index 4681cec199deb..e09b70ce12168 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java
@@ -86,6 +86,26 @@ public void accept(PathVisit t) {
}
}
+ @Override
+ public void acceptAll(String relativePath, Consumer func) {
+ final AtomicBoolean consumed = new AtomicBoolean();
+ final Consumer wrapper = new Consumer<>() {
+ @Override
+ public void accept(PathVisit t) {
+ if (t != null) {
+ func.accept(t);
+ consumed.set(true);
+ }
+ }
+ };
+ for (PathTree tree : trees) {
+ tree.accept(relativePath, wrapper);
+ }
+ if (!consumed.get()) {
+ func.accept(null);
+ }
+ }
+
@Override
public boolean contains(String relativePath) {
for (PathTree tree : trees) {
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
index b126db5924d26..5b68db900bc93 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
@@ -134,6 +134,20 @@ default boolean isEmpty() {
*/
void accept(String relativePath, Consumer consumer);
+ /**
+ * Consumes a given path relative to the root of the tree.
+ * If the path isn't found in the tree, the {@link PathVisit} argument
+ * passed to the consumer will be {@code null}.
+ *
+ * If multiple items match then the consumer will be called multiple times.
+ *
+ * @param relativePath relative path to consume
+ * @param consumer path consumer
+ */
+ default void acceptAll(String relativePath, Consumer consumer) {
+ accept(relativePath, consumer);
+ }
+
/**
* Checks whether the tree contains a relative path.
*
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java
index 139560a3d40f1..5084405c4bdcf 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java
@@ -44,6 +44,16 @@ default URL getUrl() {
}
}
+ /**
+ * Path relative to the root of the tree as a string with {@code /} as a path element separator.
+ * This method calls {@link #getRelativePath(String)} passing {@code /} as an argument.
+ *
+ * @return path relative to the root of the tree as a string with {@code /} as a path element separator
+ */
+ default String getRelativePath() {
+ return getRelativePath("/");
+ }
+
/**
* Path relative to the root of the tree as a string with a provided path element separator.
* For a {@link PathTree} created for an archive, the returned path will be relative to the root
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java
index b5cd7e9cdd3a8..1206da6cc618c 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java
@@ -158,6 +158,11 @@ public void accept(String relativePath, Consumer consumer) {
delegate.accept(relativePath, consumer);
}
+ @Override
+ public void acceptAll(String relativePath, Consumer consumer) {
+ delegate.acceptAll(relativePath, consumer);
+ }
+
@Override
public boolean contains(String relativePath) {
return delegate.contains(relativePath);
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java
index 53723350b5476..0e319d437e7b7 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java
@@ -4,6 +4,7 @@
import java.nio.file.Path;
import java.security.ProtectionDomain;
import java.util.Collections;
+import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.jar.Manifest;
@@ -141,4 +142,9 @@ public void close() {
}
};
+
+ default List getResources(String name) {
+ ClassPathResource resource = getResource(name);
+ return resource == null ? List.of() : List.of(resource);
+ }
}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
index 17dab87f95563..a1da4f0e74114 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
@@ -12,8 +12,10 @@
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
@@ -106,6 +108,26 @@ public ClassPathResource getResource(String name) {
return apply(tree -> tree.apply(sanitized, visit -> visit == null ? null : new Resource(visit)));
}
+ @Override
+ public List getResources(String name) {
+ final String sanitized = sanitize(name);
+ final Set resources = this.resources;
+ if (resources != null && !resources.contains(sanitized)) {
+ return List.of();
+ }
+ List ret = new ArrayList<>();
+ apply(tree -> {
+ tree.acceptAll(sanitized, visit -> {
+ if (visit != null) {
+ ret.add(new Resource(visit));
+
+ }
+ });
+ return List.of();
+ });
+ return ret;
+ }
+
@Override
public T apply(Function func) {
lock.readLock().lock();
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
index e1b20fe85d657..1f1c3bf2c1cdf 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
@@ -11,6 +11,7 @@
import java.sql.Driver;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@@ -246,27 +247,31 @@ public Enumeration getResources(String unsanitisedName, boolean parentAlrea
if (providers != null) {
boolean endsWithTrailingSlash = unsanitisedName.endsWith("/");
for (ClassPathElement element : providers) {
- ClassPathResource res = element.getResource(name);
+ Collection resList = element.getResources(name);
//if the requested name ends with a trailing / we make sure
//that the resource is a directory, and return a URL that ends with a /
//this matches the behaviour of URLClassLoader
- if (endsWithTrailingSlash) {
- if (res.isDirectory()) {
- try {
- resources.add(new URL(res.getUrl().toString() + "/"));
- } catch (MalformedURLException e) {
- throw new RuntimeException(e);
+ for (var res : resList) {
+ if (endsWithTrailingSlash) {
+ if (res.isDirectory()) {
+ try {
+ resources.add(new URL(res.getUrl().toString() + "/"));
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
}
+ } else {
+ resources.add(res.getUrl());
}
- } else {
- resources.add(res.getUrl());
}
}
} else if (name.isEmpty()) {
for (ClassPathElement i : elements) {
- ClassPathResource res = i.getResource("");
- if (res != null) {
- resources.add(res.getUrl());
+ List resList = i.getResources("");
+ for (var res : resList) {
+ if (res != null) {
+ resources.add(res.getUrl());
+ }
}
}
}
diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java
index 7a3fd7574fb5d..0b8eb190884e2 100644
--- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java
+++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java
@@ -427,6 +427,10 @@ private void visitRuntimeDependency(DependencyNode node) {
final ExtensionDependency extDep = getExtensionDependencyOrNull(node, artifact);
if (dep == null) {
+ // in case it was relocated it might not survive conflict resolution in the deployment graph
+ if (!node.getRelocations().isEmpty()) {
+ ((DefaultDependencyNode) node).setRelocations(List.of());
+ }
WorkspaceModule module = null;
if (resolver.getProjectModuleResolver() != null) {
module = resolver.getProjectModuleResolver().getProjectModule(artifact.getGroupId(),
diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml
index 47c5123683b40..0f143506d2965 100644
--- a/independent-projects/bootstrap/pom.xml
+++ b/independent-projects/bootstrap/pom.xml
@@ -57,16 +57,16 @@
2.1.14.0.12.0.1
- 1.16.1
- 2.16.0
+ 1.17.0
+ 2.16.13.14.033.1.0-jre1.0.12.81.2.6
- 3.0.4.Final
+ 3.0.6.Final1.1.0.Final
- 1.7.36
+ 2.0.623.1.02.6.02.0
diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml
index 62ed33efb3ff5..ffacc3a198c40 100644
--- a/independent-projects/extension-maven-plugin/pom.xml
+++ b/independent-projects/extension-maven-plugin/pom.xml
@@ -42,7 +42,7 @@
3.2.13.2.52.17.0
- 1.4.0
+ 1.4.15.10.2
diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/core/AbstractResteasyReactiveContext.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/core/AbstractResteasyReactiveContext.java
index 705e9e3075605..d78d0af28de6c 100644
--- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/core/AbstractResteasyReactiveContext.java
+++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/core/AbstractResteasyReactiveContext.java
@@ -246,12 +246,17 @@ public void requireCDIRequestScope() {
if (requestScopeActivated) {
return;
}
- requestScopeActivated = true;
if (isRequestScopeManagementRequired()) {
- if (currentRequestScope == null) {
- currentRequestScope = requestContext.activateInitial();
+ if (requestContext.isRequestContextActive()) {
+ // req. context is already active, just reuse existing one
+ currentRequestScope = requestContext.currentState();
} else {
- currentRequestScope.activate();
+ requestScopeActivated = true;
+ if (currentRequestScope == null) {
+ currentRequestScope = requestContext.activateInitial();
+ } else {
+ currentRequestScope.activate();
+ }
}
} else {
currentRequestScope = requestContext.currentState();
diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/AbstractJsonMessageBodyReader.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/AbstractJsonMessageBodyReader.java
index 50bf2637442e4..d096d04387b9c 100644
--- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/AbstractJsonMessageBodyReader.java
+++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/AbstractJsonMessageBodyReader.java
@@ -20,10 +20,11 @@ protected boolean isReadable(MediaType mediaType, Class> type) {
if (String.class.equals(type)) { // don't attempt to read plain strings
return false;
}
- String subtype = mediaType.getSubtype();
- boolean isApplicationMediaType = "application".equals(mediaType.getType());
- return (isApplicationMediaType && "json".equalsIgnoreCase(subtype) || subtype.endsWith("+json")
- || subtype.equalsIgnoreCase("x-ndjson"))
+ String subtype = mediaType.getSubtype().toLowerCase();
+ final String mainType = mediaType.getType().toLowerCase();
+ boolean isApplicationMediaType = "application".equals(mainType);
+ return (isApplicationMediaType && "json".equals(subtype) || subtype.endsWith("+json")
+ || "x-ndjson".equals(subtype))
|| (mediaType.isWildcardSubtype() && (mediaType.isWildcardType() || isApplicationMediaType));
}
}
diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/spi/ThreadSetupAction.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/spi/ThreadSetupAction.java
index 912614f2c6939..67c0fdf60727f 100644
--- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/spi/ThreadSetupAction.java
+++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/spi/ThreadSetupAction.java
@@ -6,6 +6,8 @@ public interface ThreadSetupAction {
ThreadState currentState();
+ boolean isRequestContextActive();
+
interface ThreadState {
void close();
@@ -39,5 +41,10 @@ public void deactivate() {
public ThreadState currentState() {
return activateInitial();
}
+
+ @Override
+ public boolean isRequestContextActive() {
+ return false;
+ }
};
}
diff --git a/independent-projects/resteasy-reactive/common/runtime/src/test/java/org/jboss/resteasy/reactive/common/providers/serialisers/AbstractJsonMessageBodyReaderTest.java b/independent-projects/resteasy-reactive/common/runtime/src/test/java/org/jboss/resteasy/reactive/common/providers/serialisers/AbstractJsonMessageBodyReaderTest.java
new file mode 100644
index 0000000000000..25330dd08f303
--- /dev/null
+++ b/independent-projects/resteasy-reactive/common/runtime/src/test/java/org/jboss/resteasy/reactive/common/providers/serialisers/AbstractJsonMessageBodyReaderTest.java
@@ -0,0 +1,45 @@
+package org.jboss.resteasy.reactive.common.providers.serialisers;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedMap;
+
+import org.junit.jupiter.api.Test;
+
+class AbstractJsonMessageBodyReaderTest {
+
+ class TestReader extends AbstractJsonMessageBodyReader {
+ @Override
+ public Object readFrom(Class