nativeImageFeatures) {
+ switch (transactionManagerBuildTimeConfig.unsafeMultipleLastResources
+ .orElse(UnsafeMultipleLastResourcesMode.DEFAULT)) {
+ case ALLOW -> {
+ recorder.allowUnsafeMultipleLastResources(capabilities.isPresent(Capability.AGROAL), true);
+ // we will handle the warnings ourselves at runtime init when the option is set explicitly
+ logCleanupFilters.produce(
+ new LogCleanupFilterBuildItem("com.arjuna.ats.arjuna", "ARJUNA012139", "ARJUNA012141", "ARJUNA012142"));
+ }
+ case WARN_FIRST -> {
+ recorder.allowUnsafeMultipleLastResources(capabilities.isPresent(Capability.AGROAL), true);
+ // we will handle the warnings ourselves at runtime init when the option is set explicitly
+ // but we still want Narayana to produce a warning on the first offending transaction
+ logCleanupFilters.produce(
+ new LogCleanupFilterBuildItem("com.arjuna.ats.arjuna", "ARJUNA012139", "ARJUNA012142"));
+ }
+ case WARN_EACH -> {
+ recorder.allowUnsafeMultipleLastResources(capabilities.isPresent(Capability.AGROAL), false);
+ // we will handle the warnings ourselves at runtime init when the option is set explicitly
+ // but we still want Narayana to produce one warning per offending transaction
+ logCleanupFilters.produce(
+ new LogCleanupFilterBuildItem("com.arjuna.ats.arjuna", "ARJUNA012139", "ARJUNA012142"));
+ }
+ case FAIL -> { // No need to do anything, this is the default behavior of Narayana
+ }
+ }
+ }
}
diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java
index 83d9f1b865cfa..156dbd6d1c865 100644
--- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java
+++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java
@@ -29,6 +29,7 @@
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigurationException;
+import io.quarkus.runtime.util.StringUtil;
@Recorder
public class NarayanaJtaRecorder {
@@ -110,6 +111,30 @@ public void setConfig(final TransactionManagerConfiguration transactions) {
.setXaResourceOrphanFilterClassNames(transactions.xaResourceOrphanFilters);
}
+ /**
+ * This should be removed in the future.
+ */
+ @Deprecated(forRemoval = true)
+ public void allowUnsafeMultipleLastResources(boolean agroalPresent, boolean disableMultipleLastResourcesWarning) {
+ arjPropertyManager.getCoreEnvironmentBean().setAllowMultipleLastResources(true);
+ arjPropertyManager.getCoreEnvironmentBean().setDisableMultipleLastResourcesWarning(disableMultipleLastResourcesWarning);
+ if (agroalPresent) {
+ jtaPropertyManager.getJTAEnvironmentBean()
+ .setLastResourceOptimisationInterfaceClassName("io.agroal.narayana.LocalXAResource");
+ }
+ }
+
+ /**
+ * This should be removed in the future.
+ */
+ @Deprecated(forRemoval = true)
+ public void logUnsafeMultipleLastResourcesOnStartup(
+ TransactionManagerBuildTimeConfig.UnsafeMultipleLastResourcesMode mode) {
+ log.warnf(
+ "Setting quarkus.transaction-manager.unsafe-multiple-last-resources to '%s' makes adding multiple resources to the same transaction unsafe.",
+ StringUtil.hyphenate(mode.name()).replace('_', '-'));
+ }
+
private void setObjectStoreDir(String name, TransactionManagerConfiguration config) {
BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, name).setObjectStoreDir(config.objectStore.directory);
}
diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerBuildTimeConfig.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerBuildTimeConfig.java
new file mode 100644
index 0000000000000..4fff308594ec0
--- /dev/null
+++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerBuildTimeConfig.java
@@ -0,0 +1,66 @@
+package io.quarkus.narayana.jta.runtime;
+
+import java.util.Optional;
+
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
+public final class TransactionManagerBuildTimeConfig {
+ /**
+ * Define the behavior when using multiple XA unaware resources in the same transactional demarcation.
+ *
+ * Defaults to {@code warn-first}.
+ * {@code warn-first}, {@code warn-each} and {@code allow} are UNSAFE and should only be used for compatibility.
+ * Either use XA for all resources if you want consistency, or split the code into separate
+ * methods with separate transactions.
+ *
+ * Note that using a single XA unaware resource together with XA aware resources, known as
+ * the Last Resource Commit Optimization (LRCO), is different from using multiple XA unaware
+ * resources. Although LRCO allows most transactions to complete normally, some errors can
+ * cause an inconsistent transaction outcome. Using multiple XA unaware resources is not
+ * recommended since the probability of inconsistent outcomes is significantly higher and
+ * much harder to recover from than LRCO. For this reason, use LRCO as a last resort.
+ *
+ * We do not recommend using this configuration property, and we plan to remove it in the future,
+ * so you should plan fixing your application accordingly.
+ * If you think your use case of this feature is valid and this option should be kept around,
+ * open an issue in our tracker explaining why.
+ *
+ * @deprecated This property is planned for removal in a future version.
+ */
+ @Deprecated(forRemoval = true)
+ @ConfigItem(defaultValueDocumentation = "warn-first")
+ public Optional unsafeMultipleLastResources;
+
+ public enum UnsafeMultipleLastResourcesMode {
+ /**
+ * Allow using multiple XA unaware resources in the same transactional demarcation.
+ *
+ * This will log a warning once on application startup,
+ * but not on each use of multiple XA unaware resources in the same transactional demarcation.
+ */
+ ALLOW,
+ /**
+ * Allow using multiple XA unaware resources in the same transactional demarcation,
+ * but log a warning on the first occurrence.
+ */
+ WARN_FIRST,
+ /**
+ * Allow using multiple XA unaware resources in the same transactional demarcation,
+ * but log a warning on each occurrence.
+ */
+ WARN_EACH,
+ /**
+ * Allow using multiple XA unaware resources in the same transactional demarcation,
+ * but log a warning on each occurrence.
+ */
+ FAIL;
+
+ // The default is WARN_FIRST in Quarkus 3.8, FAIL in Quarkus 3.9+
+ // Make sure to update defaultValueDocumentation on unsafeMultipleLastResources when changing this.
+ public static final UnsafeMultipleLastResourcesMode DEFAULT = WARN_FIRST;
+ }
+
+}
diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/graal/DisableLoggingFeature.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/graal/DisableLoggingFeature.java
new file mode 100644
index 0000000000000..1e32c4ec2a9d7
--- /dev/null
+++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/graal/DisableLoggingFeature.java
@@ -0,0 +1,43 @@
+package io.quarkus.narayana.jta.runtime.graal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.graalvm.nativeimage.hosted.Feature;
+
+/**
+ * Disables logging during the analysis phase
+ */
+public class DisableLoggingFeature implements Feature {
+
+ private static final String[] CATEGORIES = {
+ "com.arjuna.ats.arjuna"
+ };
+
+ private final Map categoryMap = new HashMap<>(CATEGORIES.length);
+
+ @Override
+ public void beforeAnalysis(BeforeAnalysisAccess access) {
+ for (String category : CATEGORIES) {
+ Logger logger = Logger.getLogger(category);
+ categoryMap.put(category, logger.getLevel());
+ logger.setLevel(Level.SEVERE);
+ }
+ }
+
+ @Override
+ public void afterAnalysis(AfterAnalysisAccess access) {
+ for (String category : CATEGORIES) {
+ Level level = categoryMap.remove(category);
+ Logger logger = Logger.getLogger(category);
+ logger.setLevel(level);
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return "Disables INFO and WARN logging during the analysis phase";
+ }
+}
diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java
index e7971fdf3014d..e468b6860497f 100644
--- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java
+++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java
@@ -33,7 +33,7 @@ public class DevServicesConfig {
* ends with `-legacy`.
* Override with `quarkus.keycloak.devservices.keycloak-x-image`.
*/
- @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:23.0.7")
+ @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:24.0.4")
public String imageName;
/**
diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java
index c2ef04ff3d074..f7f2e1cdff083 100644
--- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java
+++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java
@@ -109,7 +109,8 @@ public class KeycloakDevServicesProcessor {
private static final String KEYCLOAK_QUARKUS_HOSTNAME = "KC_HOSTNAME";
private static final String KEYCLOAK_QUARKUS_ADMIN_PROP = "KEYCLOAK_ADMIN";
private static final String KEYCLOAK_QUARKUS_ADMIN_PASSWORD_PROP = "KEYCLOAK_ADMIN_PASSWORD";
- private static final String KEYCLOAK_QUARKUS_START_CMD = "start --http-enabled=true --hostname-strict=false --hostname-strict-https=false";
+ private static final String KEYCLOAK_QUARKUS_START_CMD = "start --http-enabled=true --hostname-strict=false --hostname-strict-https=false "
+ + "--spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json";
private static final String JAVA_OPTS = "JAVA_OPTS";
private static final String OIDC_USERS = "oidc.users";
@@ -509,6 +510,7 @@ protected void configure() {
addEnv(KEYCLOAK_QUARKUS_ADMIN_PASSWORD_PROP, KEYCLOAK_ADMIN_PASSWORD);
withCommand(startCommand.orElse(KEYCLOAK_QUARKUS_START_CMD)
+ (useSharedNetwork ? " --hostname-port=" + fixedExposedPort.getAsInt() : ""));
+ addUpConfigResource();
} else {
addEnv(KEYCLOAK_WILDFLY_USER_PROP, KEYCLOAK_ADMIN_USER);
addEnv(KEYCLOAK_WILDFLY_PASSWORD_PROP, KEYCLOAK_ADMIN_PASSWORD);
@@ -560,6 +562,13 @@ private void mapResource(String resourcePath, String mappedResource) {
}
}
+ private void addUpConfigResource() {
+ if (Thread.currentThread().getContextClassLoader().getResource("/dev-service/upconfig.json") != null) {
+ LOG.debug("Mapping the classpath /dev-service/upconfig.json resource to /opt/keycloak/upconfig.json");
+ withClasspathResourceMapping("/dev-service/upconfig.json", "/opt/keycloak/upconfig.json", BindMode.READ_ONLY);
+ }
+ }
+
private Integer findRandomPort() {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
diff --git a/extensions/oidc/deployment/src/main/resources/dev-service/upconfig.json b/extensions/oidc/deployment/src/main/resources/dev-service/upconfig.json
new file mode 100644
index 0000000000000..8487089bc90fd
--- /dev/null
+++ b/extensions/oidc/deployment/src/main/resources/dev-service/upconfig.json
@@ -0,0 +1,60 @@
+{
+ "attributes": [
+ {
+ "name": "username",
+ "displayName": "${username}",
+ "permissions": {
+ "view": ["admin", "user"],
+ "edit": ["admin", "user"]
+ },
+ "validations": {
+ "length": { "min": 3, "max": 255 },
+ "username-prohibited-characters": {},
+ "up-username-not-idn-homograph": {}
+ }
+ },
+ {
+ "name": "email",
+ "displayName": "${email}",
+ "permissions": {
+ "view": ["admin", "user"],
+ "edit": ["admin", "user"]
+ },
+ "validations": {
+ "email" : {},
+ "length": { "max": 255 }
+ }
+ },
+ {
+ "name": "firstName",
+ "displayName": "${firstName}",
+ "permissions": {
+ "view": ["admin", "user"],
+ "edit": ["admin", "user"]
+ },
+ "validations": {
+ "length": { "max": 255 },
+ "person-name-prohibited-characters": {}
+ }
+ },
+ {
+ "name": "lastName",
+ "displayName": "${lastName}",
+ "permissions": {
+ "view": ["admin", "user"],
+ "edit": ["admin", "user"]
+ },
+ "validations": {
+ "length": { "max": 255 },
+ "person-name-prohibited-characters": {}
+ }
+ }
+ ],
+ "groups": [
+ {
+ "name": "user-metadata",
+ "displayHeader": "User metadata",
+ "displayDescription": "Attributes, which refer to user metadata"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
index d9d73cadab57b..3ab5ca1e57776 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
@@ -8,14 +8,12 @@
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.toMap;
-import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.UncheckedIOException;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
-import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -39,7 +37,6 @@
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
@@ -91,12 +88,15 @@
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
-import io.quarkus.fs.util.ZipUtils;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.MethodDescriptor;
-import io.quarkus.maven.dependency.Dependency;
+import io.quarkus.maven.dependency.ArtifactKey;
+import io.quarkus.maven.dependency.DependencyFlags;
import io.quarkus.maven.dependency.ResolvedDependency;
import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem;
+import io.quarkus.paths.FilteredPathTree;
+import io.quarkus.paths.PathFilter;
+import io.quarkus.paths.PathTree;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.Engine;
import io.quarkus.qute.EngineBuilder;
@@ -2108,9 +2108,6 @@ void collectTemplates(ApplicationArchivesBuildItem applicationArchives,
QuteConfig config,
TemplateRootsBuildItem templateRoots)
throws IOException {
- Set allApplicationArchives = applicationArchives.getAllApplicationArchives();
- List extensionArtifacts = curateOutcome.getApplicationModel().getDependencies().stream()
- .filter(Dependency::isRuntimeExtensionArtifact).collect(Collectors.toList());
// Make sure the new templates are watched as well
watchedPaths.produce(HotDeploymentWatchedFileBuildItem.builder().setLocationPredicate(new Predicate() {
@@ -2125,71 +2122,39 @@ public boolean test(String path) {
}
}).build());
- for (ResolvedDependency artifact : extensionArtifacts) {
- if (isApplicationArchive(artifact, allApplicationArchives)) {
- // Skip extension archives that are also application archives
- continue;
- }
- for (Path resolvedPath : artifact.getResolvedPaths()) {
- if (Files.isDirectory(resolvedPath)) {
- scanPath(resolvedPath, resolvedPath, config, templateRoots, watchedPaths, templatePaths,
- nativeImageResources);
- } else {
- try (FileSystem artifactFs = ZipUtils.newFileSystem(resolvedPath)) {
- for (String templateRoot : templateRoots) {
- Path artifactBasePath = artifactFs.getPath(templateRoot);
- if (Files.exists(artifactBasePath)) {
- LOGGER.debugf("Found extension templates in: %s", resolvedPath);
- scan(artifactBasePath, artifactBasePath, templateRoot + "/", watchedPaths, templatePaths,
- nativeImageResources,
- config);
- }
- }
- } catch (IOException e) {
- LOGGER.warnf(e, "Unable to create the file system from the path: %s", resolvedPath);
- }
- }
+ final Set allApplicationArchives = applicationArchives.getAllApplicationArchives();
+ final Set appArtifactKeys = new HashSet<>(allApplicationArchives.size());
+ for (var archive : allApplicationArchives) {
+ appArtifactKeys.add(archive.getKey());
+ }
+ for (ResolvedDependency artifact : curateOutcome.getApplicationModel()
+ .getDependencies(DependencyFlags.RUNTIME_EXTENSION_ARTIFACT)) {
+ // Skip extension archives that are also application archives
+ if (!appArtifactKeys.contains(artifact.getKey())) {
+ scanPathTree(artifact.getContentTree(), templateRoots, watchedPaths, templatePaths, nativeImageResources,
+ config);
}
}
for (ApplicationArchive archive : allApplicationArchives) {
- archive.accept(tree -> {
- for (Path root : tree.getRoots()) {
- // Note that we cannot use ApplicationArchive.getChildPath(String) here because we would not be able to detect
- // a wrong directory name on case-insensitive file systems
- scanPath(root, root, config, templateRoots, watchedPaths, templatePaths, nativeImageResources);
- }
- });
+ archive.accept(
+ tree -> scanPathTree(tree, templateRoots, watchedPaths, templatePaths, nativeImageResources, config));
}
}
- private void scanPath(Path rootPath, Path path, QuteConfig config, TemplateRootsBuildItem templateRoots,
+ private void scanPathTree(PathTree pathTree, TemplateRootsBuildItem templateRoots,
BuildProducer watchedPaths,
BuildProducer templatePaths,
- BuildProducer nativeImageResources) {
- if (!Files.isDirectory(path)) {
- return;
- }
- try (Stream paths = Files.list(path)) {
- for (Path file : paths.collect(Collectors.toList())) {
- if (Files.isDirectory(file)) {
- // Iterate over the directories in the root
- // "/io", "/META-INF", "/templates", "/web", etc.
- Path relativePath = rootPath.relativize(file);
- if (templateRoots.isRoot(relativePath)) {
- LOGGER.debugf("Found templates dir: %s", file);
- // The base path is an OS-specific path relative to the template root
- String basePath = relativePath.toString() + File.separatorChar;
- scan(file, file, basePath, watchedPaths, templatePaths,
- nativeImageResources,
- config);
- } else if (templateRoots.maybeRoot(relativePath)) {
- // Scan the path recursively because the template root may be nested, for example "/web/public"
- scanPath(rootPath, file, config, templateRoots, watchedPaths, templatePaths, nativeImageResources);
- }
+ BuildProducer nativeImageResources,
+ QuteConfig config) {
+ for (String templateRoot : templateRoots) {
+ pathTree.accept(templateRoot, visit -> {
+ if (visit != null) {
+ // if template root is found in this tree then walk over its subtree
+ scanTemplateRootSubtree(
+ new FilteredPathTree(pathTree, PathFilter.forIncludes(List.of(templateRoot + "/**"))),
+ visit.getRelativePath(), watchedPaths, templatePaths, nativeImageResources, config);
}
- }
- } catch (IOException e) {
- throw new UncheckedIOException(e);
+ });
}
}
@@ -3367,69 +3332,58 @@ public static String getName(InjectionPointInfo injectionPoint) {
throw new IllegalArgumentException();
}
+ /**
+ *
+ * @param templatePaths
+ * @param watchedPaths
+ * @param nativeImageResources
+ * @param resourcePath The relative resource path, including the template root
+ * @param templatePath The path relative to the template root; using the {@code /} path separator
+ * @param originalPath
+ * @param config
+ */
private static void produceTemplateBuildItems(BuildProducer templatePaths,
BuildProducer watchedPaths,
- BuildProducer nativeImageResources, String basePath, String filePath,
+ BuildProducer nativeImageResources, String resourcePath,
+ String templatePath,
Path originalPath, QuteConfig config) {
- if (filePath.isEmpty()) {
+ if (templatePath.isEmpty()) {
return;
}
- // OS-specific full path, i.e. templates\foo.html
- String osSpecificPath = basePath + filePath;
- // OS-agnostic full path, i.e. templates/foo.html
- String osAgnosticPath = osSpecificPath;
- if (File.separatorChar != '/') {
- osAgnosticPath = osAgnosticPath.replace(File.separatorChar, '/');
- }
- LOGGER.debugf("Produce template build items [filePath: %s, fullPath: %s, originalPath: %s", filePath, osSpecificPath,
+ LOGGER.debugf("Produce template build items [templatePath: %s, osSpecificResourcePath: %s, originalPath: %s",
+ templatePath,
+ resourcePath,
originalPath);
boolean restartNeeded = true;
if (config.devMode.noRestartTemplates.isPresent()) {
- restartNeeded = !config.devMode.noRestartTemplates.get().matcher(osAgnosticPath).matches();
+ restartNeeded = !config.devMode.noRestartTemplates.get().matcher(resourcePath).matches();
}
- watchedPaths.produce(new HotDeploymentWatchedFileBuildItem(osAgnosticPath, restartNeeded));
- nativeImageResources.produce(new NativeImageResourceBuildItem(osSpecificPath));
+ watchedPaths.produce(new HotDeploymentWatchedFileBuildItem(resourcePath, restartNeeded));
+ nativeImageResources.produce(new NativeImageResourceBuildItem(resourcePath));
templatePaths.produce(
- new TemplatePathBuildItem(filePath, originalPath, readTemplateContent(originalPath, config.defaultCharset)));
+ new TemplatePathBuildItem(templatePath, originalPath,
+ readTemplateContent(originalPath, config.defaultCharset)));
}
- private void scan(Path root, Path directory, String basePath, BuildProducer watchedPaths,
+ private void scanTemplateRootSubtree(PathTree pathTree, String templateRoot,
+ BuildProducer watchedPaths,
BuildProducer templatePaths,
BuildProducer nativeImageResources,
- QuteConfig config)
- throws IOException {
- try (Stream files = Files.list(directory)) {
- Iterator iter = files.iterator();
- while (iter.hasNext()) {
- Path filePath = iter.next();
- /*
- * Fix for https://github.com/quarkusio/quarkus/issues/25751 where running tests in Eclipse
- * sometimes produces `/templates/tags` (absolute) files listed for `templates` (relative)
- * directories, so we work around this
- */
- if (!directory.isAbsolute()
- && filePath.isAbsolute()
- && filePath.getRoot() != null) {
- filePath = filePath.getRoot().relativize(filePath);
- }
- if (Files.isRegularFile(filePath)) {
- LOGGER.debugf("Found template: %s", filePath);
- String templatePath = root.relativize(filePath).toString();
- if (File.separatorChar != '/') {
- templatePath = templatePath.replace(File.separatorChar, '/');
- }
- if (config.templatePathExclude.matcher(templatePath).matches()) {
- LOGGER.debugf("Template file excluded: %s", filePath);
- continue;
- }
- produceTemplateBuildItems(templatePaths, watchedPaths, nativeImageResources, basePath, templatePath,
- filePath, config);
- } else if (Files.isDirectory(filePath)) {
- LOGGER.debugf("Scan directory: %s", filePath);
- scan(root, filePath, basePath, watchedPaths, templatePaths, nativeImageResources, config);
- }
+ QuteConfig config) {
+ pathTree.walk(visit -> {
+ if (Files.isRegularFile(visit.getPath())) {
+ LOGGER.debugf("Found template: %s", visit.getPath());
+ // remove templateRoot + /
+ final String relativePath = visit.getRelativePath();
+ String templatePath = relativePath.substring(templateRoot.length() + 1);
+ if (config.templatePathExclude.matcher(templatePath).matches()) {
+ LOGGER.debugf("Template file excluded: %s", visit.getPath());
+ return;
+ }
+ produceTemplateBuildItems(templatePaths, watchedPaths, nativeImageResources,
+ relativePath, templatePath, visit.getPath(), config);
}
- }
+ });
}
private static boolean isExcluded(TypeCheck check, Iterable> excludes) {
@@ -3460,19 +3414,6 @@ private void checkDuplicatePaths(List templatePaths) {
}
}
- private boolean isApplicationArchive(ResolvedDependency dependency, Set applicationArchives) {
- for (ApplicationArchive archive : applicationArchives) {
- if (archive.getKey() == null) {
- continue;
- }
- if (dependency.getGroupId().equals(archive.getKey().getGroupId())
- && dependency.getArtifactId().equals(archive.getKey().getArtifactId())) {
- return true;
- }
- }
- return false;
- }
-
static String readTemplateContent(Path path, Charset defaultCharset) {
try {
return Files.readString(path, defaultCharset);
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templateroot/AdditionalTemplateRootTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templateroot/AdditionalTemplateRootTest.java
index 7e26be68d7834..37b3606d7a096 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templateroot/AdditionalTemplateRootTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templateroot/AdditionalTemplateRootTest.java
@@ -28,6 +28,7 @@ public class AdditionalTemplateRootTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(root -> root
.addAsResource(new StringAsset("Hi {name}!"), "templates/hi.txt")
+ .addAsResource(new StringAsset("Hoho {name}!"), "templates/nested/hoho.txt")
.addAsResource(new StringAsset("Hello {name}!"), "web/public/hello.txt"))
.addBuildChainCustomizer(buildCustomizer());
@@ -50,13 +51,12 @@ public void execute(BuildContext context) {
List items = context.consumeMulti(NativeImageResourceBuildItem.class);
for (NativeImageResourceBuildItem item : items) {
if (item.getResources().contains("web/public/hello.txt")
- || item.getResources().contains("web\\public\\hello.txt")
|| item.getResources().contains("templates/hi.txt")
- || item.getResources().contains("templates\\hi.txt")) {
+ || item.getResources().contains("templates/nested/hoho.txt")) {
found++;
}
}
- if (found != 2) {
+ if (found != 3) {
throw new IllegalStateException(items.stream().flatMap(i -> i.getResources().stream())
.collect(Collectors.toList()).toString());
}
@@ -79,6 +79,7 @@ public void execute(BuildContext context) {
public void testTemplate() {
assertEquals("Hi M!", engine.getTemplate("hi").data("name", "M").render());
assertEquals("Hello M!", hello.data("name", "M").render());
+ assertEquals("Hoho M!", engine.getTemplate("nested/hoho").data("name", "M").render());
}
}
diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/UserFriendlyQuarkusRESTCapabilityCombinationTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/UserFriendlyQuarkusRESTCapabilityCombinationTest.java
new file mode 100644
index 0000000000000..7e4ac075bfd59
--- /dev/null
+++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/UserFriendlyQuarkusRESTCapabilityCombinationTest.java
@@ -0,0 +1,32 @@
+package io.quarkus.resteasy.test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.builder.Version;
+import io.quarkus.deployment.Capability;
+import io.quarkus.maven.dependency.Dependency;
+import io.quarkus.test.QuarkusUnitTest;
+
+class UserFriendlyQuarkusRESTCapabilityCombinationTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setForcedDependencies(
+ List.of(Dependency.of("io.quarkus", "quarkus-resteasy-reactive-deployment", Version.getVersion())))
+ .assertException(t -> {
+ assertTrue(t.getMessage().contains("only one provider of the following capabilities"), t.getMessage());
+ assertTrue(t.getMessage().contains("capability %s is provided by".formatted(Capability.REST)), t.getMessage());
+ });
+
+ @Test
+ public void test() {
+ fail();
+ }
+
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java
index 1bab91fc08c5d..d19971eee9dd8 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java
@@ -1,5 +1,6 @@
package io.quarkus.resteasy.reactive.jackson.deployment.processor;
+import static io.quarkus.resteasy.reactive.common.deployment.QuarkusResteasyReactiveDotNames.JSON_IGNORE;
import static io.quarkus.security.spi.RolesAllowedConfigExpResolverBuildItem.isSecurityConfigExpressionCandidate;
import static org.jboss.resteasy.reactive.common.util.RestMediaType.APPLICATION_NDJSON;
import static org.jboss.resteasy.reactive.common.util.RestMediaType.APPLICATION_STREAM_JSON;
@@ -15,6 +16,7 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
+import java.util.function.Predicate;
import java.util.function.Supplier;
import jakarta.inject.Singleton;
@@ -59,6 +61,7 @@
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.JaxRsResourceIndexBuildItem;
+import io.quarkus.resteasy.reactive.common.deployment.QuarkusResteasyReactiveDotNames;
import io.quarkus.resteasy.reactive.common.deployment.ResourceScanningResultBuildItem;
import io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem;
import io.quarkus.resteasy.reactive.jackson.CustomDeserialization;
@@ -372,7 +375,12 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
JaxRsResourceIndexBuildItem index,
BuildProducer producer) {
IndexView indexView = index.getIndexView();
- Map typeToHasSecureField = new HashMap<>();
+ boolean noSecureFieldDetected = indexView.getAnnotations(SECURE_FIELD).isEmpty();
+ if (noSecureFieldDetected) {
+ return;
+ }
+
+ Map typeToHasSecureField = new HashMap<>(getTypesWithSecureField());
List result = new ArrayList<>();
for (ResteasyReactiveResourceMethodEntriesBuildItem.Entry entry : resourceMethodEntries.getEntries()) {
MethodInfo methodInfo = entry.getMethodInfo();
@@ -425,7 +433,7 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
}
ClassInfo effectiveReturnClassInfo = indexView.getClassByName(effectiveReturnType.name());
- if ((effectiveReturnClassInfo == null) || effectiveReturnClassInfo.name().equals(ResteasyReactiveDotNames.OBJECT)) {
+ if (effectiveReturnClassInfo == null) {
continue;
}
AtomicBoolean needToDeleteCache = new AtomicBoolean(false);
@@ -443,6 +451,7 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
}
if (needToDeleteCache.get()) {
typeToHasSecureField.clear();
+ typeToHasSecureField.putAll(getTypesWithSecureField());
}
}
if (!result.isEmpty()) {
@@ -452,6 +461,13 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
}
}
+ private static Map getTypesWithSecureField() {
+ // if any of following types is detected as an endpoint return type or a field of endpoint return type,
+ // we always need to apply security serialization as any type can be represented with them
+ return Map.of(ResteasyReactiveDotNames.OBJECT.toString(), Boolean.TRUE, ResteasyReactiveDotNames.RESPONSE.toString(),
+ Boolean.TRUE);
+ }
+
private static boolean hasSecureFields(IndexView indexView, ClassInfo currentClassInfo,
Map typeToHasSecureField, AtomicBoolean needToDeleteCache) {
// use cached result if there is any
@@ -479,10 +495,20 @@ private static boolean hasSecureFields(IndexView indexView, ClassInfo currentCla
.anyMatch(ci -> hasSecureFields(indexView, ci, typeToHasSecureField, needToDeleteCache));
} else {
// figure if any field or parent / subclass field is secured
- hasSecureFields = hasSecureFields(currentClassInfo)
- || anyFieldHasSecureFields(indexView, currentClassInfo, typeToHasSecureField, needToDeleteCache)
- || anySubclassHasSecureFields(indexView, currentClassInfo, typeToHasSecureField, needToDeleteCache)
- || anyParentClassHasSecureFields(indexView, currentClassInfo, typeToHasSecureField, needToDeleteCache);
+ if (hasSecureFields(currentClassInfo)) {
+ hasSecureFields = true;
+ } else {
+ Predicate ignoredTypesPredicate = QuarkusResteasyReactiveDotNames.IGNORE_TYPE_FOR_REFLECTION_PREDICATE;
+ if (ignoredTypesPredicate.test(currentClassInfo.name())) {
+ hasSecureFields = false;
+ } else {
+ hasSecureFields = anyFieldHasSecureFields(indexView, currentClassInfo, typeToHasSecureField,
+ needToDeleteCache)
+ || anySubclassHasSecureFields(indexView, currentClassInfo, typeToHasSecureField, needToDeleteCache)
+ || anyParentClassHasSecureFields(indexView, currentClassInfo, typeToHasSecureField,
+ needToDeleteCache);
+ }
+ }
}
typeToHasSecureField.put(className, hasSecureFields);
return hasSecureFields;
@@ -513,6 +539,7 @@ private static boolean anyFieldHasSecureFields(IndexView indexView, ClassInfo cu
return currentClassInfo
.fields()
.stream()
+ .filter(fieldInfo -> !fieldInfo.hasAnnotation(JSON_IGNORE))
.map(FieldInfo::type)
.anyMatch(fieldType -> fieldTypeHasSecureFields(fieldType, indexView, typeToHasSecureField, needToDeleteCache));
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java
index 6fd1e3c3553f8..3b8f1cf12509e 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MultipartResource.java
@@ -17,11 +17,13 @@
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;
+import io.quarkus.resteasy.reactive.jackson.DisableSecureSerialization;
import io.smallrye.common.annotation.Blocking;
@Path("/multipart")
public class MultipartResource {
+ @DisableSecureSerialization // Person has @SecureField but we want to inspect all data
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@@ -45,6 +47,7 @@ public Map greeting(@Valid @BeanParam FormData formData) {
return result;
}
+ @DisableSecureSerialization // Person has @SecureField but we want to inspect all data
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ResponseType.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ResponseType.java
new file mode 100644
index 0000000000000..dc0235c8464a1
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ResponseType.java
@@ -0,0 +1,57 @@
+package io.quarkus.resteasy.reactive.jackson.deployment.test;
+
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.resteasy.reactive.RestResponse;
+
+import io.quarkus.resteasy.reactive.jackson.SecureField;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.Uni;
+
+public enum ResponseType {
+ /**
+ * Returns DTOs directly.
+ */
+ PLAIN(true, "plain"),
+ /**
+ * Returns {@link Multi} with DTOs.
+ */
+ // TODO: enable when https://github.com/quarkusio/quarkus/issues/40447 gets fixed
+ //MULTI(true, "multi"),
+ /**
+ * Returns {@link Uni} with DTOs.
+ */
+ UNI(true, "uni"),
+ /**
+ * Returns {@link Object} that is either DTO with a {@link SecureField} or not.
+ */
+ OBJECT(false, "object"), // we must always assume it can contain SecureField
+ /**
+ * Returns {@link Response} that is either DTO with a {@link SecureField} or not.
+ */
+ RESPONSE(false, "response"), // we must always assume it can contain SecureField
+ /**
+ * Returns {@link RestResponse} with DTOs.
+ */
+ REST_RESPONSE(true, "rest-response"),
+ /**
+ * Returns {@link RestResponse} with DTOs.
+ */
+ COLLECTION(true, "collection");
+
+ private final boolean secureFieldDetectable;
+ private final String resourceSubPath;
+
+ ResponseType(boolean secureFieldDetectable, String resourceSubPath) {
+ this.secureFieldDetectable = secureFieldDetectable;
+ this.resourceSubPath = resourceSubPath;
+ }
+
+ boolean isSecureFieldDetectable() {
+ return secureFieldDetectable;
+ }
+
+ String getResourceSubPath() {
+ return resourceSubPath;
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SecureFieldDetectionTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SecureFieldDetectionTest.java
new file mode 100644
index 0000000000000..4c93d912b230a
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SecureFieldDetectionTest.java
@@ -0,0 +1,399 @@
+package io.quarkus.resteasy.reactive.jackson.deployment.test;
+
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.MethodInfo;
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.common.model.ResourceClass;
+import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
+import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer;
+import org.jboss.resteasy.reactive.server.model.ServerResourceMethod;
+import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner;
+import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import io.quarkus.resteasy.reactive.jackson.SecureField;
+import io.quarkus.resteasy.reactive.jackson.runtime.ResteasyReactiveServerJacksonRecorder;
+import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
+import io.quarkus.security.test.utils.TestIdentityController;
+import io.quarkus.security.test.utils.TestIdentityProvider;
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.Uni;
+import io.vertx.ext.web.RoutingContext;
+
+public class SecureFieldDetectionTest {
+
+ private static final String SECURITY_SERIALIZATION = "security_serialization";
+
+ @RegisterExtension
+ static QuarkusUnitTest test = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(MultiResource.class, UniResource.class, ObjectResource.class, ResponseResource.class,
+ PlainResource.class, TestIdentityProvider.class, TestIdentityController.class,
+ CollectionResource.class, NoSecureField.class, WithSecureField.class, WithNestedSecureField.class,
+ ResponseType.class, DetectSecuritySerializationHandler.class, JsonIgnoreDto.class))
+ .addBuildChainCustomizer(buildChainBuilder -> buildChainBuilder.addBuildStep(context -> context.produce(
+ new MethodScannerBuildItem(new MethodScanner() {
+ @Override
+ public List scan(MethodInfo method, ClassInfo actualEndpointClass,
+ Map methodContext) {
+ return List.of(new DetectSecuritySerializationHandler());
+ }
+ }))).produces(MethodScannerBuildItem.class).build());
+
+ @BeforeEach
+ public void setupSecurity() {
+ TestIdentityController.resetRoles().add("Georgios", "Andrianakis", "admin");
+ }
+
+ private static Stream responseTypes() {
+ return EnumSet.allOf(ResponseType.class).stream().map(Enum::toString).map(Arguments::of);
+ }
+
+ @ParameterizedTest
+ @MethodSource("responseTypes")
+ public void testSecureFieldDetection(String responseTypeStr) {
+ var responseType = ResponseType.valueOf(responseTypeStr);
+
+ // with auth
+ RestAssured
+ .given()
+ .auth().preemptive().basic("Georgios", "Andrianakis")
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/secure-field")
+ .then()
+ .statusCode(200)
+ .body(containsString("hush hush"));
+ RestAssured
+ .given()
+ .auth().preemptive().basic("Georgios", "Andrianakis")
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/no-secure-field")
+ .then()
+ .statusCode(200)
+ .body(containsString("public"));
+
+ // no auth
+ RestAssured
+ .given()
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/secure-field")
+ .then()
+ .statusCode(200)
+ .header(SECURITY_SERIALIZATION, is("true"))
+ .body(not(containsString("hush hush")));
+
+ // if endpoint returns for example Object or Response we can't really tell during the build time
+ // therefore we add custom security serialization and let decision be made dynamically based on present annotation
+ boolean isSecureSerializationApplied = !responseType.isSecureFieldDetectable();
+ RestAssured
+ .given()
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/no-secure-field")
+ .then()
+ .statusCode(200)
+ .header(SECURITY_SERIALIZATION, is(Boolean.toString(isSecureSerializationApplied)))
+ .body(containsString("public"));
+
+ RestAssured
+ .given()
+ .pathParam("sub-path", responseType.getResourceSubPath())
+ .get("/{sub-path}/json-ignore")
+ .then()
+ .statusCode(200)
+ .header(SECURITY_SERIALIZATION, is(Boolean.toString(isSecureSerializationApplied)))
+ .body(containsString("other"))
+ .body(not(containsString("ignored")));
+ }
+
+ @Path("plain")
+ public static class PlainResource {
+
+ @Path("secure-field")
+ @GET
+ public WithNestedSecureField secureField() {
+ return createEntityWithSecureField();
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public NoSecureField noSecureField() {
+ return createEntityWithoutSecureField();
+ }
+
+ @Path("json-ignore")
+ @GET
+ public JsonIgnoreDto jsonIgnore() {
+ return createEntityWithSecureFieldInIgnored();
+ }
+
+ }
+
+ @Path("multi")
+ public static class MultiResource {
+
+ @Path("secure-field")
+ @GET
+ public Multi secureField() {
+ return Multi.createFrom().item(createEntityWithSecureField());
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Multi noSecureField() {
+ return Multi.createFrom().item(createEntityWithoutSecureField());
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Multi jsonIgnore() {
+ return Multi.createFrom().item(createEntityWithSecureFieldInIgnored());
+ }
+
+ }
+
+ @Path("collection")
+ public static class CollectionResource {
+
+ @Path("secure-field")
+ @GET
+ public Collection secureField() {
+ return List.of(createEntityWithSecureField());
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Collection noSecureField() {
+ return Set.of(createEntityWithoutSecureField());
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Collection jsonIgnore() {
+ return Set.of(createEntityWithSecureFieldInIgnored());
+ }
+
+ }
+
+ @Path("uni")
+ public static class UniResource {
+
+ @Path("secure-field")
+ @GET
+ public Uni secureField() {
+ return Uni.createFrom().item(createEntityWithSecureField());
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Uni noSecureField() {
+ return Uni.createFrom().item(createEntityWithoutSecureField());
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Uni jsonIgnore() {
+ return Uni.createFrom().item(createEntityWithSecureFieldInIgnored());
+ }
+
+ }
+
+ @Produces(APPLICATION_JSON)
+ @Path("object")
+ public static class ObjectResource {
+
+ @Path("secure-field")
+ @GET
+ public Object secureField() {
+ return createEntityWithSecureField();
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Object noSecureField() {
+ return createEntityWithoutSecureField();
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Object jsonIgnore() {
+ return createEntityWithSecureFieldInIgnored();
+ }
+
+ }
+
+ @Produces(APPLICATION_JSON)
+ @Path("response")
+ public static class ResponseResource {
+
+ @Path("secure-field")
+ @GET
+ public Response secureField() {
+ return Response.ok(createEntityWithSecureField()).build();
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public Response noSecureField() {
+ return Response.ok(createEntityWithoutSecureField()).build();
+ }
+
+ @Path("json-ignore")
+ @GET
+ public Response jsonIgnore() {
+ return Response.ok(createEntityWithSecureFieldInIgnored()).build();
+ }
+
+ }
+
+ @Path("rest-response")
+ public static class RestResponseResource {
+
+ @Path("secure-field")
+ @GET
+ public RestResponse secureField() {
+ return RestResponse.ok(createEntityWithSecureField());
+ }
+
+ @Path("no-secure-field")
+ @GET
+ public RestResponse noSecureField() {
+ return RestResponse.ok(createEntityWithoutSecureField());
+ }
+
+ @Path("json-ignore")
+ @GET
+ public RestResponse jsonIgnore() {
+ return RestResponse.ok(createEntityWithSecureFieldInIgnored());
+ }
+
+ }
+
+ private static NoSecureField createEntityWithoutSecureField() {
+ var resp = new NoSecureField();
+ resp.setNotSecured("public");
+ return resp;
+ }
+
+ private static JsonIgnoreDto createEntityWithSecureFieldInIgnored() {
+ var resp = new JsonIgnoreDto();
+ resp.setOtherField("other");
+ var nested = new WithSecureField();
+ nested.setSecured("ignored");
+ resp.setWithSecureField(nested);
+ return resp;
+ }
+
+ private static WithNestedSecureField createEntityWithSecureField() {
+ var resp = new WithNestedSecureField();
+ var nested = new WithSecureField();
+ nested.setSecured("hush hush");
+ resp.setWithSecureField(nested);
+ return resp;
+ }
+
+ public static class JsonIgnoreDto {
+
+ @JsonIgnore
+ private WithSecureField withSecureField;
+
+ private String otherField;
+
+ public WithSecureField getWithSecureField() {
+ return withSecureField;
+ }
+
+ public void setWithSecureField(WithSecureField withSecureField) {
+ this.withSecureField = withSecureField;
+ }
+
+ public String getOtherField() {
+ return otherField;
+ }
+
+ public void setOtherField(String otherField) {
+ this.otherField = otherField;
+ }
+ }
+
+ public static class NoSecureField {
+
+ private String notSecured;
+
+ public String getNotSecured() {
+ return notSecured;
+ }
+
+ public void setNotSecured(String notSecured) {
+ this.notSecured = notSecured;
+ }
+ }
+
+ public static class WithNestedSecureField {
+
+ private WithSecureField withSecureField;
+
+ public WithSecureField getWithSecureField() {
+ return withSecureField;
+ }
+
+ public void setWithSecureField(WithSecureField withSecureField) {
+ this.withSecureField = withSecureField;
+ }
+ }
+
+ public static class WithSecureField {
+
+ @SecureField(rolesAllowed = "admin")
+ private String secured;
+
+ public String getSecured() {
+ return secured;
+ }
+
+ public void setSecured(String secured) {
+ this.secured = secured;
+ }
+ }
+
+ public static class DetectSecuritySerializationHandler implements ServerRestHandler, HandlerChainCustomizer {
+
+ @Override
+ public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
+ var methodId = requestContext.getResteasyReactiveResourceInfo().getMethodId();
+ var customSerialization = ResteasyReactiveServerJacksonRecorder.customSerializationForMethod(methodId);
+ var customSerializationDetected = Boolean.toString(customSerialization != null);
+ requestContext.unwrap(RoutingContext.class).response().putHeader(SECURITY_SERIALIZATION,
+ customSerializationDetected);
+ }
+
+ @Override
+ public List handlers(Phase phase, ResourceClass resourceClass, ServerResourceMethod resourceMethod) {
+ return List.of(new DetectSecuritySerializationHandler());
+ }
+ }
+}
diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java
index 8aa777f640b67..aae68a223e326 100644
--- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java
+++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java
@@ -55,6 +55,7 @@
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.ObserverInfo;
import io.quarkus.builder.item.MultiBuildItem;
+import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
@@ -518,6 +519,7 @@ void transformSecurityAnnotations(BuildProducer
}
}
+ @Consume(Capabilities.class) // make sure extension combinations are validated before default security check
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void gatherSecurityChecks(BuildProducer syntheticBeans,
@@ -528,7 +530,7 @@ void gatherSecurityChecks(BuildProducer syntheticBeans,
BuildProducer configBuilderProducer,
List additionalSecuredMethods,
SecurityCheckRecorder recorder,
- Optional defaultSecurityCheckBuildItem,
+ List defaultSecurityCheckBuildItem,
BuildProducer reflectiveClassBuildItemBuildProducer,
List additionalSecurityChecks, SecurityBuildTimeConfig config) {
classPredicate.produce(new ApplicationClassPredicateBuildItem(new SecurityCheckStorageAppPredicate()));
@@ -562,8 +564,14 @@ void gatherSecurityChecks(BuildProducer syntheticBeans,
methodEntry.getValue());
}
- if (defaultSecurityCheckBuildItem.isPresent()) {
- var roles = defaultSecurityCheckBuildItem.get().getRolesAllowed();
+ if (!defaultSecurityCheckBuildItem.isEmpty()) {
+ if (defaultSecurityCheckBuildItem.size() > 1) {
+ int itemCount = defaultSecurityCheckBuildItem.size();
+ throw new IllegalStateException("Found %d DefaultSecurityCheckBuildItem items, ".formatted(itemCount)
+ + "please make sure the item is produced exactly once");
+ }
+
+ var roles = defaultSecurityCheckBuildItem.get(0).getRolesAllowed();
if (roles == null) {
recorder.registerDefaultSecurityCheck(builder, recorder.denyAll());
} else {
diff --git a/extensions/security/spi/src/main/java/io/quarkus/security/spi/DefaultSecurityCheckBuildItem.java b/extensions/security/spi/src/main/java/io/quarkus/security/spi/DefaultSecurityCheckBuildItem.java
index ed3dafe18de0d..67765b5728cf1 100644
--- a/extensions/security/spi/src/main/java/io/quarkus/security/spi/DefaultSecurityCheckBuildItem.java
+++ b/extensions/security/spi/src/main/java/io/quarkus/security/spi/DefaultSecurityCheckBuildItem.java
@@ -3,9 +3,16 @@
import java.util.List;
import java.util.Objects;
-import io.quarkus.builder.item.SimpleBuildItem;
-
-public final class DefaultSecurityCheckBuildItem extends SimpleBuildItem {
+import io.quarkus.builder.item.MultiBuildItem;
+
+/**
+ * Registers default SecurityCheck with the SecurityCheckStorage.
+ * Please make sure this build item is produced exactly once or validation will fail and exception will be thrown.
+ */
+public final class DefaultSecurityCheckBuildItem
+ // we make this Multi to run CapabilityAggregationStep#aggregateCapabilities first
+ // so that user-friendly error message is logged when Quarkus REST and RESTEasy are used together
+ extends MultiBuildItem {
public final List rolesAllowed;
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java
index a2349c3024766..47bde335702f4 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/CachingPathTree.java
@@ -87,6 +87,11 @@ public void accept(String relativePath, Consumer func) {
delegate.accept(relativePath, func);
}
+ @Override
+ public void acceptAll(String relativePath, Consumer func) {
+ delegate.acceptAll(relativePath, func);
+ }
+
@Override
public boolean contains(String relativePath) {
final LinkedHashMap snapshot = walkSnapshot;
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java
index 11e125a04fbf1..8265ef59771f1 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java
@@ -54,6 +54,15 @@ public void accept(String relativePath, Consumer consumer) {
}
}
+ @Override
+ public void acceptAll(String relativePath, Consumer consumer) {
+ if (!PathFilter.isVisible(filter, relativePath)) {
+ consumer.accept(null);
+ } else {
+ original.acceptAll(relativePath, consumer);
+ }
+ }
+
@Override
public boolean contains(String relativePath) {
return PathFilter.isVisible(filter, relativePath) && original.contains(relativePath);
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java
index 4681cec199deb..e09b70ce12168 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java
@@ -86,6 +86,26 @@ public void accept(PathVisit t) {
}
}
+ @Override
+ public void acceptAll(String relativePath, Consumer func) {
+ final AtomicBoolean consumed = new AtomicBoolean();
+ final Consumer wrapper = new Consumer<>() {
+ @Override
+ public void accept(PathVisit t) {
+ if (t != null) {
+ func.accept(t);
+ consumed.set(true);
+ }
+ }
+ };
+ for (PathTree tree : trees) {
+ tree.accept(relativePath, wrapper);
+ }
+ if (!consumed.get()) {
+ func.accept(null);
+ }
+ }
+
@Override
public boolean contains(String relativePath) {
for (PathTree tree : trees) {
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
index b126db5924d26..5b68db900bc93 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
@@ -134,6 +134,20 @@ default boolean isEmpty() {
*/
void accept(String relativePath, Consumer consumer);
+ /**
+ * Consumes a given path relative to the root of the tree.
+ * If the path isn't found in the tree, the {@link PathVisit} argument
+ * passed to the consumer will be {@code null}.
+ *
+ * If multiple items match then the consumer will be called multiple times.
+ *
+ * @param relativePath relative path to consume
+ * @param consumer path consumer
+ */
+ default void acceptAll(String relativePath, Consumer consumer) {
+ accept(relativePath, consumer);
+ }
+
/**
* Checks whether the tree contains a relative path.
*
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java
index 139560a3d40f1..5084405c4bdcf 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java
@@ -44,6 +44,16 @@ default URL getUrl() {
}
}
+ /**
+ * Path relative to the root of the tree as a string with {@code /} as a path element separator.
+ * This method calls {@link #getRelativePath(String)} passing {@code /} as an argument.
+ *
+ * @return path relative to the root of the tree as a string with {@code /} as a path element separator
+ */
+ default String getRelativePath() {
+ return getRelativePath("/");
+ }
+
/**
* Path relative to the root of the tree as a string with a provided path element separator.
* For a {@link PathTree} created for an archive, the returned path will be relative to the root
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java
index b5cd7e9cdd3a8..1206da6cc618c 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java
@@ -158,6 +158,11 @@ public void accept(String relativePath, Consumer consumer) {
delegate.accept(relativePath, consumer);
}
+ @Override
+ public void acceptAll(String relativePath, Consumer consumer) {
+ delegate.acceptAll(relativePath, consumer);
+ }
+
@Override
public boolean contains(String relativePath) {
return delegate.contains(relativePath);
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java
index eefce68aa5f66..2378ecdfafb70 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java
@@ -4,6 +4,7 @@
import java.nio.file.Path;
import java.security.ProtectionDomain;
import java.util.Collections;
+import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.jar.Manifest;
@@ -129,4 +130,9 @@ public void close() {
}
};
+
+ default List getResources(String name) {
+ ClassPathResource resource = getResource(name);
+ return resource == null ? List.of() : List.of(resource);
+ }
}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
index 6f05de1707e71..61bf42a4cb9a5 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
@@ -12,8 +12,10 @@
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
@@ -106,6 +108,26 @@ public ClassPathResource getResource(String name) {
return apply(tree -> tree.apply(sanitized, visit -> visit == null ? null : new Resource(visit)));
}
+ @Override
+ public List getResources(String name) {
+ final String sanitized = sanitize(name);
+ final Set resources = this.resources;
+ if (resources != null && !resources.contains(sanitized)) {
+ return List.of();
+ }
+ List ret = new ArrayList<>();
+ apply(tree -> {
+ tree.acceptAll(sanitized, visit -> {
+ if (visit != null) {
+ ret.add(new Resource(visit));
+
+ }
+ });
+ return List.of();
+ });
+ return ret;
+ }
+
@Override
public T apply(Function func) {
lock.readLock().lock();
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
index e1b20fe85d657..1f1c3bf2c1cdf 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
@@ -11,6 +11,7 @@
import java.sql.Driver;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@@ -246,27 +247,31 @@ public Enumeration getResources(String unsanitisedName, boolean parentAlrea
if (providers != null) {
boolean endsWithTrailingSlash = unsanitisedName.endsWith("/");
for (ClassPathElement element : providers) {
- ClassPathResource res = element.getResource(name);
+ Collection resList = element.getResources(name);
//if the requested name ends with a trailing / we make sure
//that the resource is a directory, and return a URL that ends with a /
//this matches the behaviour of URLClassLoader
- if (endsWithTrailingSlash) {
- if (res.isDirectory()) {
- try {
- resources.add(new URL(res.getUrl().toString() + "/"));
- } catch (MalformedURLException e) {
- throw new RuntimeException(e);
+ for (var res : resList) {
+ if (endsWithTrailingSlash) {
+ if (res.isDirectory()) {
+ try {
+ resources.add(new URL(res.getUrl().toString() + "/"));
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
}
+ } else {
+ resources.add(res.getUrl());
}
- } else {
- resources.add(res.getUrl());
}
}
} else if (name.isEmpty()) {
for (ClassPathElement i : elements) {
- ClassPathResource res = i.getResource("");
- if (res != null) {
- resources.add(res.getUrl());
+ List resList = i.getResources("");
+ for (var res : resList) {
+ if (res != null) {
+ resources.add(res.getUrl());
+ }
}
}
}
diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java
index 97f6c8f1456d4..556407a142017 100644
--- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java
+++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java
@@ -232,7 +232,9 @@ private static Properties loadPomProps(Path appJar, Path artifactIdPath) throws
}
public static Model readModel(final Path pomXml) throws IOException {
- return readModel(Files.newInputStream(pomXml));
+ Model model = readModel(Files.newInputStream(pomXml));
+ model.setPomFile(pomXml.toFile());
+ return model;
}
public static Model readModel(InputStream stream) throws IOException {
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
index 657599ca1a957..4d288748ef627 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
@@ -640,7 +640,9 @@ protected void handleUnrecoverableError(Throwable throwable) {
protected void endResponse() {
if (serverResponse().headWritten()) {
- serverRequest().closeConnection();
+ if (!serverResponse().closed()) {
+ serverRequest().closeConnection();
+ }
} else {
serverResponse().setStatusCode(500).end();
}
diff --git a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java
index 0e02e22bb1b00..d6a097ed92312 100644
--- a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java
+++ b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java
@@ -406,7 +406,7 @@ public void removeResponseHeader(String name) {
@Override
public boolean closed() {
- return response.closed();
+ return response.ended() || response.closed();
}
@Override
diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute
index eade03aa1a939..286a7757dbc7c 100644
--- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute
+++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-{java.version}:1.18
+FROM registry.access.redhat.com/ubi8/openjdk-{java.version}:1.19
ENV LANGUAGE='en_US:en'
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateExtension.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateExtension.java
index 4b7d518029d31..a449bb3bd80f5 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateExtension.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateExtension.java
@@ -86,12 +86,12 @@ public enum LayoutType {
public static final String DEFAULT_QUARKIVERSE_PARENT_GROUP_ID = "io.quarkiverse";
public static final String DEFAULT_QUARKIVERSE_PARENT_ARTIFACT_ID = "quarkiverse-parent";
- public static final String DEFAULT_QUARKIVERSE_PARENT_VERSION = "15";
+ public static final String DEFAULT_QUARKIVERSE_PARENT_VERSION = "16";
public static final String DEFAULT_QUARKIVERSE_NAMESPACE_ID = "quarkus-";
public static final String DEFAULT_QUARKIVERSE_GUIDE_URL = "https://quarkiverse.github.io/quarkiverse-docs/%s/dev/";
private static final String DEFAULT_SUREFIRE_PLUGIN_VERSION = "3.2.5";
- private static final String DEFAULT_COMPILER_PLUGIN_VERSION = "3.12.1";
+ private static final String DEFAULT_COMPILER_PLUGIN_VERSION = "3.13.0";
private final QuarkusExtensionCodestartProjectInputBuilder builder = QuarkusExtensionCodestartProjectInput.builder();
private final Path baseDir;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java
index cc0a2169469c6..8cecb28fedae0 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java
@@ -381,7 +381,7 @@ protected void refreshData() {
return;
}
try {
- model = ModelUtils.readModel(projectPom);
+ model = MojoUtils.readPom(projectPom.toFile());
} catch (IOException e) {
throw new RuntimeException("Failed to read " + projectPom, e);
}
diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java
index 9e760539d8f5c..c65cd1d3b1aa3 100644
--- a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java
+++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java
@@ -320,13 +320,13 @@ private void checkDockerfilesWithMaven(Path projectDir) {
assertThat(projectDir.resolve("src/main/docker/Dockerfile.jvm")).exists()
.satisfies(checkContains("./mvnw package"))
.satisfies(checkContains("docker build -f src/main/docker/Dockerfile.jvm"))
- .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-17:1.18"))
+ .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-17:1.19"))
.satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\""))
.satisfies(checkContains("ENTRYPOINT [ \"/opt/jboss/container/java/run/run-java.sh\" ]"));
assertThat(projectDir.resolve("src/main/docker/Dockerfile.legacy-jar")).exists()
.satisfies(checkContains("./mvnw package -Dquarkus.package.type=legacy-jar"))
.satisfies(checkContains("docker build -f src/main/docker/Dockerfile.legacy-jar"))
- .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-17:1.18"))
+ .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-17:1.19"))
.satisfies(checkContains("EXPOSE 8080"))
.satisfies(checkContains("USER 185"))
.satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\""))
@@ -346,13 +346,13 @@ private void checkDockerfilesWithGradle(Path projectDir) {
assertThat(projectDir.resolve("src/main/docker/Dockerfile.jvm")).exists()
.satisfies(checkContains("./gradlew build"))
.satisfies(checkContains("docker build -f src/main/docker/Dockerfile.jvm"))
- .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-17:1.18"))
+ .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-17:1.19"))
.satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\""))
.satisfies(checkContains("ENTRYPOINT [ \"/opt/jboss/container/java/run/run-java.sh\" ]"));
assertThat(projectDir.resolve("src/main/docker/Dockerfile.legacy-jar")).exists()
.satisfies(checkContains("./gradlew build -Dquarkus.package.type=legacy-jar"))
.satisfies(checkContains("docker build -f src/main/docker/Dockerfile.legacy-jar"))
- .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-17:1.18"))
+ .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-17:1.19"))
.satisfies(checkContains("EXPOSE 8080"))
.satisfies(checkContains("USER 185"))
.satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\""))
diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-docker/src/main/docker/Dockerfile.jvm b/integration-tests/container-image/maven-invoker-way/src/it/container-build-docker/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/container-image/maven-invoker-way/src/it/container-build-docker/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-docker/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/keycloak-authorization/src/main/resources/application.properties b/integration-tests/keycloak-authorization/src/main/resources/application.properties
index a8714fb1b906a..8acc066c5a0a1 100644
--- a/integration-tests/keycloak-authorization/src/main/resources/application.properties
+++ b/integration-tests/keycloak-authorization/src/main/resources/application.properties
@@ -113,3 +113,5 @@ admin-url=${keycloak.url}
# Configure Keycloak Admin Client
quarkus.keycloak.admin-client.server-url=${admin-url}
+
+quarkus.log.category."com.gargoylesoftware.htmlunit".level=ERROR
diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-deployment/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-deployment/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-deployment/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-deployment/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-statefulset/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-statefulset/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-statefulset/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-statefulset/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-jib-build-and-deploy/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-jib-build-and-deploy/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-jib-build-and-deploy/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-jib-build-and-deploy/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-with-existing-selectorless-manifest/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-with-existing-selectorless-manifest/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-with-existing-selectorless-manifest/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-with-existing-selectorless-manifest/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/minikube-with-existing-manifest/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/minikube-with-existing-manifest/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/kubernetes/maven-invoker-way/src/it/minikube-with-existing-manifest/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/kubernetes/maven-invoker-way/src/it/minikube-with-existing-manifest/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy-deploymentconfig/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy-deploymentconfig/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy-deploymentconfig/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy-deploymentconfig/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-s2i-build-and-deploy/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-s2i-build-and-deploy/src/main/docker/Dockerfile.jvm
index 192010559a8c9..423791b5a44b9 100644
--- a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-s2i-build-and-deploy/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-s2i-build-and-deploy/src/main/docker/Dockerfile.jvm
@@ -77,7 +77,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.jvm b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.jvm
index fd5272297c2ef..c3dd7f16cdc5d 100644
--- a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.jvm
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.jvm
@@ -75,7 +75,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.18
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.legacy-jar b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.legacy-jar
index 95ce7681973c5..27e101d04d41e 100644
--- a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.legacy-jar
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.legacy-jar
@@ -75,7 +75,7 @@
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
-FROM registry.access.redhat.com/ubi8/openjdk-17:1.18
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.19
ENV LANGUAGE='en_US:en'
diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_pom.xml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_pom.xml
index faa8505392b3a..e591f173fef92 100644
--- a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_pom.xml
+++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_pom.xml
@@ -5,7 +5,7 @@
io.quarkiverse
quarkiverse-parent
- 15
+ 16
io.quarkiverse.my-quarkiverse-ext
quarkus-my-quarkiverse-ext-parent
@@ -26,7 +26,7 @@
- 3.12.1
+ 3.13.0
17
UTF-8
UTF-8
diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateStandaloneExtension/my-org-my-own-ext_pom.xml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateStandaloneExtension/my-org-my-own-ext_pom.xml
index 5fb4073ede2a6..857ee512a7a4c 100644
--- a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateStandaloneExtension/my-org-my-own-ext_pom.xml
+++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateStandaloneExtension/my-org-my-own-ext_pom.xml
@@ -13,7 +13,7 @@
- 3.12.1
+ 3.13.0
${surefire-plugin.version}
17
UTF-8
diff --git a/integration-tests/oidc-code-flow/src/main/resources/application.properties b/integration-tests/oidc-code-flow/src/main/resources/application.properties
index 60661ac0c9935..53c3417702c90 100644
--- a/integration-tests/oidc-code-flow/src/main/resources/application.properties
+++ b/integration-tests/oidc-code-flow/src/main/resources/application.properties
@@ -1,4 +1,5 @@
quarkus.keycloak.devservices.create-realm=false
+quarkus.keycloak.devservices.show-logs=true
# Default tenant configurationf
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=secret
diff --git a/integration-tests/oidc-tenancy/src/main/resources/application.properties b/integration-tests/oidc-tenancy/src/main/resources/application.properties
index 3f386b7933edb..d55ba117f264c 100644
--- a/integration-tests/oidc-tenancy/src/main/resources/application.properties
+++ b/integration-tests/oidc-tenancy/src/main/resources/application.properties
@@ -125,7 +125,7 @@ quarkus.oidc.tenant-public-key.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCg
smallrye.jwt.sign.key.location=/privateKey.pem
smallrye.jwt.new-token.lifespan=5
-quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL
+quarkus.log.category."com.gargoylesoftware.htmlunit".level=ERROR
quarkus.http.auth.proactive=false
quarkus.native.additional-build-args=-H:IncludeResources=.*\\.pem
diff --git a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java
index 29a9327e6d87b..bcd717025d989 100644
--- a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java
+++ b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java
@@ -53,7 +53,7 @@ public void testGetUserNameWithAccessTokenPropagation() {
//.statusCode(200)
//.body(equalTo("alice"));
.statusCode(500)
- .body(containsString("Feature not enabled"));
+ .body(containsString("Client not allowed to exchange"));
}
@Test
diff --git a/integration-tests/oidc/src/main/resources/upconfig.json b/integration-tests/oidc/src/main/resources/upconfig.json
new file mode 100644
index 0000000000000..8487089bc90fd
--- /dev/null
+++ b/integration-tests/oidc/src/main/resources/upconfig.json
@@ -0,0 +1,60 @@
+{
+ "attributes": [
+ {
+ "name": "username",
+ "displayName": "${username}",
+ "permissions": {
+ "view": ["admin", "user"],
+ "edit": ["admin", "user"]
+ },
+ "validations": {
+ "length": { "min": 3, "max": 255 },
+ "username-prohibited-characters": {},
+ "up-username-not-idn-homograph": {}
+ }
+ },
+ {
+ "name": "email",
+ "displayName": "${email}",
+ "permissions": {
+ "view": ["admin", "user"],
+ "edit": ["admin", "user"]
+ },
+ "validations": {
+ "email" : {},
+ "length": { "max": 255 }
+ }
+ },
+ {
+ "name": "firstName",
+ "displayName": "${firstName}",
+ "permissions": {
+ "view": ["admin", "user"],
+ "edit": ["admin", "user"]
+ },
+ "validations": {
+ "length": { "max": 255 },
+ "person-name-prohibited-characters": {}
+ }
+ },
+ {
+ "name": "lastName",
+ "displayName": "${lastName}",
+ "permissions": {
+ "view": ["admin", "user"],
+ "edit": ["admin", "user"]
+ },
+ "validations": {
+ "length": { "max": 255 },
+ "person-name-prohibited-characters": {}
+ }
+ }
+ ],
+ "groups": [
+ {
+ "name": "user-metadata",
+ "displayHeader": "User metadata",
+ "displayDescription": "Attributes, which refer to user metadata"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java
index 21c76533a6334..abe4321c0789d 100644
--- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java
+++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java
@@ -51,10 +51,12 @@ public Map start() {
keycloak = keycloak
.withClasspathResourceMapping(SERVER_KEYSTORE, SERVER_KEYSTORE_MOUNTED_PATH, BindMode.READ_ONLY)
.withClasspathResourceMapping(SERVER_TRUSTSTORE, SERVER_TRUSTSTORE_MOUNTED_PATH, BindMode.READ_ONLY)
+ .withClasspathResourceMapping("/upconfig.json", "/opt/keycloak/upconfig.json", BindMode.READ_ONLY)
.withCommand("build --https-client-auth=required")
.withCommand(String.format(
"start --https-client-auth=required --hostname-strict=false --hostname-strict-https=false"
- + " --https-key-store-file=%s --https-trust-store-file=%s --https-trust-store-password=password",
+ + " --https-key-store-file=%s --https-trust-store-file=%s --https-trust-store-password=password"
+ + " --spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json",
SERVER_KEYSTORE_MOUNTED_PATH, SERVER_TRUSTSTORE_MOUNTED_PATH));
keycloak.start();
LOGGER.info(keycloak.getLogs());
diff --git a/integration-tests/openapi/pom.xml b/integration-tests/openapi/pom.xml
index e4c6f8c826deb..4183a3d64635b 100644
--- a/integration-tests/openapi/pom.xml
+++ b/integration-tests/openapi/pom.xml
@@ -52,6 +52,11 @@
assertj-core
test