Skip to content

Commit f6a25f5

Browse files
authored
add validation for name and description for model model group and connector resources (#3805)
* add validation for name and description for model model group and connector resources Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * addressing comment, added a common method in utils to remove code redundnancy Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * making more flexible the regex pattern Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * removing restriction of 1000 characters Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * addressing comments Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * fixing unit test messages Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * fixing unit test assertion Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * adding a generic way so that we can check for optional or required field too Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> --------- Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
1 parent c3b1fd6 commit f6a25f5

15 files changed

+584
-25
lines changed

common/src/main/java/org/opensearch/ml/common/transport/connector/MLCreateConnectorRequest.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
package org.opensearch.ml.common.transport.connector;
77

88
import static org.opensearch.action.ValidateActions.addValidationError;
9+
import static org.opensearch.ml.common.utils.StringUtils.validateFields;
910

1011
import java.io.ByteArrayInputStream;
1112
import java.io.ByteArrayOutputStream;
1213
import java.io.IOException;
1314
import java.io.UncheckedIOException;
15+
import java.util.HashMap;
16+
import java.util.Map;
1417

1518
import org.opensearch.action.ActionRequest;
1619
import org.opensearch.action.ActionRequestValidationException;
1720
import org.opensearch.core.common.io.stream.InputStreamStreamInput;
1821
import org.opensearch.core.common.io.stream.OutputStreamStreamOutput;
1922
import org.opensearch.core.common.io.stream.StreamInput;
2023
import org.opensearch.core.common.io.stream.StreamOutput;
24+
import org.opensearch.ml.common.utils.FieldDescriptor;
2125

2226
import lombok.Builder;
2327
import lombok.Getter;
@@ -38,12 +42,14 @@ public MLCreateConnectorRequest(StreamInput in) throws IOException {
3842

3943
@Override
4044
public ActionRequestValidationException validate() {
41-
ActionRequestValidationException exception = null;
4245
if (mlCreateConnectorInput == null) {
43-
exception = addValidationError("ML Connector input can't be null", exception);
46+
return addValidationError("ML Connector input can't be null", null);
4447
}
48+
Map<String, FieldDescriptor> fieldsToValidate = new HashMap<>();
49+
fieldsToValidate.put("Model connector name", new FieldDescriptor(mlCreateConnectorInput.getName(), true));
50+
fieldsToValidate.put("Model connector description", new FieldDescriptor(mlCreateConnectorInput.getDescription(), false));
4551

46-
return exception;
52+
return validateFields(fieldsToValidate);
4753
}
4854

4955
@Override

common/src/main/java/org/opensearch/ml/common/transport/connector/MLUpdateConnectorRequest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
package org.opensearch.ml.common.transport.connector;
77

88
import static org.opensearch.action.ValidateActions.addValidationError;
9+
import static org.opensearch.ml.common.utils.StringUtils.validateFields;
910

1011
import java.io.ByteArrayInputStream;
1112
import java.io.ByteArrayOutputStream;
1213
import java.io.IOException;
1314
import java.io.UncheckedIOException;
15+
import java.util.HashMap;
16+
import java.util.Map;
1417

1518
import org.opensearch.action.ActionRequest;
1619
import org.opensearch.action.ActionRequestValidationException;
@@ -19,6 +22,7 @@
1922
import org.opensearch.core.common.io.stream.StreamInput;
2023
import org.opensearch.core.common.io.stream.StreamOutput;
2124
import org.opensearch.core.xcontent.XContentParser;
25+
import org.opensearch.ml.common.utils.FieldDescriptor;
2226

2327
import lombok.Builder;
2428
import lombok.Getter;
@@ -57,8 +61,12 @@ public ActionRequestValidationException validate() {
5761

5862
if (updateContent == null) {
5963
exception = addValidationError("Update connector content can't be null", exception);
64+
} else {
65+
Map<String, FieldDescriptor> fieldsToValidate = new HashMap<>();
66+
fieldsToValidate.put("Model connector name", new FieldDescriptor(updateContent.getName(), false));
67+
fieldsToValidate.put("Model connector description", new FieldDescriptor(updateContent.getDescription(), false));
68+
exception = validateFields(fieldsToValidate);
6069
}
61-
6270
return exception;
6371
}
6472

common/src/main/java/org/opensearch/ml/common/transport/model/MLUpdateModelRequest.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
package org.opensearch.ml.common.transport.model;
77

88
import static org.opensearch.action.ValidateActions.addValidationError;
9+
import static org.opensearch.ml.common.utils.StringUtils.validateFields;
910

1011
import java.io.ByteArrayInputStream;
1112
import java.io.ByteArrayOutputStream;
1213
import java.io.IOException;
1314
import java.io.UncheckedIOException;
15+
import java.util.HashMap;
16+
import java.util.Map;
1417

1518
import org.opensearch.action.ActionRequest;
1619
import org.opensearch.action.ActionRequestValidationException;
1720
import org.opensearch.core.common.io.stream.InputStreamStreamInput;
1821
import org.opensearch.core.common.io.stream.OutputStreamStreamOutput;
1922
import org.opensearch.core.common.io.stream.StreamInput;
2023
import org.opensearch.core.common.io.stream.StreamOutput;
24+
import org.opensearch.ml.common.utils.FieldDescriptor;
2125

2226
import lombok.AccessLevel;
2327
import lombok.Builder;
@@ -44,12 +48,13 @@ public MLUpdateModelRequest(StreamInput in) throws IOException {
4448

4549
@Override
4650
public ActionRequestValidationException validate() {
47-
ActionRequestValidationException exception = null;
4851
if (updateModelInput == null) {
49-
exception = addValidationError("Update Model Input can't be null", exception);
52+
return addValidationError("Update Model Input can't be null", null);
5053
}
51-
52-
return exception;
54+
Map<String, FieldDescriptor> fieldsToValidate = new HashMap<>();
55+
fieldsToValidate.put("Model Name", new FieldDescriptor(updateModelInput.getName(), false));
56+
fieldsToValidate.put("Model Description", new FieldDescriptor(updateModelInput.getDescription(), false));
57+
return validateFields(fieldsToValidate);
5358
}
5459

5560
@Override

common/src/main/java/org/opensearch/ml/common/transport/model_group/MLRegisterModelGroupRequest.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
package org.opensearch.ml.common.transport.model_group;
77

88
import static org.opensearch.action.ValidateActions.addValidationError;
9+
import static org.opensearch.ml.common.utils.StringUtils.validateFields;
910

1011
import java.io.ByteArrayInputStream;
1112
import java.io.ByteArrayOutputStream;
1213
import java.io.IOException;
1314
import java.io.UncheckedIOException;
15+
import java.util.HashMap;
16+
import java.util.Map;
1417

1518
import org.opensearch.action.ActionRequest;
1619
import org.opensearch.action.ActionRequestValidationException;
1720
import org.opensearch.core.common.io.stream.InputStreamStreamInput;
1821
import org.opensearch.core.common.io.stream.OutputStreamStreamOutput;
1922
import org.opensearch.core.common.io.stream.StreamInput;
2023
import org.opensearch.core.common.io.stream.StreamOutput;
24+
import org.opensearch.ml.common.utils.FieldDescriptor;
2125

2226
import lombok.AccessLevel;
2327
import lombok.Builder;
@@ -44,12 +48,15 @@ public MLRegisterModelGroupRequest(StreamInput in) throws IOException {
4448

4549
@Override
4650
public ActionRequestValidationException validate() {
47-
ActionRequestValidationException exception = null;
4851
if (registerModelGroupInput == null) {
49-
exception = addValidationError("Model meta input can't be null", exception);
52+
return addValidationError("Model group input can't be null", null);
5053
}
5154

52-
return exception;
55+
Map<String, FieldDescriptor> fieldsToValidate = new HashMap<>();
56+
fieldsToValidate.put("Model group name", new FieldDescriptor(registerModelGroupInput.getName(), true));
57+
fieldsToValidate.put("Model group description", new FieldDescriptor(registerModelGroupInput.getDescription(), false));
58+
59+
return validateFields(fieldsToValidate);
5360
}
5461

5562
@Override

common/src/main/java/org/opensearch/ml/common/transport/model_group/MLUpdateModelGroupRequest.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
package org.opensearch.ml.common.transport.model_group;
77

88
import static org.opensearch.action.ValidateActions.addValidationError;
9+
import static org.opensearch.ml.common.utils.StringUtils.validateFields;
910

1011
import java.io.ByteArrayInputStream;
1112
import java.io.ByteArrayOutputStream;
1213
import java.io.IOException;
1314
import java.io.UncheckedIOException;
15+
import java.util.HashMap;
16+
import java.util.Map;
1417

1518
import org.opensearch.action.ActionRequest;
1619
import org.opensearch.action.ActionRequestValidationException;
1720
import org.opensearch.core.common.io.stream.InputStreamStreamInput;
1821
import org.opensearch.core.common.io.stream.OutputStreamStreamOutput;
1922
import org.opensearch.core.common.io.stream.StreamInput;
2023
import org.opensearch.core.common.io.stream.StreamOutput;
24+
import org.opensearch.ml.common.utils.FieldDescriptor;
2125

2226
import lombok.AccessLevel;
2327
import lombok.Builder;
@@ -44,12 +48,15 @@ public MLUpdateModelGroupRequest(StreamInput in) throws IOException {
4448

4549
@Override
4650
public ActionRequestValidationException validate() {
47-
ActionRequestValidationException exception = null;
4851
if (updateModelGroupInput == null) {
49-
exception = addValidationError("Update Model group input can't be null", exception);
52+
return addValidationError("Update Model group input can't be null", null);
5053
}
5154

52-
return exception;
55+
Map<String, FieldDescriptor> fieldsToValidate = new HashMap<>();
56+
fieldsToValidate.put("Model group name", new FieldDescriptor(updateModelGroupInput.getName(), false));
57+
fieldsToValidate.put("Model group description", new FieldDescriptor(updateModelGroupInput.getDescription(), false));
58+
59+
return validateFields(fieldsToValidate);
5360
}
5461

5562
@Override

common/src/main/java/org/opensearch/ml/common/transport/register/MLRegisterModelRequest.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
package org.opensearch.ml.common.transport.register;
77

88
import static org.opensearch.action.ValidateActions.addValidationError;
9+
import static org.opensearch.ml.common.utils.StringUtils.validateFields;
910

1011
import java.io.ByteArrayInputStream;
1112
import java.io.ByteArrayOutputStream;
1213
import java.io.IOException;
1314
import java.io.UncheckedIOException;
15+
import java.util.HashMap;
16+
import java.util.Map;
1417

1518
import org.opensearch.action.ActionRequest;
1619
import org.opensearch.action.ActionRequestValidationException;
1720
import org.opensearch.core.common.io.stream.InputStreamStreamInput;
1821
import org.opensearch.core.common.io.stream.OutputStreamStreamOutput;
1922
import org.opensearch.core.common.io.stream.StreamInput;
2023
import org.opensearch.core.common.io.stream.StreamOutput;
24+
import org.opensearch.ml.common.utils.FieldDescriptor;
2125

2226
import lombok.AccessLevel;
2327
import lombok.Builder;
@@ -44,12 +48,15 @@ public MLRegisterModelRequest(StreamInput in) throws IOException {
4448

4549
@Override
4650
public ActionRequestValidationException validate() {
47-
ActionRequestValidationException exception = null;
4851
if (registerModelInput == null) {
49-
exception = addValidationError("ML input can't be null", exception);
52+
return addValidationError("ML input can't be null", null);
5053
}
5154

52-
return exception;
55+
Map<String, FieldDescriptor> fieldsToValidate = new HashMap<>();
56+
fieldsToValidate.put("Model name", new FieldDescriptor(registerModelInput.getModelName(), true));
57+
fieldsToValidate.put("Model description", new FieldDescriptor(registerModelInput.getDescription(), false));
58+
59+
return validateFields(fieldsToValidate);
5360
}
5461

5562
@Override
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.ml.common.utils;
7+
8+
public class FieldDescriptor {
9+
private final String value;
10+
private final boolean required;
11+
12+
public FieldDescriptor(String value, boolean required) {
13+
this.value = value;
14+
this.required = required;
15+
}
16+
17+
public String getValue() {
18+
return value;
19+
}
20+
21+
public boolean isRequired() {
22+
return required;
23+
}
24+
}

common/src/main/java/org/opensearch/ml/common/utils/StringUtils.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package org.opensearch.ml.common.utils;
77

8+
import static org.opensearch.action.ValidateActions.addValidationError;
9+
810
import java.nio.ByteBuffer;
911
import java.nio.charset.StandardCharsets;
1012
import java.security.AccessController;
@@ -28,6 +30,7 @@
2830
import org.json.JSONException;
2931
import org.json.JSONObject;
3032
import org.opensearch.OpenSearchParseException;
33+
import org.opensearch.action.ActionRequestValidationException;
3134

3235
import com.fasterxml.jackson.core.JsonProcessingException;
3336
import com.fasterxml.jackson.databind.JsonNode;
@@ -60,6 +63,12 @@ public class StringUtils {
6063
+ " return input;"
6164
+ "\n }\n";
6265

66+
// Regex allows letters, digits, spaces, hyphens, underscores, and dots.
67+
private static final Pattern SAFE_INPUT_PATTERN = Pattern.compile("^[\\p{L}\\p{N}\\s.,!?():@\\-_'\"]*$");
68+
69+
public static final String SAFE_INPUT_DESCRIPTION =
70+
"can only contain letters, numbers, whitespace, and basic punctuation (.,!?():@-_'\")";
71+
6372
public static final Gson gson;
6473

6574
static {
@@ -497,4 +506,51 @@ public static String hashString(String input) {
497506
}
498507
}
499508

509+
/**
510+
* Validates a map of fields to ensure that their values only contain allowed characters.
511+
* <p>
512+
* Allowed characters are: letters, digits, spaces, underscores (_), hyphens (-), dots (.), and colons (:).
513+
* If a value does not comply, a validation error is added.
514+
*
515+
* @param fields A map where the key is the field name (used for error messages) and the value is the text to validate.
516+
* @return An {@link ActionRequestValidationException} containing all validation errors, or {@code null} if all fields are valid.
517+
*/
518+
public static ActionRequestValidationException validateFields(Map<String, FieldDescriptor> fields) {
519+
ActionRequestValidationException exception = null;
520+
521+
for (Map.Entry<String, FieldDescriptor> entry : fields.entrySet()) {
522+
String key = entry.getKey();
523+
FieldDescriptor descriptor = entry.getValue();
524+
String value = descriptor.getValue();
525+
526+
if (descriptor.isRequired()) {
527+
if (!isSafeText(value)) {
528+
String reason = (value == null || value.isBlank()) ? "is required and cannot be null or blank" : SAFE_INPUT_DESCRIPTION;
529+
exception = addValidationError(key + " " + reason, exception);
530+
}
531+
} else {
532+
if (value != null && !value.isBlank() && !matchesSafePattern(value)) {
533+
exception = addValidationError(key + " " + SAFE_INPUT_DESCRIPTION, exception);
534+
}
535+
}
536+
}
537+
538+
return exception;
539+
}
540+
541+
/**
542+
* Checks if the input is safe (non-null, non-blank, matches safe character set).
543+
*
544+
* @param value The input string to validate
545+
* @return true if input is safe, false otherwise
546+
*/
547+
public static boolean isSafeText(String value) {
548+
return value != null && !value.isBlank() && matchesSafePattern(value);
549+
}
550+
551+
// Just checks pattern
552+
public static boolean matchesSafePattern(String value) {
553+
return SAFE_INPUT_PATTERN.matcher(value).matches();
554+
}
555+
500556
}

0 commit comments

Comments
 (0)