diff --git a/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java b/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java
index 91e9e9060..a8f30ad5d 100644
--- a/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java
+++ b/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java
@@ -22,12 +22,20 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.cfg.MapperConfig;
+import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.google.common.annotations.VisibleForTesting;
import org.apache.curator.x.discovery.ServiceInstance;
/**
* A serializer that uses Jackson to serialize/deserialize as JSON. IMPORTANT: The instance
* payload must support Jackson
+ *
+ * The payload is serialized with its class name (see {@link ServiceInstance#getPayload()}). To prevent
+ * deserialization of arbitrary classes, by default only the payload class and its subtypes are
+ * accepted when deserializing the payload. Polymorphic types nested within the payload whose declared
+ * base type is not {@link Object} are not restricted. Use {@link #JsonInstanceSerializer(Class, boolean, PolymorphicTypeValidator)}
+ * to supply a custom {@link PolymorphicTypeValidator}.
*/
public class JsonInstanceSerializer implements InstanceSerializer {
private final ObjectMapper mapper;
@@ -66,16 +74,69 @@ public JsonInstanceSerializer(Class payloadClass, boolean compatibleSerializa
this(payloadClass, compatibleSerializationMode, false);
}
+ /**
+ * Same as {@link #JsonInstanceSerializer(Class, boolean)} but with a custom {@link PolymorphicTypeValidator}
+ * that limits which classes may be deserialized as the payload.
+ *
+ * @param payloadClass used to validate payloads when deserializing
+ * @param compatibleSerializationMode pass true to serialize in a manner that supports clients pre-CURATOR-275
+ * @param polymorphicTypeValidator validates the payload class named in the serialized JSON
+ */
+ public JsonInstanceSerializer(
+ Class payloadClass,
+ boolean compatibleSerializationMode,
+ PolymorphicTypeValidator polymorphicTypeValidator) {
+ this(payloadClass, compatibleSerializationMode, false, polymorphicTypeValidator);
+ }
+
@VisibleForTesting
JsonInstanceSerializer(
Class payloadClass, boolean compatibleSerializationMode, boolean failOnUnknownProperties) {
+ this(payloadClass, compatibleSerializationMode, failOnUnknownProperties, defaultValidator(payloadClass));
+ }
+
+ private JsonInstanceSerializer(
+ Class payloadClass,
+ boolean compatibleSerializationMode,
+ boolean failOnUnknownProperties,
+ PolymorphicTypeValidator polymorphicTypeValidator) {
this.payloadClass = payloadClass;
this.compatibleSerializationMode = compatibleSerializationMode;
mapper = new ObjectMapper();
+ mapper.setPolymorphicTypeValidator(polymorphicTypeValidator);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
type = mapper.getTypeFactory().constructType(ServiceInstance.class);
}
+ private static PolymorphicTypeValidator defaultValidator(Class> payloadClass) {
+ return new PayloadTypeValidator(payloadClass);
+ }
+
+ /**
+ * The payload is declared as {@link Object} (due to type erasure) so this validator only restricts
+ * polymorphic values with an {@link Object} base type, which must be the payload class or a subtype of it.
+ * Polymorphic values with any other base type are allowed (as they were before this validator was added).
+ */
+ private static class PayloadTypeValidator extends PolymorphicTypeValidator.Base {
+ private static final long serialVersionUID = 1L;
+
+ private final Class> payloadClass;
+
+ PayloadTypeValidator(Class> payloadClass) {
+ this.payloadClass = payloadClass;
+ }
+
+ @Override
+ public Validity validateBaseType(MapperConfig> config, JavaType baseType) {
+ return baseType.hasRawClass(Object.class) ? Validity.INDETERMINATE : Validity.ALLOWED;
+ }
+
+ @Override
+ public Validity validateSubType(MapperConfig> config, JavaType baseType, JavaType subType) {
+ return payloadClass.isAssignableFrom(subType.getRawClass()) ? Validity.ALLOWED : Validity.DENIED;
+ }
+ }
+
@SuppressWarnings({"unchecked"})
@Override
public ServiceInstance deserialize(byte[] bytes) throws Exception {
diff --git a/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java b/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java
index 4d11cdfcc..0f1dd2fb1 100644
--- a/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java
+++ b/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java
@@ -20,8 +20,12 @@
package org.apache.curator.x.discovery;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
+import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -157,6 +161,107 @@ public void testPayloadClass() throws Exception {
assertEquals(instance.isEnabled(), rhs.isEnabled());
}
+ @Test
+ public void testDisallowedPayloadClass() throws Exception {
+ JsonInstanceSerializer