From ba916cb66e8f6879a4cf8a3c162a03183042c6f7 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 18 Dec 2024 10:30:59 -0800 Subject: [PATCH] Allow KafkaProperties to build properties with empty bundle name Update `KafkaProperties` so that properties can still be built when the bundle name has no text. Fixes gh-43561 --- .../autoconfigure/kafka/KafkaProperties.java | 82 ++++++++++--------- .../client/ClientsConfiguredCondition.java | 3 +- .../kafka/KafkaPropertiesTests.java | 14 ++++ ...usiveConfigurationPropertiesException.java | 20 ++++- 4 files changed, 77 insertions(+), 42 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java index 5ee77cd66ed2..7352d8dfe244 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java @@ -43,6 +43,7 @@ import org.springframework.kafka.listener.ContainerProperties.AckMode; import org.springframework.kafka.security.jaas.KafkaJaasLoginModuleInitializer; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import org.springframework.util.unit.DataSize; /** @@ -1427,60 +1428,67 @@ public Map buildProperties() { public Map buildProperties(SslBundles sslBundles) { validate(); - Properties properties = new Properties(); - if (getBundle() != null) { - properties.in(SslConfigs.SSL_ENGINE_FACTORY_CLASS_CONFIG) - .accept(SslBundleSslEngineFactory.class.getName()); - properties.in(SslBundle.class.getName()).accept(sslBundles.getBundle(getBundle())); - } - else { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(this::getKeyPassword).to(properties.in(SslConfigs.SSL_KEY_PASSWORD_CONFIG)); - map.from(this::getKeyStoreCertificateChain) - .to(properties.in(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG)); - map.from(this::getKeyStoreKey).to(properties.in(SslConfigs.SSL_KEYSTORE_KEY_CONFIG)); - map.from(this::getKeyStoreLocation) - .as(this::resourceToPath) - .to(properties.in(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG)); - map.from(this::getKeyStorePassword).to(properties.in(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG)); - map.from(this::getKeyStoreType).to(properties.in(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG)); - map.from(this::getTrustStoreCertificates) - .to(properties.in(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG)); - map.from(this::getTrustStoreLocation) - .as(this::resourceToPath) - .to(properties.in(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG)); - map.from(this::getTrustStorePassword).to(properties.in(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG)); - map.from(this::getTrustStoreType).to(properties.in(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG)); - map.from(this::getProtocol).to(properties.in(SslConfigs.SSL_PROTOCOL_CONFIG)); + String bundleName = getBundle(); + if (StringUtils.hasText(bundleName)) { + return buildPropertiesForSslBundle(sslBundles, bundleName); } + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getKeyPassword).to(properties.in(SslConfigs.SSL_KEY_PASSWORD_CONFIG)); + map.from(this::getKeyStoreCertificateChain) + .to(properties.in(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG)); + map.from(this::getKeyStoreKey).to(properties.in(SslConfigs.SSL_KEYSTORE_KEY_CONFIG)); + map.from(this::getKeyStoreLocation) + .as(this::resourceToPath) + .to(properties.in(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG)); + map.from(this::getKeyStorePassword).to(properties.in(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG)); + map.from(this::getKeyStoreType).to(properties.in(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG)); + map.from(this::getTrustStoreCertificates).to(properties.in(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG)); + map.from(this::getTrustStoreLocation) + .as(this::resourceToPath) + .to(properties.in(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG)); + map.from(this::getTrustStorePassword).to(properties.in(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG)); + map.from(this::getTrustStoreType).to(properties.in(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG)); + map.from(this::getProtocol).to(properties.in(SslConfigs.SSL_PROTOCOL_CONFIG)); + return properties; + } + + private Map buildPropertiesForSslBundle(SslBundles sslBundles, String name) { + Properties properties = new Properties(); + properties.in(SslConfigs.SSL_ENGINE_FACTORY_CLASS_CONFIG).accept(SslBundleSslEngineFactory.class.getName()); + properties.in(SslBundle.class.getName()).accept(sslBundles.getBundle(name)); return properties; } private void validate() { - MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> { entries.put("spring.kafka.ssl.key-store-key", getKeyStoreKey()); entries.put("spring.kafka.ssl.key-store-location", getKeyStoreLocation()); - }); - MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + }, this::hasValue); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> { entries.put("spring.kafka.ssl.trust-store-certificates", getTrustStoreCertificates()); entries.put("spring.kafka.ssl.trust-store-location", getTrustStoreLocation()); - }); - MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + }, this::hasValue); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> { entries.put("spring.kafka.ssl.bundle", getBundle()); entries.put("spring.kafka.ssl.key-store-key", getKeyStoreKey()); - }); - MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + }, this::hasValue); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> { entries.put("spring.kafka.ssl.bundle", getBundle()); entries.put("spring.kafka.ssl.key-store-location", getKeyStoreLocation()); - }); - MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + }, this::hasValue); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> { entries.put("spring.kafka.ssl.bundle", getBundle()); entries.put("spring.kafka.ssl.trust-store-certificates", getTrustStoreCertificates()); - }); - MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + }, this::hasValue); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> { entries.put("spring.kafka.ssl.bundle", getBundle()); entries.put("spring.kafka.ssl.trust-store-location", getTrustStoreLocation()); - }); + }, this::hasValue); + } + + private boolean hasValue(Object value) { + return (value instanceof String string) ? StringUtils.hasText(string) : value != null; } private String resourceToPath(Resource resource) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientsConfiguredCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientsConfiguredCondition.java index 43e7ef93f99c..d4d9df519ee2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientsConfiguredCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientsConfiguredCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ * @author Madhura Bhave * @since 2.1.0 */ - public class ClientsConfiguredCondition extends SpringBootCondition { private static final Bindable> STRING_REGISTRATION_MAP = Bindable diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaPropertiesTests.java index f6d45dfd0a69..08fb63213aeb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaPropertiesTests.java @@ -87,6 +87,20 @@ void sslPemConfiguration() { "-----BEGINchain"); } + @Test + void sslPemConfigurationWithEmptyBundle() { + KafkaProperties properties = new KafkaProperties(); + properties.getSsl().setKeyStoreKey("-----BEGINkey"); + properties.getSsl().setTrustStoreCertificates("-----BEGINtrust"); + properties.getSsl().setKeyStoreCertificateChain("-----BEGINchain"); + properties.getSsl().setBundle(""); + Map consumerProperties = properties.buildConsumerProperties(); + assertThat(consumerProperties).containsEntry(SslConfigs.SSL_KEYSTORE_KEY_CONFIG, "-----BEGINkey"); + assertThat(consumerProperties).containsEntry(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG, "-----BEGINtrust"); + assertThat(consumerProperties).containsEntry(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG, + "-----BEGINchain"); + } + @Test void sslBundleConfiguration() { KafkaProperties properties = new KafkaProperties(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MutuallyExclusiveConfigurationPropertiesException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MutuallyExclusiveConfigurationPropertiesException.java index 3509489087cf..b0fa0a3e8167 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MutuallyExclusiveConfigurationPropertiesException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MutuallyExclusiveConfigurationPropertiesException.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,10 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.springframework.util.Assert; @@ -96,11 +98,23 @@ private static String buildMessage(Set mutuallyExclusiveNames, Set> entries) { - Map map = new LinkedHashMap<>(); + throwIfMultipleMatchingValuesIn(entries, Objects::nonNull); + } + + /** + * Throw a new {@link MutuallyExclusiveConfigurationPropertiesException} if multiple + * values are defined in a set of entries that match the given predicate. + * @param the value type + * @param entries a consumer used to populate the entries to check + * @param predicate the predicate used to check for matching values + * @since 3.3.7 + */ + public static void throwIfMultipleMatchingValuesIn(Consumer> entries, Predicate predicate) { + Map map = new LinkedHashMap<>(); entries.accept(map); Set configuredNames = map.entrySet() .stream() - .filter((entry) -> entry.getValue() != null) + .filter((entry) -> predicate.test(entry.getValue())) .map(Map.Entry::getKey) .collect(Collectors.toCollection(LinkedHashSet::new)); if (configuredNames.size() > 1) {