Browse Source

Merge branch '4.0.x'

Closes gh-48827
pull/48829/head
Andy Wilkinson 3 weeks ago
parent
commit
6eaeeccd38
  1. 1
      module/spring-boot-cloudfoundry/build.gradle
  2. 17
      module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java
  3. 28
      module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityService.java
  4. 16
      module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfigurationTests.java
  5. 19
      module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityServiceTests.java

1
module/spring-boot-cloudfoundry/build.gradle

@ -29,7 +29,6 @@ dependencies { @@ -29,7 +29,6 @@ dependencies {
api(project(":module:spring-boot-actuator-autoconfigure"))
optional(project(":module:spring-boot-health"))
optional(project(":module:spring-boot-restclient"))
optional(project(":module:spring-boot-security"))
optional(project(":module:spring-boot-webclient"))
optional(project(":module:spring-boot-webflux"))

17
module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java

@ -51,7 +51,6 @@ import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.Clou @@ -51,7 +51,6 @@ import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.Clou
import org.springframework.boot.health.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.health.actuate.endpoint.HealthEndpointWebExtension;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -67,6 +66,7 @@ import org.springframework.security.web.SecurityFilterChain; @@ -67,6 +66,7 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.client.RestClient;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet;
@ -115,15 +115,15 @@ public final class CloudFoundryActuatorAutoConfiguration { @@ -115,15 +115,15 @@ public final class CloudFoundryActuatorAutoConfiguration {
@SuppressWarnings("removal")
CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
RestTemplateBuilder restTemplateBuilder,
ObjectProvider<RestClient.Builder> restClientBuilder,
org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier,
org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier controllerEndpointsSupplier,
ApplicationContext applicationContext) {
CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(applicationContext,
parameterMapper, endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList(),
Collections.emptyList());
SecurityInterceptor securityInterceptor = getSecurityInterceptor(restTemplateBuilder,
applicationContext.getEnvironment());
SecurityInterceptor securityInterceptor = getSecurityInterceptor(
restClientBuilder.getIfAvailable(RestClient::builder), applicationContext.getEnvironment());
Collection<ExposableWebEndpoint> webEndpoints = discoverer.getEndpoints();
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
allEndpoints.addAll(webEndpoints);
@ -133,22 +133,21 @@ public final class CloudFoundryActuatorAutoConfiguration { @@ -133,22 +133,21 @@ public final class CloudFoundryActuatorAutoConfiguration {
endpointMediaTypes, getCorsConfiguration(), securityInterceptor, allEndpoints);
}
private SecurityInterceptor getSecurityInterceptor(RestTemplateBuilder restTemplateBuilder,
Environment environment) {
SecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(restTemplateBuilder, environment);
private SecurityInterceptor getSecurityInterceptor(RestClient.Builder restClientBuilder, Environment environment) {
SecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(restClientBuilder, environment);
TokenValidator tokenValidator = (cloudfoundrySecurityService != null)
? new TokenValidator(cloudfoundrySecurityService) : null;
return new SecurityInterceptor(tokenValidator, cloudfoundrySecurityService,
environment.getProperty("vcap.application.application_id"));
}
private @Nullable SecurityService getCloudFoundrySecurityService(RestTemplateBuilder restTemplateBuilder,
private @Nullable SecurityService getCloudFoundrySecurityService(RestClient.Builder restClientBuilder,
Environment environment) {
String cloudControllerUrl = environment.getProperty("vcap.application.cf_api");
boolean skipSslValidation = environment.getProperty("management.cloudfoundry.skip-ssl-validation",
Boolean.class, false);
return (cloudControllerUrl != null)
? new SecurityService(restTemplateBuilder, cloudControllerUrl, skipSslValidation) : null;
? new SecurityService(restClientBuilder, cloudControllerUrl, skipSslValidation) : null;
}
private CorsConfiguration getCorsConfiguration() {

28
module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityService.java

@ -27,14 +27,12 @@ import org.jspecify.annotations.Nullable; @@ -27,14 +27,12 @@ import org.jspecify.annotations.Nullable;
import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.AccessLevel;
import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.CloudFoundryAuthorizationException;
import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.CloudFoundryAuthorizationException.Reason;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
/**
* Cloud Foundry security service to handle REST calls to the cloud controller and UAA.
@ -43,19 +41,19 @@ import org.springframework.web.client.RestTemplate; @@ -43,19 +41,19 @@ import org.springframework.web.client.RestTemplate;
*/
class SecurityService {
private final RestTemplate restTemplate;
private final RestClient restClient;
private final String cloudControllerUrl;
private @Nullable String uaaUrl;
SecurityService(RestTemplateBuilder restTemplateBuilder, String cloudControllerUrl, boolean skipSslValidation) {
Assert.notNull(restTemplateBuilder, "'restTemplateBuilder' must not be null");
SecurityService(RestClient.Builder restClientBuilder, String cloudControllerUrl, boolean skipSslValidation) {
Assert.notNull(restClientBuilder, "'restClientBuilder' must not be null");
Assert.notNull(cloudControllerUrl, "'cloudControllerUrl' must not be null");
if (skipSslValidation) {
restTemplateBuilder = restTemplateBuilder.requestFactory(SkipSslVerificationHttpRequestFactory.class);
restClientBuilder = restClientBuilder.requestFactory(new SkipSslVerificationHttpRequestFactory());
}
this.restTemplate = restTemplateBuilder.build();
this.restClient = restClientBuilder.build();
this.cloudControllerUrl = cloudControllerUrl;
}
@ -69,8 +67,11 @@ class SecurityService { @@ -69,8 +67,11 @@ class SecurityService {
AccessLevel getAccessLevel(String token, String applicationId) throws CloudFoundryAuthorizationException {
try {
URI uri = getPermissionsUri(applicationId);
RequestEntity<?> request = RequestEntity.get(uri).header("Authorization", "bearer " + token).build();
Map<?, ?> body = this.restTemplate.exchange(request, Map.class).getBody();
Map<?, ?> body = this.restClient.get()
.uri(uri)
.header("Authorization", "bearer " + token)
.retrieve()
.body(Map.class);
if (body != null && Boolean.TRUE.equals(body.get("read_sensitive_data"))) {
return AccessLevel.FULL;
}
@ -102,7 +103,7 @@ class SecurityService { @@ -102,7 +103,7 @@ class SecurityService {
*/
Map<String, String> fetchTokenKeys() {
try {
Map<?, ?> response = this.restTemplate.getForObject(getUaaUrl() + "/token_keys", Map.class);
Map<?, ?> response = this.restClient.get().uri(getUaaUrl() + "/token_keys").retrieve().body(Map.class);
Assert.state(response != null, "'response' must not be null");
return extractTokenKeys(response);
}
@ -129,7 +130,10 @@ class SecurityService { @@ -129,7 +130,10 @@ class SecurityService {
String getUaaUrl() {
if (this.uaaUrl == null) {
try {
Map<?, ?> response = this.restTemplate.getForObject(this.cloudControllerUrl + "/info", Map.class);
Map<?, ?> response = this.restClient.get()
.uri(this.cloudControllerUrl + "/info")
.retrieve()
.body(Map.class);
Assert.state(response != null, "'response' must not be null");
String tokenEndpoint = (String) response.get("token_endpoint");
Assert.state(tokenEndpoint != null, "'tokenEndpoint' must not be null");

16
module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfigurationTests.java

@ -42,7 +42,6 @@ import org.springframework.boot.health.autoconfigure.contributor.HealthContribut @@ -42,7 +42,6 @@ import org.springframework.boot.health.autoconfigure.contributor.HealthContribut
import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration;
import org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration;
import org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration;
import org.springframework.boot.servlet.autoconfigure.actuate.web.ServletManagementContextAutoConfiguration;
@ -61,7 +60,6 @@ import org.springframework.test.util.ReflectionTestUtils; @@ -61,7 +60,6 @@ import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CompositeFilter;
@ -87,7 +85,7 @@ class CloudFoundryActuatorAutoConfigurationTests { @@ -87,7 +85,7 @@ class CloudFoundryActuatorAutoConfigurationTests {
ServletWebSecurityAutoConfiguration.class, WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
RestTemplateAutoConfiguration.class, ManagementContextAutoConfiguration.class,
/* RestTemplateAutoConfiguration.class, */ ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class));
@ -164,15 +162,9 @@ class CloudFoundryActuatorAutoConfigurationTests { @@ -164,15 +162,9 @@ class CloudFoundryActuatorAutoConfigurationTests {
"management.cloudfoundry.skip-ssl-validation:true")
.run((context) -> {
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping(context);
Object interceptor = ReflectionTestUtils.getField(handlerMapping, "securityInterceptor");
assertThat(interceptor).isNotNull();
Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor,
"cloudFoundrySecurityService");
assertThat(interceptorSecurityService).isNotNull();
RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils.getField(interceptorSecurityService,
"restTemplate");
assertThat(restTemplate).isNotNull();
assertThat(restTemplate.getRequestFactory()).isInstanceOf(SkipSslVerificationHttpRequestFactory.class);
assertThat(handlerMapping)
.extracting("securityInterceptor.cloudFoundrySecurityService.restClient.clientRequestFactory")
.isInstanceOf(SkipSslVerificationHttpRequestFactory.class);
});
}

19
module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityServiceTests.java

@ -29,9 +29,8 @@ import org.springframework.boot.restclient.RestTemplateBuilder; @@ -29,9 +29,8 @@ import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.restclient.test.MockServerRestTemplateCustomizer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ -63,26 +62,24 @@ class SecurityServiceTests { @@ -63,26 +62,24 @@ class SecurityServiceTests {
void setup() {
MockServerRestTemplateCustomizer mockServerCustomizer = new MockServerRestTemplateCustomizer();
RestTemplateBuilder builder = new RestTemplateBuilder(mockServerCustomizer);
this.securityService = new SecurityService(builder, CLOUD_CONTROLLER, false);
this.securityService = new SecurityService(RestClient.builder(builder.build()), CLOUD_CONTROLLER, false);
this.server = mockServerCustomizer.getServer();
}
@Test
void skipSslValidationWhenTrue() {
RestTemplateBuilder builder = new RestTemplateBuilder();
this.securityService = new SecurityService(builder, CLOUD_CONTROLLER, true);
RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils.getField(this.securityService, "restTemplate");
assertThat(restTemplate).isNotNull();
assertThat(restTemplate.getRequestFactory()).isInstanceOf(SkipSslVerificationHttpRequestFactory.class);
this.securityService = new SecurityService(RestClient.builder(builder.build()), CLOUD_CONTROLLER, true);
assertThat(this.securityService).extracting("restClient.clientRequestFactory")
.isInstanceOf(SkipSslVerificationHttpRequestFactory.class);
}
@Test
void doNotSkipSslValidationWhenFalse() {
RestTemplateBuilder builder = new RestTemplateBuilder();
this.securityService = new SecurityService(builder, CLOUD_CONTROLLER, false);
RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils.getField(this.securityService, "restTemplate");
assertThat(restTemplate).isNotNull();
assertThat(restTemplate.getRequestFactory()).isNotInstanceOf(SkipSslVerificationHttpRequestFactory.class);
this.securityService = new SecurityService(RestClient.builder(builder.build()), CLOUD_CONTROLLER, false);
assertThat(this.securityService).extracting("restClient.clientRequestFactory")
.isNotInstanceOf(SkipSslVerificationHttpRequestFactory.class);
}
@Test

Loading…
Cancel
Save