Add pluggable abstraction for applying custom sanitization rules
Closes gh-27840
This commit is contained in:
parent
211532f08d
commit
253f98c3e7
|
@ -16,9 +16,11 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.context.properties;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpointWebExtension;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
|
@ -43,8 +45,9 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration {
|
|||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint(
|
||||
ConfigurationPropertiesReportEndpointProperties properties) {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint();
|
||||
ConfigurationPropertiesReportEndpointProperties properties,
|
||||
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(sanitizingFunctions);
|
||||
String[] keysToSanitize = properties.getKeysToSanitize();
|
||||
if (keysToSanitize != null) {
|
||||
endpoint.setKeysToSanitize(keysToSanitize);
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.env;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpointWebExtension;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
|
@ -41,8 +43,9 @@ public class EnvironmentEndpointAutoConfiguration {
|
|||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public EnvironmentEndpoint environmentEndpoint(Environment environment, EnvironmentEndpointProperties properties) {
|
||||
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment);
|
||||
public EnvironmentEndpoint environmentEndpoint(Environment environment, EnvironmentEndpointProperties properties,
|
||||
ObjectProvider<SanitizingFunction> sanitizingFunctions) {
|
||||
EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment, sanitizingFunctions);
|
||||
String[] keysToSanitize = properties.getKeysToSanitize();
|
||||
if (keysToSanitize != null) {
|
||||
endpoint.setKeysToSanitize(keysToSanitize);
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
@ -72,6 +73,14 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
|
|||
.run(validateTestProperties("******", "******"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSanitizingFunctionShouldBeApplied() {
|
||||
this.contextRunner.withUserConfiguration(Config.class, SanitizingFunctionConfiguration.class)
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=configprops",
|
||||
"test.my-test-property=abc")
|
||||
.run(validateTestProperties("******", "$$$"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWhenNotExposedShouldNotHaveEndpointBean() {
|
||||
this.contextRunner
|
||||
|
@ -129,4 +138,19 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class SanitizingFunctionConfiguration {
|
||||
|
||||
@Bean
|
||||
SanitizingFunction testSanitizingFunction() {
|
||||
return (data) -> {
|
||||
if (data.getKey().contains("my")) {
|
||||
return data.withValue("$$$");
|
||||
}
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.Map;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor;
|
||||
|
@ -28,6 +29,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
|
|||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -67,6 +70,21 @@ class EnvironmentEndpointAutoConfigurationTests {
|
|||
.run(validateSystemProperties("******", "123456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizingFunctionsCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withUserConfiguration(SanitizingFunctionConfiguration.class)
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||
.withSystemProperties("custom=123456", "password=123456").run((context) -> {
|
||||
assertThat(context).hasSingleBean(EnvironmentEndpoint.class);
|
||||
EnvironmentEndpoint endpoint = context.getBean(EnvironmentEndpoint.class);
|
||||
EnvironmentDescriptor env = endpoint.environment(null);
|
||||
Map<String, PropertyValueDescriptor> systemProperties = getSource("systemProperties", env)
|
||||
.getProperties();
|
||||
assertThat(systemProperties.get("custom").getValue()).isEqualTo("$$$");
|
||||
assertThat(systemProperties.get("password").getValue()).isEqualTo("******");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalKeysToSanitizeCanBeConfiguredViaTheEnvironment() {
|
||||
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=env")
|
||||
|
@ -91,4 +109,14 @@ class EnvironmentEndpointAutoConfigurationTests {
|
|||
.get();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class SanitizingFunctionConfiguration {
|
||||
|
||||
@Bean
|
||||
SanitizingFunction testSanitizingFunction() {
|
||||
return (data) -> data.withValue("$$$");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -53,7 +53,9 @@ import org.apache.commons.logging.LogFactory;
|
|||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
|
@ -64,6 +66,7 @@ import org.springframework.boot.context.properties.ConstructorBinding;
|
|||
import org.springframework.boot.context.properties.bind.Name;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
@ -73,6 +76,7 @@ import org.springframework.core.ParameterNameDiscoverer;
|
|||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
@ -100,12 +104,20 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||
|
||||
private static final String CONFIGURATION_PROPERTIES_FILTER_ID = "configurationPropertiesFilter";
|
||||
|
||||
private final Sanitizer sanitizer = new Sanitizer();
|
||||
private final Sanitizer sanitizer;
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public ConfigurationPropertiesReportEndpoint() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
public ConfigurationPropertiesReportEndpoint(Iterable<SanitizingFunction> sanitizingFunctions) {
|
||||
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
this.context = context;
|
||||
|
@ -236,26 +248,63 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||
map.put(key, sanitize(qualifiedKey, (List<Object>) value));
|
||||
}
|
||||
else {
|
||||
value = this.sanitizer.sanitize(key, value);
|
||||
value = this.sanitizer.sanitize(qualifiedKey, value);
|
||||
map.put(key, value);
|
||||
map.put(key, sanitizeWithPropertySourceIfPresent(qualifiedKey, value));
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
private Object sanitizeWithPropertySourceIfPresent(String qualifiedKey, Object value) {
|
||||
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
|
||||
ConfigurationProperty candidate = getCandidate(currentName);
|
||||
PropertySource<?> propertySource = getPropertySource(candidate);
|
||||
if (propertySource != null) {
|
||||
SanitizableData data = new SanitizableData(propertySource, qualifiedKey, value);
|
||||
return this.sanitizer.sanitize(data);
|
||||
}
|
||||
SanitizableData data = new SanitizableData(null, qualifiedKey, value);
|
||||
return this.sanitizer.sanitize(data);
|
||||
}
|
||||
|
||||
private PropertySource<?> getPropertySource(ConfigurationProperty configurationProperty) {
|
||||
if (configurationProperty == null) {
|
||||
return null;
|
||||
}
|
||||
ConfigurationPropertySource source = configurationProperty.getSource();
|
||||
Object underlyingSource = (source != null) ? source.getUnderlyingSource() : null;
|
||||
return (underlyingSource instanceof PropertySource<?>) ? (PropertySource<?>) underlyingSource : null;
|
||||
}
|
||||
|
||||
private ConfigurationPropertyName getCurrentName(String qualifiedKey) {
|
||||
return ConfigurationPropertyName.adapt(qualifiedKey, '.');
|
||||
}
|
||||
|
||||
private ConfigurationProperty getCandidate(ConfigurationPropertyName currentName) {
|
||||
BoundConfigurationProperties bound = BoundConfigurationProperties.get(this.context);
|
||||
if (bound == null) {
|
||||
return null;
|
||||
}
|
||||
ConfigurationProperty candidate = bound.get(currentName);
|
||||
if (candidate == null && currentName.isLastElementIndexed()) {
|
||||
candidate = bound.get(currentName.chop(currentName.getNumberOfElements() - 1));
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> sanitize(String prefix, List<Object> list) {
|
||||
List<Object> sanitized = new ArrayList<>();
|
||||
int index = 0;
|
||||
for (Object item : list) {
|
||||
String name = prefix + "[" + index++ + "]";
|
||||
if (item instanceof Map) {
|
||||
sanitized.add(sanitize(prefix, (Map<String, Object>) item));
|
||||
sanitized.add(sanitize(name, (Map<String, Object>) item));
|
||||
}
|
||||
else if (item instanceof List) {
|
||||
sanitized.add(sanitize(prefix, (List<Object>) item));
|
||||
sanitized.add(sanitize(name, (List<Object>) item));
|
||||
}
|
||||
else {
|
||||
sanitized.add(this.sanitizer.sanitize(prefix, item));
|
||||
sanitized.add(sanitizeWithPropertySourceIfPresent(name, item));
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
|
@ -299,24 +348,22 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
|||
}
|
||||
|
||||
private Map<String, Object> applyInput(String qualifiedKey) {
|
||||
BoundConfigurationProperties bound = BoundConfigurationProperties.get(this.context);
|
||||
if (bound == null) {
|
||||
return Collections.emptyMap();
|
||||
ConfigurationPropertyName currentName = getCurrentName(qualifiedKey);
|
||||
ConfigurationProperty candidate = getCandidate(currentName);
|
||||
PropertySource<?> propertySource = getPropertySource(candidate);
|
||||
if (propertySource != null) {
|
||||
Object value = stringifyIfNecessary(candidate.getValue());
|
||||
SanitizableData data = new SanitizableData(propertySource, currentName.toString(), value);
|
||||
return getInput(candidate, this.sanitizer.sanitize(data));
|
||||
}
|
||||
ConfigurationPropertyName currentName = ConfigurationPropertyName.adapt(qualifiedKey, '.');
|
||||
ConfigurationProperty candidate = bound.get(currentName);
|
||||
if (candidate == null && currentName.isLastElementIndexed()) {
|
||||
candidate = bound.get(currentName.chop(currentName.getNumberOfElements() - 1));
|
||||
}
|
||||
return (candidate != null) ? getInput(currentName.toString(), candidate) : Collections.emptyMap();
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
private Map<String, Object> getInput(String property, ConfigurationProperty candidate) {
|
||||
private Map<String, Object> getInput(ConfigurationProperty candidate, Object sanitizedValue) {
|
||||
Map<String, Object> input = new LinkedHashMap<>();
|
||||
Object value = stringifyIfNecessary(candidate.getValue());
|
||||
Origin origin = Origin.from(candidate);
|
||||
List<Origin> originParents = Origin.parentsFrom(candidate);
|
||||
input.put("value", this.sanitizer.sanitize(property, value));
|
||||
input.put("value", sanitizedValue);
|
||||
input.put("origin", (origin != null) ? origin.toString() : "none");
|
||||
if (!originParents.isEmpty()) {
|
||||
input.put("originParents", originParents.stream().map(Object::toString).toArray(String[]::new));
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2012-2021 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.boot.actuate.endpoint;
|
||||
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
/**
|
||||
* Value object that represents the data that can be used by a {@link SanitizingFunction}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
**/
|
||||
public final class SanitizableData {
|
||||
|
||||
/**
|
||||
* Represents a sanitized value.
|
||||
*/
|
||||
public static final String SANITIZED_VALUE = "******";
|
||||
|
||||
private final PropertySource<?> propertySource;
|
||||
|
||||
private final String key;
|
||||
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Create a new {@link SanitizableData} instance.
|
||||
* @param propertySource the property source that provided the data or {@code null}.
|
||||
* @param key the data key
|
||||
* @param value the data value
|
||||
*/
|
||||
public SanitizableData(PropertySource<?> propertySource, String key, Object value) {
|
||||
this.propertySource = propertySource;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property source that provided the data or {@code null} If the data was
|
||||
* not from a {@link PropertySource}.
|
||||
* @return the property source that provided the data
|
||||
*/
|
||||
public PropertySource<?> getPropertySource() {
|
||||
return this.propertySource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the data.
|
||||
* @return the data key
|
||||
*/
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the data.
|
||||
* @return the data value
|
||||
*/
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link SanitizableData} instance with a different value.
|
||||
* @param value the new value (often {@link #SANITIZED_VALUE}
|
||||
* @return a new sanitizable data instance
|
||||
*/
|
||||
public SanitizableData withValue(Object value) {
|
||||
return new SanitizableData(this.propertySource, this.key, value);
|
||||
}
|
||||
|
||||
}
|
|
@ -16,8 +16,11 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -38,6 +41,7 @@ import org.springframework.util.StringUtils;
|
|||
* @author HaiTao Zhang
|
||||
* @author Chris Bono
|
||||
* @author David Good
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class Sanitizer {
|
||||
|
@ -55,6 +59,8 @@ public class Sanitizer {
|
|||
|
||||
private Pattern[] keysToSanitize;
|
||||
|
||||
private final List<SanitizingFunction> sanitizingFunctions = new ArrayList<>();
|
||||
|
||||
static {
|
||||
DEFAULT_KEYS_TO_SANITIZE.addAll(URI_USERINFO_KEYS);
|
||||
}
|
||||
|
@ -64,9 +70,26 @@ public class Sanitizer {
|
|||
}
|
||||
|
||||
public Sanitizer(String... keysToSanitize) {
|
||||
this(Collections.emptyList(), keysToSanitize);
|
||||
}
|
||||
|
||||
public Sanitizer(Iterable<SanitizingFunction> sanitizingFunctions) {
|
||||
this(sanitizingFunctions, DEFAULT_KEYS_TO_SANITIZE.toArray(new String[0]));
|
||||
}
|
||||
|
||||
public Sanitizer(Iterable<SanitizingFunction> sanitizingFunctions, String... keysToSanitize) {
|
||||
sanitizingFunctions.forEach(this.sanitizingFunctions::add);
|
||||
this.sanitizingFunctions.add(getDefaultSanitizingFunction());
|
||||
setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
private SanitizingFunction getDefaultSanitizingFunction() {
|
||||
return (data) -> {
|
||||
Object sanitizedValue = sanitize(data.getKey(), data.getValue());
|
||||
return data.withValue(sanitizedValue);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys that should be sanitized, overwriting any existing configuration. Keys
|
||||
* can be simple strings that the property ends with or regular expressions.
|
||||
|
@ -126,12 +149,29 @@ public class Sanitizer {
|
|||
if (keyIsUriWithUserInfo(pattern)) {
|
||||
return sanitizeUris(value.toString());
|
||||
}
|
||||
return "******";
|
||||
return SanitizableData.SANITIZED_VALUE;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the value from the given {@link SanitizableData} using the available
|
||||
* {@link SanitizingFunction}s.
|
||||
* @param data the sanitizable data
|
||||
* @return the potentially updated data
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public Object sanitize(SanitizableData data) {
|
||||
if (data.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
for (SanitizingFunction sanitizingFunction : this.sanitizingFunctions) {
|
||||
data = sanitizingFunction.apply(data);
|
||||
}
|
||||
return data.getValue();
|
||||
}
|
||||
|
||||
private boolean keyIsUriWithUserInfo(Pattern pattern) {
|
||||
for (String uriKey : URI_USERINFO_KEYS) {
|
||||
if (pattern.matcher(uriKey).matches()) {
|
||||
|
@ -149,7 +189,7 @@ public class Sanitizer {
|
|||
Matcher matcher = URI_USERINFO_PATTERN.matcher(value);
|
||||
String password = matcher.matches() ? matcher.group(1) : null;
|
||||
if (password != null) {
|
||||
return StringUtils.replace(value, ":" + password + "@", ":******@");
|
||||
return StringUtils.replace(value, ":" + password + "@", ":" + SanitizableData.SANITIZED_VALUE + "@");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2012-2021 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.boot.actuate.endpoint;
|
||||
|
||||
/**
|
||||
* Function that takes a {@link SanitizableData} and applies sanitization to the value, if
|
||||
* necessary. Can be used by a {@link Sanitizer} to determine the sanitized value.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SanitizingFunction {
|
||||
|
||||
/**
|
||||
* Apply the sanitiing function to the given data.
|
||||
* @param data the data to sanitize
|
||||
* @return the sanitized data or the original instance is no sanitization is applied
|
||||
*/
|
||||
SanitizableData apply(SanitizableData data);
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.actuate.env;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -27,7 +28,9 @@ import java.util.stream.Stream;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
|
@ -64,12 +67,17 @@ import org.springframework.util.SystemPropertyUtils;
|
|||
@Endpoint(id = "env")
|
||||
public class EnvironmentEndpoint {
|
||||
|
||||
private final Sanitizer sanitizer = new Sanitizer();
|
||||
private final Sanitizer sanitizer;
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public EnvironmentEndpoint(Environment environment) {
|
||||
this(environment, Collections.emptyList());
|
||||
}
|
||||
|
||||
public EnvironmentEndpoint(Environment environment, Iterable<SanitizingFunction> sanitizingFunctions) {
|
||||
this.environment = environment;
|
||||
this.sanitizer = new Sanitizer(sanitizingFunctions);
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
|
@ -149,7 +157,8 @@ public class EnvironmentEndpoint {
|
|||
PlaceholdersResolver resolver) {
|
||||
Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
|
||||
Origin origin = ((source instanceof OriginLookup) ? ((OriginLookup<Object>) source).getOrigin(name) : null);
|
||||
return new PropertyValueDescriptor(stringifyIfNecessary(sanitize(name, resolved)), origin);
|
||||
Object sanitizedValue = sanitize(source, name, resolved);
|
||||
return new PropertyValueDescriptor(stringifyIfNecessary(sanitizedValue), origin);
|
||||
}
|
||||
|
||||
private PlaceholdersResolver getResolver() {
|
||||
|
@ -184,8 +193,21 @@ public class EnvironmentEndpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public Object sanitize(String name, Object object) {
|
||||
return this.sanitizer.sanitize(name, object);
|
||||
/**
|
||||
* Apply sanitiation to the given name and value.
|
||||
* @param key the name to sanitize
|
||||
* @param value the value to sanitize
|
||||
* @return the sanitized value
|
||||
* @deprecated since 2.6.0 for removal in 2.8.0 as sanitization should be internal to
|
||||
* the class
|
||||
*/
|
||||
@Deprecated
|
||||
public Object sanitize(String key, Object value) {
|
||||
return this.sanitizer.sanitize(key, value);
|
||||
}
|
||||
|
||||
private Object sanitize(PropertySource<?> source, String name, Object value) {
|
||||
return this.sanitizer.sanitize(new SanitizableData(source, name, value));
|
||||
}
|
||||
|
||||
protected Object stringifyIfNecessary(Object value) {
|
||||
|
@ -207,19 +229,28 @@ public class EnvironmentEndpoint {
|
|||
|
||||
private final Sanitizer sanitizer;
|
||||
|
||||
private final Iterable<PropertySource<?>> sources;
|
||||
|
||||
PropertySourcesPlaceholdersSanitizingResolver(Iterable<PropertySource<?>> sources, Sanitizer sanitizer) {
|
||||
super(sources, new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
|
||||
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true));
|
||||
this.sources = sources;
|
||||
this.sanitizer = sanitizer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolvePlaceholder(String placeholder) {
|
||||
String value = super.resolvePlaceholder(placeholder);
|
||||
if (value == null) {
|
||||
return null;
|
||||
if (this.sources != null) {
|
||||
for (PropertySource<?> source : this.sources) {
|
||||
Object value = source.getProperty(placeholder);
|
||||
if (value != null) {
|
||||
SanitizableData data = new SanitizableData(source, placeholder, value);
|
||||
Object sanitized = this.sanitizer.sanitize(data);
|
||||
return (sanitized != null) ? String.valueOf(sanitized) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (String) this.sanitizer.sanitize(placeholder, value);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
@ -57,6 +58,7 @@ import static org.assertj.core.api.Assertions.entry;
|
|||
* @author Stephane Nicoll
|
||||
* @author HaiTao Zhang
|
||||
* @author Chris Bono
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
class ConfigurationPropertiesReportEndpointTests {
|
||||
|
@ -286,6 +288,50 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeWithCustomSanitizingFunction() {
|
||||
new ApplicationContextRunner().withUserConfiguration(CustomSanitizingEndpointConfig.class,
|
||||
SanitizingFunctionConfiguration.class, TestPropertiesConfiguration.class)
|
||||
.run(assertProperties("test", (properties) -> {
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("******");
|
||||
assertThat(properties.get("myTestProperty")).isEqualTo("$$$");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeWithCustomPropertySourceBasedSanitizingFunction() {
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(CustomSanitizingEndpointConfig.class,
|
||||
PropertySourceBasedSanitizingFunctionConfiguration.class, TestPropertiesConfiguration.class)
|
||||
.withPropertyValues("test.my-test-property=abcde").run(assertProperties("test", (properties) -> {
|
||||
assertThat(properties.get("dbPassword")).isEqualTo("******");
|
||||
assertThat(properties.get("myTestProperty")).isEqualTo("$$$");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeListsWithCustomSanitizingFunction() {
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(CustomSanitizingEndpointConfig.class, SanitizingFunctionConfiguration.class,
|
||||
SensiblePropertiesConfiguration.class)
|
||||
.withPropertyValues("sensible.listItems[0].custom=my-value")
|
||||
.run(assertProperties("sensible", (properties) -> {
|
||||
assertThat(properties.get("listItems")).isInstanceOf(List.class);
|
||||
List<Object> list = (List<Object>) properties.get("listItems");
|
||||
assertThat(list).hasSize(1);
|
||||
Map<String, Object> item = (Map<String, Object>) list.get(0);
|
||||
assertThat(item.get("custom")).isEqualTo("$$$");
|
||||
}, (inputs) -> {
|
||||
List<Object> list = (List<Object>) inputs.get("listItems");
|
||||
assertThat(list).hasSize(1);
|
||||
Map<String, Object> item = (Map<String, Object>) list.get(0);
|
||||
Map<String, Object> somePassword = (Map<String, Object>) item.get("custom");
|
||||
assertThat(somePassword.get("value")).isEqualTo("$$$");
|
||||
assertThat(somePassword.get("origin"))
|
||||
.isEqualTo("\"sensible.listItems[0].custom\" from property source \"test\"");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void originParents() {
|
||||
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
|
||||
|
@ -778,6 +824,8 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||
|
||||
private String somePassword = "secret";
|
||||
|
||||
private String custom;
|
||||
|
||||
public String getSomePassword() {
|
||||
return this.somePassword;
|
||||
}
|
||||
|
@ -786,6 +834,60 @@ class ConfigurationPropertiesReportEndpointTests {
|
|||
this.somePassword = somePassword;
|
||||
}
|
||||
|
||||
public String getCustom() {
|
||||
return this.custom;
|
||||
}
|
||||
|
||||
public void setCustom(String custom) {
|
||||
this.custom = custom;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomSanitizingEndpointConfig {
|
||||
|
||||
@Bean
|
||||
ConfigurationPropertiesReportEndpoint endpoint(Environment environment, SanitizingFunction sanitizingFunction) {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(
|
||||
Collections.singletonList(sanitizingFunction));
|
||||
String[] keys = environment.getProperty("test.keys-to-sanitize", String[].class);
|
||||
if (keys != null) {
|
||||
endpoint.setKeysToSanitize(keys);
|
||||
}
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class SanitizingFunctionConfiguration {
|
||||
|
||||
@Bean
|
||||
SanitizingFunction testSanitizingFunction() {
|
||||
return (data) -> {
|
||||
if (data.getKey().contains("custom") || data.getKey().contains("test")) {
|
||||
return data.withValue("$$$");
|
||||
}
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class PropertySourceBasedSanitizingFunctionConfiguration {
|
||||
|
||||
@Bean
|
||||
SanitizingFunction testSanitizingFunction() {
|
||||
return (data) -> {
|
||||
if (data.getPropertySource() != null && data.getPropertySource().getName().startsWith("test")) {
|
||||
return data.withValue("$$$");
|
||||
}
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -31,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
* @author Stephane Nicoll
|
||||
* @author Chris Bono
|
||||
* @author David Good
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class SanitizerTests {
|
||||
|
||||
|
@ -65,6 +67,22 @@ class SanitizerTests {
|
|||
assertThat(sanitizer.sanitize("private", "secret")).isEqualTo("secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenCustomSanitizingFunctionPresentValueShouldBeSanitized() {
|
||||
Sanitizer sanitizer = new Sanitizer(Collections.singletonList((data) -> {
|
||||
if (data.getKey().equals("custom")) {
|
||||
return data.withValue("$$$$$$");
|
||||
}
|
||||
return data;
|
||||
}));
|
||||
SanitizableData secret = new SanitizableData(null, "secret", "xyz");
|
||||
assertThat(sanitizer.sanitize(secret)).isEqualTo("******");
|
||||
SanitizableData custom = new SanitizableData(null, "custom", "abcde");
|
||||
assertThat(sanitizer.sanitize(custom)).isEqualTo("$$$$$$");
|
||||
SanitizableData hello = new SanitizableData(null, "hello", "abc");
|
||||
assertThat(sanitizer.sanitize(hello)).isEqualTo("abc");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "key = {0}")
|
||||
@MethodSource("matchingUriUserInfoKeys")
|
||||
void uriWithSingleValueWithPasswordShouldBeSanitized(String key) {
|
||||
|
|
|
@ -164,6 +164,28 @@ class EnvironmentEndpointTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void keysMatchingCustomSanitizingFunctionHaveTheirValuesSanitized() {
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
TestPropertyValues.of("other.service=abcde").applyTo(environment);
|
||||
TestPropertyValues.of("system.service=123456").applyToSystemProperties(() -> {
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment,
|
||||
Collections.singletonList((data) -> {
|
||||
String name = data.getPropertySource().getName();
|
||||
if (name.equals(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)) {
|
||||
return data.withValue("******");
|
||||
}
|
||||
return data;
|
||||
})).environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("other.service").getValue())
|
||||
.isEqualTo("abcde");
|
||||
Map<String, PropertyValueDescriptor> systemProperties = propertySources(descriptor).get("systemProperties")
|
||||
.getProperties();
|
||||
assertThat(systemProperties.get("system.service").getValue()).isEqualTo("******");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyWithPlaceholderResolved() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
|
@ -199,6 +221,17 @@ class EnvironmentEndpointTests {
|
|||
.isEqualTo("http://${bar.password}://hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyWithSensitivePlaceholderWithCustomFunctionResolved() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
TestPropertyValues.of("my.foo: http://${bar.password}://hello", "bar.password: hello").applyTo(environment);
|
||||
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment,
|
||||
Collections.singletonList((data) -> data.withValue(data.getPropertySource().getName() + "******")))
|
||||
.environment(null);
|
||||
assertThat(propertySources(descriptor).get("test").getProperties().get("my.foo").getValue())
|
||||
.isEqualTo("test******");
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyWithComplexTypeShouldNotFail() {
|
||||
ConfigurableEnvironment environment = emptyEnvironment();
|
||||
|
|
|
@ -38,20 +38,46 @@ public final class ConfigurationProperty implements OriginProvider, Comparable<C
|
|||
|
||||
private final Object value;
|
||||
|
||||
private final ConfigurationPropertySource source;
|
||||
|
||||
private final Origin origin;
|
||||
|
||||
public ConfigurationProperty(ConfigurationPropertyName name, Object value, Origin origin) {
|
||||
this(null, name, value, origin);
|
||||
}
|
||||
|
||||
private ConfigurationProperty(ConfigurationPropertySource source, ConfigurationPropertyName name, Object value,
|
||||
Origin origin) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(value, "Value must not be null");
|
||||
this.source = source;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ConfigurationPropertySource} that provided the property or
|
||||
* {@code null} if the source is unknown.
|
||||
* @return the configuration property source
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public ConfigurationPropertySource getSource() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the configuration property.
|
||||
* @return the configuration property name
|
||||
*/
|
||||
public ConfigurationPropertyName getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the configuration property.
|
||||
* @return the configuration property value
|
||||
*/
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
@ -101,11 +127,12 @@ public final class ConfigurationProperty implements OriginProvider, Comparable<C
|
|||
return new ConfigurationProperty(name, value.getValue(), value.getOrigin());
|
||||
}
|
||||
|
||||
static ConfigurationProperty of(ConfigurationPropertyName name, Object value, Origin origin) {
|
||||
static ConfigurationProperty of(ConfigurationPropertySource source, ConfigurationPropertyName name, Object value,
|
||||
Origin origin) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return new ConfigurationProperty(name, value, origin);
|
||||
return new ConfigurationProperty(source, name, value, origin);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -46,7 +46,8 @@ class PrefixedConfigurationPropertySource implements ConfigurationPropertySource
|
|||
if (configurationProperty == null) {
|
||||
return null;
|
||||
}
|
||||
return ConfigurationProperty.of(name, configurationProperty.getValue(), configurationProperty.getOrigin());
|
||||
return ConfigurationProperty.of(configurationProperty.getSource(), name, configurationProperty.getValue(),
|
||||
configurationProperty.getOrigin());
|
||||
}
|
||||
|
||||
private ConfigurationPropertyName getPrefixedName(ConfigurationPropertyName name) {
|
||||
|
|
|
@ -83,8 +83,8 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
|
|||
for (String candidate : mapper.map(name)) {
|
||||
Object value = getPropertySource().getProperty(candidate);
|
||||
if (value != null) {
|
||||
Origin origin = PropertySourceOrigin.get(getPropertySource(), candidate);
|
||||
return ConfigurationProperty.of(name, value, origin);
|
||||
Origin origin = PropertySourceOrigin.get(this.propertySource, candidate);
|
||||
return ConfigurationProperty.of(this, name, value, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
|
|||
Object value = getPropertySource().getProperty(candidate);
|
||||
if (value != null) {
|
||||
Origin origin = PropertySourceOrigin.get(getPropertySource(), candidate);
|
||||
return ConfigurationProperty.of(name, value, origin);
|
||||
return ConfigurationProperty.of(this, name, value, origin);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.boot.origin.OriginProvider;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
@ -35,6 +36,8 @@ class ConfigurationPropertyTests {
|
|||
|
||||
private static final ConfigurationPropertyName NAME = ConfigurationPropertyName.of("foo");
|
||||
|
||||
private ConfigurationPropertySource source = ConfigurationPropertySource.from(mock(PropertySource.class));
|
||||
|
||||
@Test
|
||||
void createWhenNameIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ConfigurationProperty(null, "bar", null))
|
||||
|
@ -49,23 +52,30 @@ class ConfigurationPropertyTests {
|
|||
|
||||
@Test
|
||||
void getNameShouldReturnName() {
|
||||
ConfigurationProperty property = ConfigurationProperty.of(NAME, "foo", null);
|
||||
ConfigurationProperty property = ConfigurationProperty.of(this.source, NAME, "foo", null);
|
||||
assertThat((Object) property.getName()).isEqualTo(NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getValueShouldReturnValue() {
|
||||
ConfigurationProperty property = ConfigurationProperty.of(NAME, "foo", null);
|
||||
ConfigurationProperty property = ConfigurationProperty.of(this.source, NAME, "foo", null);
|
||||
assertThat(property.getValue()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPropertyOriginShouldReturnValuePropertyOrigin() {
|
||||
Origin origin = mock(Origin.class);
|
||||
OriginProvider property = ConfigurationProperty.of(NAME, "foo", origin);
|
||||
OriginProvider property = ConfigurationProperty.of(this.source, NAME, "foo", origin);
|
||||
assertThat(property.getOrigin()).isEqualTo(origin);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPropertySourceShouldReturnPropertySource() {
|
||||
Origin origin = mock(Origin.class);
|
||||
ConfigurationProperty property = ConfigurationProperty.of(this.source, NAME, "foo", origin);
|
||||
assertThat(property.getSource()).isEqualTo(this.source);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsAndHashCode() {
|
||||
ConfigurationProperty property1 = new ConfigurationProperty(ConfigurationPropertyName.of("foo"), "bar", null);
|
||||
|
@ -78,7 +88,7 @@ class ConfigurationPropertyTests {
|
|||
|
||||
@Test
|
||||
void toStringShouldReturnValue() {
|
||||
ConfigurationProperty property = ConfigurationProperty.of(NAME, "foo", null);
|
||||
ConfigurationProperty property = ConfigurationProperty.of(this.source, NAME, "foo", null);
|
||||
assertThat(property.toString()).contains("name").contains("value");
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class SpringConfigurationPropertySourceTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void getValueOrigin() {
|
||||
void getValueOriginAndPropertySource() {
|
||||
Map<String, Object> source = new LinkedHashMap<>();
|
||||
source.put("key", "value");
|
||||
PropertySource<?> propertySource = new MapPropertySource("test", source);
|
||||
|
@ -69,8 +69,9 @@ class SpringConfigurationPropertySourceTests {
|
|||
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
|
||||
mapper.addFromConfigurationProperty(name, "key");
|
||||
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(propertySource, mapper);
|
||||
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
|
||||
.isEqualTo("\"key\" from property source \"test\"");
|
||||
ConfigurationProperty configurationProperty = adapter.getConfigurationProperty(name);
|
||||
assertThat(configurationProperty.getOrigin().toString()).isEqualTo("\"key\" from property source \"test\"");
|
||||
assertThat(configurationProperty.getSource()).isEqualTo(adapter);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue