From 307f3c339912466e78fcdac648fff95a4edea573 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 20 Apr 2023 07:42:18 +0100 Subject: [PATCH] Use endpoint mappings in CloudFoundry integration Closes gh-35411 --- ...dFoundryWebFluxEndpointHandlerMapping.java | 12 +++++-- ...CloudFoundryActuatorAutoConfiguration.java | 35 ++++++++++++++----- ...CloudFoundryActuatorAutoConfiguration.java | 30 ++++++++++++---- ...undryWebEndpointServletHandlerMapping.java | 12 +++++-- ...oundryWebFluxEndpointIntegrationTests.java | 13 ++++--- ...FoundryActuatorAutoConfigurationTests.java | 30 +++++++++++----- ...FoundryActuatorAutoConfigurationTests.java | 21 ++++++++--- ...FoundryMvcWebEndpointIntegrationTests.java | 13 ++++--- 8 files changed, 126 insertions(+), 40 deletions(-) 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 0df6400d0a0..8d3e2d3f44a 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 @@ -28,6 +28,7 @@ import reactor.core.publisher.Mono; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -56,12 +57,15 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH private final EndpointLinksResolver linksResolver; + private final Collection> allEndpoints; + CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping, Collection endpoints, EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration, CloudFoundrySecurityInterceptor securityInterceptor, - EndpointLinksResolver linksResolver) { + Collection> allEndpoints) { super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, true); - this.linksResolver = linksResolver; + this.linksResolver = new EndpointLinksResolver(allEndpoints); + this.allEndpoints = allEndpoints; this.securityInterceptor = securityInterceptor; } @@ -76,6 +80,10 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH return new CloudFoundryLinksHandler(); } + Collection> getAllEndpoints() { + return this.allEndpoints; + } + class CloudFoundryLinksHandler implements LinksHandler { @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java index 8f58aeda502..172975c7912 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java @@ -33,10 +33,10 @@ import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoC import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; -import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; +import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; @@ -82,6 +82,8 @@ import org.springframework.web.server.WebFilter; @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) public class ReactiveCloudFoundryActuatorAutoConfiguration { + private static final String BASE_PATH = "/cloudfoundryapplication"; + @Bean @ConditionalOnMissingBean @ConditionalOnAvailableEndpoint @@ -117,9 +119,8 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration { List> allEndpoints = new ArrayList<>(); allEndpoints.addAll(webEndpoints); allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); - return new CloudFoundryWebFluxEndpointHandlerMapping(new EndpointMapping("/cloudfoundryapplication"), - webEndpoints, endpointMediaTypes, getCorsConfiguration(), securityInterceptor, - new EndpointLinksResolver(allEndpoints)); + return new CloudFoundryWebFluxEndpointHandlerMapping(new EndpointMapping(BASE_PATH), webEndpoints, + endpointMediaTypes, getCorsConfiguration(), securityInterceptor, allEndpoints); } private CloudFoundrySecurityInterceptor getSecurityInterceptor(WebClient.Builder webClientBuilder, @@ -155,25 +156,33 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration { static class IgnoredPathsSecurityConfiguration { @Bean - WebFilterChainPostProcessor webFilterChainPostProcessor() { - return new WebFilterChainPostProcessor(); + WebFilterChainPostProcessor webFilterChainPostProcessor( + CloudFoundryWebFluxEndpointHandlerMapping handlerMapping) { + return new WebFilterChainPostProcessor(handlerMapping); } } private static class WebFilterChainPostProcessor implements BeanPostProcessor { + private final PathMappedEndpoints pathMappedEndpoints; + + WebFilterChainPostProcessor(CloudFoundryWebFluxEndpointHandlerMapping handlerMapping) { + this.pathMappedEndpoints = new PathMappedEndpoints(BASE_PATH, handlerMapping::getAllEndpoints); + } + @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WebFilterChainProxy) { - return postProcess((WebFilterChainProxy) bean); + return postProcess((WebFilterChainProxy) bean, this.pathMappedEndpoints); } return bean; } - private WebFilterChainProxy postProcess(WebFilterChainProxy existing) { + private WebFilterChainProxy postProcess(WebFilterChainProxy existing, PathMappedEndpoints pathMappedEndpoints) { + List paths = getPaths(pathMappedEndpoints); ServerWebExchangeMatcher cloudFoundryRequestMatcher = ServerWebExchangeMatchers - .pathMatchers("/cloudfoundryapplication/**"); + .pathMatchers(paths.toArray(new String[] {})); WebFilter noOpFilter = (exchange, chain) -> chain.filter(exchange); MatcherSecurityWebFilterChain ignoredRequestFilterChain = new MatcherSecurityWebFilterChain( cloudFoundryRequestMatcher, Collections.singletonList(noOpFilter)); @@ -182,6 +191,14 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration { return new WebFilterChainProxy(ignoredRequestFilterChain, allRequestsFilterChain); } + private static List getPaths(PathMappedEndpoints pathMappedEndpoints) { + List paths = new ArrayList<>(); + pathMappedEndpoints.getAllPaths().forEach((path) -> paths.add(path + "/**")); + paths.add(BASE_PATH); + paths.add(BASE_PATH + "/"); + return paths; + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java index 04f24deeb77..a76756565f7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java @@ -31,10 +31,10 @@ import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfi import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; -import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; +import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; import org.springframework.boot.actuate.health.HealthEndpoint; @@ -66,6 +66,9 @@ import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.CollectionUtils; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.DispatcherServlet; @@ -86,6 +89,8 @@ import org.springframework.web.servlet.DispatcherServlet; @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) public class CloudFoundryActuatorAutoConfiguration { + private static final String BASE_PATH = "/cloudfoundryapplication"; + @Bean @ConditionalOnMissingBean @ConditionalOnAvailableEndpoint @@ -123,8 +128,7 @@ public class CloudFoundryActuatorAutoConfiguration { allEndpoints.addAll(servletEndpointsSupplier.getEndpoints()); allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); return new CloudFoundryWebEndpointServletHandlerMapping(new EndpointMapping("/cloudfoundryapplication"), - webEndpoints, endpointMediaTypes, getCorsConfiguration(), securityInterceptor, - new EndpointLinksResolver(allEndpoints)); + webEndpoints, endpointMediaTypes, getCorsConfiguration(), securityInterceptor, allEndpoints); } private CloudFoundrySecurityInterceptor getSecurityInterceptor(RestTemplateBuilder restTemplateBuilder, @@ -164,8 +168,9 @@ public class CloudFoundryActuatorAutoConfiguration { public static class IgnoredCloudFoundryPathsWebSecurityConfiguration { @Bean - IgnoredCloudFoundryPathsWebSecurityCustomizer ignoreCloudFoundryPathsWebSecurityCustomizer() { - return new IgnoredCloudFoundryPathsWebSecurityCustomizer(); + IgnoredCloudFoundryPathsWebSecurityCustomizer ignoreCloudFoundryPathsWebSecurityCustomizer( + CloudFoundryWebEndpointServletHandlerMapping handlerMapping) { + return new IgnoredCloudFoundryPathsWebSecurityCustomizer(handlerMapping); } } @@ -173,9 +178,22 @@ public class CloudFoundryActuatorAutoConfiguration { @Order(SecurityProperties.IGNORED_ORDER) static class IgnoredCloudFoundryPathsWebSecurityCustomizer implements WebSecurityCustomizer { + private final PathMappedEndpoints pathMappedEndpoints; + + IgnoredCloudFoundryPathsWebSecurityCustomizer(CloudFoundryWebEndpointServletHandlerMapping handlerMapping) { + this.pathMappedEndpoints = new PathMappedEndpoints(BASE_PATH, handlerMapping::getAllEndpoints); + } + @Override public void customize(WebSecurity web) { - web.ignoring().requestMatchers(new AntPathRequestMatcher("/cloudfoundryapplication/**")); + List requestMatchers = new ArrayList<>(); + this.pathMappedEndpoints.getAllPaths() + .forEach((path) -> requestMatchers.add(new AntPathRequestMatcher(path + "/**"))); + requestMatchers.add(new AntPathRequestMatcher(BASE_PATH)); + requestMatchers.add(new AntPathRequestMatcher(BASE_PATH + "/")); + if (!CollectionUtils.isEmpty(requestMatchers)) { + web.ignoring().requestMatchers(new OrRequestMatcher(requestMatchers)); + } } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java index 472535cf6b2..8bedd5cea7d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java @@ -31,6 +31,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -60,13 +61,16 @@ class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpoin private final EndpointLinksResolver linksResolver; + private final Collection> allEndpoints; + CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping, Collection endpoints, EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration, CloudFoundrySecurityInterceptor securityInterceptor, - EndpointLinksResolver linksResolver) { + Collection> allEndpoints) { super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, true); this.securityInterceptor = securityInterceptor; - this.linksResolver = linksResolver; + this.linksResolver = new EndpointLinksResolver(allEndpoints); + this.allEndpoints = allEndpoints; } @Override @@ -80,6 +84,10 @@ class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpoin return new CloudFoundryLinksHandler(); } + Collection> getAllEndpoints() { + return this.allEndpoints; + } + class CloudFoundryLinksHandler implements LinksHandler { @Override 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 7b1d3ad0338..7001df2a7f3 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 @@ -17,8 +17,11 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -28,15 +31,16 @@ import reactor.core.publisher.Mono; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper; -import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; @@ -184,9 +188,10 @@ class CloudFoundryWebFluxEndpointIntegrationTests { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOrigins(Arrays.asList("https://example.com")); corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST")); - return new CloudFoundryWebFluxEndpointHandlerMapping(new EndpointMapping("/cfApplication"), - webEndpointDiscoverer.getEndpoints(), endpointMediaTypes, corsConfiguration, interceptor, - new EndpointLinksResolver(webEndpointDiscoverer.getEndpoints())); + Collection webEndpoints = webEndpointDiscoverer.getEndpoints(); + List> allEndpoints = new ArrayList<>(webEndpoints); + return new CloudFoundryWebFluxEndpointHandlerMapping(new EndpointMapping("/cfApplication"), webEndpoints, + endpointMediaTypes, corsConfiguration, interceptor, allEndpoints); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index fadaeb8feea..31ddcdfe6ed 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -95,6 +95,8 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); + private static final String BASE_PATH = "/cloudfoundryapplication"; + @AfterEach void close() { HttpResources.reset(); @@ -170,26 +172,36 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { @Test @SuppressWarnings("unchecked") void cloudFoundryPathsIgnoredBySpringSecurity() { - this.contextRunner.withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", - "vcap.application.cf_api:https://my-cloud-controller.com").run((context) -> { + this.contextRunner.withBean(TestEndpoint.class, TestEndpoint::new).withPropertyValues("VCAP_APPLICATION:---", + "vcap.application.application_id:my-app-id", "vcap.application.cf_api:https://my-cloud-controller.com") + .run((context) -> { WebFilterChainProxy chainProxy = context.getBean(WebFilterChainProxy.class); List filters = (List) ReflectionTestUtils .getField(chainProxy, "filters"); - Boolean cfRequestMatches = filters.get(0) - .matches(MockServerWebExchange - .from(MockServerHttpRequest.get("/cloudfoundryapplication/my-path").build())) - .block(Duration.ofSeconds(30)); - Boolean otherRequestMatches = filters.get(0) - .matches(MockServerWebExchange.from(MockServerHttpRequest.get("/some-other-path").build())) - .block(Duration.ofSeconds(30)); + Boolean cfBaseRequestMatches = getMatches(filters, BASE_PATH); + Boolean cfBaseWithTrailingSlashRequestMatches = getMatches(filters, BASE_PATH + "/"); + Boolean cfRequestMatches = getMatches(filters, BASE_PATH + "/test"); + Boolean cfRequestWithAdditionalPathMatches = getMatches(filters, BASE_PATH + "/test/a"); + Boolean otherCfRequestMatches = getMatches(filters, BASE_PATH + "/other-path"); + Boolean otherRequestMatches = getMatches(filters, "/some-other-path"); + assertThat(cfBaseRequestMatches).isTrue(); + assertThat(cfBaseWithTrailingSlashRequestMatches).isTrue(); assertThat(cfRequestMatches).isTrue(); + assertThat(cfRequestWithAdditionalPathMatches).isTrue(); + assertThat(otherCfRequestMatches).isFalse(); assertThat(otherRequestMatches).isFalse(); otherRequestMatches = filters.get(1) .matches(MockServerWebExchange.from(MockServerHttpRequest.get("/some-other-path").build())) .block(Duration.ofSeconds(30)); assertThat(otherRequestMatches).isTrue(); }); + } + private static Boolean getMatches(List filters, String urlTemplate) { + Boolean cfBaseRequestMatches = filters.get(0) + .matches(MockServerWebExchange.from(MockServerHttpRequest.get(urlTemplate).build())) + .block(Duration.ofSeconds(30)); + return cfBaseRequestMatches; } @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java index 2bc35b8b061..3956b96e7d8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java @@ -77,6 +77,8 @@ class CloudFoundryActuatorAutoConfigurationTests { ServletManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class)); + private static String BASE_PATH = "/cloudfoundryapplication"; + @Test void cloudFoundryPlatformActive() { this.contextRunner.withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", @@ -160,20 +162,31 @@ class CloudFoundryActuatorAutoConfigurationTests { @Test void cloudFoundryPathsIgnoredBySpringSecurity() { - this.contextRunner.withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") + this.contextRunner.withBean(TestEndpoint.class, TestEndpoint::new) + .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") .run((context) -> { FilterChainProxy securityFilterChain = (FilterChainProxy) context .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); SecurityFilterChain chain = securityFilterChain.getFilterChains().get(0); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setServletPath("/cloudfoundryapplication/my-path"); assertThat(chain.getFilters()).isEmpty(); - assertThat(chain.matches(request)).isTrue(); + MockHttpServletRequest request = new MockHttpServletRequest(); + testCloudFoundrySecurity(request, BASE_PATH, chain); + testCloudFoundrySecurity(request, BASE_PATH + "/", chain); + testCloudFoundrySecurity(request, BASE_PATH + "/test", chain); + testCloudFoundrySecurity(request, BASE_PATH + "/test/a", chain); + request.setServletPath(BASE_PATH + "/other-path"); + assertThat(chain.matches(request)).isFalse(); request.setServletPath("/some-other-path"); assertThat(chain.matches(request)).isFalse(); }); } + private static void testCloudFoundrySecurity(MockHttpServletRequest request, String basePath, + SecurityFilterChain chain) { + request.setServletPath(basePath); + assertThat(chain.matches(request)).isTrue(); + } + @Test void cloudFoundryPlatformInactive() { this.contextRunner.withPropertyValues() diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java index a7affad6a07..0cfcaabb6ef 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java @@ -17,8 +17,11 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -28,15 +31,16 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper; -import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; @@ -180,9 +184,10 @@ class CloudFoundryMvcWebEndpointIntegrationTests { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOrigins(Arrays.asList("https://example.com")); corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST")); - return new CloudFoundryWebEndpointServletHandlerMapping(new EndpointMapping("/cfApplication"), - webEndpointDiscoverer.getEndpoints(), endpointMediaTypes, corsConfiguration, interceptor, - new EndpointLinksResolver(webEndpointDiscoverer.getEndpoints())); + Collection webEndpoints = webEndpointDiscoverer.getEndpoints(); + List> allEndpoints = new ArrayList<>(webEndpoints); + return new CloudFoundryWebEndpointServletHandlerMapping(new EndpointMapping("/cfApplication"), webEndpoints, + endpointMediaTypes, corsConfiguration, interceptor, allEndpoints); } @Bean