diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index c4e8f1af7e5a2..ce4078e4abf9e 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -835,7 +835,9 @@ jobs: # We do this so we can get better analytics for the downloaded version of the build images - name: Update Docker Client User Agent run: | - cat <<< $(jq '.HttpHeaders += {"User-Agent": "Quarkus-CI-Docker-Client"}' ~/.docker/config.json) > ~/.docker/config.json + if [ -f ~/.docker/config.json ]; then + cat <<< $(jq '.HttpHeaders += {"User-Agent": "Quarkus-CI-Docker-Client"}' ~/.docker/config.json) > ~/.docker/config.json + fi - name: Build env: TEST_MODULES: ${{matrix.test-modules}} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java index 24a1c175a3059..0e44200ee8043 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java @@ -1309,12 +1309,14 @@ boolean isRestartNeeded(String changedFile) { } } // Then try to match a new file that was added to a resource root - Boolean ret = watchedFilePaths.get(changedFile); + Boolean ret = watchedFilePaths != null ? watchedFilePaths.get(changedFile) : null; if (ret == null) { - ret = false; - for (Entry, Boolean> e : watchedFilePredicates) { - if (e.getKey().test(changedFile)) { - ret = ret || e.getValue(); + ret = Boolean.FALSE; + if (watchedFilePredicates != null) { + for (Entry, Boolean> e : watchedFilePredicates) { + if (e.getKey().test(changedFile)) { + ret = ret || e.getValue(); + } } } } 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 b08ebf2ec4c3a..2948d61ed43b5 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java @@ -12,6 +12,7 @@ import java.util.Optional; import java.util.concurrent.Callable; import java.util.function.Supplier; +import java.util.stream.Collectors; import jakarta.inject.Inject; @@ -96,7 +97,9 @@ public int run(String... args) throws Exception { boolean pluginCommand = args.length >= 1 && (args[0].equals("plug") || args[0].equals("plugin")); try { - boolean existingCommand = checkMissingCommand(cmd, args).isEmpty(); + Optional missingCommand = checkMissingCommand(cmd, args); + + boolean existingCommand = missingCommand.isEmpty(); // 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. @@ -108,8 +111,7 @@ public int run(String... args) throws Exception { pluginManager.syncIfNeeded(); Map plugins = new HashMap<>(pluginManager.getInstalledPlugins()); pluginCommandFactory.populateCommands(cmd, plugins); - Optional missing = checkMissingCommand(cmd, args); - missing.ifPresent(m -> { + missingCommand.ifPresent(m -> { try { Map installable = pluginManager.getInstallablePlugins(); if (installable.containsKey(m)) { @@ -119,11 +121,13 @@ public int run(String... args) throws Exception { output.info("Command %s not installed but the following plugin is available:\n%s", m, table.getContent()); if (interactiveMode && Prompt.yesOrNo(true, - "Would you like to install it now ?", + "Would you like to install it now?", args)) { pluginManager.addPlugin(m).ifPresent(added -> plugins.put(added.getName(), added)); pluginCommandFactory.populateCommands(cmd, plugins); } + } else { + output.error("Command %s is missing and can't be installed.", m); } } catch (Exception e) { output.error("Command %s is missing and can't be installed.", m); @@ -136,7 +140,7 @@ public int run(String... args) throws Exception { } /** - * Recursivelly processes the arguments passed to the command and checks wether a subcommand is missing. + * Process the arguments passed and return an identifier of the potentially missing subcommand if any. * * @param root the root command * @param args the arguments passed to the root command @@ -148,17 +152,26 @@ public Optional checkMissingCommand(CommandLine root, String[] args) { } try { - ParseResult result = root.parseArgs(args); - if (args.length == 1) { - return Optional.empty(); - } - CommandLine next = root.getSubcommands().get(args[0]); - if (next == null) { - return Optional.of(args[0]); - } - String[] remaining = new String[args.length - 1]; - System.arraycopy(args, 1, remaining, 0, remaining.length); - return checkMissingCommand(next, remaining).map(nextMissing -> root.getCommandName() + "-" + nextMissing); + ParseResult currentParseResult = root.parseArgs(args); + StringBuilder missingCommand = new StringBuilder(); + + do { + if (missingCommand.length() > 0) { + missingCommand.append("-"); + } + missingCommand.append(currentParseResult.commandSpec().name()); + + List unmatchedSubcommands = currentParseResult.unmatched().stream() + .filter(u -> !u.startsWith("-")).collect(Collectors.toList()); + if (!unmatchedSubcommands.isEmpty()) { + missingCommand.append("-").append(unmatchedSubcommands.get(0)); + return Optional.of(missingCommand.toString()); + } + + currentParseResult = currentParseResult.subcommand(); + } while (currentParseResult != null); + + return Optional.empty(); } catch (UnmatchedArgumentException e) { return Optional.of(args[0]); } diff --git a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc index 534901043c870..a2259e91ce324 100644 --- a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc +++ b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc @@ -251,7 +251,6 @@ Finally, you need to configure your datasource specifically to use the socket fa ---- quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.url=jdbc:postgresql:///mydatabase <1> -quarkus.datasource.jdbc.driver=org.postgresql.Driver quarkus.datasource.username=quarkus quarkus.datasource.password=quarkus quarkus.datasource.jdbc.additional-jdbc-properties.cloudSqlInstance=project-id:gcp-region:instance <2> diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index 0acbb0a762fe9..1c2140715c483 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -746,7 +746,7 @@ This can be achieved by setting to DEBUG the following log category inside your [source,properties] ---- -quarkus.log.category."io.quarkus.mongodb.panache.runtime".level=DEBUG +quarkus.log.category."io.quarkus.mongodb.panache.common.runtime".level=DEBUG ---- == The PojoCodecProvider: easy object to BSON document conversion. diff --git a/docs/src/main/asciidoc/rabbitmq-reference.adoc b/docs/src/main/asciidoc/rabbitmq-reference.adoc index bd421a5b2b202..d264e5aa97e47 100644 --- a/docs/src/main/asciidoc/rabbitmq-reference.adoc +++ b/docs/src/main/asciidoc/rabbitmq-reference.adoc @@ -714,7 +714,7 @@ Type: _boolean_ | false | `false` Type: _string_ | false | `#` -| [.no-hyphens]#*content-type-override*# | Override the content_type attribute of the incoming message, should be a valid MINE type +| [.no-hyphens]#*content-type-override*# | Override the content_type attribute of the incoming message, should be a valid MIME type Type: _string_ | false | diff --git a/docs/src/main/asciidoc/security-csrf-prevention.adoc b/docs/src/main/asciidoc/security-csrf-prevention.adoc index 880487683477f..b56e7d49ad941 100644 --- a/docs/src/main/asciidoc/security-csrf-prevention.adoc +++ b/docs/src/main/asciidoc/security-csrf-prevention.adoc @@ -11,7 +11,7 @@ include::_attributes.adoc[] https://owasp.org/www-community/attacks/csrf[Cross-Site Request Forgery (CSRF)] is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. -Quarkus Security provides a CSRF prevention feature which implements https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie[Double Submit Cookie] and [CSRF Request Header] techniques. +Quarkus Security provides a CSRF prevention feature which implements https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie[Double Submit Cookie] and <> techniques. `Double Submit Cookie` technique requires that the CSRF token sent as `HTTPOnly`, optionally signed, cookie to the client, and directly embedded in a hidden form input of server-side rendered HTML forms, or submitted as a request header value. @@ -139,6 +139,7 @@ You can get `HMAC` signatures created for the generated CSRF tokens and have the quarkus.csrf-reactive.token-signature-key=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow ---- +[[csrf-request-header]] == CSRF Request Header If HTML `form` tags are not used and you need to pass CSRF token as a header, then inject the header name and token, for example, into HTMX: diff --git a/docs/src/main/asciidoc/security-getting-started-tutorial.adoc b/docs/src/main/asciidoc/security-getting-started-tutorial.adoc index da06f8669bc1a..30d84156ad5fa 100644 --- a/docs/src/main/asciidoc/security-getting-started-tutorial.adoc +++ b/docs/src/main/asciidoc/security-getting-started-tutorial.adoc @@ -462,9 +462,9 @@ As you can see in this code sample, you do not need to start the test container [NOTE] ==== -When you start your application in dev mode, `Dev Services for PostgreSQL` launches a `PostgreSQL` `devmode` container so that you can start developing your application. +When you start your application in dev mode, Dev Services for PostgreSQL launches a PostgreSQL dev mode container so that you can start developing your application. While developing your application, you can add tests one by one and run them using the xref:continuous-testing.adoc[Continuous Testing] feature. -`Dev Services for PostgreSQL` supports testing while you develop by providing a separate `PostgreSQL` test container that does not conflict with the `devmode` container. +Dev Services for PostgreSQL supports testing while you develop by providing a separate PostgreSQL test container that does not conflict with the dev mode container. ==== === Use Curl or a browser to test your application diff --git a/docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc b/docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc index f652fa7ca262a..e0796c3b007c0 100644 --- a/docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc +++ b/docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc @@ -914,7 +914,7 @@ public class GreetingResourceTest { } ---- -If you recall, when the application was started in devmode, the following could be seen in the CLI window: +If you recall, when the application was started in dev mode, the following could be seen in the CLI window: image::auth0-devmode-started.png[Auth0 DevMode started] @@ -1075,7 +1075,7 @@ Open a browser, access http://localhost:8080/hello and get the name displayed in == Troubleshooting -The steps described in this tutorial should work exactly as the tutorial describes. You might have to clear the browser cookies when accessing the updated Quarkus endpoint if you have already completed the authentication. You might need to restart the Quarkus application manually in devmode but it is not expected. If you need help completing this tutorial, you can get in touch with the Quarkus team. +The steps described in this tutorial should work exactly as the tutorial describes. You might have to clear the browser cookies when accessing the updated Quarkus endpoint if you have already completed the authentication. You might need to restart the Quarkus application manually in dev mode but it is not expected. If you need help completing this tutorial, you can get in touch with the Quarkus team. == Summary diff --git a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc index 0d99374a5b348..dbb796ef7f5c9 100644 --- a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc @@ -204,7 +204,7 @@ To make Dev UI more useful for supporting the development of OIDC `web-app` appl It will ensure that all Dev UI options described in <> will be available when your `web-app` application is run in dev mode. The limitation of this approach is that both access and ID tokens returned with the code flow and acquired with Dev UI will be sent to the endpoint as HTTP `Bearer` tokens - which will not work well if your endpoint requires the injection of `IdToken`. However, it will work as expected if your `web-app` application only uses the access token, for example, as a source of roles or to get `UserInfo`, even if it is assumed to be a `service` application in dev mode. -Even a better option is to use a `hybrid` application type in devmode: +Even a better option is to use a `hybrid` application type in dev mode: [source,properties] ---- diff --git a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc index 7c9bb2dd06daf..70894fafbc9ab 100644 --- a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc @@ -270,7 +270,7 @@ public class CustomTenantResolver implements TenantResolver { You can define multiple tenants in your configuration file, just make sure they have a unique alias so that you can map them properly when resolving a tenant from your `TenantResolver` implementation. -However, using a static tenant resolution (configuring tenants in `application.properties` and resolving them with `TenantResolver`) prevents testing the endpoint with `Dev Services for Keycloak` since `Dev Services for Keycloak` has no knowledge of how the requests will be mapped to individual tenants and can not dynamically provide tenant-specific `quarkus.oidc..auth-server-url` values and therefore using `%prod` prefixes with the tenant-specific URLs in `application.properties` will not work in tests or devmode. +However, using a static tenant resolution (configuring tenants in `application.properties` and resolving them with `TenantResolver`) prevents testing the endpoint with `Dev Services for Keycloak` since `Dev Services for Keycloak` has no knowledge of how the requests will be mapped to individual tenants and can not dynamically provide tenant-specific `quarkus.oidc..auth-server-url` values and therefore using `%prod` prefixes with the tenant-specific URLs in `application.properties` will not work in test or dev mode. [NOTE] ==== diff --git a/docs/src/main/asciidoc/security-openid-connect-providers.adoc b/docs/src/main/asciidoc/security-openid-connect-providers.adoc index 2c0d7860d3c40..79a39a1e0aa7a 100644 --- a/docs/src/main/asciidoc/security-openid-connect-providers.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-providers.adoc @@ -662,7 +662,7 @@ The pattern of authenticating with a given provider, where the endpoint uses eit == HTTPS Redirect URL -Some providers will only accept HTTPS-based redirect URLs. Tools such as https://ngrok.com/[ngrok] https://linuxhint.com/set-up-use-ngrok/[can be set up] to help testing such providers with Quarkus endpoints running on localhost in devmode. +Some providers will only accept HTTPS-based redirect URLs. Tools such as https://ngrok.com/[ngrok] https://linuxhint.com/set-up-use-ngrok/[can be set up] to help testing such providers with Quarkus endpoints running on localhost in dev mode. == Rate Limiting diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java index 694b690fe797f..b75764772a912 100644 --- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java +++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java @@ -66,8 +66,6 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi String cookieToken = getCookieToken(routing, config); if (cookieToken != null) { - routing.put(CSRF_TOKEN_KEY, cookieToken); - try { int cookieTokenSize = Base64.getUrlDecoder().decode(cookieToken).length; // HMAC SHA256 output is 32 bytes long @@ -98,10 +96,10 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi // unsafe HTTP method, token is required // Check the header first - String csrfTokenInHeader = requestContext.getHeaderString(config.tokenHeaderName); - if (csrfTokenInHeader != null) { + String csrfTokenHeaderParam = requestContext.getHeaderString(config.tokenHeaderName); + if (csrfTokenHeaderParam != null) { LOG.debugf("CSRF token found in the token header"); - verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenInHeader); + verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenHeaderParam); return; } @@ -128,9 +126,9 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi ResteasyReactiveRequestContext rrContext = (ResteasyReactiveRequestContext) requestContext .getServerRequestContext(); - String csrfToken = (String) rrContext.getFormParameter(config.formFieldName, true, false); + String csrfTokenFormParam = (String) rrContext.getFormParameter(config.formFieldName, true, false); LOG.debugf("CSRF token found in the form parameter"); - verifyCsrfToken(requestContext, routing, config, cookieToken, csrfToken); + verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenFormParam); return; } else if (cookieToken == null) { @@ -159,6 +157,7 @@ private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestCont requestContext.abortWith(badClientRequest()); return; } else { + routing.put(CSRF_TOKEN_KEY, csrfToken); routing.put(CSRF_TOKEN_VERIFIED, true); return; } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java index e3a4b7de2a890..384f10ed0bc43 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java @@ -10,6 +10,7 @@ import io.vertx.core.datagram.DatagramSocketOptions; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.impl.NoStackTraceException; import io.vertx.core.metrics.MetricsOptions; import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.NetServerOptions; @@ -60,7 +61,7 @@ public MetricsOptions newOptions() { @Override public HttpServerMetrics createHttpServerMetrics(HttpServerOptions options, SocketAddress localAddress) { if (httpBinderConfiguration == null) { - throw new IllegalStateException("HttpBinderConfiguration was not found"); + throw new NoStackTraceException("HttpBinderConfiguration was not found"); } if (httpBinderConfiguration.isServerEnabled()) { log.debugf("Create HttpServerMetrics with options %s and address %s", options, localAddress); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java index c10b4923d2f1f..740d1782f26b9 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java @@ -4,6 +4,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; import java.net.URI; import java.util.List; import java.util.Map; @@ -13,17 +17,24 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Priorities; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.ContextResolver; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; +import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import com.fasterxml.jackson.databind.ObjectMapper; + import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; import io.quarkus.test.QuarkusUnitTest; @@ -78,6 +89,7 @@ public Map> callClient(String uri) { } } + @RegisterProvider(ErroneousJacksonBasicMessageBodyReader.class) public interface Client { @GET Map> get(); @@ -101,6 +113,20 @@ public ClientHeadersFactory getContext(Class aClass) { } } + @Priority(Priorities.USER + 100) + public static class ErroneousJacksonBasicMessageBodyReader extends JacksonBasicMessageBodyReader { + public ErroneousJacksonBasicMessageBodyReader() { + super(new ObjectMapper()); + } + + @Override + public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + throw new IllegalStateException("should never be called"); + } + } + public static class CustomClientHeadersFactory implements ClientHeadersFactory { private final String value; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index 27340b943a063..76693f02a196b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -84,7 +84,8 @@ public final class Types { // TODO: add a extensible banning mechanism based on predicates if we find that this set needs to grow... private static final Set BANNED_INTERFACE_TYPES = new HashSet<>( Arrays.asList(DotName.createSimple("java.lang.constant.ConstantDesc"), - DotName.createSimple("java.lang.constant.Constable"))); + DotName.createSimple("java.lang.constant.Constable"), + DotName.createSimple("java.util.SequencedCollection"))); private Types() { } diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java index 72a269f62e419..26af7a3155e4e 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java @@ -280,6 +280,9 @@ private void register(Object component, Integer priority) { resourceReader .setMediaTypeStrings( consumes != null ? Arrays.asList(consumes.value()) : WILDCARD_STRING_LIST); + if (priority != null) { + resourceReader.setPriority(priority); + } Type[] args = Types.findParameterizedTypes(componentClass, MessageBodyReader.class); resourceReaders.add(args != null && args.length == 1 ? Types.getRawType(args[0]) : Object.class, resourceReader); @@ -298,6 +301,9 @@ private void register(Object component, Integer priority) { resourceWriter .setMediaTypeStrings( produces != null ? Arrays.asList(produces.value()) : WILDCARD_STRING_LIST); + if (priority != null) { + resourceWriter.setPriority(priority); + } Type[] args = Types.findParameterizedTypes(componentClass, MessageBodyWriter.class); resourceWriters.add(args != null && args.length == 1 ? Types.getRawType(args[0]) : Object.class, resourceWriter); diff --git a/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java b/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java index 0f66abf3ed4a1..d3c7b18c47306 100644 --- a/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java +++ b/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java @@ -29,6 +29,12 @@ public class TestResource { @Inject Template csrfTokenForm; + @Inject + Template csrfTokenFirstForm; + + @Inject + Template csrfTokenSecondForm; + @Inject Template csrfTokenHeader; @@ -49,6 +55,14 @@ public TemplateInstance getCsrfTokenForm() { return csrfTokenForm.instance(); } + @GET + @Path("/csrfTokenFirstForm") + @Produces(MediaType.TEXT_HTML) + @Authenticated + public TemplateInstance getCsrfTokenFirstForm() { + return csrfTokenFirstForm.instance(); + } + @GET @Path("/csrfTokenWithFormRead") @Produces(MediaType.TEXT_HTML) @@ -71,6 +85,22 @@ public String postCsrfTokenForm(@FormParam("name") String name, @HeaderParam("X- return name + ":" + routingContext.get("csrf_token_verified", false) + ":tokenHeaderIsSet=" + (csrfHeader != null); } + @POST + @Path("/csrfTokenFirstForm") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.TEXT_HTML) + public TemplateInstance postCsrfTokenFirstForm() { + return csrfTokenSecondForm.instance(); + } + + @POST + @Path("/csrfTokenSecondForm") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.TEXT_PLAIN) + public String postCsrfTokenSecondForm(@FormParam("name") String name, @HeaderParam("X-CSRF-TOKEN") String csrfHeader) { + return name + ":" + routingContext.get("csrf_token_verified", false) + ":tokenHeaderIsSet=" + (csrfHeader != null); + } + @POST @Path("/csrfTokenWithFormRead") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) diff --git a/integration-tests/csrf-reactive/src/main/resources/application.properties b/integration-tests/csrf-reactive/src/main/resources/application.properties index 23f5d88b8c1f0..9a778c50dda03 100644 --- a/integration-tests/csrf-reactive/src/main/resources/application.properties +++ b/integration-tests/csrf-reactive/src/main/resources/application.properties @@ -1,5 +1,5 @@ quarkus.csrf-reactive.cookie-name=csrftoken -quarkus.csrf-reactive.create-token-path=/service/csrfTokenForm,/service/csrfTokenWithFormRead,/service/csrfTokenMultipart,/service/csrfTokenWithHeader +quarkus.csrf-reactive.create-token-path=/service/csrfTokenForm,/service/csrfTokenFirstForm,/service/csrfTokenSecondForm,/service/csrfTokenWithFormRead,/service/csrfTokenMultipart,/service/csrfTokenWithHeader quarkus.csrf-reactive.token-signature-key=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow quarkus.http.auth.basic=true diff --git a/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html new file mode 100644 index 0000000000000..71dadecdf41e2 --- /dev/null +++ b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html @@ -0,0 +1,17 @@ + + + + +CSRF Token First Form Test + + +

CSRF Test

+ +
+ + +

Your Name:

+

+
+ + diff --git a/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html new file mode 100644 index 0000000000000..7c28b45f662a6 --- /dev/null +++ b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html @@ -0,0 +1,17 @@ + + + + +CSRF Token Second Form Test + + +

CSRF Test

+ +
+ + +

Your Name:

+

+
+ + diff --git a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java index e6d50f0fc39b7..770d36441e300 100644 --- a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java +++ b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java @@ -61,6 +61,36 @@ public void testCsrfTokenInForm() throws Exception { } } + @Test + public void testCsrfTokenTwoForms() throws Exception { + try (final WebClient webClient = createWebClient()) { + webClient.addRequestHeader("Authorization", basicAuth("alice", "alice")); + HtmlPage htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenFirstForm"); + + assertEquals("CSRF Token First Form Test", htmlPage.getTitleText()); + + HtmlForm loginForm = htmlPage.getForms().get(0); + + loginForm.getInputByName("name").setValueAttribute("alice"); + + assertNotNull(webClient.getCookieManager().getCookie("csrftoken")); + + htmlPage = loginForm.getInputByName("submit").click(); + + assertEquals("CSRF Token Second Form Test", htmlPage.getTitleText()); + + loginForm = htmlPage.getForms().get(0); + + loginForm.getInputByName("name").setValueAttribute("alice"); + + TextPage textPage = loginForm.getInputByName("submit").click(); + assertNotNull(webClient.getCookieManager().getCookie("csrftoken")); + assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); + + webClient.getCookieManager().clearCookies(); + } + } + @Test public void testCsrfTokenWithFormRead() throws Exception { try (final WebClient webClient = createWebClient()) { diff --git a/pom.xml b/pom.xml index 3e14413b69ea7..e041b8d91bff9 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ jdbc:postgresql:hibernate_orm_test 4.5.1 - 0.0.100 + 0.0.101 false false