From 58fea2c2b144ce91da007ae08686b5018466dafb Mon Sep 17 00:00:00 2001 From: kvmw Date: Tue, 6 Jan 2026 17:00:40 +0100 Subject: [PATCH] Fix decryption of indexed (list) properties. When decrypting indexed properties all items of the list with at least one encrypted item should be copied to the new property-source but not the other lists. When searching for properties relaxed-binding should be considered. Signed-off-by: kvmw --- .../encrypt/AbstractEnvironmentDecrypt.java | 152 +++++++----- .../DecryptEnvironmentPostProcessor.java | 4 +- ...ironmentDecryptApplicationInitializer.java | 21 +- .../bootstrap/encrypt/TextEncryptorUtils.java | 8 +- .../AbstractEnvironmentDecryptTests.java | 225 ++++++++++++++++++ ...entDecryptApplicationInitializerTests.java | 35 ++- 6 files changed, 338 insertions(+), 107 deletions(-) create mode 100644 spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/AbstractEnvironmentDecryptTests.java diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/AbstractEnvironmentDecrypt.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/AbstractEnvironmentDecrypt.java index 7546cd7ea..cf4e023f2 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/AbstractEnvironmentDecrypt.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/AbstractEnvironmentDecrypt.java @@ -16,20 +16,19 @@ package org.springframework.cloud.bootstrap.encrypt; -import java.util.ArrayList; -import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; +import org.springframework.core.env.SystemEnvironmentPropertySource; import org.springframework.security.crypto.encrypt.TextEncryptor; /** @@ -66,78 +65,73 @@ public boolean isFailOnError() { } protected Map decrypt(TextEncryptor encryptor, PropertySources propertySources) { - Map properties = merge(propertySources); - decrypt(encryptor, properties); - return properties; - } - - protected Map merge(PropertySources propertySources) { - Map properties = new LinkedHashMap<>(); - List> sources = new ArrayList<>(); - for (PropertySource source : propertySources) { - sources.add(0, source); - } - for (PropertySource source : sources) { - merge(source, properties); - } - return properties; - } - - protected void merge(PropertySource source, Map properties) { - if (source instanceof CompositePropertySource) { - - List> sources = new ArrayList<>(((CompositePropertySource) source).getPropertySources()); - Collections.reverse(sources); - - for (PropertySource nested : sources) { - merge(nested, properties); - } + Map decryptedProperties = new LinkedHashMap<>(); + var visitor = new PropertyVisitor(); + + for (PropertySource propertySource : propertySources) { + if (propertySource instanceof EnumerablePropertySource enumerable) { + for (String propertyName : enumerable.getPropertyNames()) { + if (visitor.isVisited(propertyName)) { + continue; + } - } - else if (source instanceof EnumerablePropertySource enumerable) { - Map otherCollectionProperties = new LinkedHashMap<>(); - boolean sourceHasDecryptedCollection = false; - - for (String key : enumerable.getPropertyNames()) { - Object property = source.getProperty(key); - if (property != null) { - String value = property.toString(); - if (value.startsWith(ENCRYPTED_PROPERTY_PREFIX)) { - properties.put(key, value); - if (COLLECTION_PROPERTY.matcher(key).matches()) { - sourceHasDecryptedCollection = true; + var collectionMatcher = COLLECTION_PROPERTY.matcher(propertyName); + if (collectionMatcher.matches()) { + // It is an indexed property. All items should be checked. + var prefix = collectionMatcher.group(1); + if (prefix == null) { + prefix = ""; } - } - else if (COLLECTION_PROPERTY.matcher(key).matches()) { - // put non-encrypted properties so merging of index properties - // happens correctly - otherCollectionProperties.put(key, value); + var indexed = getPropertyValues(enumerable, encryptor, prefix); + // Include only if contains decrypted values + if (indexed.containsDecrypted) { + decryptedProperties.putAll(indexed.values); + } + visitor.visited(indexed.values.keySet()); } else { - // override previously encrypted with non-encrypted property - properties.remove(key); + var single = getPropertyValue(enumerable, encryptor, propertyName); + // Include only if decrypted + if (single.isDecrypted) { + decryptedProperties.put(propertyName, single.value); + } + visitor.visited(propertyName); } } } - // copy all indexed properties even if not encrypted - if (sourceHasDecryptedCollection && !otherCollectionProperties.isEmpty()) { - properties.putAll(otherCollectionProperties); - } + } + + return decryptedProperties; + } + private IndexedValue getPropertyValues(EnumerablePropertySource source, TextEncryptor encryptor, String prefix) { + boolean containsDecrypted = false; + Map elements = new HashMap<>(); + for (String name : source.getPropertyNames()) { + if (COLLECTION_PROPERTY.matcher(name).matches() && name.startsWith(prefix)) { + var value = getPropertyValue(source, encryptor, name); + elements.put(name, value.value); + if (value.isDecrypted) { + containsDecrypted = true; + } + } } + + return new IndexedValue(elements, containsDecrypted); } - protected void decrypt(TextEncryptor encryptor, Map properties) { - properties.replaceAll((key, value) -> { - String valueString = value.toString(); - if (!valueString.startsWith(ENCRYPTED_PROPERTY_PREFIX)) { - return value; + private SingleValue getPropertyValue(PropertySource source, TextEncryptor encryptor, String name) { + var value = source.getProperty(name); + if (value != null) { + var valueString = value.toString(); + if (valueString.startsWith(ENCRYPTED_PROPERTY_PREFIX)) { + return new SingleValue(this.decrypt(encryptor, name, valueString), true); } - return decrypt(encryptor, key, valueString); - }); + } + return new SingleValue(value, false); } - protected String decrypt(TextEncryptor encryptor, String key, String original) { + private String decrypt(TextEncryptor encryptor, String key, String original) { String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length()); try { value = encryptor.decrypt(value); @@ -161,4 +155,38 @@ protected String decrypt(TextEncryptor encryptor, String key, String original) { } } + private record SingleValue(Object value, boolean isDecrypted) { + } + + private record IndexedValue(Map values, boolean containsDecrypted) { + } + + private static final class PropertyVisitor { + + /** + * Using SystemEnvironmentPropertySource, instead of a simple Map, just to cover + * relaxed-binding cases. + *

+ * See {@link SystemEnvironmentPropertySource#containsProperty(String) } for more + * details. + */ + private final SystemEnvironmentPropertySource propertySource = new SystemEnvironmentPropertySource("visitor", + new HashMap<>()); + + boolean isVisited(String name) { + return this.propertySource.containsProperty(name); + } + + void visited(String name) { + propertySource.getSource().put(name, ""); + } + + void visited(Set names) { + for (String name : names) { + propertySource.getSource().put(name, ""); + } + } + + } + } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/DecryptEnvironmentPostProcessor.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/DecryptEnvironmentPostProcessor.java index f551dae98..36ea7698d 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/DecryptEnvironmentPostProcessor.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/DecryptEnvironmentPostProcessor.java @@ -61,9 +61,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp MutablePropertySources propertySources = environment.getPropertySources(); - environment.getPropertySources().remove(DECRYPTED_PROPERTY_SOURCE_NAME); - - Map map = TextEncryptorUtils.decrypt(this, environment, propertySources); + Map map = TextEncryptorUtils.decrypt(this, environment); if (!map.isEmpty()) { // We have some decrypted properties propertySources.addFirst(new SystemEnvironmentPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, map)); diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java index e4d2da609..65a8af7f7 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java @@ -16,7 +16,6 @@ package org.springframework.cloud.bootstrap.encrypt; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -53,7 +52,7 @@ public class EnvironmentDecryptApplicationInitializer extends AbstractEnvironmen private int order = Ordered.HIGHEST_PRECEDENCE + 15; - private TextEncryptor encryptor; + private final TextEncryptor encryptor; public EnvironmentDecryptApplicationInitializer(TextEncryptor encryptor) { this.encryptor = encryptor; @@ -83,7 +82,9 @@ public void initialize(ConfigurableApplicationContext applicationContext) { PropertySource bootstrap = propertySources .get(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME); if (bootstrap != null) { - Map map = decrypt(bootstrap); + MutablePropertySources bootstrapSources = new MutablePropertySources(); + bootstrapSources.addFirst(bootstrap); + Map map = decrypt(this.encryptor, bootstrapSources); if (!map.isEmpty()) { found.addAll(map.keySet()); insert(applicationContext, @@ -92,7 +93,7 @@ public void initialize(ConfigurableApplicationContext applicationContext) { } } removeDecryptedProperties(applicationContext); - Map map = decrypt(this.encryptor, propertySources); + Map map = decrypt(this.encryptor, environment.getPropertySources()); if (!map.isEmpty()) { // We have some decrypted properties found.addAll(map.keySet()); @@ -145,16 +146,4 @@ private void removeDecryptedProperties(ApplicationContext applicationContext) { } } - private Map decrypt(PropertySource source) { - Map properties = merge(source); - decrypt(this.encryptor, properties); - return properties; - } - - private Map merge(PropertySource source) { - Map properties = new LinkedHashMap<>(); - merge(source, properties); - return properties; - } - } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/TextEncryptorUtils.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/TextEncryptorUtils.java index e4c939f50..e20227bf1 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/TextEncryptorUtils.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/TextEncryptorUtils.java @@ -30,7 +30,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; -import org.springframework.core.env.MutablePropertySources; import org.springframework.security.crypto.encrypt.KeyStoreKeyFactory; import org.springframework.security.crypto.encrypt.RsaSecretEncryptor; import org.springframework.security.crypto.encrypt.TextEncryptor; @@ -45,13 +44,12 @@ public abstract class TextEncryptorUtils { * Decrypt environment. See {@link DecryptEnvironmentPostProcessor}. * @param decryptor the {@link AbstractEnvironmentDecrypt} * @param environment the environment to get key properties from. - * @param propertySources the property sources to decrypt. * @return the decrypted properties. */ - static Map decrypt(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment, - MutablePropertySources propertySources) { + static Map decrypt(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment) { TextEncryptor encryptor = getTextEncryptor(decryptor, environment); - return decryptor.decrypt(encryptor, propertySources); + + return decryptor.decrypt(encryptor, environment.getPropertySources()); } static TextEncryptor getTextEncryptor(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment) { diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/AbstractEnvironmentDecryptTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/AbstractEnvironmentDecryptTests.java new file mode 100644 index 000000000..0caaaac88 --- /dev/null +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/AbstractEnvironmentDecryptTests.java @@ -0,0 +1,225 @@ +/* + * Copyright 2013-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.bootstrap.encrypt; + +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.SystemEnvironmentPropertySource; +import org.springframework.security.crypto.encrypt.Encryptors; +import org.springframework.security.crypto.encrypt.TextEncryptor; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.DECRYPTED_PROPERTY_SOURCE_NAME; + +@ExtendWith(OutputCaptureExtension.class) +public class AbstractEnvironmentDecryptTests { + + private final AbstractEnvironmentDecrypt decryptor = new AbstractEnvironmentDecrypt() { + }; + + private ConfigurableEnvironment environment; + + @BeforeEach + void setup() { + environment = new AnnotationConfigApplicationContext().getEnvironment(); + } + + @Test + void decryptCipherKey() { + environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar"))); + + decrypt(); + + then(environment.getProperty("foo")).isEqualTo("bar"); + } + + @Test + void decryptCipherKeyWithPriority() { + environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar"))); + environment.getPropertySources().addFirst(new MapPropertySource("source-2", Map.of("foo", "{cipher}spam"))); + + decrypt(); + then(environment.getProperty("foo")).isEqualTo("spam"); + } + + @Test + void relaxedBinding() { + environment.getPropertySources() + .addFirst(new MapPropertySource("source-1", + Map.of("foo.text", "{cipher}foo1", "bar_text", "bar1", "baz[0].text", "baz1"))); + environment.getPropertySources() + .addFirst(new MapPropertySource("source-2", + Map.of("FOO_TEXT", "{cipher}foo2", "BAR_TEXT", "{cipher}bar2", "BAZ[0].TEXT", "{cipher}baz2"))); + + decrypt(); + + then(environment.getProperty("foo.text")).isEqualTo("foo2"); + then(environment.getProperty("bar-text")).isEqualTo("bar2"); + then(environment.getProperty("baz[0].text")).isEqualTo("baz2"); + } + + @Test + void errorOnDecrypt(CapturedOutput output) { + environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar"))); + + assertThatThrownBy(() -> decrypt(Encryptors.text("deadbeef", "AFFE37"))) + .isInstanceOf(IllegalStateException.class); + + // Assert logs contain warning even when exception thrown + then(output.toString()).contains("Cannot decrypt: key=foo"); + } + + @Test + void errorOnDecryptWhenFailOnErrorIsOff(CapturedOutput output) { + environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar"))); + decryptor.setFailOnError(false); + + decrypt(Encryptors.text("deadbeef", "AFFE37")); + + // Assert logs contain warning + then(output.toString()).contains("Cannot decrypt: key=foo"); + // Empty is the safest fallback for undecryptable cipher + then(environment.getProperty("foo")).isEqualTo(""); + } + + @Test + void indexedPropertiesAreCopied() { + environment.getPropertySources() + .addFirst(new MapPropertySource("source-1", + Map.of("yours[0].someValue", "yourFoo", "yours[1].someValue", "yourBar"))); + // collection with some encrypted keys and some not encrypted + environment.getPropertySources() + .addFirst(new MapPropertySource("source-2", + Map.of("mine[0].someValue", "Foo", "mine[0].someKey", "{cipher}Foo0", "mine[1].someValue", "Bar", + "mine[1].someKey", "{cipher}Bar1", "nonindexed", "nonindexval"))); + + decrypt(); + + then(environment.getProperty("mine[0].someValue")).isEqualTo("Foo"); + then(environment.getProperty("mine[0].someKey")).isEqualTo("Foo0"); + then(environment.getProperty("mine[1].someValue")).isEqualTo("Bar"); + then(environment.getProperty("mine[1].someKey")).isEqualTo("Bar1"); + + then(environment.getProperty("yours[0].someValue")).isEqualTo("yourFoo"); + then(environment.getProperty("yours[1].someValue")).isEqualTo("yourBar"); + } + + @Test + void indexedPropertiesAreCopiedOnlyIfEncrypted() { + environment.getPropertySources() + .addFirst(new MapPropertySource("source-1", + Map.of("a[0]", "a0", "a[1]", "{cipher}a1", "b[0]", "b0", "b[1]", "b1"))); + environment.getPropertySources() + .addFirst(new MapPropertySource("source-2", Map.of("b[0]", "updated-b0", "b[1]", "updated-b1"))); + + decrypt(); + + then(environment.getProperty("a[0]")).isEqualTo("a0"); + then(environment.getProperty("a[1]")).isEqualTo("a1"); + + then(environment.getProperty("b[0]")).isEqualTo("updated-b0"); + then(environment.getProperty("b[1]")).isEqualTo("updated-b1"); + + var decryptedPropertySource = environment.getPropertySources().get("decrypted"); + then(decryptedPropertySource).isNotNull(); + var source = decryptedPropertySource.getSource(); + then(source).isInstanceOf(Map.class); + then(((Map) source).size()).as("decrypted property source had wrong size").isEqualTo(2); + } + + @Test + void decryptCompositePropertySource() { + var cps = new CompositePropertySource("composite-source"); + cps.addPropertySource(new MapPropertySource("dev-profile", Map.of("key", "{cipher}value1"))); + cps.addPropertySource(new MapPropertySource("default-profile", Map.of("key", "{cipher}value2"))); + environment.getPropertySources().addFirst(cps); + + decrypt(); + then(environment.getProperty("key")).isEqualTo("value1"); + } + + @Test + void propertySourcesOrderedCorrectlyWithUnencryptedOverrides() { + environment.getPropertySources().addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar"))); + environment.getPropertySources().addFirst(new MapPropertySource("source-2", Map.of("foo", "spam"))); + + decrypt(); + + then(environment.getProperty("foo")).isEqualTo("spam"); + } + + @Test + void decryptOnlyIfNotOverridden() { + environment.getPropertySources() + .addFirst(new MapPropertySource("source-1", Map.of("foo", "{cipher}bar", "foo2", "{cipher}bar2"))); + environment.getPropertySources().addFirst(new MapPropertySource("source-2", Map.of("foo", "spam"))); + + decrypt(); + + then(environment.getProperty("foo2")).isEqualTo("bar2"); + then(environment.getProperty("foo")).isEqualTo("spam"); + } + + @Test + void indexedPropertiesAreHandledCorrectly() { + environment.getPropertySources() + .addFirst(new MapPropertySource("source-1", + Map.of("list[0].plain", "good", "list[0].cipher", "{cipher}bad"))); + environment.getPropertySources() + .addFirst(new MapPropertySource("source-2", Map.of("list[0].plain", "well", "list[0].cipher", "worse"))); + + decrypt(); + + then(environment.getProperty("list[0].plain")).isEqualTo("well"); + then(environment.getProperty("list[0].cipher")).isEqualTo("worse"); + } + + @Test + void anonymousIndexedPropertiesAreHandledCorrectly() { + environment.getPropertySources() + .addFirst(new MapPropertySource("source-1", Map.of("[0].plain", "good", "[0].cipher", "{cipher}bad"))); + environment.getPropertySources() + .addFirst(new MapPropertySource("source-2", Map.of("[0].plain", "well", "[0].cipher", "{cipher}worse"))); + + decrypt(); + + then(environment.getProperty("[0].plain")).isEqualTo("well"); + then(environment.getProperty("[0].cipher")).isEqualTo("worse"); + } + + private void decrypt() { + decrypt(Encryptors.noOpText()); + } + + private void decrypt(TextEncryptor encryptor) { + var decrypted = decryptor.decrypt(encryptor, environment.getPropertySources()); + environment.getPropertySources() + .addFirst(new SystemEnvironmentPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, decrypted)); + } + +} diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializerTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializerTests.java index 85ddf0459..e9a7aecf1 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializerTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializerTests.java @@ -16,8 +16,6 @@ package org.springframework.cloud.bootstrap.encrypt; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -34,7 +32,6 @@ import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.security.crypto.encrypt.Encryptors; import org.springframework.security.crypto.encrypt.TextEncryptor; @@ -83,7 +80,7 @@ public void propertySourcesOrderedCorrectly() { TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "foo: {cipher}bar").applyTo(context); context.getEnvironment() .getPropertySources() - .addFirst(new MapPropertySource("test_override", Collections.singletonMap("foo", "{cipher}spam"))); + .addFirst(new MapPropertySource("test_override", Map.of("foo", "{cipher}spam"))); this.listener.initialize(context); then(context.getEnvironment().getProperty("foo")).isEqualTo("spam"); } @@ -120,7 +117,6 @@ public void errorOnDecryptWithEmpty(CapturedOutput output) { } @Test - @SuppressWarnings("unchecked") public void indexedPropertiesCopied() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); // tests that collections in another property source don't get copied into @@ -143,10 +139,11 @@ public void indexedPropertiesCopied() { then(context.getEnvironment().getProperty("yours[0].someValue")).isEqualTo("yourFoo"); then(context.getEnvironment().getProperty("yours[1].someValue")).isEqualTo("yourBar"); - MutablePropertySources propertySources = context.getEnvironment().getPropertySources(); - PropertySource> decrypted = (PropertySource>) propertySources - .get(DECRYPTED_PROPERTY_SOURCE_NAME); - then(decrypted.getSource().size()).as("decrypted property source had wrong size").isEqualTo(4); + var decryptedPropertySource = context.getEnvironment().getPropertySources().get("decrypted"); + then(decryptedPropertySource).isNotNull(); + var source = decryptedPropertySource.getSource(); + then(source).isInstanceOf(Map.class); + then(((Map) source).size()).as("decrypted property source had wrong size").isEqualTo(4); } @Test @@ -174,16 +171,13 @@ public void testDecryptCompositePropertySource() { EnvironmentDecryptApplicationInitializer initializer = new EnvironmentDecryptApplicationInitializer( Encryptors.noOpText()); - MapPropertySource devProfile = new MapPropertySource("dev-profile", - Collections.singletonMap("key", "{cipher}value1")); + MapPropertySource devProfile = new MapPropertySource("dev-profile", Map.of("key", "{cipher}value1")); - MapPropertySource defaultProfile = new MapPropertySource("default-profile", - Collections.singletonMap("key", "{cipher}value2")); + MapPropertySource defaultProfile = new MapPropertySource("default-profile", Map.of("key", "{cipher}value2")); - CompositePropertySource cps = mock(CompositePropertySource.class); - when(cps.getName()).thenReturn("mock-composite-source"); - when(cps.getPropertyNames()).thenReturn(devProfile.getPropertyNames()); - when(cps.getPropertySources()).thenReturn(Arrays.asList(devProfile, defaultProfile)); + CompositePropertySource cps = new CompositePropertySource("mock-composite-source"); + cps.addPropertySource(devProfile); + cps.addPropertySource(defaultProfile); ctx.getEnvironment().getPropertySources().addLast(cps); initializer.initialize(ctx); @@ -196,7 +190,7 @@ public void propertySourcesOrderedCorrectlyWithUnencryptedOverrides() { TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "foo: {cipher}bar").applyTo(context); context.getEnvironment() .getPropertySources() - .addFirst(new MapPropertySource("test_override", Collections.singletonMap("foo", "spam"))); + .addFirst(new MapPropertySource("test_override", Map.of("foo", "spam"))); this.listener.initialize(context); then(context.getEnvironment().getProperty("foo")).isEqualTo("spam"); } @@ -214,8 +208,7 @@ public void doNotDecryptBootstrapTwice() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); TestPropertyValues.of("spring.cloud.bootstrap.enabled=true").applyTo(context); CompositePropertySource bootstrap = new CompositePropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME); - bootstrap - .addPropertySource(new MapPropertySource("configService", Collections.singletonMap("foo", "{cipher}bar"))); + bootstrap.addPropertySource(new MapPropertySource("configService", Map.of("foo", "{cipher}bar"))); context.getEnvironment().getPropertySources().addFirst(bootstrap); Map props = new HashMap<>(); @@ -259,7 +252,7 @@ public void testOnlyDecryptIfNotOverridden() { .applyTo(context); context.getEnvironment() .getPropertySources() - .addFirst(new MapPropertySource("test_override", Collections.singletonMap("foo", "spam"))); + .addFirst(new MapPropertySource("test_override", Map.of("foo", "spam"))); initializer.initialize(context); then(context.getEnvironment().getProperty("foo")).isEqualTo("spam"); then(context.getEnvironment().getProperty("foo2")).isEqualTo("bar2");