Skip to content

Commit 0be0bed

Browse files
YangSiJun528mhalbritter
authored andcommitted
Tighten rules around profile naming
Profiles are only allowed to use dashes, underscores, digits or letters. See gh-43176
1 parent 579be1c commit 0be0bed

File tree

4 files changed

+110
-18
lines changed

4 files changed

+110
-18
lines changed

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java

+2-12
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
* @author Stephane Nicoll
6161
* @author Scott Frederick
6262
* @author Madhura Bhave
63+
* @author Sijun Yang
6364
*/
6465
class SpringBootContextLoaderTests {
6566

@@ -127,11 +128,6 @@ void multipleActiveProfiles() {
127128
assertThat(getActiveProfiles(MultipleActiveProfiles.class)).containsExactly("profile1", "profile2");
128129
}
129130

130-
@Test
131-
void activeProfileWithComma() {
132-
assertThat(getActiveProfiles(ActiveProfileWithComma.class)).containsExactly("profile1,2");
133-
}
134-
135131
@Test // gh-28776
136132
void testPropertyValuesShouldTakePrecedenceWhenInlinedPropertiesPresent() {
137133
TestContext context = new ExposedTestContextManager(SimpleConfig.class).getExposedTestContext();
@@ -314,14 +310,8 @@ static class MultipleActiveProfiles {
314310

315311
}
316312

317-
@SpringBootTest(classes = Config.class)
318-
@ActiveProfiles({ "profile1,2" })
319-
static class ActiveProfileWithComma {
320-
321-
}
322-
323313
@SpringBootTest(properties = { "key=myValue" }, classes = Config.class)
324-
@ActiveProfiles({ "profile1,2" })
314+
@ActiveProfiles({ "profile1" })
325315
static class ActiveProfileWithInlinedProperties {
326316

327317
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java

+22
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
* @author Madhura Bhave
5555
* @author Phillip Webb
5656
* @author Scott Frederick
57+
* @author Sijun Yang
5758
* @since 2.4.0
5859
*/
5960
public class StandardConfigDataLocationResolver
@@ -154,6 +155,7 @@ public List<StandardConfigDataResource> resolveProfileSpecific(ConfigDataLocatio
154155
private Set<StandardConfigDataReference> getProfileSpecificReferences(ConfigDataLocationResolverContext context,
155156
ConfigDataLocation[] configDataLocations, Profiles profiles) {
156157
Set<StandardConfigDataReference> references = new LinkedHashSet<>();
158+
validateProfiles(profiles);
157159
for (String profile : profiles) {
158160
for (ConfigDataLocation configDataLocation : configDataLocations) {
159161
String resourceLocation = getResourceLocation(context, configDataLocation);
@@ -163,6 +165,26 @@ private Set<StandardConfigDataReference> getProfileSpecificReferences(ConfigData
163165
return references;
164166
}
165167

168+
private void validateProfiles(Profiles profiles) {
169+
for (String profile : profiles) {
170+
validateProfile(profile);
171+
}
172+
}
173+
174+
private void validateProfile(String profile) {
175+
Assert.hasText(profile, "Profile must contain text");
176+
Assert.state(!profile.startsWith("-") && !profile.startsWith("_"),
177+
() -> String.format("Invalid profile '%s': must not start with '-' or '_'", profile));
178+
Assert.state(!profile.endsWith("-") && !profile.endsWith("_"),
179+
() -> String.format("Invalid profile '%s': must not end with '-' or '_'", profile));
180+
profile.codePoints().forEach((codePoint) -> {
181+
if (codePoint == '-' || codePoint == '_' || Character.isLetterOrDigit(codePoint)) {
182+
return;
183+
}
184+
throw new IllegalStateException(String.format("Invalid profile '%s': must contain only letters or digits or '-' or '_'", profile));
185+
});
186+
}
187+
166188
private String getResourceLocation(ConfigDataLocationResolverContext context,
167189
ConfigDataLocation configDataLocation) {
168190
String resourceLocation = configDataLocation.getNonPrefixedValue(PREFIX);

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
* @author Moritz Halbritter
166166
* @author Tadaya Tsuyukubo
167167
* @author Yanming Zhou
168+
* @author Sijun Yang
168169
*/
169170
@ExtendWith(OutputCaptureExtension.class)
170171
class SpringApplicationTests {
@@ -252,13 +253,13 @@ void logsActiveProfilesWithoutProfileAndSingleDefault(CapturedOutput output) {
252253
@Test
253254
void logsActiveProfilesWithoutProfileAndMultipleDefaults(CapturedOutput output) {
254255
MockEnvironment environment = new MockEnvironment();
255-
environment.setDefaultProfiles("p0,p1", "default");
256+
environment.setDefaultProfiles("p0", "default");
256257
SpringApplication application = new SpringApplication(ExampleConfig.class);
257258
application.setWebApplicationType(WebApplicationType.NONE);
258259
application.setEnvironment(environment);
259260
this.context = application.run();
260261
assertThat(output)
261-
.contains("No active profile set, falling back to 2 default profiles: \"p0,p1\", \"default\"");
262+
.contains("No active profile set, falling back to 2 default profiles: \"p0\", \"default\"");
262263
}
263264

264265
@Test
@@ -273,9 +274,9 @@ void logsActiveProfilesWithSingleProfile(CapturedOutput output) {
273274
void logsActiveProfilesWithMultipleProfiles(CapturedOutput output) {
274275
SpringApplication application = new SpringApplication(ExampleConfig.class);
275276
application.setWebApplicationType(WebApplicationType.NONE);
276-
application.setAdditionalProfiles("p1,p2", "p3");
277+
application.setAdditionalProfiles("p1", "p2");
277278
application.run();
278-
assertThat(output).contains("The following 2 profiles are active: \"p1,p2\", \"p3\"");
279+
assertThat(output).contains("The following 2 profiles are active: \"p1\", \"p2\"");
279280
}
280281

281282
@Test

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataLocationResolverTests.java

+81-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
* @author Madhura Bhave
4545
* @author Phillip Webb
4646
* @author Moritz Halbritter
47+
* @author Sijun Yang
4748
*/
4849
class StandardConfigDataLocationResolverTests {
4950

@@ -254,8 +255,8 @@ void resolveWhenLocationUsesOptionalExtensionSyntaxResolves() throws Exception {
254255
@Test
255256
void resolveProfileSpecificReturnsProfileSpecificFiles() {
256257
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
257-
Profiles profiles = mock(Profiles.class);
258-
given(profiles.iterator()).willReturn(Collections.singletonList("dev").iterator());
258+
this.environment.setActiveProfiles("dev");
259+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
259260
List<StandardConfigDataResource> locations = this.resolver.resolveProfileSpecific(this.context, location,
260261
profiles);
261262
assertThat(locations).hasSize(1);
@@ -293,6 +294,84 @@ void resolveWhenOptionalAndExtensionIsUnknownShouldNotFail() {
293294
assertThatNoException().isThrownBy(() -> this.resolver.resolve(this.context, location));
294295
}
295296

297+
@Test
298+
void resolveProfileSpecificWhenProfileIsValidShouldNotThrowException() {
299+
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
300+
this.environment.setActiveProfiles("dev-test_123");
301+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
302+
assertThatNoException()
303+
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles));
304+
}
305+
306+
@Test
307+
void resolveProfileSpecificWithNonAsciiCharactersShouldNotThrowException() {
308+
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
309+
this.environment.setActiveProfiles("dev-테스트_123");
310+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
311+
assertThatNoException()
312+
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles));
313+
}
314+
315+
@Test
316+
void resolveProfileSpecificWithAdditionalValidProfilesShouldNotThrowException() {
317+
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
318+
this.environment.setActiveProfiles("dev-test");
319+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, List.of("prod-test", "stage-test"));
320+
assertThatNoException()
321+
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles));
322+
}
323+
324+
@Test
325+
void resolveProfileSpecificWhenProfileStartsWithSymbolThrowsException() {
326+
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
327+
this.environment.setActiveProfiles("-dev");
328+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
329+
assertThatIllegalStateException()
330+
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
331+
.withMessageStartingWith("Invalid profile '-dev': must not start with '-' or '_'");
332+
}
333+
334+
@Test
335+
void resolveProfileSpecificWhenProfileStartsWithUnderscoreThrowsException() {
336+
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
337+
this.environment.setActiveProfiles("_dev");
338+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
339+
assertThatIllegalStateException()
340+
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
341+
.withMessageStartingWith("Invalid profile '_dev': must not start with '-' or '_'");
342+
}
343+
344+
@Test
345+
void resolveProfileSpecificWhenProfileEndsWithSymbolThrowsException() {
346+
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
347+
this.environment.setActiveProfiles("dev-");
348+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
349+
assertThatIllegalStateException()
350+
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
351+
.withMessageStartingWith("Invalid profile 'dev-': must not end with '-' or '_'");
352+
}
353+
354+
@Test
355+
void resolveProfileSpecificWhenProfileEndsWithUnderscoreThrowsException() {
356+
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
357+
this.environment.setActiveProfiles("dev_");
358+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
359+
assertThatIllegalStateException()
360+
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
361+
.withMessageStartingWith("Invalid profile 'dev_': must not end with '-' or '_'");
362+
}
363+
364+
@Test
365+
void resolveProfileSpecificWhenProfileContainsInvalidCharactersThrowsException() {
366+
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
367+
this.environment.setActiveProfiles("dev*test");
368+
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
369+
assertThatIllegalStateException()
370+
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
371+
.withMessageStartingWith(
372+
"Invalid profile 'dev*test': must contain only letters or digits or '-' or '_'");
373+
}
374+
296375
private String filePath(String... components) {
297376
return "file [" + String.join(File.separator, components) + "]";
298377
}

0 commit comments

Comments
 (0)