Skip to content

Commit 07831a7

Browse files
committed
Merge branch '4.3.x'
2 parents d0b1339 + c8364f7 commit 07831a7

7 files changed

Lines changed: 309 additions & 38 deletions

File tree

README.adoc

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ parsing or rendering it, just copying it to `${main.basedir}`
113113
any changes in the README it will then show up after a Maven build as
114114
a modified file in the correct place. Just commit it and push the change.
115115

116+
You can generate the docs site using the following command:
117+
118+
[indent=0]
119+
----
120+
./mvnw -pl docs -P docs antora:antora
121+
----
122+
116123
[[working-with-the-code]]
117124
== Working with the code
118125
If you don't have an IDE preference we would recommend that you use
@@ -156,20 +163,6 @@ The generated eclipse projects can be imported by selecting `import existing pro
156163
from the `file` menu.
157164

158165

159-
[[jce]]
160-
== JCE
161-
162-
If you get an exception due to "Illegal key size" and you are using Sun’s JDK, you need to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files.
163-
See the following links for more information:
164-
165-
https://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html[Java 6 JCE]
166-
167-
https://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html[Java 7 JCE]
168-
169-
https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html[Java 8 JCE]
170-
171-
Extract the JCE files into the `JDK/jre/lib/security` folder for whichever version of JRE/JDK x64/x86 you use.
172-
173166
[[contributing]]
174167
= Contributing
175168

docs/modules/ROOT/pages/server/encryption-and-decryption.adoc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
[[encryption-and-decryption]]
22
= Encryption and Decryption
33

4-
IMPORTANT: To use the encryption and decryption features you need the full-strength JCE installed in your JVM (it is not included by default).
5-
You can download the "`Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files`" from Oracle and follow the installation instructions (essentially, you need to replace the two policy files in the JRE lib/security directory with the ones that you downloaded). This is only applicable for old versions of Java. Starting from Java 9, and definitively from Java 8u161 onwards, unlimited strength cryptography is enabled by default
6-
74
If the remote property sources contain encrypted content (values starting with `\{cipher}`), they are decrypted before sending to clients over HTTP.
85
The main advantage of this setup is that the property values need not be in plain text when they are "`at rest`" (for example, in a git repository).
96
If a value cannot be decrypted, it is removed from the property source and an additional property is added with the same key but prefixed with `invalid` and a value that means "`not applicable`" (usually `<n/a>`).

docs/src/main/asciidoc/README.adoc

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,6 @@ The refresh endpoint reports that the "sample" property changed.
6464

6565
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/building.adoc[]
6666

67-
[[jce]]
68-
== JCE
69-
70-
If you get an exception due to "Illegal key size" and you are using Sun’s JDK, you need to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files.
71-
See the following links for more information:
72-
73-
https://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html[Java 6 JCE]
74-
75-
https://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html[Java 7 JCE]
76-
77-
https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html[Java 8 JCE]
78-
79-
Extract the JCE files into the `JDK/jre/lib/security` folder for whichever version of JRE/JDK x64/x86 you use.
80-
8167
[[contributing]]
8268
= Contributing
8369

spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.springframework.web.bind.annotation.RestController;
5050

5151
import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.prepareEnvironment;
52+
import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.resolveMapPlaceholders;
5253
import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.resolvePlaceholders;
5354
import static org.springframework.cloud.config.server.support.PathUtils.isInvalidEncodedLocation;
5455
import static org.springframework.cloud.config.server.support.PathUtils.isInvalidProfiles;
@@ -199,10 +200,10 @@ public ResponseEntity<String> labelledJsonProperties(@PathVariable String name,
199200
validateProfiles(profiles);
200201
Environment environment = labelled(name, profiles, label);
201202
Map<String, Object> properties = convertToMap(environment);
202-
String json = this.objectMapper.writeValueAsString(properties);
203203
if (resolvePlaceholders) {
204-
json = resolvePlaceholders(prepareEnvironment(environment), json);
204+
properties = resolveMapPlaceholders(prepareEnvironment(environment), properties);
205205
}
206+
String json = this.objectMapper.writeValueAsString(properties);
206207
return getSuccess(json, MediaType.APPLICATION_JSON);
207208
}
208209

@@ -230,6 +231,9 @@ public ResponseEntity<String> labelledYaml(@PathVariable String name, @PathVaria
230231
validateProfiles(profiles);
231232
Environment environment = labelled(name, profiles, label);
232233
Map<String, Object> result = convertToMap(environment);
234+
if (resolvePlaceholders) {
235+
result = resolveMapPlaceholders(prepareEnvironment(environment), result);
236+
}
233237
if (this.stripDocument && result.size() == 1 && result.keySet().iterator().next().equals("document")) {
234238
Object value = result.get("document");
235239
if (value instanceof Collection) {
@@ -240,11 +244,6 @@ public ResponseEntity<String> labelledYaml(@PathVariable String name, @PathVaria
240244
}
241245
}
242246
String yaml = new Yaml().dumpAsMap(result);
243-
244-
if (resolvePlaceholders) {
245-
yaml = resolvePlaceholders(prepareEnvironment(environment), yaml);
246-
}
247-
248247
return getSuccess(yaml);
249248
}
250249

spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/EnvironmentPropertySource.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,31 @@
1616

1717
package org.springframework.cloud.config.server.support;
1818

19+
import java.util.LinkedHashMap;
20+
import java.util.List;
1921
import java.util.Map;
2022
import java.util.regex.Pattern;
23+
import java.util.stream.Collectors;
2124

2225
import org.springframework.cloud.config.environment.Environment;
2326
import org.springframework.core.env.PropertySource;
2427
import org.springframework.core.env.StandardEnvironment;
28+
import org.springframework.util.PropertyPlaceholderHelper;
2529

2630
/**
2731
* @author Spencer Gibb
2832
*/
2933
public class EnvironmentPropertySource extends PropertySource<Environment> {
3034

35+
// Use PropertyPlaceholderHelper directly to trim whitespace from keys
36+
private static final PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER = new PropertyPlaceholderHelper("${",
37+
"}", ":", null, true);
38+
3139
// "\${" (from text) or "\\${" from JSON to signal escaped placeholder
3240
private static final Pattern ESCAPED_PLACEHOLDERS = Pattern.compile("[\\\\]{1,2}\\$\\{");
3341

42+
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^}]+)}");
43+
3444
public EnvironmentPropertySource(Environment sources) {
3545
super("cloudEnvironment", sources);
3646
}
@@ -43,12 +53,63 @@ public static StandardEnvironment prepareEnvironment(Environment environment) {
4353
return standardEnvironment;
4454
}
4555

56+
/**
57+
* Resolve placeholders in flat text (used for .properties output). Handles escaped
58+
* placeholders (\${...}) by masking and restoring them.
59+
*/
4660
public static String resolvePlaceholders(StandardEnvironment preparedEnvironment, String text) {
4761
// Mask out escaped placeholders
4862
text = ESCAPED_PLACEHOLDERS.matcher(text).replaceAll("\\$_{");
4963
return preparedEnvironment.resolvePlaceholders(text).replace("$_{", "${");
5064
}
5165

66+
/**
67+
* Resolve placeholders in a nested Map structure. Walks all values recursively,
68+
* resolving any String values that contain ${...} expressions. Returns a new Map with
69+
* resolved values — the original is not modified.
70+
*
71+
* <p>
72+
* Use this before serializing to YAML or JSON so that the serializer handles
73+
* multiline values and escaping natively.
74+
*/
75+
public static Map<String, Object> resolveMapPlaceholders(StandardEnvironment env, Map<String, Object> map) {
76+
Map<String, Object> resolved = new LinkedHashMap<>();
77+
for (Map.Entry<String, Object> entry : map.entrySet()) {
78+
resolved.put(entry.getKey(), resolveValue(env, entry.getValue()));
79+
}
80+
return resolved;
81+
}
82+
83+
private static Object resolveValue(StandardEnvironment env, Object value) {
84+
if (value instanceof String s) {
85+
return resolveStringValue(env, s);
86+
}
87+
else if (value instanceof Map) {
88+
Map<String, Object> mapValue = (Map<String, Object>) value;
89+
Map<String, Object> resolved = new LinkedHashMap<>();
90+
for (Map.Entry<String, Object> entry : mapValue.entrySet()) {
91+
resolved.put(entry.getKey(), resolveValue(env, entry.getValue()));
92+
}
93+
return resolved;
94+
}
95+
else if (value instanceof List) {
96+
List<Object> listValue = (List<Object>) value;
97+
return listValue.stream().map(item -> resolveValue(env, item)).collect(Collectors.toList());
98+
}
99+
return value;
100+
}
101+
102+
private static String resolveStringValue(StandardEnvironment env, String value) {
103+
if (!PLACEHOLDER_PATTERN.matcher(value).find()) {
104+
return value;
105+
}
106+
// Mask escaped placeholders
107+
String masked = ESCAPED_PLACEHOLDERS.matcher(value).replaceAll("\\$_{");
108+
String resolved = PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(masked,
109+
(placeholder) -> env.getProperty(placeholder.strip()));
110+
return resolved.replace("$_{", "${");
111+
}
112+
52113
@Override
53114
public Object getProperty(String name) {
54115
for (org.springframework.cloud.config.environment.PropertySource source : getSource().getPropertySources()) {

spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,37 @@ private void whenPlaceholdersSystemPropsWithDefault() {
525525
when(this.repository.findOne("foo", "bar", null, false)).thenReturn(this.environment);
526526
}
527527

528+
private void whenMultilinePlaceholders() {
529+
Map<String, Object> map = new LinkedHashMap<String, Object>();
530+
map.put("multiline", "line1\nline2\nline3\n");
531+
map.put("ref", "${multiline}");
532+
this.environment.add(new PropertySource("one", map));
533+
when(this.repository.findOne("foo", "bar", null, false)).thenReturn(this.environment);
534+
}
535+
536+
@Test
537+
public void multilinePlaceholderResolvedInYaml() throws Exception {
538+
whenMultilinePlaceholders();
539+
String yaml = this.controller.yaml("foo", "bar", true).getBody();
540+
Map<String, Object> map = new Yaml().load(yaml);
541+
assertThat(map).containsEntry("multiline", "line1\nline2\nline3\n");
542+
assertThat(map).containsEntry("ref", "line1\nline2\nline3\n");
543+
}
544+
545+
@Test
546+
public void multilinePlaceholderResolvedInJson() throws Exception {
547+
whenMultilinePlaceholders();
548+
String json = this.controller.jsonProperties("foo", "bar", true).getBody();
549+
JSONAssert.assertEquals("{\"multiline\":\"line1\\nline2\\nline3\\n\",\"ref\":\"line1\\nline2\\nline3\\n\"}",
550+
json, JSONCompareMode.STRICT);
551+
}
552+
553+
@Test
554+
public void nameStartsWithSlash() {
555+
assertThatThrownBy(() -> this.controller.labelled("(_)spam", "bar", null))
556+
.isInstanceOf(InvalidEnvironmentRequestException.class);
557+
}
558+
528559
@Test
529560
public void nameStartsWithSlash() {
530561
assertThatThrownBy(() -> this.controller.labelled("(_)spam", "bar", null))

0 commit comments

Comments
 (0)