Merge branch '3.0.x'

Closes gh-35087
pull/35090/head
Andy Wilkinson 2 years ago
commit 04780650a1

@ -34,6 +34,7 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.CloudFoundryWebFluxEndpointHandlerMapping.CloudFoundryWebFluxEndpointHandlerMappingRuntimeHints; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.CloudFoundryWebFluxEndpointHandlerMapping.CloudFoundryWebFluxEndpointHandlerMappingRuntimeHints;
import org.springframework.boot.actuate.endpoint.EndpointId; 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.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
@ -64,12 +65,15 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH
private final EndpointLinksResolver linksResolver; private final EndpointLinksResolver linksResolver;
private final Collection<ExposableEndpoint<?>> allEndpoints;
CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping, CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes, Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes,
CorsConfiguration corsConfiguration, CloudFoundrySecurityInterceptor securityInterceptor, CorsConfiguration corsConfiguration, CloudFoundrySecurityInterceptor securityInterceptor,
EndpointLinksResolver linksResolver) { Collection<ExposableEndpoint<?>> allEndpoints) {
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, true); super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, true);
this.linksResolver = linksResolver; this.linksResolver = new EndpointLinksResolver(allEndpoints);
this.allEndpoints = allEndpoints;
this.securityInterceptor = securityInterceptor; this.securityInterceptor = securityInterceptor;
} }
@ -84,6 +88,10 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH
return new CloudFoundryLinksHandler(); return new CloudFoundryLinksHandler();
} }
Collection<ExposableEndpoint<?>> getAllEndpoints() {
return this.allEndpoints;
}
class CloudFoundryLinksHandler implements LinksHandler { class CloudFoundryLinksHandler implements LinksHandler {
@Override @Override

@ -32,10 +32,10 @@ import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoC
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; 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.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; 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.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
@ -80,6 +80,8 @@ import org.springframework.web.server.WebFilter;
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public class ReactiveCloudFoundryActuatorAutoConfiguration { public class ReactiveCloudFoundryActuatorAutoConfiguration {
private static final String BASE_PATH = "/cloudfoundryapplication";
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint @ConditionalOnAvailableEndpoint
@ -115,9 +117,8 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>(); List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
allEndpoints.addAll(webEndpoints); allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
return new CloudFoundryWebFluxEndpointHandlerMapping(new EndpointMapping("/cloudfoundryapplication"), return new CloudFoundryWebFluxEndpointHandlerMapping(new EndpointMapping(BASE_PATH), webEndpoints,
webEndpoints, endpointMediaTypes, getCorsConfiguration(), securityInterceptor, endpointMediaTypes, getCorsConfiguration(), securityInterceptor, allEndpoints);
new EndpointLinksResolver(allEndpoints));
} }
private CloudFoundrySecurityInterceptor getSecurityInterceptor(WebClient.Builder webClientBuilder, private CloudFoundrySecurityInterceptor getSecurityInterceptor(WebClient.Builder webClientBuilder,
@ -153,25 +154,33 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
static class IgnoredPathsSecurityConfiguration { static class IgnoredPathsSecurityConfiguration {
@Bean @Bean
WebFilterChainPostProcessor webFilterChainPostProcessor() { WebFilterChainPostProcessor webFilterChainPostProcessor(
return new WebFilterChainPostProcessor(); CloudFoundryWebFluxEndpointHandlerMapping handlerMapping) {
return new WebFilterChainPostProcessor(handlerMapping);
} }
} }
static class WebFilterChainPostProcessor implements BeanPostProcessor { static class WebFilterChainPostProcessor implements BeanPostProcessor {
private final PathMappedEndpoints pathMappedEndpoints;
WebFilterChainPostProcessor(CloudFoundryWebFluxEndpointHandlerMapping handlerMapping) {
this.pathMappedEndpoints = new PathMappedEndpoints(BASE_PATH, handlerMapping::getAllEndpoints);
}
@Override @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebFilterChainProxy webFilterChainProxy) { if (bean instanceof WebFilterChainProxy webFilterChainProxy) {
return postProcess(webFilterChainProxy); return postProcess(webFilterChainProxy, this.pathMappedEndpoints);
} }
return bean; return bean;
} }
private WebFilterChainProxy postProcess(WebFilterChainProxy existing) { private WebFilterChainProxy postProcess(WebFilterChainProxy existing, PathMappedEndpoints pathMappedEndpoints) {
List<String> paths = getPaths(pathMappedEndpoints);
ServerWebExchangeMatcher cloudFoundryRequestMatcher = ServerWebExchangeMatchers ServerWebExchangeMatcher cloudFoundryRequestMatcher = ServerWebExchangeMatchers
.pathMatchers("/cloudfoundryapplication/**"); .pathMatchers(paths.toArray(new String[] {}));
WebFilter noOpFilter = (exchange, chain) -> chain.filter(exchange); WebFilter noOpFilter = (exchange, chain) -> chain.filter(exchange);
MatcherSecurityWebFilterChain ignoredRequestFilterChain = new MatcherSecurityWebFilterChain( MatcherSecurityWebFilterChain ignoredRequestFilterChain = new MatcherSecurityWebFilterChain(
cloudFoundryRequestMatcher, Collections.singletonList(noOpFilter)); cloudFoundryRequestMatcher, Collections.singletonList(noOpFilter));
@ -180,6 +189,14 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
return new WebFilterChainProxy(ignoredRequestFilterChain, allRequestsFilterChain); return new WebFilterChainProxy(ignoredRequestFilterChain, allRequestsFilterChain);
} }
private static List<String> getPaths(PathMappedEndpoints pathMappedEndpoints) {
List<String> paths = new ArrayList<>();
pathMappedEndpoints.getAllPaths().forEach((path) -> paths.add(path + "/**"));
paths.add(BASE_PATH);
paths.add(BASE_PATH + "/");
return paths;
}
} }
} }

@ -30,10 +30,10 @@ import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfi
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; 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.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; 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.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
@ -65,6 +65,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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 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.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
@ -84,6 +87,8 @@ import org.springframework.web.servlet.DispatcherServlet;
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public class CloudFoundryActuatorAutoConfiguration { public class CloudFoundryActuatorAutoConfiguration {
private static final String BASE_PATH = "/cloudfoundryapplication";
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint @ConditionalOnAvailableEndpoint
@ -121,8 +126,7 @@ public class CloudFoundryActuatorAutoConfiguration {
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints()); allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
return new CloudFoundryWebEndpointServletHandlerMapping(new EndpointMapping("/cloudfoundryapplication"), return new CloudFoundryWebEndpointServletHandlerMapping(new EndpointMapping("/cloudfoundryapplication"),
webEndpoints, endpointMediaTypes, getCorsConfiguration(), securityInterceptor, webEndpoints, endpointMediaTypes, getCorsConfiguration(), securityInterceptor, allEndpoints);
new EndpointLinksResolver(allEndpoints));
} }
private CloudFoundrySecurityInterceptor getSecurityInterceptor(RestTemplateBuilder restTemplateBuilder, private CloudFoundrySecurityInterceptor getSecurityInterceptor(RestTemplateBuilder restTemplateBuilder,
@ -162,8 +166,9 @@ public class CloudFoundryActuatorAutoConfiguration {
public static class IgnoredCloudFoundryPathsWebSecurityConfiguration { public static class IgnoredCloudFoundryPathsWebSecurityConfiguration {
@Bean @Bean
IgnoredCloudFoundryPathsWebSecurityCustomizer ignoreCloudFoundryPathsWebSecurityCustomizer() { IgnoredCloudFoundryPathsWebSecurityCustomizer ignoreCloudFoundryPathsWebSecurityCustomizer(
return new IgnoredCloudFoundryPathsWebSecurityCustomizer(); CloudFoundryWebEndpointServletHandlerMapping handlerMapping) {
return new IgnoredCloudFoundryPathsWebSecurityCustomizer(handlerMapping);
} }
} }
@ -171,9 +176,22 @@ public class CloudFoundryActuatorAutoConfiguration {
@Order(SecurityProperties.IGNORED_ORDER) @Order(SecurityProperties.IGNORED_ORDER)
static class IgnoredCloudFoundryPathsWebSecurityCustomizer implements WebSecurityCustomizer { static class IgnoredCloudFoundryPathsWebSecurityCustomizer implements WebSecurityCustomizer {
private final PathMappedEndpoints pathMappedEndpoints;
IgnoredCloudFoundryPathsWebSecurityCustomizer(CloudFoundryWebEndpointServletHandlerMapping handlerMapping) {
this.pathMappedEndpoints = new PathMappedEndpoints(BASE_PATH, handlerMapping::getAllEndpoints);
}
@Override @Override
public void customize(WebSecurity web) { public void customize(WebSecurity web) {
web.ignoring().requestMatchers(new AntPathRequestMatcher("/cloudfoundryapplication/**")); List<RequestMatcher> 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));
}
} }
} }

@ -36,6 +36,7 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryWebEndpointServletHandlerMapping.CloudFoundryWebEndpointServletHandlerMappingRuntimeHints; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryWebEndpointServletHandlerMapping.CloudFoundryWebEndpointServletHandlerMappingRuntimeHints;
import org.springframework.boot.actuate.endpoint.EndpointId; 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.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
@ -67,13 +68,16 @@ class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpoin
private final EndpointLinksResolver linksResolver; private final EndpointLinksResolver linksResolver;
private final Collection<ExposableEndpoint<?>> allEndpoints;
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping, CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes, Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes,
CorsConfiguration corsConfiguration, CloudFoundrySecurityInterceptor securityInterceptor, CorsConfiguration corsConfiguration, CloudFoundrySecurityInterceptor securityInterceptor,
EndpointLinksResolver linksResolver) { Collection<ExposableEndpoint<?>> allEndpoints) {
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, true); super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, true);
this.securityInterceptor = securityInterceptor; this.securityInterceptor = securityInterceptor;
this.linksResolver = linksResolver; this.linksResolver = new EndpointLinksResolver(allEndpoints);
this.allEndpoints = allEndpoints;
} }
@Override @Override
@ -87,6 +91,10 @@ class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpoin
return new CloudFoundryLinksHandler(); return new CloudFoundryLinksHandler();
} }
Collection<ExposableEndpoint<?>> getAllEndpoints() {
return this.allEndpoints;
}
class CloudFoundryLinksHandler implements LinksHandler { class CloudFoundryLinksHandler implements LinksHandler {
@Override @Override

@ -17,9 +17,12 @@
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive; package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -29,15 +32,16 @@ import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; 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.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper; 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.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; 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.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
@ -245,9 +249,10 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
CorsConfiguration corsConfiguration = new CorsConfiguration(); CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("https://example.com")); corsConfiguration.setAllowedOrigins(Arrays.asList("https://example.com"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST")); corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
return new CloudFoundryWebFluxEndpointHandlerMapping(new EndpointMapping("/cfApplication"), Collection<ExposableWebEndpoint> webEndpoints = webEndpointDiscoverer.getEndpoints();
webEndpointDiscoverer.getEndpoints(), endpointMediaTypes, corsConfiguration, interceptor, List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>(webEndpoints);
new EndpointLinksResolver(webEndpointDiscoverer.getEndpoints())); return new CloudFoundryWebFluxEndpointHandlerMapping(new EndpointMapping("/cfApplication"), webEndpoints,
endpointMediaTypes, corsConfiguration, interceptor, allEndpoints);
} }
@Bean @Bean

@ -94,6 +94,8 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests {
InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class));
private static final String BASE_PATH = "/cloudfoundryapplication";
@AfterEach @AfterEach
void close() { void close() {
HttpResources.reset(); HttpResources.reset();
@ -175,21 +177,24 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void cloudFoundryPathsIgnoredBySpringSecurity() { void cloudFoundryPathsIgnoredBySpringSecurity() {
this.contextRunner this.contextRunner.withBean(TestEndpoint.class, TestEndpoint::new)
.withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",
"vcap.application.cf_api:https://my-cloud-controller.com") "vcap.application.cf_api:https://my-cloud-controller.com")
.run((context) -> { .run((context) -> {
WebFilterChainProxy chainProxy = context.getBean(WebFilterChainProxy.class); WebFilterChainProxy chainProxy = context.getBean(WebFilterChainProxy.class);
List<SecurityWebFilterChain> filters = (List<SecurityWebFilterChain>) ReflectionTestUtils List<SecurityWebFilterChain> filters = (List<SecurityWebFilterChain>) ReflectionTestUtils
.getField(chainProxy, "filters"); .getField(chainProxy, "filters");
Boolean cfRequestMatches = filters.get(0) Boolean cfBaseRequestMatches = getMatches(filters, BASE_PATH);
.matches(MockServerWebExchange Boolean cfBaseWithTrailingSlashRequestMatches = getMatches(filters, BASE_PATH + "/");
.from(MockServerHttpRequest.get("/cloudfoundryapplication/my-path").build())) Boolean cfRequestMatches = getMatches(filters, BASE_PATH + "/test");
.block(Duration.ofSeconds(30)); Boolean cfRequestWithAdditionalPathMatches = getMatches(filters, BASE_PATH + "/test/a");
Boolean otherRequestMatches = filters.get(0) Boolean otherCfRequestMatches = getMatches(filters, BASE_PATH + "/other-path");
.matches(MockServerWebExchange.from(MockServerHttpRequest.get("/some-other-path").build())) Boolean otherRequestMatches = getMatches(filters, "/some-other-path");
.block(Duration.ofSeconds(30)); assertThat(cfBaseRequestMatches).isTrue();
assertThat(cfBaseWithTrailingSlashRequestMatches).isTrue();
assertThat(cfRequestMatches).isTrue(); assertThat(cfRequestMatches).isTrue();
assertThat(cfRequestWithAdditionalPathMatches).isTrue();
assertThat(otherCfRequestMatches).isFalse();
assertThat(otherRequestMatches).isFalse(); assertThat(otherRequestMatches).isFalse();
otherRequestMatches = filters.get(1) otherRequestMatches = filters.get(1)
.matches(MockServerWebExchange.from(MockServerHttpRequest.get("/some-other-path").build())) .matches(MockServerWebExchange.from(MockServerHttpRequest.get("/some-other-path").build()))
@ -199,6 +204,13 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests {
} }
private static Boolean getMatches(List<SecurityWebFilterChain> filters, String urlTemplate) {
Boolean cfBaseRequestMatches = filters.get(0)
.matches(MockServerWebExchange.from(MockServerHttpRequest.get(urlTemplate).build()))
.block(Duration.ofSeconds(30));
return cfBaseRequestMatches;
}
@Test @Test
void cloudFoundryPlatformInactive() { void cloudFoundryPlatformInactive() {
this.contextRunner this.contextRunner

@ -77,6 +77,8 @@ class CloudFoundryActuatorAutoConfigurationTests {
ServletManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class)); WebEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class));
private static String BASE_PATH = "/cloudfoundryapplication";
@Test @Test
void cloudFoundryPlatformActive() { void cloudFoundryPlatformActive() {
this.contextRunner this.contextRunner
@ -168,20 +170,31 @@ class CloudFoundryActuatorAutoConfigurationTests {
@Test @Test
void cloudFoundryPathsIgnoredBySpringSecurity() { 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) -> { .run((context) -> {
FilterChainProxy securityFilterChain = (FilterChainProxy) context FilterChainProxy securityFilterChain = (FilterChainProxy) context
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
SecurityFilterChain chain = securityFilterChain.getFilterChains().get(0); SecurityFilterChain chain = securityFilterChain.getFilterChains().get(0);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setServletPath("/cloudfoundryapplication/my-path");
assertThat(chain.getFilters()).isEmpty(); 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"); request.setServletPath("/some-other-path");
assertThat(chain.matches(request)).isFalse(); assertThat(chain.matches(request)).isFalse();
}); });
} }
private static void testCloudFoundrySecurity(MockHttpServletRequest request, String basePath,
SecurityFilterChain chain) {
request.setServletPath(basePath);
assertThat(chain.matches(request)).isTrue();
}
@Test @Test
void cloudFoundryPlatformInactive() { void cloudFoundryPlatformInactive() {
this.contextRunner.withPropertyValues() this.contextRunner.withPropertyValues()

@ -17,9 +17,12 @@
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet; package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -29,15 +32,16 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; 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.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper; 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.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; 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.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
@ -242,9 +246,10 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
CorsConfiguration corsConfiguration = new CorsConfiguration(); CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("https://example.com")); corsConfiguration.setAllowedOrigins(Arrays.asList("https://example.com"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST")); corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
return new CloudFoundryWebEndpointServletHandlerMapping(new EndpointMapping("/cfApplication"), Collection<ExposableWebEndpoint> webEndpoints = webEndpointDiscoverer.getEndpoints();
webEndpointDiscoverer.getEndpoints(), endpointMediaTypes, corsConfiguration, interceptor, List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>(webEndpoints);
new EndpointLinksResolver(webEndpointDiscoverer.getEndpoints())); return new CloudFoundryWebEndpointServletHandlerMapping(new EndpointMapping("/cfApplication"), webEndpoints,
endpointMediaTypes, corsConfiguration, interceptor, allEndpoints);
} }
@Bean @Bean

Loading…
Cancel
Save