This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 050af52b29fd707c4289fc30a931d2ccdf66eb4d Author: jacekszymanski <[email protected]> AuthorDate: Sun Apr 9 09:52:02 2023 +0200 CAMEL-19257: deep serialize only safe data types (#9830) Safe datatypes, i.e. primitives, records, Serializables (including primitive wrappers, Strings etc.) and Collections/Maps thereof. Unsafe datatypes are serialized by their toString(). --- .../apache/camel/component/jslt/JsltEndpoint.java | 43 +++++++++++++- .../component/jslt/JsltSafeSerializationTest.java | 67 ++++++++++++++++++++++ .../org/apache/camel/component/jslt/SafeBean.java | 29 ++++++++++ .../apache/camel/component/jslt/UnsafeBean.java | 29 ++++++++++ .../camel/component/jslt/serialization/input.json | 1 + .../camel/component/jslt/serialization/output.json | 1 + .../jslt/serialization/transformation.jslt | 7 +++ 7 files changed, 176 insertions(+), 1 deletion(-) diff --git a/components/camel-jslt/src/main/java/org/apache/camel/component/jslt/JsltEndpoint.java b/components/camel-jslt/src/main/java/org/apache/camel/component/jslt/JsltEndpoint.java index 686f42ac836..f95d1d6ca1c 100644 --- a/components/camel-jslt/src/main/java/org/apache/camel/component/jslt/JsltEndpoint.java +++ b/components/camel-jslt/src/main/java/org/apache/camel/component/jslt/JsltEndpoint.java @@ -20,16 +20,22 @@ import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.io.Serializable; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.schibsted.spt.data.jslt.Expression; import com.schibsted.spt.data.jslt.Function; import com.schibsted.spt.data.jslt.JsltException; @@ -56,7 +62,14 @@ import org.apache.camel.util.ObjectHelper; category = { Category.TRANSFORMATION }, headersClass = JsltConstants.class) public class JsltEndpoint extends ResourceEndpoint { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER; + + static { + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.setSerializerFactory(OBJECT_MAPPER.getSerializerFactory().withSerializerModifier( + new SafeTypesOnlySerializerModifier())); + } + private Expression transform; @UriParam(defaultValue = "false") @@ -269,4 +282,32 @@ public class JsltEndpoint extends ResourceEndpoint { public void setObjectMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } + + private static class SafeTypesOnlySerializerModifier extends BeanSerializerModifier { + // Serialize only safe types: primitives, records, serializable objects and + // collections/maps/arrays of them. To avoid serializing something like Response object. + // Types that are not safe are serialized as their toString() value. + @Override + public JsonSerializer<?> modifySerializer( + SerializationConfig config, BeanDescription beanDesc, + JsonSerializer<?> serializer) { + final Class<?> beanClass = beanDesc.getBeanClass(); + + if (Collection.class.isAssignableFrom(beanClass) + || Map.class.isAssignableFrom(beanClass) + || beanClass.isArray() + || beanClass.isPrimitive() + || isRecord(beanClass) + || Serializable.class.isAssignableFrom(beanClass)) { + return serializer; + } + + return ToStringSerializer.instance; + } + + private static boolean isRecord(Class<?> clazz) { + final Class<?> parent = clazz.getSuperclass(); + return parent != null && parent.getName().equals("java.lang.Record"); + } + } } diff --git a/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/JsltSafeSerializationTest.java b/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/JsltSafeSerializationTest.java new file mode 100644 index 00000000000..c580bf02bef --- /dev/null +++ b/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/JsltSafeSerializationTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jslt; + +import java.util.List; +import java.util.Map; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.camel.util.IOHelper; +import org.junit.jupiter.api.Test; + +public class JsltSafeSerializationTest extends CamelTestSupport { + + @Test + public void testSafeHeaderSerialization() throws Exception { + getMockEndpoint("mock:result").expectedMinimumMessageCount(1); + getMockEndpoint("mock:result").expectedBodiesReceived( + IOHelper.loadText( + ResourceHelper.resolveMandatoryResourceAsInputStream( + context, "org/apache/camel/component/jslt/serialization/output.json")) + .trim() // Remove the last newline added by IOHelper.loadText() + ); + + final Exchange resultExchange = template().send("direct://start", + exchange -> { + exchange.getIn().setBody(IOHelper.loadText(ResourceHelper.resolveMandatoryResourceAsInputStream( + context, "org/apache/camel/component/jslt/serialization/input.json"))); + exchange.getIn().setHeader("unsafe", new UnsafeBean()); + exchange.getIn().setHeader("safe", new SafeBean()); + exchange.getIn().setHeader("array", List.of(1, 2, 3)); + exchange.getIn().setHeader("map", Map.of("a", new UnsafeBean())); + }); + + MockEndpoint.assertIsSatisfied(context); + + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + from("direct://start") + .to("jslt:org/apache/camel/component/jslt/serialization/transformation.jslt") + .to("mock:result"); + } + }; + } + +} diff --git a/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/SafeBean.java b/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/SafeBean.java new file mode 100644 index 00000000000..997a05f823b --- /dev/null +++ b/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/SafeBean.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jslt; + +import java.io.Serializable; + +public class SafeBean implements Serializable { + + private static final long serialVersionUID = 1L; + + public String getSafe() { + return "safe"; + } + +} diff --git a/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/UnsafeBean.java b/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/UnsafeBean.java new file mode 100644 index 00000000000..54d493871b4 --- /dev/null +++ b/components/camel-jslt/src/test/java/org/apache/camel/component/jslt/UnsafeBean.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jslt; + +// This bean when in a header, should get serialized as "sanitized unsafe", not { "unsafe": "unsafe" } +public class UnsafeBean { + public String getUnsafe() { + return "unsafe"; + } + + @Override + public String toString() { + return "sanitized unsafe"; + } +} diff --git a/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/input.json b/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/input.json new file mode 100644 index 00000000000..ef138da1a78 --- /dev/null +++ b/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/input.json @@ -0,0 +1 @@ +{ "foo" : "foo" } diff --git a/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/output.json b/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/output.json new file mode 100644 index 00000000000..9a0779a642e --- /dev/null +++ b/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/output.json @@ -0,0 +1 @@ +{"safe":{"safe":"safe"},"unsafe":"sanitized unsafe","array":[1,2,3],"map":{"a":"sanitized unsafe"},"foo":"foo"} diff --git a/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/transformation.jslt b/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/transformation.jslt new file mode 100644 index 00000000000..f8203e1e29a --- /dev/null +++ b/components/camel-jslt/src/test/resources/org/apache/camel/component/jslt/serialization/transformation.jslt @@ -0,0 +1,7 @@ +{ + "safe": $headers.safe, + "unsafe": $headers.unsafe, + "array": $headers.array, + "map": $headers.map, + * : . +}
