diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java index 50833eb8fce..8f8648aabdf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java @@ -49,6 +49,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.UriComponentsBuilder; /** * A custom {@link RequestMappingInfoHandlerMapping} that makes web endpoints available on @@ -110,8 +111,9 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH return new ResponseEntity<>(securityResponse.getStatus()); } AccessLevel accessLevel = exchange.getAttribute(AccessLevel.REQUEST_ATTRIBUTE); + String requestUri = UriComponentsBuilder.fromUri(request.getURI()).replaceQuery(null).toUriString(); Map links = CloudFoundryWebFluxEndpointHandlerMapping.this.linksResolver - .resolveLinks(request.getURI().toString()); + .resolveLinks(requestUri); return new ResponseEntity<>( Collections.singletonMap("_links", getAccessibleLinks(accessLevel, links)), HttpStatus.OK); }); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java index ed973e4c3aa..df1aa433bea 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java @@ -61,6 +61,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.cors.CorsConfiguration; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -166,6 +167,26 @@ class CloudFoundryWebFluxEndpointIntegrationTests { .isEqualTo(true))); } + @Test + void linksToOtherEndpointsWithQueryStringShouldNotContainQueryInHref() { + given(this.tokenValidator.validate(any())).willReturn(Mono.empty()); + given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.FULL)); + this.contextRunner.run(withWebTestClient((client) -> client.get() + .uri("/cfApplication?x=1") + .accept(MediaType.APPLICATION_JSON) + .header("Authorization", "bearer " + mockAccessToken()) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("_links.self.href") + .value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication")) + .jsonPath("_links.info.href") + .value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication/info")) + .jsonPath("_links.env.href") + .value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication/env")))); + } + @Test void linksToOtherEndpointsForbidden() { CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,