diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryAuthorizationException.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryAuthorizationException.java index de572b5addc..d7726b4cea1 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryAuthorizationException.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryAuthorizationException.java @@ -16,6 +16,8 @@ package org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpStatus; /** @@ -28,11 +30,11 @@ public class CloudFoundryAuthorizationException extends RuntimeException { private final Reason reason; - public CloudFoundryAuthorizationException(Reason reason, String message) { + public CloudFoundryAuthorizationException(Reason reason, @Nullable String message) { this(reason, message, null); } - public CloudFoundryAuthorizationException(Reason reason, String message, Throwable cause) { + public CloudFoundryAuthorizationException(Reason reason, @Nullable String message, @Nullable Throwable cause) { super(message, cause); this.reason = reason; } diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryEndpointExposureOutcomeContributor.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryEndpointExposureOutcomeContributor.java index 38569941500..f89bda98e04 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryEndpointExposureOutcomeContributor.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryEndpointExposureOutcomeContributor.java @@ -18,6 +18,8 @@ package org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.actuate.autoconfigure.endpoint.condition.EndpointExposureOutcomeContributor; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter; @@ -38,7 +40,7 @@ class CloudFoundryEndpointExposureOutcomeContributor implements EndpointExposure private static final String PROPERTY = "management.endpoints.cloud-foundry.exposure"; - private final IncludeExcludeEndpointFilter filter; + private final @Nullable IncludeExcludeEndpointFilter filter; CloudFoundryEndpointExposureOutcomeContributor(Environment environment) { this.filter = (!CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) ? null @@ -46,7 +48,7 @@ class CloudFoundryEndpointExposureOutcomeContributor implements EndpointExposure } @Override - public ConditionOutcome getExposureOutcome(EndpointId endpointId, Set exposures, + public @Nullable ConditionOutcome getExposureOutcome(EndpointId endpointId, Set exposures, Builder message) { if (exposures.contains(EndpointExposure.WEB) && this.filter != null && this.filter.match(endpointId)) { return ConditionOutcome.match(message.because("marked as exposed by a '" + PROPERTY + "' property")); diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryWebEndpointDiscoverer.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryWebEndpointDiscoverer.java index f6c52faa326..8ddd384e2bf 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryWebEndpointDiscoverer.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/CloudFoundryWebEndpointDiscoverer.java @@ -19,6 +19,8 @@ package org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint; import java.util.Collection; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -60,7 +62,7 @@ public class CloudFoundryWebEndpointDiscoverer extends WebEndpointDiscoverer { */ public CloudFoundryWebEndpointDiscoverer(ApplicationContext applicationContext, ParameterValueMapper parameterValueMapper, EndpointMediaTypes endpointMediaTypes, - List endpointPathMappers, Collection invokerAdvisors, + @Nullable List endpointPathMappers, Collection invokerAdvisors, Collection> endpointFilters, Collection> operationFilters) { super(applicationContext, parameterValueMapper, endpointMediaTypes, endpointPathMappers, null, invokerAdvisors, @@ -89,7 +91,7 @@ public class CloudFoundryWebEndpointDiscoverer extends WebEndpointDiscoverer { static class CloudFoundryWebEndpointDiscovererRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.reflection() .registerType(CloudFoundryEndpointFilter.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); } diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/SecurityResponse.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/SecurityResponse.java index 1dc501ef73b..c03c23d0b98 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/SecurityResponse.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/SecurityResponse.java @@ -16,6 +16,8 @@ package org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpStatus; /** @@ -28,13 +30,13 @@ public class SecurityResponse { private final HttpStatus status; - private final String message; + private final @Nullable String message; public SecurityResponse(HttpStatus status) { this(status, null); } - public SecurityResponse(HttpStatus status, String message) { + public SecurityResponse(HttpStatus status, @Nullable String message) { this.status = status; this.message = message; } @@ -43,7 +45,7 @@ public class SecurityResponse { return this.status; } - public String getMessage() { + public @Nullable String getMessage() { return this.message; } diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/package-info.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/package-info.java index b68f0463dc6..8102dbf4b9d 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/package-info.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for Cloud Foundry endpoints. */ +@NullMarked package org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java index f4536811c4f..7cc6843a4ed 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java @@ -23,6 +23,8 @@ import java.util.Collections; import java.util.List; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -128,12 +130,13 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration { private SecurityInterceptor getSecurityInterceptor(WebClient.Builder webClientBuilder, Environment environment) { SecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(webClientBuilder, environment); - TokenValidator tokenValidator = new TokenValidator(cloudfoundrySecurityService); + TokenValidator tokenValidator = (cloudfoundrySecurityService != null) + ? new TokenValidator(cloudfoundrySecurityService) : null; return new SecurityInterceptor(tokenValidator, cloudfoundrySecurityService, environment.getProperty("vcap.application.application_id")); } - private SecurityService getCloudFoundrySecurityService(WebClient.Builder webClientBuilder, + private @Nullable SecurityService getCloudFoundrySecurityService(WebClient.Builder webClientBuilder, Environment environment) { String cloudControllerUrl = environment.getProperty("vcap.application.cf_api"); boolean skipSslValidation = environment.getProperty("management.cloudfoundry.skip-ssl-validation", diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java index 8cadc032088..378ae969f2b 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java @@ -22,6 +22,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -111,7 +112,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH }); } - private Map getAccessibleLinks(AccessLevel accessLevel, Map links) { + private Map getAccessibleLinks(@Nullable AccessLevel accessLevel, Map links) { if (accessLevel == null) { return new LinkedHashMap<>(); } @@ -169,7 +170,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { this.reflectiveRegistrar.registerRuntimeHints(hints, CloudFoundryLinksHandler.class); this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class); } diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/SecurityInterceptor.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/SecurityInterceptor.java index 033b563befe..0f3fed250e0 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/SecurityInterceptor.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/SecurityInterceptor.java @@ -20,6 +20,7 @@ import java.util.Locale; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.CloudFoundryAuthorizationException; @@ -28,6 +29,7 @@ import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.Secu import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.Token; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.cors.reactive.CorsUtils; import org.springframework.web.server.ServerWebExchange; @@ -41,16 +43,16 @@ class SecurityInterceptor { private static final Log logger = LogFactory.getLog(SecurityInterceptor.class); - private final TokenValidator tokenValidator; + private final @Nullable TokenValidator tokenValidator; - private final SecurityService cloudFoundrySecurityService; + private final @Nullable SecurityService cloudFoundrySecurityService; - private final String applicationId; + private final @Nullable String applicationId; private static final Mono SUCCESS = Mono.just(SecurityResponse.success()); - SecurityInterceptor(TokenValidator tokenValidator, SecurityService cloudFoundrySecurityService, - String applicationId) { + SecurityInterceptor(@Nullable TokenValidator tokenValidator, @Nullable SecurityService cloudFoundrySecurityService, + @Nullable String applicationId) { this.tokenValidator = tokenValidator; this.cloudFoundrySecurityService = cloudFoundrySecurityService; this.applicationId = applicationId; @@ -65,7 +67,7 @@ class SecurityInterceptor { return Mono.error(new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, "Application id is not available")); } - if (this.cloudFoundrySecurityService == null) { + if (this.cloudFoundrySecurityService == null || this.tokenValidator == null) { return Mono.error(new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, "Cloud controller URL is not available")); } @@ -77,6 +79,9 @@ class SecurityInterceptor { } private Mono check(ServerWebExchange exchange, String id) { + Assert.state(this.tokenValidator != null, "'tokenValidator' must not be null"); + Assert.state(this.cloudFoundrySecurityService != null, "'cloudFoundrySecurityService' must not be null"); + Assert.state(this.applicationId != null, "'applicationId' must not be null"); try { Token token = getToken(exchange.getRequest()); return this.tokenValidator.validate(token) diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/SecurityService.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/SecurityService.java index 8e34c1d6ba9..85436605a2e 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/SecurityService.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/SecurityService.java @@ -135,7 +135,9 @@ class SecurityService { private Map extractTokenKeys(Map response) { Map tokenKeys = new HashMap<>(); - for (Object key : (List) response.get("keys")) { + List keys = (List) response.get("keys"); + Assert.state(keys != null, "'keys' must not be null"); + for (Object key : keys) { Map tokenKey = (Map) key; tokenKeys.put((String) tokenKey.get("kid"), (String) tokenKey.get("value")); } diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/package-info.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/package-info.java index 6bb756a3eb9..b1c632f0cd8 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/package-info.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/reactive/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for Cloud Foundry WebFlux endpoints. */ +@NullMarked package org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.reactive; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java index b272954435f..5fe555cb663 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java @@ -22,6 +22,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; @@ -134,12 +136,13 @@ public final class CloudFoundryActuatorAutoConfiguration { private SecurityInterceptor getSecurityInterceptor(RestTemplateBuilder restTemplateBuilder, Environment environment) { SecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(restTemplateBuilder, environment); - TokenValidator tokenValidator = new TokenValidator(cloudfoundrySecurityService); + TokenValidator tokenValidator = (cloudfoundrySecurityService != null) + ? new TokenValidator(cloudfoundrySecurityService) : null; return new SecurityInterceptor(tokenValidator, cloudfoundrySecurityService, environment.getProperty("vcap.application.application_id")); } - private SecurityService getCloudFoundrySecurityService(RestTemplateBuilder restTemplateBuilder, + private @Nullable SecurityService getCloudFoundrySecurityService(RestTemplateBuilder restTemplateBuilder, Environment environment) { String cloudControllerUrl = environment.getProperty("vcap.application.cf_api"); boolean skipSslValidation = environment.getProperty("management.cloudfoundry.skip-ssl-validation", diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/CloudFoundryWebEndpointServletHandlerMapping.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/CloudFoundryWebEndpointServletHandlerMapping.java index e2725aa1607..a190f9e0c57 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/CloudFoundryWebEndpointServletHandlerMapping.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/CloudFoundryWebEndpointServletHandlerMapping.java @@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.BindingReflectionHintsRegistrar; import org.springframework.aot.hint.RuntimeHints; @@ -172,7 +173,7 @@ class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpoin private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { this.reflectiveRegistrar.registerRuntimeHints(hints, CloudFoundryLinksHandler.class); this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class); } diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/SecurityInterceptor.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/SecurityInterceptor.java index 3513c0b4164..3b18f976066 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/SecurityInterceptor.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/SecurityInterceptor.java @@ -21,6 +21,7 @@ import java.util.Locale; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.AccessLevel; @@ -30,6 +31,7 @@ import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.Secu import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.Token; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsUtils; @@ -42,22 +44,22 @@ class SecurityInterceptor { private static final Log logger = LogFactory.getLog(SecurityInterceptor.class); - private final TokenValidator tokenValidator; + private final @Nullable TokenValidator tokenValidator; - private final SecurityService cloudFoundrySecurityService; + private final @Nullable SecurityService cloudFoundrySecurityService; - private final String applicationId; + private final @Nullable String applicationId; private static final SecurityResponse SUCCESS = SecurityResponse.success(); - SecurityInterceptor(TokenValidator tokenValidator, SecurityService cloudFoundrySecurityService, - String applicationId) { + SecurityInterceptor(@Nullable TokenValidator tokenValidator, @Nullable SecurityService cloudFoundrySecurityService, + @Nullable String applicationId) { this.tokenValidator = tokenValidator; this.cloudFoundrySecurityService = cloudFoundrySecurityService; this.applicationId = applicationId; } - SecurityResponse preHandle(HttpServletRequest request, EndpointId endpointId) { + SecurityResponse preHandle(HttpServletRequest request, @Nullable EndpointId endpointId) { if (CorsUtils.isPreFlightRequest(request)) { return SecurityResponse.success(); } @@ -66,7 +68,7 @@ class SecurityInterceptor { throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, "Application id is not available"); } - if (this.cloudFoundrySecurityService == null) { + if (this.cloudFoundrySecurityService == null || this.tokenValidator == null) { throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, "Cloud controller URL is not available"); } @@ -86,7 +88,10 @@ class SecurityInterceptor { return SecurityResponse.success(); } - private void check(HttpServletRequest request, EndpointId endpointId) { + private void check(HttpServletRequest request, @Nullable EndpointId endpointId) { + Assert.state(this.cloudFoundrySecurityService != null, "'cloudFoundrySecurityService' must not be null"); + Assert.state(this.applicationId != null, "'applicationId' must not be null"); + Assert.state(this.tokenValidator != null, "'tokenValidator' must not be null"); Token token = getToken(request); this.tokenValidator.validate(token); AccessLevel accessLevel = this.cloudFoundrySecurityService.getAccessLevel(token.toString(), this.applicationId); diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/SecurityService.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/SecurityService.java index 94639e41409..94efb348aea 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/SecurityService.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/SecurityService.java @@ -22,6 +22,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.AccessLevel; import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.CloudFoundryAuthorizationException; import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.CloudFoundryAuthorizationException.Reason; @@ -45,7 +47,7 @@ class SecurityService { private final String cloudControllerUrl; - private String uaaUrl; + private @Nullable String uaaUrl; SecurityService(RestTemplateBuilder restTemplateBuilder, String cloudControllerUrl, boolean skipSslValidation) { Assert.notNull(restTemplateBuilder, "'restTemplateBuilder' must not be null"); @@ -69,7 +71,7 @@ class SecurityService { URI uri = getPermissionsUri(applicationId); RequestEntity request = RequestEntity.get(uri).header("Authorization", "bearer " + token).build(); Map body = this.restTemplate.exchange(request, Map.class).getBody(); - if (Boolean.TRUE.equals(body.get("read_sensitive_data"))) { + if (body != null && Boolean.TRUE.equals(body.get("read_sensitive_data"))) { return AccessLevel.FULL; } return AccessLevel.RESTRICTED; @@ -100,7 +102,9 @@ class SecurityService { */ Map fetchTokenKeys() { try { - return extractTokenKeys(this.restTemplate.getForObject(getUaaUrl() + "/token_keys", Map.class)); + Map response = this.restTemplate.getForObject(getUaaUrl() + "/token_keys", Map.class); + Assert.state(response != null, "'response' must not be null"); + return extractTokenKeys(response); } catch (HttpStatusCodeException ex) { throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, "UAA not reachable"); @@ -109,7 +113,9 @@ class SecurityService { private Map extractTokenKeys(Map response) { Map tokenKeys = new HashMap<>(); - for (Object key : (List) response.get("keys")) { + List keys = (List) response.get("keys"); + Assert.state(keys != null, "'keys' must not be null"); + for (Object key : keys) { Map tokenKey = (Map) key; tokenKeys.put((String) tokenKey.get("kid"), (String) tokenKey.get("value")); } @@ -124,7 +130,10 @@ class SecurityService { if (this.uaaUrl == null) { try { Map response = this.restTemplate.getForObject(this.cloudControllerUrl + "/info", Map.class); - this.uaaUrl = (String) response.get("token_endpoint"); + 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"); + this.uaaUrl = tokenEndpoint; } catch (HttpStatusCodeException ex) { throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/TokenValidator.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/TokenValidator.java index 3c11810a086..7bca4f8fe0d 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/TokenValidator.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/TokenValidator.java @@ -27,9 +27,12 @@ import java.util.Base64; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.CloudFoundryAuthorizationException; import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.CloudFoundryAuthorizationException.Reason; import org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.Token; +import org.springframework.util.Assert; /** * Validator used to ensure that a signed {@link Token} has not been tampered with. @@ -40,7 +43,7 @@ class TokenValidator { private final SecurityService securityService; - private Map tokenKeys; + private @Nullable Map tokenKeys; TokenValidator(SecurityService cloudFoundrySecurityService) { this.securityService = cloudFoundrySecurityService; @@ -67,22 +70,23 @@ class TokenValidator { private void validateKeyIdAndSignature(Token token) { String keyId = token.getKeyId(); - if (this.tokenKeys == null || !hasValidKeyId(keyId)) { + if (this.tokenKeys == null || !hasValidKeyId(this.tokenKeys, keyId)) { this.tokenKeys = this.securityService.fetchTokenKeys(); - if (!hasValidKeyId(keyId)) { + if (!hasValidKeyId(this.tokenKeys, keyId)) { throw new CloudFoundryAuthorizationException(Reason.INVALID_KEY_ID, "Key Id present in token header does not match"); } } - - if (!hasValidSignature(token, this.tokenKeys.get(keyId))) { + String key = this.tokenKeys.get(keyId); + Assert.state(key != null, "'key' must not be null"); + if (!hasValidSignature(token, key)) { throw new CloudFoundryAuthorizationException(Reason.INVALID_SIGNATURE, "RSA Signature did not match content"); } } - private boolean hasValidKeyId(String tokenKey) { - return this.tokenKeys.containsKey(tokenKey); + private boolean hasValidKeyId(Map tokenKeys, String tokenKey) { + return tokenKeys.containsKey(tokenKey); } private boolean hasValidSignature(Token token, String key) { diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/package-info.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/package-info.java index f9befe27a4d..a2549857d55 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/package-info.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/actuate/autoconfigure/endpoint/servlet/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for Cloud Foundry Servlet endpoints. */ +@NullMarked package org.springframework.boot.cloudfoundry.actuate.autoconfigure.endpoint.servlet; + +import org.jspecify.annotations.NullMarked;