Skip to content

Commit 1a3f810

Browse files
Prevent serialization exception from Env actuator
When `EnvironmentEndpoint` is building a response to return to the web infrastructure, it creates a data structure containing all property values from all property sources. Prior to this commit, it was possible for the response data structure to contain property values that were not serializable to JSON by Jackson, which would cause an exception to be thrown by the web infrastructure. This commit ensures the data structure is serializable to JSON by ensuring property values are primitives or Strings, and returning a placeholder value if a property value is of any other type. Fixes spring-projectsgh-23805
1 parent 008ab4d commit 1a3f810

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
* @author Christian Dupuis
5858
* @author Madhura Bhave
5959
* @author Stephane Nicoll
60+
* @author Scott Frederick
6061
* @since 2.0.0
6162
*/
6263
@Endpoint(id = "env")
@@ -143,7 +144,7 @@ private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> s
143144
PlaceholdersResolver resolver) {
144145
Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
145146
Origin origin = ((source instanceof OriginLookup) ? ((OriginLookup<Object>) source).getOrigin(name) : null);
146-
return new PropertyValueDescriptor(sanitize(name, resolved), origin);
147+
return new PropertyValueDescriptor(stringifyIfNecessary(sanitize(name, resolved)), origin);
147148
}
148149

149150
private PlaceholdersResolver getResolver() {
@@ -182,6 +183,16 @@ public Object sanitize(String name, Object object) {
182183
return this.sanitizer.sanitize(name, object);
183184
}
184185

186+
protected Object stringifyIfNecessary(Object value) {
187+
if (value == null || value.getClass().isPrimitive()) {
188+
return value;
189+
}
190+
if (CharSequence.class.isAssignableFrom(value.getClass())) {
191+
return value.toString();
192+
}
193+
return "Complex property type " + value.getClass().getName();
194+
}
195+
185196
/**
186197
* {@link PropertySourcesPlaceholdersResolver} that sanitizes sensitive placeholders
187198
* if present.

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot.actuate.env;
1818

19+
import java.io.ByteArrayInputStream;
20+
import java.io.IOException;
21+
import java.io.InputStream;
1922
import java.util.Collections;
2023
import java.util.LinkedHashMap;
2124
import java.util.Map;
@@ -39,6 +42,7 @@
3942
import org.springframework.core.env.Environment;
4043
import org.springframework.core.env.MapPropertySource;
4144
import org.springframework.core.env.StandardEnvironment;
45+
import org.springframework.core.io.InputStreamSource;
4246
import org.springframework.mock.env.MockPropertySource;
4347

4448
import static org.assertj.core.api.Assertions.assertThat;
@@ -54,6 +58,7 @@
5458
* @author Andy Wilkinson
5559
* @author HaiTao Zhang
5660
* @author Chris Bono
61+
* @author Scott Frederick
5762
*/
5863
class EnvironmentEndpointTests {
5964

@@ -194,15 +199,22 @@ void propertyWithSensitivePlaceholderNotResolved() {
194199
}
195200

196201
@Test
197-
@SuppressWarnings("unchecked")
198202
void propertyWithTypeOtherThanStringShouldNotFail() {
199203
ConfigurableEnvironment environment = emptyEnvironment();
200204
environment.getPropertySources()
201205
.addFirst(singleKeyPropertySource("test", "foo", Collections.singletonMap("bar", "baz")));
202206
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
203-
Map<String, String> foo = (Map<String, String>) propertySources(descriptor).get("test").getProperties()
204-
.get("foo").getValue();
205-
assertThat(foo.get("bar")).isEqualTo("baz");
207+
String value = (String) propertySources(descriptor).get("test").getProperties().get("foo").getValue();
208+
assertThat(value).isEqualTo("Complex property type java.util.Collections$SingletonMap");
209+
}
210+
211+
@Test
212+
void propertyWithCharSequenceTypeIsConvertedToString() throws Exception {
213+
ConfigurableEnvironment environment = emptyEnvironment();
214+
environment.getPropertySources().addFirst(singleKeyPropertySource("test", "foo", new CharSequenceProperty()));
215+
EnvironmentDescriptor descriptor = new EnvironmentEndpoint(environment).environment(null);
216+
String value = (String) propertySources(descriptor).get("test").getProperties().get("foo").getValue();
217+
assertThat(value).isEqualTo("test value");
206218
}
207219

208220
@Test
@@ -360,4 +372,35 @@ EnvironmentEndpoint environmentEndpoint(Environment environment) {
360372

361373
}
362374

375+
public static class CharSequenceProperty implements CharSequence, InputStreamSource {
376+
377+
private final String value = "test value";
378+
379+
@Override
380+
public int length() {
381+
return this.value.length();
382+
}
383+
384+
@Override
385+
public char charAt(int index) {
386+
return this.value.charAt(index);
387+
}
388+
389+
@Override
390+
public CharSequence subSequence(int start, int end) {
391+
return this.value.subSequence(start, end);
392+
}
393+
394+
@Override
395+
public String toString() {
396+
return this.value;
397+
}
398+
399+
@Override
400+
public InputStream getInputStream() throws IOException {
401+
return new ByteArrayInputStream(this.value.getBytes());
402+
}
403+
404+
}
405+
363406
}

0 commit comments

Comments
 (0)