diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 9cf453c2a4347..263db5eef26e3 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -34,7 +34,7 @@ 1.32.0 1.32.0-alpha 1.21.0-alpha - 5.1.0.Final + 5.2.0.Final 1.12.2 2.1.12 0.22.0 @@ -61,8 +61,8 @@ 2.1.0 1.0.13 3.0.1 - 3.9.0 - 4.16.2 + 3.10.0 + 4.18.0 2.5.0 2.1.2 2.1.1 @@ -108,7 +108,7 @@ 7.0.0.Final 7.0.0.Final - 2.1 + 2.3 8.0.0.Final 8.11.4 2.2.21 @@ -121,7 +121,7 @@ 1.0.1.Final 2.2.3.Final 3.5.1.Final - 4.5.3 + 4.5.4 4.5.14 4.4.16 4.1.5 @@ -141,10 +141,10 @@ 2.2 5.10.1 1.5.0 - 14.0.24.Final + 14.0.25.Final 4.6.5.Final 3.1.5 - 4.1.106.Final + 4.1.107.Final 1.14.0 1.0.4 3.5.3.Final @@ -207,8 +207,8 @@ 1.11.3 2.5.8.Final 0.1.18.Final - 1.19.4 - 3.3.4 + 1.19.6 + 3.3.5 2.0.0 1.4.4 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java index fdcbe78e85e25..a81be9578412e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java @@ -926,9 +926,11 @@ private void copyDependency(Set parentFirstArtifacts, OutputTargetB } } if (removedFromThisArchive.isEmpty()) { - Files.copy(resolvedDep, targetPath, StandardCopyOption.REPLACE_EXISTING); + Files.copy(resolvedDep, targetPath, StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.COPY_ATTRIBUTES); } else { - //we have removed classes, we need to handle them correctly + // we copy jars for which we remove entries to the same directory + // which seems a bit odd to me filterZipFile(resolvedDep, targetPath, removedFromThisArchive); } } @@ -1251,6 +1253,8 @@ private void filterZipFile(Path resolvedDep, Path targetPath, Set transf } } } + // let's make sure we keep the original timestamp + Files.setLastModifiedTime(targetPath, Files.getLastModifiedTime(resolvedDep)); } } catch (IOException e) { throw new RuntimeException(e); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java index 7ab2f19910dd1..237e18cafa344 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java @@ -7,6 +7,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -15,7 +16,6 @@ import org.jboss.logging.Logger; import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.pkg.NativeConfig; import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem; @@ -34,7 +34,6 @@ public class UpxCompressionBuildStep { */ private static final String PATH = "PATH"; - @BuildStep(onlyIf = NativeBuild.class) public void compress(NativeConfig nativeConfig, NativeImageRunnerBuildItem nativeImageRunner, NativeImageBuildItem image, BuildProducer upxCompressedProducer, @@ -70,11 +69,13 @@ public void compress(NativeConfig nativeConfig, NativeImageRunnerBuildItem nativ } private boolean runUpxFromHost(File upx, File executable, NativeConfig nativeConfig) { - String level = getCompressionLevel(nativeConfig.compression().level().getAsInt()); List extraArgs = nativeConfig.compression().additionalArgs().orElse(Collections.emptyList()); - List args = Stream.concat( - Stream.concat(Stream.of(upx.getAbsolutePath(), level), extraArgs.stream()), + List args = Stream.of( + Stream.of(upx.getAbsolutePath()), + nativeConfig.compression().level().stream().mapToObj(this::getCompressionLevel), + extraArgs.stream(), Stream.of(executable.getAbsolutePath())) + .flatMap(Function.identity()) .collect(Collectors.toList()); log.infof("Executing %s", String.join(" ", args)); final ProcessBuilder processBuilder = new ProcessBuilder(args) @@ -104,7 +105,6 @@ private boolean runUpxFromHost(File upx, File executable, NativeConfig nativeCon private boolean runUpxInContainer(NativeImageBuildItem nativeImage, NativeConfig nativeConfig, String effectiveBuilderImage) { - String level = getCompressionLevel(nativeConfig.compression().level().getAsInt()); List extraArgs = nativeConfig.compression().additionalArgs().orElse(Collections.emptyList()); List commandLine = new ArrayList<>(); @@ -140,7 +140,9 @@ private boolean runUpxInContainer(NativeImageBuildItem nativeImage, NativeConfig volumeOutputPath + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + ":z"); commandLine.add(effectiveBuilderImage); - commandLine.add(level); + if (nativeConfig.compression().level().isPresent()) { + commandLine.add(getCompressionLevel(nativeConfig.compression().level().getAsInt())); + } commandLine.addAll(extraArgs); commandLine.add(nativeImage.getPath().toFile().getName()); diff --git a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java index b1ef86a4788e9..febcb49601869 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java @@ -93,6 +93,7 @@ public int run(String... args) throws Exception { //When running tests the cli should not prompt for user input. boolean interactiveMode = Arrays.stream(args).noneMatch(arg -> arg.equals("--cli-test")); Optional testDir = Arrays.stream(args).dropWhile(arg -> !arg.equals("--cli-test-dir")).skip(1).findFirst(); + boolean noCommand = args.length == 0 || args[0].startsWith("-"); boolean helpCommand = Arrays.stream(args).anyMatch(arg -> arg.equals("--help")); boolean pluginCommand = args.length >= 1 && (args[0].equals("plug") || args[0].equals("plugin")); @@ -103,7 +104,7 @@ public int run(String... args) throws Exception { // If the command already exists and is not a help command (that lists subcommands) or plugin command, then just execute // without dealing with plugins. // The reason that we check if its a plugin command is that plugin commands need PluginManager initialization. - if (existingCommand && !helpCommand && !pluginCommand) { + if (existingCommand && !noCommand && !helpCommand && !pluginCommand) { return cmd.execute(args); } PluginCommandFactory pluginCommandFactory = new PluginCommandFactory(output); @@ -111,14 +112,15 @@ public int run(String... args) throws Exception { pluginManager.syncIfNeeded(); Map plugins = new HashMap<>(pluginManager.getInstalledPlugins()); pluginCommandFactory.populateCommands(cmd, plugins); - missingCommand.ifPresent(m -> { + missingCommand.filter(m -> !plugins.containsKey(m)).ifPresent(m -> { try { + output.info("Command %s is not available, looking for available plugins ...", m); Map installable = pluginManager.getInstallablePlugins(); if (installable.containsKey(m)) { Plugin candidate = installable.get(m); PluginListItem item = new PluginListItem(false, candidate); PluginListTable table = new PluginListTable(List.of(item)); - output.info("Command %s not installed but the following plugin is available:\n%s", m, + output.info("Plugin %s is available:\n%s", m, table.getContent()); if (interactiveMode && Prompt.yesOrNo(true, "Would you like to install it now?", diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java index 1a6c34842d24c..718a53f8798f9 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java @@ -73,7 +73,7 @@ public void beforeTest(Test task) { Map props = task.getSystemProperties(); ApplicationModel appModel = getApplicationModel(TEST); - SmallRyeConfig config = buildEffectiveConfiguration(appModel.getAppArtifact()).config(); + SmallRyeConfig config = buildEffectiveConfiguration(appModel.getAppArtifact()).getConfig(); config.getOptionalValue(TEST.getProfileKey(), String.class) .ifPresent(value -> props.put(TEST.getProfileKey(), value)); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java index f2696a9266f23..85f67e430dfc1 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java @@ -1,12 +1,17 @@ package io.quarkus.gradle.tasks; import static io.quarkus.gradle.tasks.QuarkusGradleUtils.getSourceSet; +import static io.smallrye.common.expression.Expression.Flag.DOUBLE_COLON; +import static io.smallrye.common.expression.Expression.Flag.LENIENT_SYNTAX; +import static io.smallrye.common.expression.Expression.Flag.NO_SMART_BRACES; +import static io.smallrye.common.expression.Expression.Flag.NO_TRIM; import static java.util.Collections.emptyList; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -24,6 +29,7 @@ import io.quarkus.gradle.dsl.Manifest; import io.quarkus.maven.dependency.ResolvedDependency; +import io.smallrye.common.expression.Expression; /** * This base class exists to hide internal properties, make those only available in the {@link io.quarkus.gradle.tasks} @@ -138,7 +144,7 @@ private EffectiveConfig buildEffectiveConfiguration(Map properti * @param appArtifact the application dependency to retrive the quarkus application name and version. * @return a filtered view of the configuration only with quarkus. names. */ - protected Map buildSystemProperties(ResolvedDependency appArtifact) { + protected Map buildSystemProperties(ResolvedDependency appArtifact, Map quarkusProperties) { Map buildSystemProperties = new HashMap<>(); buildSystemProperties.putIfAbsent("quarkus.application.name", appArtifact.getArtifactId()); buildSystemProperties.putIfAbsent("quarkus.application.version", appArtifact.getVersion()); @@ -158,6 +164,33 @@ protected Map buildSystemProperties(ResolvedDependency appArtifa buildSystemProperties.put(entry.getKey(), entry.getValue().toString()); } } + + Set quarkusValues = new HashSet<>(); + quarkusValues.addAll(quarkusProperties.values()); + quarkusValues.addAll(buildSystemProperties.values()); + + for (String value : quarkusValues) { + Expression expression = Expression.compile(value, LENIENT_SYNTAX, NO_TRIM, NO_SMART_BRACES, DOUBLE_COLON); + for (String reference : expression.getReferencedStrings()) { + String expanded = forcedPropertiesProperty.get().get(reference); + if (expanded != null) { + buildSystemProperties.put(reference, expanded); + continue; + } + + expanded = quarkusBuildProperties.get().get(reference); + if (expanded != null) { + buildSystemProperties.put(reference, expanded); + continue; + } + + expanded = (String) project.getProperties().get(reference); + if (expanded != null) { + buildSystemProperties.put(reference, expanded); + } + } + } + return buildSystemProperties; } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java index 90c229c043b9b..5e0d9533e92dc 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java @@ -21,20 +21,20 @@ final class BaseConfig { private final Manifest manifest; private final PackageConfig packageConfig; - private final Map configMap; + private final Map values; // Note: EffectiveConfig has all the code to load the configurations from all the sources. BaseConfig(EffectiveConfig config) { manifest = new Manifest(); packageConfig = new PackageConfig(); - ConfigInstantiator.handleObject(packageConfig, config.config()); + ConfigInstantiator.handleObject(packageConfig, config.getConfig()); // populate the Gradle Manifest object manifest.attributes(packageConfig.manifest.attributes); packageConfig.manifest.manifestSections.forEach((section, attribs) -> manifest.attributes(attribs, section)); - configMap = config.configMap(); + values = config.getValues(); } PackageConfig packageConfig() { @@ -53,7 +53,7 @@ Map cachingRelevantProperties(List propertyPatterns) { List patterns = propertyPatterns.stream().map(s -> "^(" + s + ")$").map(Pattern::compile) .collect(Collectors.toList()); Predicate> keyPredicate = e -> patterns.stream().anyMatch(p -> p.matcher(e.getKey()).matches()); - return configMap.entrySet().stream() + return values.entrySet().stream() .filter(keyPredicate) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/Deploy.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/Deploy.java index 132d956279771..d5b13e65e4a06 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/Deploy.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/Deploy.java @@ -90,9 +90,8 @@ public Deploy() { @TaskAction public void checkRequiredExtensions() { ApplicationModel appModel = resolveAppModelForBuild(); - Map configMap = extension().buildEffectiveConfiguration(appModel.getAppArtifact()).configMap(); Properties sysProps = new Properties(); - sysProps.putAll(configMap); + sysProps.putAll(extension().buildEffectiveConfiguration(appModel.getAppArtifact()).getValues()); try (CuratedApplication curatedApplication = QuarkusBootstrap.builder() .setBaseClassLoader(getClass().getClassLoader()) .setExistingModel(appModel) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java index 1166d55e410d3..1c67c52ac4637 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import org.eclipse.microprofile.config.spi.ConfigSource; import org.eclipse.microprofile.config.spi.ConfigSourceProvider; @@ -26,6 +27,7 @@ import io.quarkus.runtime.configuration.ConfigUtils; import io.smallrye.config.AbstractLocationConfigSourceLoader; import io.smallrye.config.EnvConfigSource; +import io.smallrye.config.Expressions; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.PropertiesConfigSourceProvider; import io.smallrye.config.SmallRyeConfig; @@ -42,9 +44,8 @@ * the Quarkus config objects like {@link PackageConfig}, {@link ClassLoadingConfig} and the underlying {@link SmallRyeConfig}. */ public final class EffectiveConfig { - private final Map fullConfig; - private final SmallRyeConfig config; + private final Map values; private EffectiveConfig(Builder builder) { List configSources = new ArrayList<>(); @@ -81,13 +82,17 @@ private EffectiveConfig(Builder builder) { .addAll(PropertiesConfigSourceProvider.classPathSources(META_INF_MICROPROFILE_CONFIG_PROPERTIES, classLoader)); this.config = buildConfig(builder.profile, configSources); - this.fullConfig = generateFullConfigMap(config); + this.values = generateFullConfigMap(config); } - public SmallRyeConfig config() { + public SmallRyeConfig getConfig() { return config; } + public Map getValues() { + return values; + } + private Map asStringMap(Map map) { Map target = new HashMap<>(); map.forEach((k, v) -> { @@ -100,14 +105,19 @@ private Map asStringMap(Map map) { @VisibleForTesting static Map generateFullConfigMap(SmallRyeConfig config) { - Map map = new HashMap<>(); - config.getPropertyNames().forEach(property -> { - String v = config.getConfigValue(property).getValue(); - if (v != null) { - map.put(property, v); + return Expressions.withoutExpansion(new Supplier>() { + @Override + public Map get() { + Map properties = new HashMap<>(); + for (String propertyName : config.getPropertyNames()) { + String value = config.getRawValue(propertyName); + if (value != null) { + properties.put(propertyName, value); + } + } + return unmodifiableMap(properties); } }); - return unmodifiableMap(map); } @VisibleForTesting @@ -126,10 +136,6 @@ static Builder builder() { return new Builder(); } - public Map configMap() { - return fullConfig; - } - static final class Builder { private Map buildProperties = emptyMap(); private Map projectProperties = emptyMap(); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java index a410e1af41270..d18dd3c70636f 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java @@ -24,6 +24,7 @@ import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.DependencyFlags; import io.quarkus.maven.dependency.ResolvedDependency; +import io.smallrye.config.SmallRyeConfig; /** * Collect the Quarkus app dependencies, the contents of the {@code quarkus-app/lib} folder, without making the task @@ -139,22 +140,23 @@ private void jarDependencies(Path libBoot, Path libMain) { } ApplicationModel appModel = resolveAppModelForBuild(); - Map configMap = extension().buildEffectiveConfiguration(appModel.getAppArtifact()).configMap(); + SmallRyeConfig config = extension().buildEffectiveConfiguration(appModel.getAppArtifact()).getConfig(); // see https://quarkus.io/guides/class-loading-reference#configuring-class-loading - Set removedArtifacts = java.util.Optional.ofNullable( - configMap.getOrDefault(CLASS_LOADING_REMOVED_ARTIFACTS, null)) + Set removedArtifacts = config.getOptionalValue(CLASS_LOADING_REMOVED_ARTIFACTS, String.class) .map(QuarkusBuildDependencies::dependenciesListToArtifactKeySet) .orElse(Collections.emptySet()); - getLogger().info("Removed artifacts: {}", configMap.getOrDefault(CLASS_LOADING_REMOVED_ARTIFACTS, "(none)")); + getLogger().info("Removed artifacts: {}", + config.getOptionalValue(CLASS_LOADING_REMOVED_ARTIFACTS, String.class).orElse("(none)")); - String parentFirstArtifactsProp = configMap.getOrDefault(CLASS_LOADING_PARENT_FIRST_ARTIFACTS, ""); + String parentFirstArtifactsProp = config.getOptionalValue(CLASS_LOADING_PARENT_FIRST_ARTIFACTS, String.class) + .orElse(""); Set parentFirstArtifacts = dependenciesListToArtifactKeySet(parentFirstArtifactsProp); - getLogger().info("parent first artifacts: {}", configMap.getOrDefault(CLASS_LOADING_PARENT_FIRST_ARTIFACTS, "(none)")); + getLogger().info("parent first artifacts: {}", + config.getOptionalValue(CLASS_LOADING_PARENT_FIRST_ARTIFACTS, String.class).orElse("(none)")); - String optionalDependenciesProp = configMap.getOrDefault(INCLUDED_OPTIONAL_DEPENDENCIES, ""); - boolean filterOptionalDependencies = Boolean - .parseBoolean(configMap.getOrDefault(FILTER_OPTIONAL_DEPENDENCIES, "false")); + String optionalDependenciesProp = config.getOptionalValue(INCLUDED_OPTIONAL_DEPENDENCIES, String.class).orElse(""); + boolean filterOptionalDependencies = config.getOptionalValue(FILTER_OPTIONAL_DEPENDENCIES, Boolean.class).orElse(false); Set optionalDependencies = filterOptionalDependencies ? dependenciesListToArtifactKeySet(optionalDependenciesProp) : Collections.emptySet(); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java index 88196dbba173a..827ace86ae845 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -29,6 +29,7 @@ import io.quarkus.gradle.tasks.worker.BuildWorker; import io.quarkus.maven.dependency.GACTV; import io.smallrye.config.Expressions; +import io.smallrye.config.SmallRyeConfig; /** * Base class for the {@link QuarkusBuildDependencies}, {@link QuarkusBuildCacheableAppParts}, {@link QuarkusBuild} tasks @@ -207,30 +208,29 @@ void generateBuild() { }); ApplicationModel appModel = resolveAppModelForBuild(); - Map configMap = new HashMap<>(); - EffectiveConfig effectiveConfig = extension().buildEffectiveConfiguration(appModel.getAppArtifact()); - Expressions.withoutExpansion(() -> { - for (Map.Entry entry : effectiveConfig.configMap().entrySet()) { - if (entry.getKey().startsWith("quarkus.")) { - configMap.put(entry.getKey(), effectiveConfig.config().getRawValue(entry.getKey())); - } - } + SmallRyeConfig config = extension().buildEffectiveConfiguration(appModel.getAppArtifact()).getConfig(); + Map quarkusProperties = Expressions.withoutExpansion(() -> { + Map values = new HashMap<>(); + config.getValues("quarkus", String.class, String.class) + .forEach((key, value) -> values.put("quarkus." + key, value)); + return values; }); getLogger().info("Starting Quarkus application build for package type {}", packageType); if (getLogger().isEnabled(LogLevel.INFO)) { getLogger().info("Effective properties: {}", - configMap.entrySet().stream() + quarkusProperties.entrySet().stream() .map(Object::toString) .sorted() .collect(Collectors.joining("\n ", "\n ", ""))); } - WorkQueue workQueue = workQueue(configMap, () -> extension().buildForkOptions); + WorkQueue workQueue = workQueue(quarkusProperties, () -> extension().buildForkOptions); workQueue.submit(BuildWorker.class, params -> { - params.getBuildSystemProperties().putAll(extension().buildSystemProperties(appModel.getAppArtifact())); + params.getBuildSystemProperties() + .putAll(extension().buildSystemProperties(appModel.getAppArtifact(), quarkusProperties)); params.getBaseName().set(extension().finalName()); params.getTargetDirectory().set(buildDir.toFile()); params.getAppModel().set(appModel); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java index b01ffdb6732f0..1b1cc049c8bd5 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java @@ -102,17 +102,17 @@ public Set getInputDirectory() { @TaskAction public void generateCode() { ApplicationModel appModel = extension().getApplicationModel(launchMode); - Map configMap = extension().buildEffectiveConfiguration(appModel.getAppArtifact()).configMap(); + Map values = extension().buildEffectiveConfiguration(appModel.getAppArtifact()).getValues(); File outputPath = getGeneratedOutputDirectory().get().getAsFile(); getLogger().debug("Will trigger preparing sources for source directories: {} buildDir: {}", sourcesDirectories, buildDir.getAbsolutePath()); - WorkQueue workQueue = workQueue(configMap, () -> extension().codeGenForkOptions); + WorkQueue workQueue = workQueue(values, () -> extension().codeGenForkOptions); workQueue.submit(CodeGenWorker.class, params -> { - params.getBuildSystemProperties().putAll(configMap); + params.getBuildSystemProperties().putAll(values); params.getBaseName().set(extension().finalName()); params.getTargetDirectory().set(buildDir); params.getAppModel().set(appModel); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRun.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRun.java index ea37bcb6875d2..5935f9d46caea 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRun.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRun.java @@ -100,9 +100,8 @@ public void setJvmArgs(List jvmArgs) { @TaskAction public void runQuarkus() { ApplicationModel appModel = resolveAppModelForBuild(); - Map configMap = extension().buildEffectiveConfiguration(appModel.getAppArtifact()).configMap(); Properties sysProps = new Properties(); - sysProps.putAll(configMap); + sysProps.putAll(extension().buildEffectiveConfiguration(appModel.getAppArtifact()).getValues()); try (CuratedApplication curatedApplication = QuarkusBootstrap.builder() .setBaseClassLoader(getClass().getClassLoader()) .setExistingModel(appModel) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java index cd37b8b1aad86..2115ee6ee6ad6 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java @@ -9,7 +9,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; @@ -22,6 +21,7 @@ import org.gradle.api.tasks.options.Option; import io.quarkus.gradle.QuarkusPlugin; +import io.smallrye.config.SmallRyeConfig; /** * Just show the effective configuration and settings. @@ -45,21 +45,21 @@ public Property getSaveConfigProperties() { @TaskAction public void dumpEffectiveConfiguration() { try { - EffectiveConfig effective = extension() + EffectiveConfig effectiveConfig = extension() .buildEffectiveConfiguration(extension().getApplicationModel().getAppArtifact()); - Map configMap = effective.configMap(); + SmallRyeConfig config = effectiveConfig.getConfig(); List sourceNames = new ArrayList<>(); - effective.config().getConfigSources().forEach(configSource -> sourceNames.add(configSource.getName())); + config.getConfigSources().forEach(configSource -> sourceNames.add(configSource.getName())); - String config = configMap.entrySet().stream() - .filter(e -> e.getKey().startsWith("quarkus.")) - .map(e -> format("%s=%s", e.getKey(), e.getValue())).sorted() + String quarkusConfig = config.getValues("quarkus", String.class, String.class) + .entrySet() + .stream() + .map(e -> format("quarkus.%s=%s", e.getKey(), e.getValue())).sorted() .collect(Collectors.joining("\n ", "\n ", "\n")); - - getLogger().lifecycle("Effective Quarkus configuration options: {}", config); + getLogger().lifecycle("Effective Quarkus configuration options: {}", quarkusConfig); String finalName = extension().finalName(); - String packageType = configMap.getOrDefault(QuarkusPlugin.QUARKUS_PACKAGE_TYPE, "fast-jar"); + String packageType = config.getOptionalValue(QuarkusPlugin.QUARKUS_PACKAGE_TYPE, String.class).orElse("fast-jar"); File fastJar = fastJar(); getLogger().lifecycle( "Quarkus package type: {}\n" + @@ -79,7 +79,7 @@ public void dumpEffectiveConfiguration() { if (getSaveConfigProperties().get()) { Properties props = new Properties(); - props.putAll(configMap); + props.putAll(effectiveConfig.getValues()); Path file = buildDir.toPath().resolve(finalName + ".quarkus-build.properties"); try (BufferedWriter writer = newBufferedWriter(file)) { props.store(writer, format("Quarkus build properties with package type %s", packageType)); diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigTest.java index ad239fe7fc39f..443c8695741b3 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigTest.java @@ -32,21 +32,21 @@ void empty() { // Cannot do an exact match, because `map` contains both the "raw" environment variables AND the // "property-key-ish" entries - i.e. environment appears "twice". - soft.assertThat(effectiveConfig.configMap()).containsAllEntriesOf(expect); + soft.assertThat(effectiveConfig.getValues()).containsAllEntriesOf(expect); } @Test void fromProjectProperties() { EffectiveConfig effectiveConfig = EffectiveConfig.builder().withProjectProperties(Map.of("quarkus.foo", "bar")).build(); - soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.foo", "bar"); + soft.assertThat(effectiveConfig.getValues()).containsEntry("quarkus.foo", "bar"); } @Test void fromForcedProperties() { EffectiveConfig effectiveConfig = EffectiveConfig.builder().withTaskProperties(Map.of("quarkus.foo", "bar")).build(); - soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.foo", "bar"); + soft.assertThat(effectiveConfig.getValues()).containsEntry("quarkus.foo", "bar"); } @Test @@ -59,13 +59,13 @@ void appPropsOverload() throws Exception { EffectiveConfig effectiveConfig = EffectiveConfig.builder().withSourceDirectories(source).build(); - SmallRyeConfig config = effectiveConfig.config(); + SmallRyeConfig config = effectiveConfig.getConfig(); List sourceNames = new ArrayList<>(); config.getConfigSources().forEach(configSource -> sourceNames.add(configSource.getName())); soft.assertThat(sourceNames).anyMatch(s -> s.contains(url1.getPath())); soft.assertThat(sourceNames).anyMatch(s -> s.contains(url2.getPath())); // The YAML source is always higher in ordinal than the properties source - soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.prop.overload", "from-yaml"); + soft.assertThat(effectiveConfig.getValues()).containsEntry("quarkus.prop.overload", "from-yaml"); } @Test @@ -80,14 +80,14 @@ void appPropsOverloadWrongProfile() throws Exception { EffectiveConfig effectiveConfig = EffectiveConfig.builder().withSourceDirectories(source).build(); - SmallRyeConfig config = effectiveConfig.config(); + SmallRyeConfig config = effectiveConfig.getConfig(); List sourceNames = new ArrayList<>(); config.getConfigSources().forEach(configSource -> sourceNames.add(configSource.getName())); soft.assertThat(sourceNames).anyMatch(s -> s.contains(url1.getPath())); soft.assertThat(sourceNames).anyMatch(s -> s.contains(url2.getPath())); soft.assertThat(sourceNames).anyMatch(s -> s.contains(url3.getPath())); // The YAML source is always higher in ordinal than the properties source - soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.prop.overload", "from-yaml"); + soft.assertThat(effectiveConfig.getValues()).containsEntry("quarkus.prop.overload", "from-yaml"); } @Test @@ -106,7 +106,7 @@ void appPropsOverloadProdProfile() throws Exception { EffectiveConfig effectiveConfig = EffectiveConfig.builder().withSourceDirectories(source).build(); - SmallRyeConfig config = effectiveConfig.config(); + SmallRyeConfig config = effectiveConfig.getConfig(); List sourceNames = new ArrayList<>(); config.getConfigSources().forEach(configSource -> sourceNames.add(configSource.getName())); soft.assertThat(sourceNames).anyMatch(s -> s.contains(url1.getPath())); @@ -115,6 +115,6 @@ void appPropsOverloadProdProfile() throws Exception { soft.assertThat(sourceNames).anyMatch(s -> s.contains(url4.getPath())); soft.assertThat(sourceNames).anyMatch(s -> s.contains(url5.getPath())); // The YAML source is always higher in ordinal than the properties source, even for profile property names - soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.prop.overload", "from-yaml-prod"); + soft.assertThat(effectiveConfig.getValues()).containsEntry("quarkus.prop.overload", "from-yaml-prod"); } } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 2781edf9f02f8..bdcd48eeff5d3 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -588,6 +588,13 @@ private String handleAutoCompile() throws MojoExecutionException { continue; } for (PluginExecution e : p.getExecutions()) { + if (e.getPhase() != null && !PRE_DEV_MODE_PHASES.contains(e.getPhase())) { + // skip executions with phases post quarkus:dev, such as install, deploy, site, etc + if (getLog().isDebugEnabled()) { + getLog().debug("Skipping " + e.getId() + " of " + p.getId()); + } + continue; + } String goalPrefix = null; if (!e.getGoals().isEmpty()) { goalPrefix = getMojoDescriptor(p, e.getGoals().get(0)).getPluginDescriptor().getGoalPrefix(); diff --git a/docs/src/main/asciidoc/amqp-reference.adoc b/docs/src/main/asciidoc/amqp-reference.adoc index 85355b4b98e0d..10cdff6374bae 100644 --- a/docs/src/main/asciidoc/amqp-reference.adoc +++ b/docs/src/main/asciidoc/amqp-reference.adoc @@ -448,7 +448,7 @@ public AmqpClientOptions getNamedOptions() { .setPemKeyCertOptions(keycert) .setPemTrustOptions(trust) .addEnabledSaslMechanism("EXTERNAL") - .setHostnameVerificationAlgorithm("") + .setHostnameVerificationAlgorithm("") // Disables the hostname verification. Defaults is "HTTPS" .setConnectTimeout(30000) .setReconnectInterval(5000) .setContainerId("my-container"); diff --git a/docs/src/main/asciidoc/config-reference.adoc b/docs/src/main/asciidoc/config-reference.adoc index 1980740a757c3..c205732044559 100644 --- a/docs/src/main/asciidoc/config-reference.adoc +++ b/docs/src/main/asciidoc/config-reference.adoc @@ -377,7 +377,12 @@ Properties in the profile aware file have priority over profile aware properties [WARNING] ==== -The profile aware file must be present in the exact same location as the main `application.properties` file. +Do not use profile aware files to set `quarkus.profile` or `quarkus.test.profile`. This will not work because the +profile is required in advance to load the profile aware files. + +A profile aware file is only loaded if the unprofiled `application.properties` is also available in the same location +and the file extension matches between the files. This is required to keep a consistent loading order and pair all the +resources together. ==== === Parent Profile diff --git a/docs/src/main/asciidoc/dev-services.adoc b/docs/src/main/asciidoc/dev-services.adoc index 39c49c6c55d39..d8dff1bd729e2 100644 --- a/docs/src/main/asciidoc/dev-services.adoc +++ b/docs/src/main/asciidoc/dev-services.adoc @@ -100,6 +100,12 @@ xref:rabbitmq-dev-services.adoc[RabbitMQ Dev Services Guide]. include::{generated-dir}/config/quarkus-smallrye-reactivemessaging-rabbitmq-config-group-rabbit-mq-dev-services-build-time-config.adoc[opts=optional, leveloffset=+1] +== Pulsar + +The Pulsar Dev Service will be enabled when the `quarkus-smallrye-reactive-messaging-pulsar` extension is present in your application, and +the broker address has not been explicitly configured. More information can be found in the +xref:pulsar-dev-services.adoc[Pulsar Dev Services Guide]. + == Redis The Redis Dev Service will be enabled when the `quarkus-redis-client` extension is present in your application, and diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index 9ae069a0b0235..45dbbe6f9ab0e 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -843,7 +843,20 @@ So if you need to call methods such as `verify` you should hang on to the mock i ==== Further simplification with `@InjectMock` Building on the features provided by `QuarkusMock`, Quarkus also allows users to effortlessly take advantage of link:https://site.mockito.org/[Mockito] for mocking the beans supported by `QuarkusMock`. -This functionality is available with the `@io.quarkus.test.InjectMock` annotation if the `quarkus-junit5-mockito` dependency is present. + +[IMPORTANT] +==== +This functionality is available with the `@io.quarkus.test.InjectMock` annotation **only if** the `quarkus-junit5-mockito` dependency is present: +[source,xml] +---- + + io.quarkus + quarkus-junit5-mockito + test + +---- + +==== Using `@InjectMock`, the previous example could be written as follows: diff --git a/docs/src/main/asciidoc/picocli.adoc b/docs/src/main/asciidoc/picocli.adoc index 1d0edcf3ec241..4ec8182a34c41 100644 --- a/docs/src/main/asciidoc/picocli.adoc +++ b/docs/src/main/asciidoc/picocli.adoc @@ -337,10 +337,6 @@ metadata: name: app spec: completionMode: NonIndexed - selector: - matchLabels: - app.kubernetes.io/name: app - app.kubernetes.io/version: 0.1-SNAPSHOT suspend: false template: metadata: diff --git a/docs/src/main/asciidoc/redis-reference.adoc b/docs/src/main/asciidoc/redis-reference.adoc index a87fca831ff98..24709b8fe4c51 100644 --- a/docs/src/main/asciidoc/redis-reference.adoc +++ b/docs/src/main/asciidoc/redis-reference.adoc @@ -225,6 +225,8 @@ To use TLS, you need to: 1. Set the `quarkus.redis.tls.enabled=true` property 2. Make sure that your URL starts with `rediss://` (with two `s`) +IMPORTANT: The default hostname verifier is set to `NONE`, meaning it does not verify the host name. You can change this behavior by setting the `quarkus.redis.tls.hostname-verification-algorithm` property, to `HTTPS` for example. + === Configure the authentication The Redis password can be set in the `redis://` URL or with the `quarkus.redis.password` property. diff --git a/docs/src/main/asciidoc/security-jdbc.adoc b/docs/src/main/asciidoc/security-jdbc.adoc index e9447819c373a..a8718e70ebd10 100644 --- a/docs/src/main/asciidoc/security-jdbc.adoc +++ b/docs/src/main/asciidoc/security-jdbc.adoc @@ -167,6 +167,8 @@ quarkus.datasource.jdbc.url=jdbc:postgresql:elytron-security-jdbc ---- In our context, we are using PostgreSQL as identity store, and we initialize the database with users and roles. +We will use the salted and hashed version of `password` as a password in this example. +We can use the `BcryptUtil` class to generate passwords in the Modular Crypt Format (MCF). [source,sql] ---- @@ -177,16 +179,27 @@ CREATE TABLE test_user ( role VARCHAR(255) ); -INSERT INTO test_user (id, username, password, role) VALUES (1, 'admin', 'admin', 'admin'); -INSERT INTO test_user (id, username, password, role) VALUES (2, 'user','user', 'user'); +INSERT INTO test_user (id, username, password, role) VALUES (1, 'admin', '$2a$10$Uc.SZ0hvGJQlYdsAp7be1.lFjmOnc7aAr4L0YY3/VN3oK.F8zJHRG', 'admin'); +INSERT INTO test_user (id, username, password, role) VALUES (2, 'user','$2a$10$Uc.SZ0hvGJQlYdsAp7be1.lFjmOnc7aAr4L0YY3/VN3oK.F8zJHRG', 'user'); ---- -[NOTE] -==== -It is probably useless, but we kindly remind you that you must not store clear-text passwords in production environment ;-). -The `elytron-security-jdbc` extension offers a built-in bcrypt password mapper. -Please refer to the xref:security-getting-started-tutorial.adoc#define-the-user-entity[Define the user entity] section of the Getting started with Security by using Basic authentication and Jakarta Persistence tutorial for practical example. -==== +When signing up new users, we can encrypt their password as follows: + +[source,java] +---- +package org.acme.security.jdbc; + +import io.quarkus.elytron.security.common.BcryptUtil; + +public class AccountService { + + public void signupUser(String username, String password) { + String encryptedPassword = BcryptUtil.bcryptHash(password); + + // store user with the encrypted password in the database + } +} +---- We can now configure the Elytron JDBC Realm. @@ -194,8 +207,10 @@ We can now configure the Elytron JDBC Realm. ---- quarkus.security.jdbc.enabled=true quarkus.security.jdbc.principal-query.sql=SELECT u.password, u.role FROM test_user u WHERE u.username=? <1> -quarkus.security.jdbc.principal-query.clear-password-mapper.enabled=true <2> -quarkus.security.jdbc.principal-query.clear-password-mapper.password-index=1 +quarkus.security.jdbc.principal-query.bcrypt-password-mapper.enabled=true <2> +quarkus.security.jdbc.principal-query.bcrypt-password-mapper.password-index=1 +quarkus.security.jdbc.principal-query.bcrypt-password-mapper.salt-index=-1 +quarkus.security.jdbc.principal-query.bcrypt-password-mapper.iteration-count-index=-1 quarkus.security.jdbc.principal-query.attribute-mappings.0.index=2 <3> quarkus.security.jdbc.principal-query.attribute-mappings.0.to=groups ---- @@ -203,7 +218,7 @@ quarkus.security.jdbc.principal-query.attribute-mappings.0.to=groups The `elytron-security-jdbc` extension requires at least one principal query to authenticate the user and its identity. <1> We define a parameterized SQL statement (with exactly 1 parameter) which should return the user's password plus any additional information you want to load. -<2> We configure the password mapper with the position of the password field in the `SELECT` fields and other information like salt, hash encoding, etc. +<2> We configure the password mapper with the position of the password field in the `SELECT` fields and other information like salt, hash encoding, etc. Setting the salt and iteration count indexes to `-1` is required for MCF. <3> We use `attribute-mappings` to bind the `SELECT` projection fields (i.e. `u.role` here) to the target Principal representation attributes. [NOTE] @@ -242,21 +257,21 @@ So far so good, now let's try with an allowed user. [source,shell] ---- -$ curl -i -X GET -u admin:admin http://localhost:8080/api/admin +$ curl -i -X GET -u admin:password http://localhost:8080/api/admin HTTP/1.1 200 OK Content-Length: 5 Content-Type: text/plain;charset=UTF-8 admin% ---- -By providing the `admin:admin` credentials, the extension authenticated the user and loaded their roles. +By providing the `admin:password` credentials, the extension authenticated the user and loaded their roles. The `admin` user is authorized to access to the protected resources. The user `admin` should be forbidden to access a resource protected with `@RolesAllowed("user")` because it doesn't have this role. [source,shell] ---- -$ curl -i -X GET -u admin:admin http://localhost:8080/api/users/me +$ curl -i -X GET -u admin:password http://localhost:8080/api/users/me HTTP/1.1 403 Forbidden Content-Length: 34 Content-Type: text/html;charset=UTF-8 @@ -268,7 +283,7 @@ Finally, using the user `user` works and the security context contains the princ [source,shell] ---- -$ curl -i -X GET -u user:user http://localhost:8080/api/users/me +$ curl -i -X GET -u user:password http://localhost:8080/api/users/me HTTP/1.1 200 OK Content-Length: 4 Content-Type: text/plain;charset=UTF-8 @@ -294,8 +309,10 @@ quarkus.datasource.permissions.jdbc.url=jdbc:postgresql:multiple-data-sources-pe quarkus.security.jdbc.enabled=true quarkus.security.jdbc.principal-query.sql=SELECT u.password FROM test_user u WHERE u.username=? -quarkus.security.jdbc.principal-query.clear-password-mapper.enabled=true -quarkus.security.jdbc.principal-query.clear-password-mapper.password-index=1 +quarkus.security.jdbc.principal-query.bcrypt-password-mapper.enabled=true +quarkus.security.jdbc.principal-query.bcrypt-password-mapper.password-index=1 +quarkus.security.jdbc.principal-query.bcrypt-password-mapper.salt-index=-1 +quarkus.security.jdbc.principal-query.bcrypt-password-mapper.iteration-count-index=-1 quarkus.security.jdbc.principal-query.roles.sql=SELECT r.role_name FROM test_role r, test_user_role ur WHERE ur.username=? AND ur.role_id = r.id quarkus.security.jdbc.principal-query.roles.datasource=permissions diff --git a/docs/src/main/asciidoc/vertx-reference.adoc b/docs/src/main/asciidoc/vertx-reference.adoc index b7956de6f443b..0d940abd968b8 100644 --- a/docs/src/main/asciidoc/vertx-reference.adoc +++ b/docs/src/main/asciidoc/vertx-reference.adoc @@ -27,13 +27,14 @@ With this extension, you can retrieve the managed instance of Vert.x using eithe ---- @ApplicationScoped public class MyBean { -// Field injection -@Inject Vertx vertx; -// Constructor injection -MyBean(Vertx vertx) { - // ... -} + // Field injection + @Inject Vertx vertx; + + // Constructor injection + MyBean(Vertx vertx) { + // ... + } } ---- @@ -83,7 +84,7 @@ Check the associated documentation to learn how to use them. |AMQP Client |`io.quarkus:quarkus-smallrye-reactive-messaging-amqp` (extension) -|xref:amqp.adoc +|xref:amqp.adoc[Getting Started to SmallRye Reactive Messaging with AMQP] |Circuit Breaker |`io.smallrye.reactive:smallrye-mutiny-vertx-circuit-breaker` (external dependency) @@ -95,15 +96,15 @@ Check the associated documentation to learn how to use them. |DB2 Client |`io.quarkus:quarkus-reactive-db2-client` (extension) -|xref:reactive-sql-clients.adoc +|xref:reactive-sql-clients.adoc[Reactive SQL Clients] |Kafka Client |`io.quarkus:quarkus-smallrye-reactive-messaging-kafka` (extension) -|xref:kafka.adoc +|xref:kafka.adoc[Apache Kafka Reference Guide] |Mail Client |`io.quarkus:quarkus-mailer` (extension) -|xref:mailer.adoc +|xref:mailer.adoc[Sending emails using SMTP] |MQTT Client |`io.quarkus:quarkus-smallrye-reactive-messaging-mqtt` (extension) @@ -111,19 +112,19 @@ Check the associated documentation to learn how to use them. |MS SQL Client |`io.quarkus:quarkus-reactive-mssql-client` (extension) -|xref:reactive-sql-clients.adoc +|xref:reactive-sql-clients.adoc[Reactive SQL Clients] |MySQL Client |`io.quarkus:quarkus-reactive-mysql-client` (extension) -|xref:reactive-sql-clients.adoc +|xref:reactive-sql-clients.adoc[Reactive SQL Clients] |Oracle Client |`io.quarkus:quarkus-reactive-oracle-client` (extension) -|xref:reactive-sql-clients.adoc +|xref:reactive-sql-clients.adoc[Reactive SQL Clients] |PostgreSQL Client |`io.quarkus:quarkus-reactive-pg-client` (extension) -|xref:reactive-sql-clients.adoc +|xref:reactive-sql-clients.adoc[Reactive SQL Clients] |RabbitMQ Client |`io.smallrye.reactive:smallrye-mutiny-vertx-rabbitmq-client` (external dependency) @@ -131,7 +132,7 @@ Check the associated documentation to learn how to use them. |Redis Client |`io.quarkus:quarkus-redis-client` (extension) -|xref:redis.adoc +|xref:redis.adoc[Using the Redis Client] |Web Client |`io.smallrye.reactive:smallrye-mutiny-vertx-web-client` (external dependency) diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/SyntheticBeanBuildItemProxyTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/SyntheticBeanBuildItemProxyTest.java index 50275150a2162..6ce4b6c9d5477 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/SyntheticBeanBuildItemProxyTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/SyntheticBeanBuildItemProxyTest.java @@ -1,10 +1,11 @@ package io.quarkus.arc.test.synthetic; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Method; +import java.util.List; import java.util.function.Consumer; import jakarta.enterprise.context.ApplicationScoped; @@ -14,6 +15,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator; import io.quarkus.builder.BuildChainBuilder; @@ -46,20 +48,31 @@ public void execute(BuildContext context) { // We need to use reflection due to some class loading problems Object recorderProxy = bytecodeRecorder.getRecordingProxy(TestRecorder.class); try { - Method test = recorderProxy.getClass().getDeclaredMethod("test"); - Object proxy = test.invoke(recorderProxy); - ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem.configure(SynthBean.class) + Method test = recorderProxy.getClass().getDeclaredMethod("test", String.class); + + Object proxy1 = test.invoke(recorderProxy, "ok"); + ExtendedBeanConfigurator configurator1 = SyntheticBeanBuildItem.configure(SynthBean.class) .scope(ApplicationScoped.class) + .identifier("ok") .unremovable(); // No creator assertThrows(IllegalStateException.class, - () -> configurator.done()); + () -> configurator1.done()); // Not a returned proxy assertThrows(IllegalArgumentException.class, - () -> configurator.runtimeProxy(new SynthBean())); - context.produce(configurator - .runtimeProxy(proxy) + () -> configurator1.runtimeProxy(new SynthBean())); + context.produce(configurator1 + .runtimeProxy(proxy1) + .done()); + + // Register a synthetic bean with same types and qualifiers but different identifier + context.produce(SyntheticBeanBuildItem.configure(SynthBean.class) + .scope(ApplicationScoped.class) + .identifier("nok") + .unremovable() + .runtimeProxy(test.invoke(recorderProxy, "nok")) .done()); + } catch (Exception e) { throw new RuntimeException(e); } @@ -73,9 +86,9 @@ public void execute(BuildContext context) { @Recorder public static class TestRecorder { - public SynthBean test() { + public SynthBean test(String val) { SynthBean bean = new SynthBean(); - bean.setValue("ok"); + bean.setValue(val); return bean; } @@ -83,9 +96,12 @@ public SynthBean test() { @Test public void testBeans() { - SynthBean bean = Arc.container().instance(SynthBean.class).get(); - assertNotNull(bean); - assertEquals("ok", bean.getValue()); + List> beans = Arc.container().listAll(SynthBean.class); + assertEquals(2, beans.size()); + for (InstanceHandle handle : beans) { + String val = handle.get().getValue(); + assertTrue("ok".equals(val) || "nok".equals(val)); + } } @Vetoed diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java index 7eaa9bde7be69..e70d0948e7f2a 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java @@ -527,13 +527,14 @@ private JibContainerBuilder createContainerBuilderFromFastJar(String baseJvmImag try { Instant now = Instant.now(); + boolean enforceModificationTime = !jibConfig.useCurrentTimestampFileModification; Instant modificationTime = jibConfig.useCurrentTimestampFileModification ? now : Instant.EPOCH; JibContainerBuilder jibContainerBuilder = toJibContainerBuilder(baseJvmImage, jibConfig); if (fastChangingLibPaths.isEmpty()) { // just create a layer with the entire lib structure intact addLayer(jibContainerBuilder, Collections.singletonList(componentsPath.resolve(JarResultBuildStep.LIB)), - workDirInContainer, "fast-jar-lib", isMutableJar, modificationTime); + workDirInContainer, "fast-jar-lib", isMutableJar, enforceModificationTime, modificationTime); } else { // we need to manually create each layer // the idea here is that the fast changing libraries are created in a later layer, thus when they do change, @@ -547,14 +548,9 @@ private JibContainerBuilder createContainerBuilderFromFastJar(String baseJvmImag AbsoluteUnixPath libPathInContainer = workDirInContainer.resolve(JarResultBuildStep.LIB) .resolve(JarResultBuildStep.BOOT_LIB) .resolve(lib.getFileName()); - if (appCDSResult.isPresent()) { - // the boot lib jars need to preserve the modification time because otherwise AppCDS won't work - bootLibsLayerBuilder.addEntry(lib, libPathInContainer, - Files.getLastModifiedTime(lib).toInstant()); - } else { - bootLibsLayerBuilder.addEntry(lib, libPathInContainer); - } - + // the boot lib jars need to preserve the modification time because otherwise AppCDS won't work + bootLibsLayerBuilder.addEntry(lib, libPathInContainer, + Files.getLastModifiedTime(lib).toInstant()); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -567,15 +563,15 @@ private JibContainerBuilder createContainerBuilderFromFastJar(String baseJvmImag .resolve(JarResultBuildStep.DEPLOYMENT_LIB); addLayer(jibContainerBuilder, Collections.singletonList(deploymentPath), workDirInContainer.resolve(JarResultBuildStep.LIB), - "fast-jar-deployment-libs", true, modificationTime); + "fast-jar-deployment-libs", true, enforceModificationTime, modificationTime); } AbsoluteUnixPath libsMainPath = workDirInContainer.resolve(JarResultBuildStep.LIB) .resolve(JarResultBuildStep.MAIN); addLayer(jibContainerBuilder, nonFastChangingLibPaths, libsMainPath, "fast-jar-normal-libs", - isMutableJar, modificationTime); + isMutableJar, enforceModificationTime, modificationTime); addLayer(jibContainerBuilder, new ArrayList<>(fastChangingLibPaths), libsMainPath, "fast-jar-changing-libs", - isMutableJar, modificationTime); + isMutableJar, enforceModificationTime, modificationTime); } if (appCDSResult.isPresent()) { @@ -599,9 +595,9 @@ private JibContainerBuilder createContainerBuilderFromFastJar(String baseJvmImag } addLayer(jibContainerBuilder, Collections.singletonList(componentsPath.resolve(JarResultBuildStep.APP)), - workDirInContainer, "fast-jar-quarkus-app", isMutableJar, modificationTime); + workDirInContainer, "fast-jar-quarkus-app", isMutableJar, enforceModificationTime, modificationTime); addLayer(jibContainerBuilder, Collections.singletonList(componentsPath.resolve(JarResultBuildStep.QUARKUS)), - workDirInContainer, "fast-jar-quarkus", isMutableJar, modificationTime); + workDirInContainer, "fast-jar-quarkus", isMutableJar, enforceModificationTime, modificationTime); if (ContainerImageJibConfig.DEFAULT_WORKING_DIR.equals(jibConfig.workingDirectory)) { // this layer ensures that the working directory is writeable // see https://github.com/GoogleContainerTools/jib/issues/1270 @@ -665,7 +661,7 @@ private boolean containsRunJava(String baseJvmImage) { public JibContainerBuilder addLayer(JibContainerBuilder jibContainerBuilder, List files, AbsoluteUnixPath pathInContainer, String name, boolean isMutableJar, - Instant now) + boolean enforceModificationTime, Instant forcedModificationTime) throws IOException { FileEntriesLayer.Builder layerConfigurationBuilder = FileEntriesLayer.builder().setName(name); @@ -673,7 +669,17 @@ public JibContainerBuilder addLayer(JibContainerBuilder jibContainerBuilder, Lis layerConfigurationBuilder.addEntryRecursive( file, pathInContainer.resolve(file.getFileName()), isMutableJar ? REMOTE_DEV_FOLDER_PERMISSIONS_PROVIDER : DEFAULT_FILE_PERMISSIONS_PROVIDER, - (sourcePath, destinationPath) -> now, + (sourcePath, destinationPath) -> { + if (enforceModificationTime) { + return forcedModificationTime; + } + + try { + return Files.getLastModifiedTime(sourcePath).toInstant(); + } catch (IOException e) { + throw new RuntimeException("Unable to get last modified time for " + sourcePath, e); + } + }, isMutableJar ? REMOTE_DEV_OWNERSHIP_PROVIDER : DEFAULT_OWNERSHIP_PROVIDER); } diff --git a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/Volumes.java b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/Volumes.java index 39219be41eed6..2488bcf52540e 100644 --- a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/Volumes.java +++ b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/Volumes.java @@ -1,6 +1,5 @@ package io.quarkus.devservices.common; -import java.net.URL; import java.util.Map; import org.testcontainers.containers.BindMode; @@ -18,19 +17,12 @@ private Volumes() { public static void addVolumes(GenericContainer container, Map volumes) { for (Map.Entry volume : volumes.entrySet()) { String hostLocation = volume.getKey(); - BindMode bindMode = BindMode.READ_WRITE; if (volume.getKey().startsWith(CLASSPATH)) { - URL url = Thread.currentThread().getContextClassLoader() - .getResource(hostLocation.replaceFirst(CLASSPATH, EMPTY)); - if (url == null) { - throw new IllegalStateException("Classpath resource at '" + hostLocation + "' not found!"); - } - - hostLocation = url.getPath(); - bindMode = BindMode.READ_ONLY; + container.withClasspathResourceMapping(hostLocation.replaceFirst(CLASSPATH, EMPTY), volume.getValue(), + BindMode.READ_ONLY); + } else { + container.withFileSystemBind(hostLocation, volume.getValue(), BindMode.READ_WRITE); } - - container.withFileSystemBind(hostLocation, volume.getValue(), bindMode); } } } diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsInterceptorTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsInterceptorTest.java index d5f73061f69ad..74b7d037bf1c0 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsInterceptorTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsInterceptorTest.java @@ -60,8 +60,12 @@ public void initData() throws Exception { transaction.begin(); DefaultEntity entity = new DefaultEntity("default"); defaultSession.persist(entity); + transaction.commit(); + transaction.begin(); User user = new User("user"); usersSession.persist(user); + transaction.commit(); + transaction.begin(); Plane plane = new Plane("plane"); inventorySession.persist(plane); transaction.commit(); diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsPackageAnnotationsTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsPackageAnnotationsTest.java index 86265c074ecc7..5ed881244eb25 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsPackageAnnotationsTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsPackageAnnotationsTest.java @@ -6,6 +6,7 @@ import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; +import jakarta.transaction.UserTransaction; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -39,26 +40,34 @@ public class MultiplePersistenceUnitsPackageAnnotationsTest { @PersistenceUnit("inventory") EntityManager inventoryEntityManager; + @Inject + UserTransaction transaction; + @Test - @Transactional - public void testDefault() { + public void testDefault() throws Exception { + transaction.begin(); SharedEntity defaultEntity = new SharedEntity("default"); defaultEntityManager.persist(defaultEntity); SharedEntity savedDefaultEntity = defaultEntityManager.find(SharedEntity.class, defaultEntity.getId()); assertEquals(defaultEntity.getName(), savedDefaultEntity.getName()); + transaction.commit(); + transaction.begin(); SharedEntity defaultEntity2 = new SharedEntity("default2"); usersEntityManager.persist(defaultEntity2); SharedEntity savedDefaultEntity2 = usersEntityManager.find(SharedEntity.class, defaultEntity2.getId()); assertEquals(defaultEntity2.getName(), savedDefaultEntity2.getName()); + transaction.commit(); + transaction.begin(); SharedEntity defaultEntity3 = new SharedEntity("default3"); inventoryEntityManager.persist(defaultEntity3); SharedEntity savedDefaultEntity3 = inventoryEntityManager.find(SharedEntity.class, defaultEntity3.getId()); assertEquals(defaultEntity3.getName(), savedDefaultEntity3.getName()); + transaction.commit(); } @Test diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsResourceInjectionEntityManagerTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsResourceInjectionEntityManagerTest.java index d115d67dfb842..52a5fafc14ea1 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsResourceInjectionEntityManagerTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsResourceInjectionEntityManagerTest.java @@ -58,7 +58,6 @@ public void testUserInInventoryEntityManager() { } @Test - @Transactional public void testAccessBothPersistenceUnits() { testUser(); testPlane(); diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsResourceInjectionSessionTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsResourceInjectionSessionTest.java index b13741e7a9490..1d5e0ab49a7dc 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsResourceInjectionSessionTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/multiplepersistenceunits/MultiplePersistenceUnitsResourceInjectionSessionTest.java @@ -58,7 +58,6 @@ public void testUserInInventorySession() { } @Test - @Transactional public void testAccessBothPersistenceUnits() { testUser(); testPlane(); diff --git a/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/KafkaStreamsProducer.java b/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/KafkaStreamsProducer.java index f03ddabeb9bda..604017f831591 100644 --- a/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/KafkaStreamsProducer.java +++ b/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/KafkaStreamsProducer.java @@ -109,18 +109,22 @@ public KafkaStreamsProducer(KafkaStreamsSupport kafkaStreamsSupport, KafkaStream this.executorService = executorService; this.topicsTimeout = runtimeConfig.topicsTimeout; - this.trimmedTopics = runtimeConfig.getTrimmedTopics(); + this.trimmedTopics = isTopicsCheckEnabled() ? runtimeConfig.getTrimmedTopics() : Collections.emptyList(); this.streamsConfig = new StreamsConfig(kafkaStreamsProperties); this.kafkaStreams = initializeKafkaStreams(streamsConfig, topology.get(), kafkaClientSupplier, stateListener, globalStateRestoreListener, uncaughtExceptionHandlerListener); this.kafkaStreamsTopologyManager = new KafkaStreamsTopologyManager(kafkaAdminClient); } + private boolean isTopicsCheckEnabled() { + return topicsTimeout.compareTo(Duration.ZERO) > 0; + } + public void onStartup(@Observes StartupEvent event, Event kafkaStreamsEvent) { if (kafkaStreams != null) { kafkaStreamsEvent.fire(kafkaStreams); executorService.execute(() -> { - if (topicsTimeout.compareTo(Duration.ZERO) > 0) { + if (isTopicsCheckEnabled()) { try { waitForTopicsToBeCreated(kafkaAdminClient, trimmedTopics, topicsTimeout); } catch (InterruptedException e) { diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java index f2d1a07153ba7..101e88aab1ae8 100644 --- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java +++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java @@ -122,7 +122,8 @@ protected static Uni createOidcClientUni(OidcClientConfig oidcConfig OidcCommonUtils.setHttpClientOptions(oidcConfig, tlsConfig, options); - WebClient client = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx.get()), options); + var mutinyVertx = new io.vertx.mutiny.core.Vertx(vertx.get()); + WebClient client = WebClient.create(mutinyVertx, options); Map> oidcRequestFilters = OidcCommonUtils.getOidcRequestFilters(); @@ -139,7 +140,8 @@ protected static Uni createOidcClientUni(OidcClientConfig oidcConfig OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.tokenPath), OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.revokePath))); } else { - tokenUrisUni = discoverTokenUris(client, oidcRequestFilters, authServerUriString.toString(), oidcConfig); + tokenUrisUni = discoverTokenUris(client, oidcRequestFilters, authServerUriString.toString(), oidcConfig, + mutinyVertx); } } return tokenUrisUni.onItemOrFailure() @@ -220,9 +222,11 @@ private static void setGrantClientParams(OidcClientConfig oidcConfig, MultiMap g private static Uni discoverTokenUris(WebClient client, Map> oidcRequestFilters, - String authServerUrl, OidcClientConfig oidcConfig) { + String authServerUrl, OidcClientConfig oidcConfig, io.vertx.mutiny.core.Vertx vertx) { final long connectionDelayInMillisecs = OidcCommonUtils.getConnectionDelayInMillis(oidcConfig); - return OidcCommonUtils.discoverMetadata(client, oidcRequestFilters, authServerUrl, connectionDelayInMillisecs) + return OidcCommonUtils + .discoverMetadata(client, oidcRequestFilters, authServerUrl, connectionDelayInMillisecs, vertx, + oidcConfig.useBlockingDnsLookup) .onItem().transform(json -> new OidcConfigurationMetadata(json.getString("token_endpoint"), json.getString("revocation_endpoint"))); } diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java index b3c9f05ff21d4..678d1d9241ff3 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java @@ -74,6 +74,13 @@ public class OidcCommonConfig { @ConfigItem(defaultValue = "10s") public Duration connectionTimeout = Duration.ofSeconds(10); + /** + * Whether DNS lookup should be performed on the worker thread. + * Use this option when you can see logged warnings about blocked Vert.x event loop by HTTP requests to OIDC server. + */ + @ConfigItem(defaultValue = "false") + public boolean useBlockingDnsLookup; + /** * The maximum size of the connection pool used by the WebClient. */ diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index 9e43d63452b73..b73f623dc8c8f 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -4,8 +4,10 @@ import java.io.IOException; import java.io.InputStream; import java.net.ConnectException; +import java.net.InetAddress; import java.net.URI; import java.net.URLEncoder; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -20,6 +22,8 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; +import java.util.concurrent.Callable; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -54,8 +58,10 @@ import io.vertx.core.net.KeyStoreOptions; import io.vertx.core.net.ProxyOptions; import io.vertx.mutiny.core.MultiMap; +import io.vertx.mutiny.core.Vertx; import io.vertx.mutiny.core.buffer.Buffer; import io.vertx.mutiny.ext.web.client.HttpRequest; +import io.vertx.mutiny.ext.web.client.HttpResponse; import io.vertx.mutiny.ext.web.client.WebClient; public class OidcCommonUtils { @@ -433,7 +439,7 @@ public static Predicate oidcEndpointNotAvailable() { } public static Uni discoverMetadata(WebClient client, Map> filters, - String authServerUrl, long connectionDelayInMillisecs) { + String authServerUrl, long connectionDelayInMillisecs, Vertx vertx, boolean blockingDnsLookup) { final String discoveryUrl = getDiscoveryUri(authServerUrl); HttpRequest request = client.getAbs(discoveryUrl); if (!filters.isEmpty()) { @@ -443,7 +449,7 @@ public static Uni discoverMetadata(WebClient client, Map { + return sendRequest(vertx, request, blockingDnsLookup).onItem().transform(resp -> { if (resp.statusCode() == 200) { return resp.bodyAsJsonObject(); } else { @@ -531,4 +537,37 @@ public static List getMatchingOidcRequestFilters(Map> sendRequest(io.vertx.core.Vertx vertx, HttpRequest request, + boolean blockingDnsLookup) { + if (blockingDnsLookup) { + return sendRequest(new Vertx(vertx), request, true); + } else { + return request.send(); + } + } + + public static Uni> sendRequest(Vertx vertx, HttpRequest request, boolean blockingDnsLookup) { + if (blockingDnsLookup) { + return vertx.executeBlocking(new Callable() { + @Override + public Void call() { + try { + // cache DNS lookup + InetAddress.getByName(request.host()); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + return null; + } + }).flatMap(new Function>>() { + @Override + public Uni> apply(Void unused) { + return request.send(); + } + }); + } else { + return request.send(); + } + } } diff --git a/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties b/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties index 26c7ff4e26bd1..4aaaec7a7eb44 100644 --- a/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties +++ b/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties @@ -11,3 +11,5 @@ quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyle quarkus.log.category."io.quarkus.oidc.runtime.TenantConfigContext".level=DEBUG quarkus.log.file.enable=true +# use blocking DNS lookup so that we have it tested somewhere +quarkus.oidc.use-blocking-dns-lookup=true \ No newline at end of file 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 4c8cdfc3beb36..a952de400ca48 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 @@ -176,14 +176,32 @@ public void setIncludeClientId(boolean includeClientId) { /** * Configuration of the certificate chain which can be used to verify tokens. - * If the certificate chain trusstore is configured, the tokens can be verified using the certificate + * If the certificate chain truststore is configured, the tokens can be verified using the certificate * chain inlined in the Base64-encoded format as an `x5c` header in the token itself. + *

+ * The certificate chain inlined in the token is verified. + * Signature of every certificate in the chain but the root certificate is verified by the next certificate in the chain. + * Thumbprint of the root certificate in the chain must match a thumbprint of one of the certificates in the truststore. + *

+ * Additionally, a direct trust in the leaf chain certificate which will be used to verify the token signature must + * be established. + * By default, the leaf certificate's thumbprint must match a thumbprint of one of the certificates in the truststore. + * If the truststore does not have the leaf certificate imported, then the leaf certificate must be identified by its Common + * Name. */ @ConfigItem public CertificateChain certificateChain = new CertificateChain(); @ConfigGroup public static class CertificateChain { + /** + * Common name of the leaf certificate. It must be set if the {@link #trustStoreFile} does not have + * this certificate imported. + * + */ + @ConfigItem + public Optional leafCertificateName = Optional.empty(); + /** * Truststore file which keeps thumbprints of the trusted certificates. */ @@ -233,6 +251,14 @@ public Optional getTrustStoreFileType() { public void setTrustStoreFileType(Optional trustStoreFileType) { this.trustStoreFileType = trustStoreFileType; } + + public Optional getLeafCertificateName() { + return leafCertificateName; + } + + public void setLeafCertificateName(String leafCertificateName) { + this.leafCertificateName = Optional.of(leafCertificateName); + } } /** diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java index ae0105fce3bcf..069ad2efb7704 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java @@ -3,6 +3,7 @@ import java.security.Key; import java.security.cert.X509Certificate; import java.util.List; +import java.util.Optional; import java.util.Set; import org.jboss.logging.Logger; @@ -12,11 +13,13 @@ import io.quarkus.oidc.OidcTenantConfig.CertificateChain; import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.security.runtime.X509IdentityProvider; import io.vertx.ext.auth.impl.CertificateHelper; public class CertChainPublicKeyResolver implements RefreshableVerificationKeyResolver { private static final Logger LOG = Logger.getLogger(OidcProvider.class); final Set thumbprints; + final Optional expectedLeafCertificateName; public CertChainPublicKeyResolver(CertificateChain chain) { if (chain.trustStorePassword.isEmpty()) { @@ -25,6 +28,7 @@ public CertChainPublicKeyResolver(CertificateChain chain) { } this.thumbprints = TrustStoreUtils.getTrustedCertificateThumbprints(chain.trustStoreFile.get(), chain.trustStorePassword.get(), chain.trustStoreCertAlias, chain.getTrustStoreFileType()); + this.expectedLeafCertificateName = chain.leafCertificateName; } @Override @@ -37,9 +41,29 @@ public Key resolveKey(JsonWebSignature jws, List nestingContex LOG.debug("Token does not have an 'x5c' certificate chain header"); return null; } - String thumbprint = TrustStoreUtils.calculateThumprint(chain.get(0)); - if (!thumbprints.contains(thumbprint)) { - throw new UnresolvableKeyException("Certificate chain thumprint is invalid"); + if (chain.size() == 0) { + LOG.debug("Token 'x5c' certificate chain is empty"); + return null; + } + LOG.debug("Checking a thumbprint of the root chain certificate"); + String rootThumbprint = TrustStoreUtils.calculateThumprint(chain.get(chain.size() - 1)); + if (!thumbprints.contains(rootThumbprint)) { + LOG.error("Thumprint of the root chain certificate is invalid"); + throw new UnresolvableKeyException("Thumprint of the root chain certificate is invalid"); + } + if (expectedLeafCertificateName.isEmpty()) { + LOG.debug("Checking a thumbprint of the leaf chain certificate"); + String thumbprint = TrustStoreUtils.calculateThumprint(chain.get(0)); + if (!thumbprints.contains(thumbprint)) { + LOG.error("Thumprint of the leaf chain certificate is invalid"); + throw new UnresolvableKeyException("Thumprint of the leaf chain certificate is invalid"); + } + } else { + String leafCertificateName = X509IdentityProvider.getCommonName(chain.get(0).getSubjectX500Principal()); + if (!expectedLeafCertificateName.get().equals(leafCertificateName)) { + LOG.errorf("Wrong leaf certificate common name: %s", leafCertificateName); + throw new UnresolvableKeyException("Wrong leaf certificate common name"); + } } //TODO: support revocation lists CertificateHelper.checkValidity(chain, null); @@ -50,6 +74,8 @@ public Key resolveKey(JsonWebSignature jws, List nestingContex root.verify(root.getPublicKey()); } return chain.get(0).getPublicKey(); + } catch (UnresolvableKeyException ex) { + throw ex; } catch (Exception ex) { throw new UnresolvableKeyException("Invalid certificate chain", ex); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java index 2f19f90c46ac7..40bc06da33605 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java @@ -84,16 +84,22 @@ public OidcConfigurationMetadata getMetadata() { } public Uni getJsonWebKeySet(OidcRequestContextProperties contextProperties) { - return filter(OidcEndpoint.Type.JWKS, client.getAbs(metadata.getJsonWebKeySetUri()), null, contextProperties).send() + return OidcCommonUtils + .sendRequest(vertx, + filter(OidcEndpoint.Type.JWKS, client.getAbs(metadata.getJsonWebKeySetUri()), null, contextProperties), + oidcConfig.useBlockingDnsLookup) .onItem() .transform(resp -> getJsonWebKeySet(resp)); } public Uni getUserInfo(String token) { LOG.debugf("Get UserInfo on: %s auth: %s", metadata.getUserInfoUri(), OidcConstants.BEARER_SCHEME + " " + token); - return filter(OidcEndpoint.Type.USERINFO, client.getAbs(metadata.getUserInfoUri()), null, null) - .putHeader(AUTHORIZATION_HEADER, OidcConstants.BEARER_SCHEME + " " + token) - .send().onItem().transform(resp -> getUserInfo(resp)); + return OidcCommonUtils + .sendRequest(vertx, + filter(OidcEndpoint.Type.USERINFO, client.getAbs(metadata.getUserInfoUri()), null, null) + .putHeader(AUTHORIZATION_HEADER, OidcConstants.BEARER_SCHEME + " " + token), + oidcConfig.useBlockingDnsLookup) + .onItem().transform(resp -> getUserInfo(resp)); } public Uni introspectToken(String token) { 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 376419f0758bd..71fc623600191 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 @@ -470,8 +470,8 @@ protected static Uni createOidcClientUni(OidcTenantConfig oi WebClientOptions options = new WebClientOptions(); OidcCommonUtils.setHttpClientOptions(oidcConfig, tlsConfig, options); - - WebClient client = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); + var mutinyVertx = new io.vertx.mutiny.core.Vertx(vertx); + WebClient client = WebClient.create(mutinyVertx, options); Map> oidcRequestFilters = OidcCommonUtils.getOidcRequestFilters(); @@ -481,7 +481,8 @@ protected static Uni createOidcClientUni(OidcTenantConfig oi } else { final long connectionDelayInMillisecs = OidcCommonUtils.getConnectionDelayInMillis(oidcConfig); metadataUni = OidcCommonUtils - .discoverMetadata(client, oidcRequestFilters, authServerUriString, connectionDelayInMillisecs) + .discoverMetadata(client, oidcRequestFilters, authServerUriString, connectionDelayInMillisecs, mutinyVertx, + oidcConfig.useBlockingDnsLookup) .onItem() .transform(new Function() { @Override diff --git a/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/DataSourceReactiveRuntimeConfig.java b/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/DataSourceReactiveRuntimeConfig.java index 6d336c2e0875e..555ecb3b82b77 100644 --- a/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/DataSourceReactiveRuntimeConfig.java +++ b/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/DataSourceReactiveRuntimeConfig.java @@ -111,9 +111,11 @@ public interface DataSourceReactiveRuntimeConfig { /** * The hostname verification algorithm to use in case the server's identity should be checked. - * Should be HTTPS, LDAPS or an empty string. + * Should be {@code HTTPS}, {@code LDAPS} or {@code NONE}. + * {@code NONE} is the default value and disables the verification. */ - Optional hostnameVerificationAlgorithm(); + @WithDefault("NONE") + String hostnameVerificationAlgorithm(); /** * The maximum time a connection remains unused in the pool before it is closed. diff --git a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java index 85e16402885f6..134ccf43fa381 100644 --- a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java +++ b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java @@ -217,9 +217,11 @@ private DB2ConnectOptions toConnectOptions(String dataSourceName, DataSourceRunt connectOptions.setReconnectInterval(dataSourceReactiveRuntimeConfig.reconnectInterval().toMillis()); - if (dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().isPresent()) { - connectOptions.setHostnameVerificationAlgorithm( - dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().get()); + var algo = dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm(); + if ("NONE".equalsIgnoreCase(algo)) { + connectOptions.setHostnameVerificationAlgorithm(""); + } else { + connectOptions.setHostnameVerificationAlgorithm(algo); } dataSourceReactiveRuntimeConfig.additionalProperties().forEach(connectOptions::addProperty); diff --git a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java index b3c2c8cf1da72..bce9406f67eaf 100644 --- a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java +++ b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java @@ -220,9 +220,11 @@ private MSSQLConnectOptions toMSSQLConnectOptions(String dataSourceName, DataSou configureJksKeyCertOptions(mssqlConnectOptions, dataSourceReactiveRuntimeConfig.keyCertificateJks()); configurePfxKeyCertOptions(mssqlConnectOptions, dataSourceReactiveRuntimeConfig.keyCertificatePfx()); - if (dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().isPresent()) { - mssqlConnectOptions.setHostnameVerificationAlgorithm( - dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().get()); + var algo = dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm(); + if ("NONE".equalsIgnoreCase(algo)) { + mssqlConnectOptions.setHostnameVerificationAlgorithm(""); + } else { + mssqlConnectOptions.setHostnameVerificationAlgorithm(algo); } dataSourceReactiveRuntimeConfig.additionalProperties().forEach(mssqlConnectOptions::addProperty); diff --git a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java index fda8b5372f2a1..51c911af4836b 100644 --- a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java +++ b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java @@ -215,8 +215,8 @@ private List toMySQLConnectOptions(String dataSourceName, mysqlConnectOptions.setSslMode(sslMode); // If sslMode is verify-identity, we also need a hostname verification algorithm - if (sslMode == SslMode.VERIFY_IDENTITY && (!dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm() - .isPresent() || "".equals(dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().get()))) { + var algo = dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm(); + if ("NONE".equalsIgnoreCase(algo) && sslMode == SslMode.VERIFY_IDENTITY) { throw new IllegalArgumentException( "quarkus.datasource.reactive.hostname-verification-algorithm must be specified under verify-identity sslmode"); } @@ -236,8 +236,12 @@ private List toMySQLConnectOptions(String dataSourceName, mysqlConnectOptions.setReconnectInterval(dataSourceReactiveRuntimeConfig.reconnectInterval().toMillis()); - dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().ifPresent( - mysqlConnectOptions::setHostnameVerificationAlgorithm); + var algo = dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm(); + if ("NONE".equalsIgnoreCase(algo)) { + mysqlConnectOptions.setHostnameVerificationAlgorithm(""); + } else { + mysqlConnectOptions.setHostnameVerificationAlgorithm(algo); + } dataSourceReactiveMySQLConfig.authenticationPlugin().ifPresent(mysqlConnectOptions::setAuthenticationPlugin); diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java index 4afab742f0163..554614a412d1f 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java @@ -202,10 +202,9 @@ private List toPgConnectOptions(String dataSourceName, DataSou final SslMode sslMode = dataSourceReactivePostgreSQLConfig.sslMode().get(); pgConnectOptions.setSslMode(sslMode); + var algo = dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm(); // If sslMode is verify-full, we also need a hostname verification algorithm - if (sslMode == SslMode.VERIFY_FULL - && (!dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().isPresent() - || "".equals(dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().get()))) { + if ("NONE".equalsIgnoreCase(algo) && sslMode == SslMode.VERIFY_FULL) { throw new IllegalArgumentException( "quarkus.datasource.reactive.hostname-verification-algorithm must be specified under verify-full sslmode"); } @@ -227,8 +226,12 @@ private List toPgConnectOptions(String dataSourceName, DataSou pgConnectOptions.setReconnectInterval(dataSourceReactiveRuntimeConfig.reconnectInterval().toMillis()); - dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm().ifPresent( - pgConnectOptions::setHostnameVerificationAlgorithm); + var algo = dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm(); + if ("NONE".equalsIgnoreCase(algo)) { + pgConnectOptions.setHostnameVerificationAlgorithm(""); + } else { + pgConnectOptions.setHostnameVerificationAlgorithm(algo); + } dataSourceReactiveRuntimeConfig.additionalProperties().forEach(pgConnectOptions::addProperty); diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java index 7c89ffd7e8319..e21e438e23998 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java @@ -116,7 +116,14 @@ private static NetClientOptions toNetClientOptions(RedisClientConfig config) { tcp.alpn().ifPresent(net::setUseAlpn); tcp.applicationLayerProtocols().ifPresent(net::setApplicationLayerProtocols); tcp.connectionTimeout().ifPresent(d -> net.setConnectTimeout((int) d.toMillis())); - tls.hostnameVerificationAlgorithm().ifPresent(net::setHostnameVerificationAlgorithm); + + String verificationAlgorithm = tls.hostnameVerificationAlgorithm(); + if ("NONE".equalsIgnoreCase(verificationAlgorithm)) { + net.setHostnameVerificationAlgorithm(""); + } else { + net.setHostnameVerificationAlgorithm(verificationAlgorithm); + } + tcp.idleTimeout().ifPresent(d -> net.setIdleTimeout((int) d.toSeconds())); tcp.keepAlive().ifPresent(b -> net.setTcpKeepAlive(true)); @@ -163,8 +170,6 @@ private static NetClientOptions toNetClientOptions(RedisClientConfig config) { tcp.quickAck().ifPresent(net::setTcpQuickAck); tcp.writeIdleTimeout().ifPresent(d -> net.setWriteIdleTimeout((int) d.toSeconds())); - tls.hostnameVerificationAlgorithm().ifPresent(net::setHostnameVerificationAlgorithm); - return net; } diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/TlsConfig.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/TlsConfig.java index 44a4126b52752..e8a6c46a75e95 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/TlsConfig.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/TlsConfig.java @@ -1,7 +1,5 @@ package io.quarkus.redis.runtime.client.config; -import java.util.Optional; - import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.vertx.core.runtime.config.JksConfiguration; import io.quarkus.vertx.core.runtime.config.PemKeyCertConfiguration; @@ -68,8 +66,12 @@ public interface TlsConfig { /** * The hostname verification algorithm to use in case the server's identity should be checked. - * Should be HTTPS, LDAPS or an empty string. + * Should be {@code HTTPS}, {@code LDAPS} or an {@code NONE} (default). + *

+ * If set to {@code NONE}, it does not verify the hostname. + *

*/ - Optional hostnameVerificationAlgorithm(); + @WithDefault("NONE") + String hostnameVerificationAlgorithm(); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomMessageBodyReaderUsesAnnotationsTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomMessageBodyReaderUsesAnnotationsTest.java new file mode 100644 index 0000000000000..eab0b5aced44b --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomMessageBodyReaderUsesAnnotationsTest.java @@ -0,0 +1,114 @@ +package io.quarkus.rest.client.reactive; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Type; +import java.net.URI; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.MessageBodyReader; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; + +public class CustomMessageBodyReaderUsesAnnotationsTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest(); + + @TestHTTPResource + URI baseUri; + + private Client client; + + @BeforeEach + public void before() { + client = QuarkusRestClientBuilder.newBuilder() + .baseUri(baseUri) + .register(PersonMessageBodyReader.class) + .build(Client.class); + } + + @Test + public void fromAnnotation() { + Person person = client.fromAnnotation(); + assertEquals("from-annotation", person.name()); + } + + @Test + public void unset() { + Person person = client.unset(); + assertEquals("unset", person.name()); + } + + @Path("test") + public interface Client { + + @GET + @PersonName("from-annotation") + Person fromAnnotation(); + + @GET + Person unset(); + } + + @Path("test") + public static class Endpoint { + + @GET + public Person get() { + return new Person("dummy"); + } + } + + public record Person(String name) { + + } + + @Documented + @Target({ ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface PersonName { + String value(); + } + + public static class PersonMessageBodyReader implements MessageBodyReader { + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type.equals(Person.class); + } + + @Override + public Person readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) { + PersonName personName = null; + if (annotations != null) { + for (Annotation annotation : annotations) { + if (annotation instanceof PersonName pn) { + personName = pn; + break; + } + } + } + if (personName == null) { + return new Person("unset"); + } + return new Person(personName.value()); + } + } +} diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java index d7bcff7deb67c..63d79961e261b 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java @@ -60,7 +60,7 @@ private Set extractRoles(X509Certificate certificate, Map failureUniNonBlocking() { + return Uni.createFrom().failure(new BusinessException("boom")); + } + + @Query + @Blocking + public Uni failureUniBlocking() { + return Uni.createFrom().failure(new BusinessException("boom")); + } + + @Query + @NonBlocking + public String failureSyncNonBlocking() throws BusinessException { + throw new BusinessException("boom"); + } + + @Query + @Blocking + public String failureSyncBlocking() throws BusinessException { + throw new BusinessException("boom"); + } + @Query public TestPojo systemserror() { throw new RuntimeException("Some system problem"); diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java index e443449ff4580..f5df3abed7ba7 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; import jakarta.validation.ConstraintViolationException; @@ -49,6 +50,9 @@ protected O invokeAndTransform( deactivate(requestContext); }); if (throwable != null) { + if (throwable instanceof ExecutionException && throwable.getCause() != null) { + throwable = throwable.getCause(); + } eventEmitter.fireOnDataFetchError(c, throwable); if (throwable instanceof GraphQLException) { GraphQLException graphQLException = (GraphQLException) throwable; diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java index eada958ba8648..b5134c2c1ca48 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java @@ -81,9 +81,9 @@ import io.smallrye.reactive.messaging.EmitterConfiguration; import io.smallrye.reactive.messaging.Invoker; import io.smallrye.reactive.messaging.annotations.Blocking; -import io.smallrye.reactive.messaging.health.SmallRyeReactiveMessagingLivenessCheck; -import io.smallrye.reactive.messaging.health.SmallRyeReactiveMessagingReadinessCheck; -import io.smallrye.reactive.messaging.health.SmallRyeReactiveMessagingStartupCheck; +import io.smallrye.reactive.messaging.extension.health.SmallRyeReactiveMessagingLivenessCheck; +import io.smallrye.reactive.messaging.extension.health.SmallRyeReactiveMessagingReadinessCheck; +import io.smallrye.reactive.messaging.extension.health.SmallRyeReactiveMessagingStartupCheck; import io.smallrye.reactive.messaging.providers.extension.ChannelConfiguration; public class SmallRyeReactiveMessagingProcessor { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java index 7f56930b8be3a..87cfaaca00bfd 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java @@ -72,6 +72,7 @@ protected BeanConfiguratorBase(DotName implClazz) { */ public THIS read(BeanConfiguratorBase base) { super.read(base); + identifier = base.identifier; types.clear(); types.addAll(base.types); qualifiers.clear(); diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java index 5150d282ca810..4f9af82270835 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java @@ -157,6 +157,7 @@ public BootstrapMavenContext(BootstrapMavenContextConfig config) this.remoteRepos = config.remoteRepos; this.remotePluginRepos = config.remotePluginRepos; this.remoteRepoManager = config.remoteRepoManager; + this.settingsDecrypter = config.settingsDecrypter; this.cliOptions = config.cliOptions; this.excludeSisuBeanPackages = config.getExcludeSisuBeanPackages(); this.includeSisuBeanPackages = config.getIncludeSisuBeanPackages(); @@ -299,7 +300,7 @@ public List getRemotePluginRepositories() throws BootstrapMave return remotePluginRepos == null ? remotePluginRepos = resolveRemotePluginRepos() : remotePluginRepos; } - private SettingsDecrypter getSettingsDecrypter() { + public SettingsDecrypter getSettingsDecrypter() { if (settingsDecrypter == null) { initRepoSystemAndManager(); } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContextConfig.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContextConfig.java index 0f44dcc37a8ec..9cef569d8c6f4 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContextConfig.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContextConfig.java @@ -9,6 +9,7 @@ import java.util.function.Function; import org.apache.maven.model.Model; +import org.apache.maven.settings.crypto.SettingsDecrypter; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.impl.RemoteRepositoryManager; @@ -28,6 +29,7 @@ public class BootstrapMavenContextConfig remoteRepos; protected List remotePluginRepos; protected RemoteRepositoryManager remoteRepoManager; + protected SettingsDecrypter settingsDecrypter; protected String alternatePomName; protected File userSettings; protected boolean artifactTransferLogging = true; @@ -190,6 +192,18 @@ public T setRemoteRepositoryManager(RemoteRepositoryManager remoteRepoManager) { return (T) this; } + /** + * Settings decryptor + * + * @param settingsDecrypter settings decrypter + * @return + */ + @SuppressWarnings("unchecked") + public T setSettingsDecrypter(SettingsDecrypter settingsDecrypter) { + this.settingsDecrypter = settingsDecrypter; + return (T) this; + } + /** * The meaning of this option is equivalent to alternative POM in Maven, * which can be specified with command line argument '-f'. diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientResponseCompleteRestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientResponseCompleteRestHandler.java index 873bb8fd62d19..36d165192f98b 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientResponseCompleteRestHandler.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientResponseCompleteRestHandler.java @@ -76,6 +76,7 @@ public static ResponseImpl mapToResponse(RestClientRequestContext context, Object fieldValue = context.readEntity(in, fieldFiller.getFieldType(), MediaType.valueOf(fieldFiller.getMediaType()), + context.getMethodDeclaredAnnotationsSafe(), // FIXME: we have strings, it wants objects, perhaps there's // an Object->String conversion too many (MultivaluedMap) responseContext.getHeaders()); @@ -104,6 +105,7 @@ public static ResponseImpl mapToResponse(RestClientRequestContext context, Object entity = context.readEntity(entityStream, context.getResponseType(), responseContext.getMediaType(), + context.getMethodDeclaredAnnotationsSafe(), // FIXME: we have strings, it wants objects, perhaps there's // an Object->String conversion too many (MultivaluedMap) responseContext.getHeaders()); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java index 4459e66000227..235a3937d0605 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java @@ -318,7 +318,11 @@ public void handle(Buffer buffer) { if (start < end) { ByteArrayInputStream in = new ByteArrayInputStream(bytes, start, end); - R item = restClientRequestContext.readEntity(in, responseType, mediaType, + R item = restClientRequestContext.readEntity( + in, + responseType, + mediaType, + restClientRequestContext.getMethodDeclaredAnnotationsSafe(), response.getMetadata()); multiRequest.emitter.emit(item); } @@ -326,7 +330,11 @@ public void handle(Buffer buffer) { } } else { ByteArrayInputStream in = new ByteArrayInputStream(bytes); - R item = restClientRequestContext.readEntity(in, responseType, mediaType, + R item = restClientRequestContext.readEntity( + in, + responseType, + mediaType, + restClientRequestContext.getMethodDeclaredAnnotationsSafe(), response.getMetadata()); multiRequest.emitter.emit(item); } @@ -360,7 +368,10 @@ public void handle(Buffer chunk) { ByteArrayInputStream in = new ByteArrayInputStream(chunk.getBytes()); try { - R item = restClientRequestContext.readEntity(in, responseType, response.getMediaType(), + R item = restClientRequestContext.readEntity(in, + responseType, + response.getMediaType(), + restClientRequestContext.getMethodDeclaredAnnotationsSafe(), response.getMetadata()); multiRequest.emit(item); } catch (IOException e) { diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java index a2f7e9fba9102..dca9e4fe33bf7 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java @@ -167,6 +167,14 @@ public Method getInvokedMethod() { return null; } + public Annotation[] getMethodDeclaredAnnotationsSafe() { + Method invokedMethod = getInvokedMethod(); + if (invokedMethod != null) { + return invokedMethod.getDeclaredAnnotations(); + } + return null; + } + @Override protected Throwable unwrapException(Throwable t) { var res = super.unwrapException(t); @@ -190,12 +198,14 @@ protected Throwable unwrapException(Throwable t) { @SuppressWarnings("unchecked") public T readEntity(InputStream in, - GenericType responseType, MediaType mediaType, + GenericType responseType, + MediaType mediaType, + Annotation[] annotations, MultivaluedMap metadata) throws IOException { if (in == null) return null; - return (T) ClientSerialisers.invokeClientReader(null, responseType.getRawType(), responseType.getType(), + return (T) ClientSerialisers.invokeClientReader(annotations, responseType.getRawType(), responseType.getType(), mediaType, properties, this, metadata, restClient.getClientContext().getSerialisers(), in, getReaderInterceptors(), configuration); } diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 585fea1730e62..f2797fbbf7b36 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -61,7 +61,7 @@ 3.2.5 2.5.7 2.1.2 - 4.5.3 + 4.5.4 5.4.0 1.0.0.Final 2.16.1 diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ResponseBuilderImpl.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ResponseBuilderImpl.java index f1a546ff5c846..7b04ab947a610 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ResponseBuilderImpl.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ResponseBuilderImpl.java @@ -28,7 +28,7 @@ public Response.ResponseBuilder location(URI location) { try { String host = req.getRequestHost(); int port = -1; - int index = host.indexOf(":"); + int index = host.lastIndexOf(":"); if (index > -1) { port = Integer.parseInt(host.substring(index + 1)); host = host.substring(0, index); @@ -69,7 +69,7 @@ public Response.ResponseBuilder contentLocation(URI location) { try { String host = req.getRequestHost(); int port = -1; - int index = host.indexOf(":"); + int index = host.lastIndexOf(":"); if (index > -1) { port = Integer.parseInt(host.substring(index + 1)); host = host.substring(0, index); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/RestResponseBuilderImpl.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/RestResponseBuilderImpl.java index a62003cbef6a5..43c98757afe21 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/RestResponseBuilderImpl.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/RestResponseBuilderImpl.java @@ -28,7 +28,7 @@ public RestResponse.ResponseBuilder location(URI location) { try { String host = req.getRequestHost(); int port = -1; - int index = host.indexOf(":"); + int index = host.lastIndexOf(":"); if (index > -1) { port = Integer.parseInt(host.substring(index + 1)); host = host.substring(0, index); @@ -69,7 +69,7 @@ public RestResponse.ResponseBuilder contentLocation(URI location) { try { String host = req.getRequestHost(); int port = -1; - int index = host.indexOf(":"); + int index = host.lastIndexOf(":"); if (index > -1) { port = Integer.parseInt(host.substring(index + 1)); host = host.substring(0, index); diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/AdminResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/AdminResource.java index 3a6e8c4eb2825..59664d4e21510 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/AdminResource.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/AdminResource.java @@ -61,6 +61,14 @@ public String bearerCertificateFullChain() { return "granted:" + identity.getRoles(); } + @Path("bearer-certificate-full-chain-root-only") + @GET + @RolesAllowed("admin") + @Produces(MediaType.APPLICATION_JSON) + public String bearerCertificateFullChainRootOnly() { + return "granted:" + identity.getRoles(); + } + @Path("bearer-kid-or-chain") @GET @RolesAllowed("admin") diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index f78e4dc9dd018..f775048d5ac31 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -104,7 +104,6 @@ quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.credentials.client-sec quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.credentials.client-secret.method=query quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.certificate-chain.trust-store-file=truststore.p12 quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.certificate-chain.trust-store-password=storepassword -quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.certificate-chain.trust-store-cert-alias=fullchain quarkus.oidc.code-flow-token-introspection.provider=github @@ -134,7 +133,6 @@ quarkus.oidc.bearer-kid-or-chain.token.audience=https://service.example.com quarkus.oidc.bearer-kid-or-chain.allow-token-introspection-cache=false quarkus.oidc.bearer-kid-or-chain.certificate-chain.trust-store-file=truststore.p12 quarkus.oidc.bearer-kid-or-chain.certificate-chain.trust-store-password=storepassword -quarkus.oidc.bearer-kid-or-chain.certificate-chain.trust-store-cert-alias=fullchain quarkus.oidc.bearer-id.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.bearer-id.client-id=quarkus-app @@ -156,7 +154,6 @@ quarkus.oidc.bearer-azure.token.lifespan-grace=2147483647 quarkus.oidc.bearer-azure.token.customizer-name=azure-access-token-customizer quarkus.oidc.bearer-azure.certificate-chain.trust-store-file=truststore.p12 quarkus.oidc.bearer-azure.certificate-chain.trust-store-password=storepassword -quarkus.oidc.bearer-azure.certificate-chain.trust-store-cert-alias=fullchain quarkus.oidc.bearer-role-claim-path.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.bearer-role-claim-path.client-id=quarkus-app @@ -173,7 +170,14 @@ quarkus.oidc.bearer-no-introspection.token.allow-jwt-introspection=false quarkus.oidc.bearer-certificate-full-chain.certificate-chain.trust-store-file=truststore.p12 quarkus.oidc.bearer-certificate-full-chain.certificate-chain.trust-store-password=storepassword -quarkus.oidc.bearer-certificate-full-chain.certificate-chain.trust-store-cert-alias=fullchain + +quarkus.oidc.bearer-certificate-full-chain-root-only.certificate-chain.trust-store-file=truststore-rootcert.p12 +quarkus.oidc.bearer-certificate-full-chain-root-only.certificate-chain.trust-store-password=storepassword +quarkus.oidc.bearer-certificate-full-chain-root-only.certificate-chain.leaf-certificate-name=www.quarkustest.com + +quarkus.oidc.bearer-certificate-full-chain-root-only-wrongcname.certificate-chain.trust-store-file=truststore-rootcert.p12 +quarkus.oidc.bearer-certificate-full-chain-root-only-wrongcname.certificate-chain.trust-store-password=storepassword +quarkus.oidc.bearer-certificate-full-chain-root-only-wrongcname.certificate-chain.leaf-certificate-name=www.quarkusio.com quarkus.oidc.bearer-key-without-kid-thumbprint.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.bearer-key-without-kid-thumbprint.discovery-enabled=false diff --git a/integration-tests/oidc-wiremock/src/main/resources/truststore-rootcert.p12 b/integration-tests/oidc-wiremock/src/main/resources/truststore-rootcert.p12 new file mode 100644 index 0000000000000..e6a5a80173a45 Binary files /dev/null and b/integration-tests/oidc-wiremock/src/main/resources/truststore-rootcert.p12 differ diff --git a/integration-tests/oidc-wiremock/src/main/resources/truststore.p12 b/integration-tests/oidc-wiremock/src/main/resources/truststore.p12 index 81b0be2ede57e..b0c1f8bcb4164 100644 Binary files a/integration-tests/oidc-wiremock/src/main/resources/truststore.p12 and b/integration-tests/oidc-wiremock/src/main/resources/truststore.p12 differ diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java index ac7ddb516d975..bee2e7e42f83f 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -65,12 +65,12 @@ public void testAccessResourceAzure() throws Exception { wireMockServer.stubFor(WireMock.get("/auth/azure/jwk") .withHeader("Authorization", matching("Access token: " + azureToken)) .willReturn(WireMock.aResponse().withBody(azureJwk))); - // RestAssured.given().auth().oauth2(azureToken) - // .when().get("/api/admin/bearer-azure") - // .then() - // .statusCode(200) - // .body(Matchers.equalTo( - // "Name:jondoe@quarkusoidctest.onmicrosoft.com,Issuer:https://sts.windows.net/e7861267-92c5-4a03-bdb2-2d3e491e7831/")); + RestAssured.given().auth().oauth2(azureToken) + .when().get("/api/admin/bearer-azure") + .then() + .statusCode(200) + .body(Matchers.equalTo( + "Name:jondoe@quarkusoidctest.onmicrosoft.com,Issuer:https://sts.windows.net/e7861267-92c5-4a03-bdb2-2d3e491e7831/")); String accessTokenWithCert = TestUtils.createTokenWithInlinedCertChain("alice-certificate"); @@ -217,7 +217,7 @@ public void testAccessAdminResourceWithFullCertChain() throws Exception { .then() .statusCode(401); - // Send the token with the valid certificates but which are are in the wrong order in the chain + // Send the token with the valid certificates but which are in the wrong order in the chain accessToken = getAccessTokenWithCertChain( List.of(intermediateCert, subjectCert, rootCert), subjectPrivateKey); @@ -246,6 +246,58 @@ public void testAccessAdminResourceWithFullCertChain() throws Exception { } + @Test + public void testFullCertChainWithOnlyRootInTruststore() throws Exception { + X509Certificate rootCert = KeyUtils.getCertificate(ResourceUtils.readResource("/ca.cert.pem")); + X509Certificate intermediateCert = KeyUtils.getCertificate(ResourceUtils.readResource("/intermediate.cert.pem")); + X509Certificate subjectCert = KeyUtils.getCertificate(ResourceUtils.readResource("/www.quarkustest.com.cert.pem")); + PrivateKey subjectPrivateKey = KeyUtils.readPrivateKey("/www.quarkustest.com.key.pem"); + + // Send the token with the valid certificate chain + String accessToken = getAccessTokenWithCertChain( + List.of(subjectCert, intermediateCert, rootCert), + subjectPrivateKey); + + RestAssured.given().auth().oauth2(accessToken) + .when().get("/api/admin/bearer-certificate-full-chain-root-only") + .then() + .statusCode(200) + .body(Matchers.containsString("admin")); + + // Send the same token to the service expecting a different leaf certificate name + RestAssured.given().auth().oauth2(accessToken) + .when().get("/api/admin/bearer-certificate-full-chain-root-only-wrongcname") + .then() + .statusCode(401); + + // Send the token with the valid certificates but which are in the wrong order in the chain + accessToken = getAccessTokenWithCertChain( + List.of(intermediateCert, subjectCert, rootCert), + subjectPrivateKey); + RestAssured.given().auth().oauth2(accessToken) + .when().get("/api/admin/bearer-certificate-full-chain-root-only") + .then() + .statusCode(401); + + // Send the token with the valid certificates but with the intermediate one omitted from the chain + accessToken = getAccessTokenWithCertChain( + List.of(subjectCert, rootCert), + subjectPrivateKey); + RestAssured.given().auth().oauth2(accessToken) + .when().get("/api/admin/bearer-certificate-full-chain-root-only") + .then() + .statusCode(401); + + // Send the token with the only the last valid certificate + accessToken = getAccessTokenWithCertChain( + List.of(subjectCert), + subjectPrivateKey); + RestAssured.given().auth().oauth2(accessToken) + .when().get("/api/admin/bearer-certificate-full-chain-root-only") + .then() + .statusCode(401); + } + @Test public void testAccessAdminResourceWithKidOrChain() throws Exception { // token with a matching kid, not x5c diff --git a/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/JacocoConfig.java b/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/JacocoConfig.java index 86aaf74c3f5fb..307c58c6e7fd9 100644 --- a/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/JacocoConfig.java +++ b/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/JacocoConfig.java @@ -17,8 +17,7 @@ public class JacocoConfig { public static final String TARGET_JACOCO_REPORT = "target/" + JACOCO_REPORT; /** - * Whether or not the jacoco extension is enabled. Disabling it can come in handy when runnig tests in IDEs that do their - * own jacoco instrumentation, e.g. EclEmma in Eclipse. + * Whether or not the jacoco extension is enabled. */ @ConfigItem(defaultValue = "true") public boolean enabled;