Skip to content

Commit b1f4e97

Browse files
authored
Merge pull request #5 from kderusso/kderusso/carlos_template_validation
Template parameters validation
2 parents 6033fc2 + 310b148 commit b1f4e97

File tree

8 files changed

+116
-46
lines changed

8 files changed

+116
-46
lines changed

x-pack/plugin/ent-search/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
api project(':modules:lang-mustache')
1717

1818
implementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}"
19+
1920
implementation "com.networknt:json-schema-validator:${versions.networknt_json_schema_validator}"
2021
implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
2122

x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/55_search_application_search.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ setup:
4141
params:
4242
field_name: field1
4343
field_value: value1
44+
dictionary:
45+
field_name:
46+
type: string
47+
field_value:
48+
type: string
4449

4550
- do:
4651
index:
@@ -122,6 +127,18 @@ teardown:
122127
- match: { hits.total.value: 1 }
123128
- match: { hits.hits.0._id: "doc2" }
124129

130+
---
131+
"Query Search Application with invalid parameter validation":
132+
133+
- do:
134+
catch: "bad_request"
135+
search_application.search:
136+
name: test-search-application
137+
body:
138+
params:
139+
field_name: field3
140+
field_value: 35
141+
125142
---
126143
"Query Search Application - not found":
127144

x-pack/plugin/ent-search/src/main/java/module-info.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
*/
77

88
module org.elasticsearch.application {
9+
requires com.fasterxml.jackson.core;
10+
requires com.fasterxml.jackson.databind;
11+
requires com.fasterxml.jackson.annotation;
912
requires org.apache.logging.log4j;
1013
requires org.apache.lucene.core;
1114

@@ -15,6 +18,7 @@
1518
requires org.elasticsearch.server;
1619
requires org.elasticsearch.xcontent;
1720
requires org.elasticsearch.xcore;
21+
requires json.schema.validator;
1822

1923
exports org.elasticsearch.xpack.application.analytics;
2024
exports org.elasticsearch.xpack.application.analytics.action;

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/search/SearchApplication.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import org.elasticsearch.ElasticsearchParseException;
1111
import org.elasticsearch.common.Strings;
12+
import org.elasticsearch.common.ValidationException;
1213
import org.elasticsearch.common.bytes.BytesReference;
1314
import org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput;
1415
import org.elasticsearch.common.io.stream.StreamInput;
@@ -86,7 +87,7 @@ public SearchApplication(
8687
this.searchApplicationTemplate = searchApplicationTemplate;
8788
}
8889

89-
public SearchApplication(StreamInput in) throws IOException {
90+
public SearchApplication(StreamInput in) throws IOException, ValidationException {
9091
this.name = in.readString();
9192
this.indices = in.readStringArray();
9293
this.analyticsCollectionName = in.readOptionalString();

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/search/SearchApplicationIndexService.java

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.cluster.metadata.IndexMetadata;
3434
import org.elasticsearch.cluster.metadata.Metadata;
3535
import org.elasticsearch.cluster.service.ClusterService;
36+
import org.elasticsearch.common.ValidationException;
3637
import org.elasticsearch.common.bytes.BytesReference;
3738
import org.elasticsearch.common.document.DocumentField;
3839
import org.elasticsearch.common.io.stream.InputStreamStreamInput;
@@ -186,24 +187,11 @@ private static XContentBuilder getIndexMappings() {
186187
}
187188
}
188189

189-
/**
190-
* Gets the {@link SearchApplication} from the index if present, or delegate a {@link ResourceNotFoundException} failure to the provided
191-
* listener if not.
192-
*
193-
* @param resourceName The resource name.
194-
* @param listener The action listener to invoke on response/failure.
195-
*/
196-
public void getSearchApplication(String resourceName, ActionListener<SearchApplication> listener) {
197-
final GetRequest getRequest = new GetRequest(SEARCH_APPLICATION_ALIAS_NAME).id(resourceName).realtime(true);
198-
clientWithOrigin.get(getRequest, new DelegatingIndexNotFoundActionListener<>(resourceName, listener, (l, getResponse) -> {
199-
if (getResponse.isExists() == false) {
200-
l.onFailure(new ResourceNotFoundException(resourceName));
201-
return;
202-
}
203-
final BytesReference source = getResponse.getSourceInternal();
204-
final SearchApplication res = parseSearchApplicationBinaryFromSource(source);
205-
l.onResponse(res);
206-
}));
190+
static SearchApplication parseSearchApplicationBinaryWithVersion(StreamInput in) throws IOException, ValidationException {
191+
TransportVersion version = TransportVersion.readVersion(in);
192+
assert version.onOrBefore(TransportVersion.CURRENT) : version + " >= " + TransportVersion.CURRENT;
193+
in.setTransportVersion(version);
194+
return new SearchApplication(in);
207195
}
208196

209197
private static String getSearchAliasName(SearchApplication app) {
@@ -423,6 +411,30 @@ private static SearchApplicationListItem hitToSearchApplicationListItem(SearchHi
423411
);
424412
}
425413

414+
/**
415+
* Gets the {@link SearchApplication} from the index if present, or delegate a {@link ResourceNotFoundException} failure to the provided
416+
* listener if not.
417+
*
418+
* @param resourceName The resource name.
419+
* @param listener The action listener to invoke on response/failure.
420+
*/
421+
public void getSearchApplication(String resourceName, ActionListener<SearchApplication> listener) {
422+
final GetRequest getRequest = new GetRequest(SEARCH_APPLICATION_ALIAS_NAME).id(resourceName).realtime(true);
423+
clientWithOrigin.get(getRequest, new DelegatingIndexNotFoundActionListener<>(resourceName, listener, (l, getResponse) -> {
424+
if (getResponse.isExists() == false) {
425+
l.onFailure(new ResourceNotFoundException(resourceName));
426+
return;
427+
}
428+
try {
429+
final BytesReference source = getResponse.getSourceInternal();
430+
final SearchApplication res = parseSearchApplicationBinaryFromSource(source);
431+
l.onResponse(res);
432+
} catch (Exception e) {
433+
l.onFailure(e);
434+
}
435+
}));
436+
}
437+
426438
private SearchApplication parseSearchApplicationBinaryFromSource(BytesReference source) {
427439
try (XContentParser parser = XContentHelper.createParser(XContentParserConfiguration.EMPTY, source, XContentType.JSON)) {
428440
ensureExpectedToken(parser.nextToken(), XContentParser.Token.START_OBJECT, parser);
@@ -453,16 +465,11 @@ public int read() {
453465
throw new ElasticsearchParseException("[" + SearchApplication.BINARY_CONTENT_FIELD.getPreferredName() + "] field is missing");
454466
} catch (IOException e) {
455467
throw new ElasticsearchParseException("Failed to parse: " + source.utf8ToString(), e);
468+
} catch (ValidationException e) {
469+
throw new ElasticsearchParseException("Invalid Search Application: " + source.utf8ToString(), e);
456470
}
457471
}
458472

459-
static SearchApplication parseSearchApplicationBinaryWithVersion(StreamInput in) throws IOException {
460-
TransportVersion version = TransportVersion.readVersion(in);
461-
assert version.onOrBefore(TransportVersion.CURRENT) : version + " >= " + TransportVersion.CURRENT;
462-
in.setTransportVersion(version);
463-
return new SearchApplication(in);
464-
}
465-
466473
static void writeSearchApplicationBinaryWithVersion(SearchApplication app, OutputStream os, Version minNodeVersion) throws IOException {
467474
// do not close the output
468475
os = Streams.noCloseStream(os);

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/search/SearchApplicationTemplate.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.application.search;
99

10+
import org.elasticsearch.common.ValidationException;
1011
import org.elasticsearch.common.io.stream.StreamInput;
1112
import org.elasticsearch.common.io.stream.StreamOutput;
1213
import org.elasticsearch.common.io.stream.Writeable;
@@ -22,6 +23,7 @@
2223
import org.elasticsearch.xpack.application.search.action.QuerySearchApplicationAction.Request;
2324

2425
import java.io.IOException;
26+
import java.util.Map;
2527
import java.util.Objects;
2628

2729
import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
@@ -44,14 +46,15 @@ public class SearchApplicationTemplate implements ToXContentObject, Writeable {
4446
static {
4547
PARSER.declareObject(optionalConstructorArg(), (p, c) -> Script.parse(p, Script.DEFAULT_TEMPLATE_LANG), TEMPLATE_SCRIPT_FIELD);
4648
PARSER.declareObject(optionalConstructorArg(), (p, c) -> {
47-
XContentBuilder builder = XContentFactory.jsonBuilder();
48-
return new TemplateParamValidator(builder.copyCurrentStructure(p));
49+
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
50+
return new TemplateParamValidator(builder.copyCurrentStructure(p));
51+
}
4952
}, DICTIONARY_FIELD);
5053
}
5154

5255
private final TemplateParamValidator templateParamValidator;
5356

54-
public SearchApplicationTemplate(StreamInput in) throws IOException {
57+
public SearchApplicationTemplate(StreamInput in) throws IOException, ValidationException {
5558
this.script = in.readOptionalWriteable(Script::new);
5659
this.templateParamValidator = in.readOptionalWriteable(TemplateParamValidator::new);
5760
}
@@ -90,6 +93,18 @@ public void writeTo(StreamOutput out) throws IOException {
9093
out.writeOptionalWriteable(templateParamValidator);
9194
}
9295

96+
public static SearchApplicationTemplate parse(XContentParser parser) {
97+
return PARSER.apply(parser, null);
98+
}
99+
100+
static {
101+
PARSER.declareObject(
102+
optionalConstructorArg(),
103+
(p, c) -> Script.parse(p, Script.DEFAULT_TEMPLATE_LANG),
104+
SearchApplication.TEMPLATE_SCRIPT_FIELD
105+
);
106+
}
107+
93108
@Override
94109
public int hashCode() {
95110
return Objects.hash(script, templateParamValidator);
@@ -103,6 +118,12 @@ public boolean equals(Object o) {
103118
return Objects.equals(script, template.script) && Objects.equals(templateParamValidator, template.templateParamValidator);
104119
}
105120

121+
public void validateTemplateParams(Map<String, Object> templateParams) throws ValidationException {
122+
if (templateParamValidator != null) {
123+
templateParamValidator.validate(templateParams);
124+
}
125+
}
126+
106127
public Script script() {
107128
return script;
108129
}

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/search/TemplateParamValidator.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package org.elasticsearch.xpack.application.search;
99

1010
import com.fasterxml.jackson.core.JsonProcessingException;
11+
import com.fasterxml.jackson.databind.JsonNode;
1112
import com.fasterxml.jackson.databind.ObjectMapper;
1213
import com.fasterxml.jackson.databind.node.ObjectNode;
1314
import com.networknt.schema.JsonSchema;
@@ -26,6 +27,7 @@
2627

2728
import java.io.IOException;
2829
import java.io.InputStream;
30+
import java.util.Map;
2931
import java.util.Objects;
3032
import java.util.Set;
3133

@@ -54,16 +56,7 @@ public TemplateParamValidator(String dictionaryContent) throws ValidationExcepti
5456
// Create a new Schema with "properties" node based on the dictionary content
5557
final ObjectNode schemaJsonNode = OBJECT_MAPPER.createObjectNode();
5658
schemaJsonNode.set(PROPERTIES_NODE, OBJECT_MAPPER.readTree(dictionaryContent));
57-
final Set<ValidationMessage> validationMessages = META_SCHEMA.validate(schemaJsonNode);
58-
59-
if (validationMessages.isEmpty() == false) {
60-
ValidationException validationException = new ValidationException();
61-
for (ValidationMessage validationMessage : validationMessages) {
62-
validationException.addValidationError(validationMessage.getMessage());
63-
}
64-
65-
throw validationException;
66-
}
59+
validateWithSchema(META_SCHEMA, schemaJsonNode);
6760

6861
this.jsonSchema = SCHEMA_FACTORY.getSchema(schemaJsonNode);
6962
} catch (JsonProcessingException e) {
@@ -75,6 +68,22 @@ public TemplateParamValidator(XContentBuilder xContentBuilder) {
7568
this(Strings.toString(xContentBuilder));
7669
}
7770

71+
private static void validateWithSchema(JsonSchema jsonSchema, JsonNode jsonNode) {
72+
final Set<ValidationMessage> validationMessages = jsonSchema.validate(jsonNode);
73+
if (validationMessages.isEmpty() == false) {
74+
ValidationException validationException = new ValidationException();
75+
for (ValidationMessage message : validationMessages) {
76+
validationException.addValidationError(message.getMessage());
77+
}
78+
79+
throw validationException;
80+
}
81+
}
82+
83+
public void validate(Map<String, Object> templateParams) throws ValidationException {
84+
validateWithSchema(this.jsonSchema, OBJECT_MAPPER.valueToTree(templateParams));
85+
}
86+
7887
@Override
7988
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
8089
try (InputStream stream = new BytesArray(getSchemaPropertiesAsString()).streamInput()) {

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/search/action/TransportQuerySearchApplicationAction.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.action.support.ActionFilters;
1717
import org.elasticsearch.client.internal.Client;
1818
import org.elasticsearch.cluster.service.ClusterService;
19+
import org.elasticsearch.common.ValidationException;
1920
import org.elasticsearch.common.inject.Inject;
2021
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
2122
import org.elasticsearch.common.util.BigArrays;
@@ -31,6 +32,8 @@
3132
import org.elasticsearch.xcontent.XContentParser;
3233
import org.elasticsearch.xcontent.XContentParserConfiguration;
3334
import org.elasticsearch.xcontent.XContentType;
35+
import org.elasticsearch.xpack.application.search.SearchApplication;
36+
import org.elasticsearch.xpack.application.search.SearchApplicationTemplate;
3437

3538
import java.io.IOException;
3639
import java.util.HashMap;
@@ -78,19 +81,17 @@ public TransportQuerySearchApplicationAction(
7881
@Override
7982
protected void doExecute(QuerySearchApplicationAction.Request request, ActionListener<SearchResponse> listener) {
8083
systemIndexService.getSearchApplication(request.name(), listener.delegateFailure((l, searchApplication) -> {
81-
final Script script = searchApplication.searchApplicationTemplate().script();
82-
8384
try {
84-
final SearchSourceBuilder sourceBuilder = renderTemplate(script, mergeTemplateParams(request, script));
85+
final SearchSourceBuilder sourceBuilder = renderTemplate(searchApplication, request);
8586
SearchRequest searchRequest = new SearchRequest(searchApplication.indices()).source(sourceBuilder);
8687

8788
client.execute(
8889
SearchAction.INSTANCE,
8990
searchRequest,
9091
listener.delegateFailure((l2, searchResponse) -> l2.onResponse(searchResponse))
9192
);
92-
} catch (IOException e) {
93-
l.onFailure(e);
93+
} catch (Exception e) {
94+
listener.onFailure(e);
9495
}
9596
}));
9697
}
@@ -102,8 +103,17 @@ private static Map<String, Object> mergeTemplateParams(QuerySearchApplicationAct
102103
return mergedTemplateParams;
103104
}
104105

105-
private SearchSourceBuilder renderTemplate(Script script, Map<String, Object> templateParams) throws IOException {
106-
TemplateScript compiledTemplate = scriptService.compile(script, TemplateScript.CONTEXT).newInstance(templateParams);
106+
private SearchSourceBuilder renderTemplate(SearchApplication searchApplication, QuerySearchApplicationAction.Request request)
107+
throws IOException, ValidationException {
108+
109+
final SearchApplicationTemplate template = searchApplication.searchApplicationTemplate();
110+
final Map<String, Object> queryParams = request.queryParams();
111+
final Script script = template.script();
112+
113+
template.validateTemplateParams(queryParams);
114+
115+
TemplateScript compiledTemplate = scriptService.compile(script, TemplateScript.CONTEXT)
116+
.newInstance(mergeTemplateParams(request, script));
107117
String requestSource = compiledTemplate.execute();
108118

109119
XContentParserConfiguration parserConfig = XContentParserConfiguration.EMPTY.withRegistry(xContentRegistry)

0 commit comments

Comments
 (0)