Skip to content

Commit f6801d2

Browse files
authored
Stricter UpdateSettingsRequest parsing on the REST layer (#79227)
As per the comment in issue #29268, in 8.0 this commit: * Changes behaviour to disallow a request with mixed settings. * A mixed request will no longer successfully parse. *A mixed request will result in a 400 "Bad Request" response. The test has been expanded to cover more cases per test invocation run, rather than relying on randomization from run to run to cover mixed request scenarios.
1 parent 26fa3eb commit f6801d2

2 files changed

Lines changed: 116 additions & 6 deletions

File tree

server/src/main/java/org/elasticsearch/action/admin/indices/settings/put/UpdateSettingsRequest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.elasticsearch.action.admin.indices.settings.put;
1010

11+
import org.elasticsearch.ElasticsearchParseException;
1112
import org.elasticsearch.Version;
1213
import org.elasticsearch.action.ActionRequestValidationException;
1314
import org.elasticsearch.action.IndicesRequest;
@@ -204,12 +205,25 @@ public UpdateSettingsRequest fromXContent(XContentParser parser) throws IOExcept
204205
@SuppressWarnings("unchecked")
205206
Map<String, Object> innerBodySettingsMap = (Map<String, Object>) innerBodySettings;
206207
settings.putAll(innerBodySettingsMap);
208+
checkMixedRequest(bodySettings);
207209
} else {
208210
settings.putAll(bodySettings);
209211
}
210212
return this.settings(settings);
211213
}
212214

215+
/**
216+
* Checks if the request is a "mixed request". A mixed request contains both a
217+
* "settings" map and "other" properties. Detection of a mixed request
218+
* will result in a parse exception being thrown.
219+
*/
220+
private static void checkMixedRequest(Map<String, Object> bodySettings) {
221+
assert bodySettings.containsKey("settings");
222+
if (bodySettings.size() > 1) {
223+
throw new ElasticsearchParseException("mix of settings map and top-level properties");
224+
}
225+
}
226+
213227
@Override
214228
public String toString() {
215229
return "indices : " + Arrays.toString(indices) + "," + Strings.toString(this);

server/src/test/java/org/elasticsearch/action/admin/indices/settings/put/UpdateSettingsRequestTests.java

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,61 @@
88

99
package org.elasticsearch.action.admin.indices.settings.put;
1010

11+
import org.elasticsearch.ElasticsearchParseException;
12+
import org.elasticsearch.common.bytes.BytesReference;
13+
import org.elasticsearch.common.xcontent.XContentHelper;
14+
import org.elasticsearch.test.XContentTestUtils;
15+
import org.elasticsearch.xcontent.ToXContent;
1116
import org.elasticsearch.xcontent.XContentBuilder;
17+
import org.elasticsearch.xcontent.XContentFactory;
1218
import org.elasticsearch.xcontent.XContentParser;
1319
import org.elasticsearch.test.AbstractXContentTestCase;
20+
import org.elasticsearch.xcontent.XContentType;
1421

1522
import java.io.IOException;
1623
import java.util.function.Predicate;
1724

25+
import static org.hamcrest.Matchers.equalTo;
26+
import static org.hamcrest.Matchers.nullValue;
27+
1828
public class UpdateSettingsRequestTests extends AbstractXContentTestCase<UpdateSettingsRequest> {
1929

20-
private final boolean enclosedSettings = randomBoolean();
30+
/** True if the setting should be enclosed in a settings map. */
31+
private final boolean enclosedSettings;
32+
/** True if the request should contain unknown top-level properties. */
33+
private final boolean unknownFields;
34+
35+
public UpdateSettingsRequestTests() {
36+
this(randomBoolean(), false);
37+
}
38+
39+
private UpdateSettingsRequestTests(boolean enclosedSettings, boolean unknownFields) {
40+
this.enclosedSettings = enclosedSettings;
41+
this.unknownFields = unknownFields;
42+
}
43+
44+
final UpdateSettingsRequestTests withEnclosedSettings() {
45+
return new UpdateSettingsRequestTests(true, unknownFields);
46+
}
47+
48+
final UpdateSettingsRequestTests withoutEnclosedSettings() {
49+
return new UpdateSettingsRequestTests(false, unknownFields);
50+
}
51+
52+
final UpdateSettingsRequestTests withUnknownFields() {
53+
return new UpdateSettingsRequestTests(enclosedSettings, true);
54+
}
55+
56+
final UpdateSettingsRequestTests withoutUnknownFields() {
57+
return new UpdateSettingsRequestTests(enclosedSettings, false);
58+
}
2159

2260
@Override
2361
protected UpdateSettingsRequest createTestInstance() {
62+
return createTestInstance(enclosedSettings);
63+
}
64+
65+
private static UpdateSettingsRequest createTestInstance(boolean enclosedSettings) {
2466
UpdateSettingsRequest testRequest = UpdateSettingsRequestSerializationTests.createTestItem();
2567
if (enclosedSettings) {
2668
UpdateSettingsRequest requestWithEnclosingSettings = new UpdateSettingsRequest(testRequest.settings()) {
@@ -40,14 +82,21 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
4082

4183
@Override
4284
protected UpdateSettingsRequest doParseInstance(XContentParser parser) throws IOException {
43-
return new UpdateSettingsRequest().fromXContent(parser);
85+
if (mixedRequest() == false) {
86+
return new UpdateSettingsRequest().fromXContent(parser);
87+
} else {
88+
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
89+
() -> (new UpdateSettingsRequest()).fromXContent(parser));
90+
assertThat(e.getMessage(), equalTo("mix of settings map and top-level properties"));
91+
return null;
92+
}
4493
}
4594

4695
@Override
4796
protected boolean supportsUnknownFields() {
48-
// if the settings are enclose as a "settings" object
97+
// if the settings are enclosed as a "settings" object
4998
// then all other top-level elements will be ignored during the parsing
50-
return enclosedSettings;
99+
return enclosedSettings && unknownFields;
51100
}
52101

53102
@Override
@@ -62,8 +111,12 @@ protected Predicate<String> getRandomFieldsExcludeFilter() {
62111
protected void assertEqualInstances(UpdateSettingsRequest expectedInstance, UpdateSettingsRequest newInstance) {
63112
// here only the settings should be tested, as this test covers explicitly only the XContent parsing
64113
// the rest of the request fields are tested by the SerializingTests
65-
super.assertEqualInstances(new UpdateSettingsRequest(expectedInstance.settings()),
66-
new UpdateSettingsRequest(newInstance.settings()));
114+
if (mixedRequest() == false) {
115+
super.assertEqualInstances(new UpdateSettingsRequest(expectedInstance.settings()),
116+
new UpdateSettingsRequest(newInstance.settings()));
117+
} else {
118+
assertThat(newInstance, nullValue()); // sanity
119+
}
67120
}
68121

69122
@Override
@@ -73,4 +126,47 @@ protected boolean assertToXContentEquivalence() {
73126
return enclosedSettings == false;
74127
}
75128

129+
final boolean mixedRequest() {
130+
return enclosedSettings && unknownFields;
131+
}
132+
133+
public void testWithEnclosedSettingsWithUnknownFields() throws IOException {
134+
testFromXContent((new UpdateSettingsRequestTests()).withEnclosedSettings().withUnknownFields());
135+
}
136+
137+
public void testWithEnclosedSettingsWithoutUnknownFields() throws IOException {
138+
testFromXContent((new UpdateSettingsRequestTests()).withEnclosedSettings().withoutUnknownFields());
139+
}
140+
141+
public void testWithoutEnclosedSettingsWithoutUnknownFields() throws IOException {
142+
testFromXContent((new UpdateSettingsRequestTests()).withoutEnclosedSettings().withoutUnknownFields());
143+
}
144+
145+
private static void testFromXContent(UpdateSettingsRequestTests test) throws IOException {
146+
AbstractXContentTestCase.testFromXContent(NUMBER_OF_TEST_RUNS / 2,
147+
test::createTestInstance,
148+
test.supportsUnknownFields(),
149+
test.getShuffleFieldsExceptions(),
150+
test.getRandomFieldsExcludeFilter(),
151+
test::createParser,
152+
test::doParseInstance,
153+
test::assertEqualInstances,
154+
test.assertToXContentEquivalence(),
155+
test.getToXContentParams());
156+
}
157+
158+
/** Tests that mixed requests, containing both an enclosed settings and top-level fields, generate a validation error message. */
159+
public void testMixedFields() throws Exception {
160+
UpdateSettingsRequestTests test = (new UpdateSettingsRequestTests()).withEnclosedSettings().withUnknownFields();
161+
UpdateSettingsRequest updateSettingsRequest = test.createTestInstance();
162+
XContentType xContentType = randomFrom(XContentType.values());
163+
BytesReference originalXContent = XContentHelper.toXContent(updateSettingsRequest, xContentType,
164+
ToXContent.EMPTY_PARAMS, false);
165+
BytesReference updatedXContent = XContentTestUtils.insertRandomFields(xContentType, originalXContent,
166+
test.getRandomFieldsExcludeFilter(), random());
167+
XContentParser parser = test.createParser(XContentFactory.xContent(xContentType), updatedXContent);
168+
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
169+
() -> (new UpdateSettingsRequest()).fromXContent(parser));
170+
assertThat(e.getMessage(), equalTo("mix of settings map and top-level properties"));
171+
}
76172
}

0 commit comments

Comments
 (0)