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 7376ca25cfa3..144f93a65f89 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 @@ -34,6 +34,7 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.CloudFoundryWebFluxEndpointHandlerMapping.CloudFoundryWebFluxEndpointHandlerMappingRuntimeHints; 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; @@ -64,12 +65,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; } @@ -84,6 +88,10 @@ protected LinksHandler getLinksHandler() { 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 7dc19baddfa2..3317dad70a5a 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 @@ -32,10 +32,10 @@ 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; @@ -80,6 +80,8 @@ @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) public class ReactiveCloudFoundryActuatorAutoConfiguration { + private static final String BASE_PATH = "/cloudfoundryapplication"; + @Bean @ConditionalOnMissingBean @ConditionalOnAvailableEndpoint @@ -115,9 +117,8 @@ public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHand 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, @@ -153,25 +154,33 @@ private CorsConfiguration getCorsConfiguration() { static class IgnoredPathsSecurityConfiguration { @Bean - WebFilterChainPostProcessor webFilterChainPostProcessor() { - return new WebFilterChainPostProcessor(); + WebFilterChainPostProcessor webFilterChainPostProcessor( + CloudFoundryWebFluxEndpointHandlerMapping handlerMapping) { + return new WebFilterChainPostProcessor(handlerMapping); } } 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 webFilterChainProxy) { - return postProcess(webFilterChainProxy); + return postProcess(webFilterChainProxy, 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)); @@ -180,6 +189,14 @@ private WebFilterChainProxy postProcess(WebFilterChainProxy existing) { 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 15fb1fcecd97..35ba2cab29e9 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 @@ -30,10 +30,10 @@ 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; @@ -65,6 +65,9 @@ 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; @@ -84,6 +87,8 @@ @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) public class CloudFoundryActuatorAutoConfiguration { + private static final String BASE_PATH = "/cloudfoundryapplication"; + @Bean @ConditionalOnMissingBean @ConditionalOnAvailableEndpoint @@ -121,8 +126,7 @@ public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServl 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, @@ -162,8 +166,9 @@ private CorsConfiguration getCorsConfiguration() { public static class IgnoredCloudFoundryPathsWebSecurityConfiguration { @Bean - IgnoredCloudFoundryPathsWebSecurityCustomizer ignoreCloudFoundryPathsWebSecurityCustomizer() { - return new IgnoredCloudFoundryPathsWebSecurityCustomizer(); + IgnoredCloudFoundryPathsWebSecurityCustomizer ignoreCloudFoundryPathsWebSecurityCustomizer( + CloudFoundryWebEndpointServletHandlerMapping handlerMapping) { + return new IgnoredCloudFoundryPathsWebSecurityCustomizer(handlerMapping); } } @@ -171,9 +176,22 @@ IgnoredCloudFoundryPathsWebSecurityCustomizer ignoreCloudFoundryPathsWebSecurity @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 4513c7ad3dcf..4b3b498a279c 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 @@ -36,6 +36,7 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryWebEndpointServletHandlerMapping.CloudFoundryWebEndpointServletHandlerMappingRuntimeHints; 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; @@ -67,13 +68,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 @@ -87,6 +91,10 @@ protected LinksHandler getLinksHandler() { 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 ca2c9fd94f0a..3c718d1d4533 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,9 +17,12 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; +import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -29,15 +32,16 @@ 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; @@ -245,9 +249,10 @@ CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebEndpointServletHandlerM 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 715f15c934b9..fb163a736457 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 @@ -94,6 +94,8 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); + private static final String BASE_PATH = "/cloudfoundryapplication"; + @AfterEach void close() { HttpResources.reset(); @@ -175,21 +177,24 @@ void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent() { @Test @SuppressWarnings("unchecked") void cloudFoundryPathsIgnoredBySpringSecurity() { - this.contextRunner + 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())) @@ -199,6 +204,13 @@ void cloudFoundryPathsIgnoredBySpringSecurity() { } + 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 void cloudFoundryPlatformInactive() { this.contextRunner 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 5314b4e6b8b2..a1bbf024ebe3 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 @@ -168,20 +170,31 @@ void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent() { @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 6663f31739fc..9ff41ba1f316 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,9 +17,12 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; +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; @@ -29,15 +32,16 @@ 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; @@ -242,9 +246,10 @@ CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandl 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