Skip to content

Commit d5380e2

Browse files
authored
Ensure PackageMetadata is created with SafeConstructor (spring-attic#5871)
Use root provided to determine type of conversion to use. * Added tests when root is specified * Added tests for builder Remove PackagMetadataBuilder, it is not needed Update default constructors to use the safe constructor.
1 parent 95743c1 commit d5380e2

File tree

12 files changed

+286
-15
lines changed

12 files changed

+286
-15
lines changed

spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/service/ReleaseReportService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.yaml.snakeyaml.DumperOptions;
2222
import org.yaml.snakeyaml.LoaderOptions;
2323
import org.yaml.snakeyaml.Yaml;
24+
import org.yaml.snakeyaml.constructor.Constructor;
2425
import org.yaml.snakeyaml.constructor.SafeConstructor;
2526

2627
import org.springframework.cloud.skipper.SkipperException;
@@ -44,6 +45,7 @@
4445
import org.springframework.transaction.annotation.Transactional;
4546
import org.springframework.util.Assert;
4647
import org.springframework.util.StringUtils;
48+
import org.yaml.snakeyaml.representer.Representer;
4749

4850
/**
4951
* @author Mark Pollack
@@ -131,7 +133,7 @@ private Release updateReplacingReleaseConfigValues(Release targetRelease, Releas
131133
DumperOptions dumperOptions = new DumperOptions();
132134
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
133135
dumperOptions.setPrettyFlow(true);
134-
Yaml yaml = new Yaml(dumperOptions);
136+
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()), new Representer(dumperOptions), dumperOptions);
135137
ConfigValues mergedConfigValues = new ConfigValues();
136138
mergedConfigValues.setRaw(yaml.dump(targetConfigValueMap));
137139
replacingRelease.setConfigValues(mergedConfigValues);

spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/util/ArgumentSanitizer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import org.slf4j.Logger;
2323
import org.slf4j.LoggerFactory;
2424
import org.yaml.snakeyaml.DumperOptions;
25+
import org.yaml.snakeyaml.LoaderOptions;
2526
import org.yaml.snakeyaml.Yaml;
27+
import org.yaml.snakeyaml.constructor.SafeConstructor;
28+
import org.yaml.snakeyaml.representer.Representer;
2629

2730
/**
2831
* Sanitizes potentially sensitive keys from manifest data.
@@ -60,7 +63,7 @@ public static String sanitizeYml(String yml) {
6063
DumperOptions options = new DumperOptions();
6164
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
6265
options.setPrettyFlow(true);
63-
Yaml yaml = new Yaml(options);
66+
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()), new Representer(options), options);
6467
Iterator<Object> iter = yaml.loadAll(yml).iterator();
6568
while (iter.hasNext()) {
6669
Object o = iter.next();

spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/util/ManifestUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ private static Yaml createYaml() {
152152
dumperOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.DOUBLE_QUOTED);
153153
dumperOptions.setPrettyFlow(true);
154154
dumperOptions.setSplitLines(false);
155-
return new Yaml(new ValueTypeRepresenter(), dumperOptions);
155+
return new Yaml(new SafeConstructor(new LoaderOptions()), new ValueTypeRepresenter(), dumperOptions);
156156
}
157157

158158
private static class ValueTypeRepresenter extends Representer {

spring-cloud-skipper/spring-cloud-skipper-server-core/src/test/java/org/springframework/cloud/skipper/server/controller/docs/BaseDocumentation.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.fasterxml.jackson.databind.ObjectMapper;
2727
import org.junit.jupiter.api.BeforeEach;
2828
import org.yaml.snakeyaml.DumperOptions;
29+
import org.yaml.snakeyaml.LoaderOptions;
2930
import org.yaml.snakeyaml.Yaml;
3031

3132
import org.springframework.beans.factory.annotation.Autowired;
@@ -66,6 +67,8 @@
6667
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
6768
import org.springframework.web.context.WebApplicationContext;
6869
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
70+
import org.yaml.snakeyaml.constructor.SafeConstructor;
71+
import org.yaml.snakeyaml.representer.Representer;
6972

7073
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
7174
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
@@ -206,7 +209,7 @@ private ConfigValues getSampleConfigValues() {
206209
DumperOptions dumperOptions = new DumperOptions();
207210
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
208211
dumperOptions.setPrettyFlow(true);
209-
Yaml yaml = new Yaml(dumperOptions);
212+
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()), new Representer(dumperOptions), dumperOptions);
210213
Map<String, String> configMap = new HashMap<>();
211214
configMap.put("config1", "value1");
212215
configMap.put("config2", "value2");

spring-cloud-skipper/spring-cloud-skipper-server-core/src/test/java/org/springframework/cloud/skipper/server/service/ConfigValueUtilsTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.junit.jupiter.api.Test;
2525
import org.yaml.snakeyaml.DumperOptions;
26+
import org.yaml.snakeyaml.LoaderOptions;
2627
import org.yaml.snakeyaml.Yaml;
2728

2829
import org.springframework.beans.factory.annotation.Autowired;
@@ -46,6 +47,8 @@
4647
import org.springframework.statemachine.boot.autoconfigure.StateMachineJpaRepositoriesAutoConfiguration;
4748
import org.springframework.test.annotation.DirtiesContext;
4849
import org.springframework.util.StreamUtils;
50+
import org.yaml.snakeyaml.constructor.SafeConstructor;
51+
import org.yaml.snakeyaml.representer.Representer;
4952

5053
/**
5154
* @author Mark Pollack
@@ -63,7 +66,7 @@ public void testYamlMerge() throws IOException {
6366
DumperOptions dumperOptions = new DumperOptions();
6467
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
6568
dumperOptions.setPrettyFlow(true);
66-
Yaml yaml = new Yaml(dumperOptions);
69+
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()), new Representer(dumperOptions), dumperOptions);
6770

6871
Resource resource = new ClassPathResource("/org/springframework/cloud/skipper/server/service/ticktock-1.0.0");
6972

spring-cloud-skipper/spring-cloud-skipper-shell-commands/src/main/java/org/springframework/cloud/skipper/shell/command/ManifestCommands.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import javax.validation.constraints.NotNull;
1919

2020
import org.yaml.snakeyaml.DumperOptions;
21+
import org.yaml.snakeyaml.LoaderOptions;
2122
import org.yaml.snakeyaml.Yaml;
2223

2324
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +28,8 @@
2728
import org.springframework.shell.standard.ShellMethod;
2829
import org.springframework.shell.standard.ShellOption;
2930
import org.springframework.web.client.HttpStatusCodeException;
31+
import org.yaml.snakeyaml.constructor.SafeConstructor;
32+
import org.yaml.snakeyaml.representer.Representer;
3033

3134
/**
3235
* Commands that operation on the manifest.
@@ -43,7 +46,7 @@ public ManifestCommands(SkipperClient skipperClient) {
4346
DumperOptions dumperOptions = new DumperOptions();
4447
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
4548
dumperOptions.setPrettyFlow(true);
46-
this.yaml = new Yaml(dumperOptions);
49+
this.yaml = new Yaml(new SafeConstructor(new LoaderOptions()), new Representer(dumperOptions), dumperOptions);
4750
}
4851

4952
@ShellMethod(key = "manifest get", value = "Get the manifest for a release")

spring-cloud-skipper/spring-cloud-skipper/src/main/java/org/springframework/cloud/skipper/io/DefaultPackageReader.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 the original author or authors.
2+
* Copyright 2017-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,8 +15,10 @@
1515
*/
1616
package org.springframework.cloud.skipper.io;
1717

18+
import java.io.ByteArrayInputStream;
1819
import java.io.File;
1920
import java.io.IOException;
21+
import java.io.InputStream;
2022
import java.nio.file.Files;
2123
import java.nio.file.Path;
2224
import java.nio.file.Paths;
@@ -28,7 +30,6 @@
2830
import org.yaml.snakeyaml.DumperOptions;
2931
import org.yaml.snakeyaml.LoaderOptions;
3032
import org.yaml.snakeyaml.Yaml;
31-
import org.yaml.snakeyaml.constructor.Constructor;
3233
import org.yaml.snakeyaml.representer.Representer;
3334
import org.zeroturnaround.zip.commons.FileUtils;
3435

@@ -163,15 +164,14 @@ private PackageMetadata loadPackageMetadata(File file) {
163164
Representer representer = new Representer(options);
164165
representer.getPropertyUtils().setSkipMissingProperties(true);
165166
LoaderOptions loaderOptions = new LoaderOptions();
166-
Yaml yaml = new Yaml(new Constructor(PackageMetadata.class, loaderOptions), representer);
167-
String fileContents = null;
167+
Yaml yaml = new Yaml(new PackageMetadataSafeConstructor(loaderOptions), representer);
168+
String fileContents;
168169
try {
169170
fileContents = FileUtils.readFileToString(file);
170171
}
171172
catch (IOException e) {
172173
throw new SkipperException("Error reading yaml file", e);
173174
}
174-
PackageMetadata pkgMetadata = (PackageMetadata) yaml.load(fileContents);
175-
return pkgMetadata;
175+
return yaml.load(fileContents);
176176
}
177177
}

spring-cloud-skipper/spring-cloud-skipper/src/main/java/org/springframework/cloud/skipper/io/DefaultPackageWriter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import java.nio.charset.Charset;
2323

2424
import org.yaml.snakeyaml.DumperOptions;
25+
import org.yaml.snakeyaml.LoaderOptions;
2526
import org.yaml.snakeyaml.Yaml;
27+
import org.yaml.snakeyaml.constructor.SafeConstructor;
28+
import org.yaml.snakeyaml.representer.Representer;
2629
import org.zeroturnaround.zip.ZipUtil;
2730

2831
import org.springframework.cloud.skipper.domain.Package;
@@ -45,7 +48,8 @@ public DefaultPackageWriter() {
4548
DumperOptions dumperOptions = new DumperOptions();
4649
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
4750
dumperOptions.setPrettyFlow(true);
48-
this.yaml = new Yaml(dumperOptions);
51+
this.yaml = new Yaml(new SafeConstructor(new LoaderOptions()), new Representer(dumperOptions), dumperOptions);
52+
4953
}
5054

5155
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.skipper.io;
18+
19+
import org.springframework.cloud.skipper.domain.PackageMetadata;
20+
21+
import org.yaml.snakeyaml.LoaderOptions;
22+
import org.yaml.snakeyaml.TypeDescription;
23+
import org.yaml.snakeyaml.constructor.Construct;
24+
import org.yaml.snakeyaml.constructor.SafeConstructor;
25+
import org.yaml.snakeyaml.error.YAMLException;
26+
import org.yaml.snakeyaml.nodes.MappingNode;
27+
import org.yaml.snakeyaml.nodes.Node;
28+
import org.yaml.snakeyaml.nodes.NodeTuple;
29+
import org.yaml.snakeyaml.nodes.ScalarNode;
30+
import org.yaml.snakeyaml.nodes.Tag;
31+
32+
/**
33+
* Extends {@link SafeConstructor} so that we can construct an instance of {@link org.springframework.cloud.skipper.domain.PackageMetadata}
34+
* When deserializing yaml for deploying apps in stream definitions.
35+
*
36+
* @author Glenn Renfro
37+
*/
38+
class PackageMetadataSafeConstructor extends SafeConstructor {
39+
private static final String API_VERSION = "apiVersion";
40+
private static final String ORIGIN = "origin";
41+
private static final String REPOSITORY_ID = "repositoryId";
42+
private static final String REPOSITORY_NAME = "repositoryName";
43+
private static final String PACKAGE_KIND = "kind";
44+
private static final String NAME = "name";
45+
private static final String DISPLAY_NAME = "displayName";
46+
private static final String PACKAGE_VERSION = "version";
47+
private static final String PACKAGE_SOURCE_URL = "packageSourceUrl";
48+
private static final String PACKAGE_HOME_URL = "packageHomeUrl";
49+
private static final String TAGS = "tags";
50+
private static final String MAINTAINER = "maintainer";
51+
private static final String DESCRIPTION = "description";
52+
private static final String SHA256 = "sha256";
53+
private static final String ICON_URL = "iconUrl";
54+
55+
PackageMetadataSafeConstructor(LoaderOptions loadingConfig) {
56+
super(loadingConfig);
57+
this.yamlConstructors.put(new TypeDescription(PackageMetadata.class).getTag(), new ConstructYamlPackageMetadata());
58+
rootTag = new Tag(new TypeDescription(PackageMetadata.class).getType());
59+
}
60+
61+
private class ConstructYamlPackageMetadata implements Construct {
62+
@Override
63+
public Object construct(Node node) {
64+
MappingNode mappingNode = (MappingNode) node;
65+
PackageMetadata packageMetadata = new PackageMetadata();
66+
try {
67+
for (NodeTuple tuple : mappingNode.getValue()) {
68+
ScalarNode keyNode = (ScalarNode) tuple.getKeyNode();
69+
ScalarNode valueNode = (ScalarNode) tuple.getValueNode();
70+
String key = keyNode.getValue();
71+
setKeyValue(packageMetadata, key, valueNode.getValue());
72+
}
73+
}
74+
catch (ClassCastException cce) {
75+
throw new YAMLException("Unable to Parse yaml to PackageMetadata type", cce);
76+
}
77+
return packageMetadata;
78+
}
79+
80+
@Override
81+
public void construct2ndStep(Node node, Object object) {
82+
83+
}
84+
85+
public PackageMetadata setKeyValue(PackageMetadata packageMetadata, String key, String value) {
86+
switch (key) {
87+
case API_VERSION:
88+
packageMetadata.setApiVersion(value);
89+
break;
90+
case ORIGIN:
91+
packageMetadata.setOrigin(value);
92+
break;
93+
case REPOSITORY_ID:
94+
packageMetadata.setRepositoryId(isLong(value) ? Long.parseLong(value) : null);
95+
break;
96+
case REPOSITORY_NAME:
97+
packageMetadata.setRepositoryName(value);
98+
break;
99+
case PACKAGE_KIND:
100+
packageMetadata.setKind(value);
101+
break;
102+
case NAME:
103+
packageMetadata.setName(value);
104+
break;
105+
case DISPLAY_NAME:
106+
packageMetadata.setDisplayName(value);
107+
break;
108+
case PACKAGE_VERSION:
109+
packageMetadata.setVersion(value);
110+
break;
111+
case PACKAGE_SOURCE_URL:
112+
packageMetadata.setPackageSourceUrl(value);
113+
break;
114+
case PACKAGE_HOME_URL:
115+
packageMetadata.setPackageHomeUrl(value);
116+
break;
117+
case TAGS:
118+
packageMetadata.setTags(value);
119+
break;
120+
case MAINTAINER:
121+
packageMetadata.setMaintainer(value);
122+
break;
123+
case DESCRIPTION:
124+
packageMetadata.setDescription(value);
125+
break;
126+
case SHA256:
127+
packageMetadata.setSha256(value);
128+
break;
129+
case ICON_URL:
130+
packageMetadata.setIconUrl(value);
131+
break;
132+
}
133+
return packageMetadata;
134+
}
135+
private boolean isLong(String str) {
136+
if (str == null || str.isEmpty()) {
137+
return false;
138+
}
139+
try {
140+
Long.parseLong(str);
141+
return true;
142+
} catch (NumberFormatException e) {
143+
return false;
144+
}
145+
}
146+
}
147+
}

spring-cloud-skipper/spring-cloud-skipper/src/main/java/org/springframework/cloud/skipper/support/yaml/DefaultYamlConverter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333

3434
import org.yaml.snakeyaml.DumperOptions;
3535
import org.yaml.snakeyaml.Yaml;
36+
import org.yaml.snakeyaml.constructor.SafeConstructor;
37+
import org.yaml.snakeyaml.representer.Representer;
3638

3739
/**
3840
* Default implementation of a {@link YamlConverter}.
@@ -127,7 +129,7 @@ private YamlConversionResult convert(Map<String, Collection<String>> properties)
127129
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
128130
options.setPrettyFlow(true);
129131

130-
Yaml yaml = new Yaml(options);
132+
Yaml yaml = new Yaml(new SafeConstructor(), new Representer(options), options);
131133
String output = yaml.dump(object);
132134
return new YamlConversionResult(status, output);
133135
}

0 commit comments

Comments
 (0)