From 2d53570f4cc148250378b852e671fcb0369f754b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 11 Jan 2021 19:07:20 +0100 Subject: [PATCH] Prevent kotlinx.serialization usage on interfaces Remove support for open polymorphic serialization in kotlinx.serialization web converters and codecs in order to prevent serialization handling suitable for Jackson or other general purpose Java JSON libraries. This will probably need further refinements for collections for example, and could ultimately be fixed when kotlinx.serialization will provide a dedicated function to evaluate upfront if a type can be serialized or not. Closes gh-26298 --- .../json/KotlinSerializationJsonDecoder.java | 8 ++++- .../json/KotlinSerializationJsonEncoder.java | 8 ++++- ...SerializationJsonHttpMessageConverter.java | 10 ++++-- .../KotlinSerializationJsonDecoderTests.kt | 2 ++ .../KotlinSerializationJsonEncoderTests.kt | 2 ++ ...ializationJsonHttpMessageConverterTests.kt | 31 ++++++++++++------- 6 files changed, 46 insertions(+), 15 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java index 58f582bb1e8..64e2d43ddb4 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java @@ -21,6 +21,7 @@ import java.util.Map; import kotlinx.serialization.KSerializer; import kotlinx.serialization.SerializersKt; +import kotlinx.serialization.descriptors.PolymorphicKind; import kotlinx.serialization.json.Json; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -39,7 +40,9 @@ import org.springframework.util.MimeType; * Decode a byte stream into JSON and convert to Object's with * kotlinx.serialization. * - *

This decoder can be used to bind {@code @Serializable} Kotlin classes. + *

This decoder can be used to bind {@code @Serializable} Kotlin classes, + * open polymorphic serialization + * is not supported. * It supports {@code application/json} and {@code application/*+json} with * various character sets, {@code UTF-8} being the default. * @@ -132,6 +135,9 @@ public class KotlinSerializationJsonDecoder extends AbstractDecoder { KSerializer serializer = serializerCache.get(type); if (serializer == null) { serializer = SerializersKt.serializer(type); + if (serializer.getDescriptor().getKind().equals(PolymorphicKind.OPEN.INSTANCE)) { + throw new UnsupportedOperationException("Open polymorphic serialization is not supported yet"); + } serializerCache.put(type, serializer); } return serializer; diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonEncoder.java index cd577d4c828..3c30b2b0e00 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonEncoder.java @@ -22,6 +22,7 @@ import java.util.Map; import kotlinx.serialization.KSerializer; import kotlinx.serialization.SerializersKt; +import kotlinx.serialization.descriptors.PolymorphicKind; import kotlinx.serialization.json.Json; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -42,7 +43,9 @@ import org.springframework.util.MimeType; * Encode from an {@code Object} stream to a byte stream of JSON objects using * kotlinx.serialization. * - *

This encoder can be used to bind {@code @Serializable} Kotlin classes. + *

This encoder can be used to bind {@code @Serializable} Kotlin classes, + * open polymorphic serialization + * is not supported. * It supports {@code application/json} and {@code application/*+json} with * various character sets, {@code UTF-8} being the default. * @@ -120,6 +123,9 @@ public class KotlinSerializationJsonEncoder extends AbstractEncoder { KSerializer serializer = serializerCache.get(type); if (serializer == null) { serializer = SerializersKt.serializer(type); + if (serializer.getDescriptor().getKind().equals(PolymorphicKind.OPEN.INSTANCE)) { + throw new UnsupportedOperationException("Open polymorphic serialization is not supported yet"); + } serializerCache.put(type, serializer); } return serializer; diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverter.java index 7149868217a..6025255e19e 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import java.util.Map; import kotlinx.serialization.KSerializer; import kotlinx.serialization.SerializationException; import kotlinx.serialization.SerializersKt; +import kotlinx.serialization.descriptors.PolymorphicKind; import kotlinx.serialization.json.Json; import org.springframework.core.GenericTypeResolver; @@ -43,7 +44,9 @@ import org.springframework.util.StreamUtils; * that can read and write JSON using * kotlinx.serialization. * - *

This converter can be used to bind {@code @Serializable} Kotlin classes. + *

This converter can be used to bind {@code @Serializable} Kotlin classes, + * open polymorphic serialization + * is not supported. * It supports {@code application/json} and {@code application/*+json} with * various character sets, {@code UTF-8} being the default. * @@ -182,6 +185,9 @@ public class KotlinSerializationJsonHttpMessageConverter extends AbstractGeneric KSerializer serializer = serializerCache.get(type); if (serializer == null) { serializer = SerializersKt.serializer(type); + if (serializer.getDescriptor().getKind().equals(PolymorphicKind.OPEN.INSTANCE)) { + throw new UnsupportedOperationException("Open polymorphic serialization is not supported yet"); + } serializerCache.put(type, serializer); } return serializer; diff --git a/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt b/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt index f1489814db1..d333bd0a86b 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt @@ -19,6 +19,7 @@ package org.springframework.http.codec.json import kotlinx.serialization.Serializable import org.assertj.core.api.Assertions import org.junit.jupiter.api.Test +import org.springframework.core.Ordered import org.springframework.core.ResolvableType import org.springframework.core.io.buffer.DataBuffer import org.springframework.core.testfixture.codec.AbstractDecoderTests @@ -58,6 +59,7 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canRead(typeTokenOf>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), null, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), null, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), null, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canRead(typeTokenOf>(), null, MediaType.APPLICATION_PDF)).isFalse() + assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canRead(typeTokenOf>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse() + + assertThat(converter.canRead(typeTokenOf(), Ordered::class.java, MediaType.APPLICATION_JSON)).isFalse() } @Test @@ -65,14 +70,18 @@ class KotlinSerializationJsonHttpMessageConverterTests { assertThat(converter.canWrite(String::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(NotSerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() - assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canWrite(typeTokenOf>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() + assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_JSON)).isFalse() + assertThat(converter.canWrite(typeTokenOf>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() - assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(typeTokenOf>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse() + + assertThat(converter.canWrite(typeTokenOf(), Ordered::class.java, MediaType.APPLICATION_JSON)).isFalse() } @Test