Expose originParents on actuator endpoints

Update `ConfigurationPropertiesReportEndpoint` and `EnvironmentEndpoint`
so that they expose `originParents` when they are available.

Closes gh-23018
pull/23127/head
Phillip Webb 4 years ago
parent 3c1e141aef
commit cf8776b83c

@ -60,6 +60,7 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.origin.Origin;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
@ -292,10 +293,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
private Map<String, Object> getInput(String property, ConfigurationProperty candidate) { private Map<String, Object> getInput(String property, ConfigurationProperty candidate) {
Map<String, Object> input = new LinkedHashMap<>(); Map<String, Object> input = new LinkedHashMap<>();
String origin = (candidate.getOrigin() != null) ? candidate.getOrigin().toString() : "none";
Object value = candidate.getValue(); Object value = candidate.getValue();
input.put("origin", origin); Origin origin = Origin.from(candidate);
List<Origin> originParents = Origin.parentsFrom(candidate);
input.put("value", this.sanitizer.sanitize(property, value)); input.put("value", this.sanitizer.sanitize(property, value));
input.put("origin", (origin != null) ? origin.toString() : "none");
if (!originParents.isEmpty()) {
input.put("originParents", originParents.stream().map(Object::toString).toArray(String[]::new));
}
return input; return input;
} }

@ -142,15 +142,10 @@ public class EnvironmentEndpoint {
private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source, private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source,
PlaceholdersResolver resolver) { PlaceholdersResolver resolver) {
Object resolved = resolver.resolvePlaceholders(source.getProperty(name)); Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
String origin = ((source instanceof OriginLookup) ? getOrigin((OriginLookup<Object>) source, name) : null); Origin origin = ((source instanceof OriginLookup) ? ((OriginLookup<Object>) source).getOrigin(name) : null);
return new PropertyValueDescriptor(sanitize(name, resolved), origin); return new PropertyValueDescriptor(sanitize(name, resolved), origin);
} }
private String getOrigin(OriginLookup<Object> lookup, String name) {
Origin origin = lookup.getOrigin(name);
return (origin != null) ? origin.toString() : null;
}
private PlaceholdersResolver getResolver() { private PlaceholdersResolver getResolver() {
return new PropertySourcesPlaceholdersSanitizingResolver(getPropertySources(), this.sanitizer); return new PropertySourcesPlaceholdersSanitizingResolver(getPropertySources(), this.sanitizer);
} }
@ -353,9 +348,14 @@ public class EnvironmentEndpoint {
private final String origin; private final String origin;
private PropertyValueDescriptor(Object value, String origin) { private final String[] originParents;
private PropertyValueDescriptor(Object value, Origin origin) {
this.value = value; this.value = value;
this.origin = origin; this.origin = (origin != null) ? origin.toString() : null;
List<Origin> originParents = Origin.parentsFrom(origin);
this.originParents = originParents.isEmpty() ? null
: originParents.stream().map(Object::toString).toArray(String[]::new);
} }
public Object getValue() { public Object getValue() {
@ -366,6 +366,10 @@ public class EnvironmentEndpoint {
return this.origin; return this.origin;
} }
public String[] getOriginParents() {
return this.originParents;
}
} }
} }

@ -34,12 +34,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.entry;
@ -281,6 +285,23 @@ class ConfigurationPropertiesReportEndpointTests {
})); }));
} }
@Test
void originParents() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.withInitializer(this::initializeOriginParents).run(assertProperties("sensible", (properties) -> {
}, (inputs) -> {
Map<String, Object> stringInputs = (Map<String, Object>) inputs.get("string");
String[] originParents = (String[]) stringInputs.get("originParents");
assertThat(originParents).containsExactly("spring", "boot");
}));
}
private void initializeOriginParents(ConfigurableApplicationContext context) {
MockPropertySource propertySource = new OriginParentMockPropertySource();
propertySource.setProperty("sensible.string", "spring");
context.getEnvironment().getPropertySources().addFirst(propertySource);
}
private ContextConsumer<AssertableApplicationContext> assertProperties(String prefix, private ContextConsumer<AssertableApplicationContext> assertProperties(String prefix,
Consumer<Map<String, Object>> properties) { Consumer<Map<String, Object>> properties) {
return assertProperties(prefix, properties, (inputs) -> { return assertProperties(prefix, properties, (inputs) -> {
@ -310,6 +331,38 @@ class ConfigurationPropertiesReportEndpointTests {
return prefix.equals(candidate); return prefix.equals(candidate);
} }
static class OriginParentMockPropertySource extends MockPropertySource implements OriginLookup<String> {
@Override
public Origin getOrigin(String key) {
return new MockOrigin(key, new MockOrigin("spring", new MockOrigin("boot", null)));
}
}
static class MockOrigin implements Origin {
private final String value;
private final MockOrigin parent;
MockOrigin(String value, MockOrigin parent) {
this.value = value;
this.parent = parent;
}
@Override
public Origin getParent() {
return this.parent;
}
@Override
public String toString() {
return this.value;
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class EndpointConfig { static class EndpointConfig {
@ -649,6 +702,8 @@ class ConfigurationPropertiesReportEndpointTests {
@ConfigurationProperties("sensible") @ConfigurationProperties("sensible")
public static class SensibleProperties { public static class SensibleProperties {
private String string;
private URI sensitiveUri = URI.create("http://user:password@localhost:8080"); private URI sensitiveUri = URI.create("http://user:password@localhost:8080");
private URI noPasswordUri = URI.create("http://user:@localhost:8080"); private URI noPasswordUri = URI.create("http://user:@localhost:8080");
@ -666,6 +721,14 @@ class ConfigurationPropertiesReportEndpointTests {
this.listOfListItems.add(Collections.singletonList(new ListItem())); this.listOfListItems.add(Collections.singletonList(new ListItem()));
} }
public void setString(String string) {
this.string = string;
}
public String getString() {
return this.string;
}
public void setSensitiveUri(URI sensitiveUri) { public void setSensitiveUri(URI sensitiveUri) {
this.sensitiveUri = sensitiveUri; this.sensitiveUri = sensitiveUri;
} }

@ -29,6 +29,8 @@ import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDe
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceEntryDescriptor; import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceEntryDescriptor;
import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertyValueDescriptor; import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertyValueDescriptor;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -37,6 +39,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -222,6 +225,18 @@ class EnvironmentEndpointTests {
}); });
} }
@Test
void originAndOriginParents() {
StandardEnvironment environment = new StandardEnvironment();
OriginParentMockPropertySource propertySource = new OriginParentMockPropertySource();
propertySource.setProperty("name", "test");
environment.getPropertySources().addFirst(propertySource);
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("name");
PropertySourceEntryDescriptor entryDescriptor = propertySources(descriptor).get("mockProperties");
assertThat(entryDescriptor.getProperty().getOrigin()).isEqualTo("name");
assertThat(entryDescriptor.getProperty().getOriginParents()).containsExactly("spring", "boot");
}
@Test @Test
void propertyEntryNotFound() { void propertyEntryNotFound() {
ConfigurableEnvironment environment = emptyEnvironment(); ConfigurableEnvironment environment = emptyEnvironment();
@ -302,6 +317,38 @@ class EnvironmentEndpointTests {
} }
static class OriginParentMockPropertySource extends MockPropertySource implements OriginLookup<String> {
@Override
public Origin getOrigin(String key) {
return new MockOrigin(key, new MockOrigin("spring", new MockOrigin("boot", null)));
}
}
static class MockOrigin implements Origin {
private final String value;
private final MockOrigin parent;
MockOrigin(String value, MockOrigin parent) {
this.value = value;
this.parent = parent;
}
@Override
public Origin getParent() {
return this.parent;
}
@Override
public String toString() {
return this.value;
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties @EnableConfigurationProperties
static class Config { static class Config {

Loading…
Cancel
Save