From 8c82d9c0ab4edad8e5e49a6c0712f0cb607875dd Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 13 May 2026 08:14:45 +0100 Subject: [PATCH] Add support for switching off profile validation Using the already documented spring.profiles.validate flag from Spring Boot. Signed-off-by: Dave Syer --- .../ConfigServerEncryptionConfiguration.java | 9 +++++++++ .../config/ConfigServerMvcConfiguration.java | 16 ++++++++++++++++ .../server/encryption/EncryptionController.java | 8 +++++++- .../environment/EnvironmentController.java | 13 ++++++++++++- .../server/resource/ResourceController.java | 15 +++++++++++++-- .../environment/EnvironmentControllerTests.java | 7 +++++++ 6 files changed, 64 insertions(+), 4 deletions(-) diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerEncryptionConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerEncryptionConfiguration.java index 71691b4e41..da50b74565 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerEncryptionConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerEncryptionConfiguration.java @@ -17,10 +17,12 @@ package org.springframework.cloud.config.server.config; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.bind.Binder; import org.springframework.cloud.config.server.encryption.EncryptionController; import org.springframework.cloud.config.server.encryption.TextEncryptorLocator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; /** * @author Bartosz Wojtkiewicz @@ -36,11 +38,18 @@ public class ConfigServerEncryptionConfiguration { @Autowired private ConfigServerProperties properties; + @Autowired + private Environment environment; + @Bean public EncryptionController encryptionController() { EncryptionController controller = new EncryptionController(this.encryptor); controller.setDefaultApplicationName(this.properties.getDefaultApplicationName()); controller.setDefaultProfile(this.properties.getDefaultProfile()); + boolean validateProfiles = Binder.get(this.environment) + .bind("spring.profiles.validate", Boolean.class) + .orElse(true); + controller.setValidateProfiles(validateProfiles); return controller; } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java index 64bf3c1afe..0eae352a82 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.bind.Binder; import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor; import org.springframework.cloud.config.server.encryption.ResourceEncryptor; import org.springframework.cloud.config.server.environment.EnvironmentController; @@ -38,6 +39,7 @@ import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.http.MediaType; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -74,6 +76,14 @@ static class EnvironmentControllerConfiguration { @Autowired(required = false) private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; + private boolean validateProfiles = true; + + EnvironmentControllerConfiguration(Environment environment) { + this.validateProfiles = Binder.get(environment) + .bind("spring.profiles.validate", Boolean.class) + .orElse(true); + } + @Bean public EnvironmentController environmentController(EnvironmentRepository envRepository, ConfigServerProperties server) { @@ -86,6 +96,7 @@ protected EnvironmentController delegateController(EnvironmentRepository envRepo this.objectMapper); controller.setStripDocumentFromYaml(server.isStripDocumentFromYaml()); controller.setAcceptEmpty(server.isAcceptEmpty()); + controller.setValidateProfiles(this.validateProfiles); return controller; } @@ -97,6 +108,7 @@ public ResourceController resourceController(ResourceRepository repository, Envi this.resourceEncryptorMap); controller.setEncryptEnabled(server.getEncrypt().isEnabled()); controller.setPlainTextEncryptEnabled(server.getEncrypt().isPlainTextEncrypt()); + controller.setValidateProfiles(this.validateProfiles); return controller; } @@ -119,6 +131,10 @@ private EnvironmentRepository encrypted(EnvironmentRepository envRepository, Con @ConditionalOnBean(org.springframework.cloud.context.scope.refresh.RefreshScope.class) static class RefreshableEnvironmentControllerConfiguration extends EnvironmentControllerConfiguration { + RefreshableEnvironmentControllerConfiguration(Environment environment) { + super(environment); + } + @Override @Bean @RefreshScope diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EncryptionController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EncryptionController.java index d540e2a309..a5416d57b7 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EncryptionController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EncryptionController.java @@ -66,6 +66,8 @@ public class EncryptionController { private String defaultProfile = "default"; + private boolean validateProfiles = true; + public EncryptionController(TextEncryptorLocator encryptorLocator) { this.encryptorLocator = encryptorLocator; } @@ -151,7 +153,7 @@ private TextEncryptor getEncryptor(String name, String profiles, String data) { if (isInvalidEncodedLocation(name)) { throw new InvalidEnvironmentRequestException("Invalid request"); } - if (isInvalidProfiles(profiles)) { + if (this.validateProfiles && isInvalidProfiles(profiles)) { throw new InvalidEnvironmentRequestException("Invalid request"); } @@ -266,6 +268,10 @@ public ResponseEntity> invalidCipher() { return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); } + public void setValidateProfiles(boolean validateProfiles) { + this.validateProfiles = validateProfiles; + } + } @SuppressWarnings("serial") diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java index ea54937da4..0fd392f099 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java @@ -79,6 +79,8 @@ public class EnvironmentController { private boolean acceptEmpty = true; + private boolean validateProfiles = true; + public EnvironmentController(EnvironmentRepository repository) { this(repository, new JsonMapper()); } @@ -105,6 +107,15 @@ public void setAcceptEmpty(boolean acceptEmpty) { this.acceptEmpty = acceptEmpty; } + /** + * Flag to indicate that spring profiles are to be validated (default true). If set to + * false, then profiles with invalid characters (e.g. '-') will throw an exception. + * @param validateProfiles the flag to set + */ + public void setValidateProfiles(boolean validateProfiles) { + this.validateProfiles = validateProfiles; + } + @GetMapping(path = "/{name}/{profiles:(?!.*\\b\\.(?:ya?ml|properties|json)\\b).*}", produces = MediaType.APPLICATION_JSON_VALUE) public Environment defaultLabel(@PathVariable String name, @PathVariable String profiles) { @@ -132,7 +143,7 @@ public Environment getEnvironment(String name, String profiles, String label, bo try { name = normalize(name); label = normalize(label); - if (isInvalidProfiles(profiles)) { + if (this.validateProfiles && isInvalidProfiles(profiles)) { throw new InvalidEnvironmentRequestException("Invalid request"); } Environment environment = this.repository.findOne(name, profiles, label, includeOrigin); diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java index b4a17db170..0ac9826d5b 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java @@ -82,6 +82,8 @@ public class ResourceController { private boolean plainTextEncryptEnabled = false; + private boolean validateProfiles = true; + public ResourceController(ResourceRepository resourceRepository, EnvironmentRepository environmentRepository, Map resourceEncryptorMap) { this.resourceRepository = resourceRepository; @@ -104,6 +106,15 @@ public void setPlainTextEncryptEnabled(boolean plainTextEncryptEnabled) { this.plainTextEncryptEnabled = plainTextEncryptEnabled; } + /** + * Flag to indicate that spring profiles are to be validated (default true). If set to + * false, then profiles with invalid characters (e.g. '-') will throw an exception. + * @param validateProfiles the flag to set + */ + public void setValidateProfiles(boolean validateProfiles) { + this.validateProfiles = validateProfiles; + } + @GetMapping("/{name}/{profile}/{label}/**") public String retrieve(@PathVariable String name, @PathVariable String profile, @PathVariable String label, ServletWebRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders, @@ -145,7 +156,7 @@ synchronized String retrieve(ServletWebRequest request, String name, String prof boolean resolvePlaceholders, String acceptedCharset) throws IOException { name = normalize(name); label = normalize(label); - if (isInvalidProfiles(profile)) { + if (this.validateProfiles && isInvalidProfiles(profile)) { throw new InvalidEnvironmentRequestException("Invalid request"); } path = normalize(path); @@ -227,7 +238,7 @@ private synchronized byte[] binary(ServletWebRequest request, String name, Strin String path) throws IOException { name = normalize(name); label = normalize(label); - if (isInvalidProfiles(profile)) { + if (this.validateProfiles && isInvalidProfiles(profile)) { throw new InvalidEnvironmentRequestException("Invalid request"); } path = normalize(path); diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java index 0cfd08ac8c..9dc2b23cf6 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java @@ -44,6 +44,7 @@ import org.springframework.web.util.pattern.PathPatternParser; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; import static org.mockito.ArgumentMatchers.any; @@ -580,6 +581,12 @@ public void invalidProfileTests() { .isInstanceOf(InvalidEnvironmentRequestException.class); } + @Test + public void invalidProfileTestsDisabled() { + this.controller.setValidateProfiles(false); + assertThatNoException().isThrownBy(() -> this.controller.labelled("application", "bar,..,foo", "label")); + } + abstract class MockMvcTestCases { protected MockMvc mvc;