Low severityNVD Advisory· Published Apr 23, 2021· Updated Sep 16, 2024
Potential sensitive data exposure in applications using Vaadin 15
CVE-2020-36319
Description
Insecure configuration of default ObjectMapper in com.vaadin:flow-server versions 3.0.0 through 3.0.5 (Vaadin 15.0.0 through 15.0.4) may expose sensitive data if the application also uses e.g. @RestController
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
com.vaadin:flow-serverMaven | >= 3.0.0, < 3.0.6 | 3.0.6 |
Affected products
2- Vaadin/flow-serverv5Range: 3.0.0
Patches
41 file changed · +1 −1
package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "@vaadin/vaadin", - "version": "15.0.4", + "version": "15.0.5", "description": "Vaadin components is an evolving set of open sourced custom HTML elements for building mobile and desktop web applications in modern browsers.", "author": "Vaadin Ltd", "license": "(Apache-2.0 OR SEE LICENSE IN https://vaadin.com/license/cval-3.0)",
30a303ec4204Merge pull request #8093 from vaadin/cp/3.0
27 files changed · +429 −143
flow-server/pom.xml+6 −2 modified@@ -15,7 +15,6 @@ <properties> <validation.api.version>2.0.1.Final</validation.api.version> <jackson.version>2.10.2</jackson.version> - <jackson.databind.version>2.10.2</jackson.databind.version> <spring.version>5.2.0.RELEASE</spring.version> <spring.autoconfigure.version>2.2.0.RELEASE</spring.autoconfigure.version> <javax.annotation.api.version>1.3.2</javax.annotation.api.version> @@ -198,7 +197,12 @@ <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> - <version>${jackson.databind.version}</version> + <version>${jackson.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-jsr310</artifactId> + <version>${jackson.version}</version> </dependency> <!-- Needed for security annotations --> <dependency>
flow-server/src/main/java/com/vaadin/flow/server/connect/VaadinConnectControllerConfiguration.java+0 −28 modified@@ -18,13 +18,7 @@ import java.lang.reflect.Method; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jackson.JacksonProperties; import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; @@ -33,8 +27,6 @@ import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker; -import static com.vaadin.flow.server.connect.VaadinConnectController.VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER; - /** * A configuration class for customizing the {@link VaadinConnectController} * class. @@ -142,24 +134,4 @@ public VaadinConnectAccessChecker accessChecker() { public ExplicitNullableTypeChecker typeChecker() { return new ExplicitNullableTypeChecker(); } - - /** - * Registers a {@link ObjectMapper} bean instance. - * - * @param context - * Spring application context - * @return the object mapper for endpoint. - */ - @Bean - @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) - public ObjectMapper vaadinEndpointMapper(ApplicationContext context) { - ObjectMapper objectMapper = new ObjectMapper(); - JacksonProperties jacksonProperties = context - .getBean(JacksonProperties.class); - if (jacksonProperties.getVisibility().isEmpty()) { - objectMapper.setVisibility(PropertyAccessor.ALL, - JsonAutoDetect.Visibility.ANY); - } - return objectMapper; - } }
flow-server/src/main/java/com/vaadin/flow/server/connect/VaadinConnectController.java+22 −2 modified@@ -39,18 +39,23 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jackson.JacksonProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.ClassUtils; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -126,12 +131,15 @@ public class VaadinConnectController { * from */ public VaadinConnectController( - @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) ObjectMapper vaadinEndpointMapper, + @Autowired(required = false) @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) + ObjectMapper vaadinEndpointMapper, VaadinConnectAccessChecker accessChecker, EndpointNameChecker endpointNameChecker, ExplicitNullableTypeChecker explicitNullableTypeChecker, ApplicationContext context) { - this.vaadinEndpointMapper = vaadinEndpointMapper; + this.vaadinEndpointMapper = vaadinEndpointMapper != null + ? vaadinEndpointMapper + : createVaadinConnectObjectMapper(context); this.accessChecker = accessChecker; this.explicitNullableTypeChecker = explicitNullableTypeChecker; @@ -140,6 +148,18 @@ public VaadinConnectController( name, endpointBean)); } + private ObjectMapper createVaadinConnectObjectMapper(ApplicationContext context) { + Jackson2ObjectMapperBuilder builder = context.getBean(Jackson2ObjectMapperBuilder.class); + ObjectMapper objectMapper = builder.createXmlMapper(false).build(); + JacksonProperties jacksonProperties = context + .getBean(JacksonProperties.class); + if (jacksonProperties.getVisibility().isEmpty()) { + objectMapper.setVisibility(PropertyAccessor.ALL, + JsonAutoDetect.Visibility.ANY); + } + return objectMapper; + } + private static Logger getLogger() { return LoggerFactory.getLogger(VaadinConnectController.class); }
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/BeanWithJacksonAnnotation.java+40 −0 added@@ -0,0 +1,40 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BeanWithJacksonAnnotation { + @JsonProperty("bookId") + private String id; + private String name; + + @JsonProperty("name") + public void setFirstName(String name) { + this.name = name; + } + + @JsonProperty("name") + public String getFirstName() { + return name; + } + + @JsonProperty + public int getRating() { + return 2; + } +}
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/BeanWithPrivateFields.java+40 −0 added@@ -0,0 +1,40 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +public class BeanWithPrivateFields { + @SuppressWarnings("unused") + private String codeNumber = "007"; + private String name = "Bond"; + private String firstName = "James"; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + protected String getFirstName() { + return firstName; + } + + protected void setFirstName(String firstName) { + this.firstName = firstName; + } +}
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/EndpointWithRestControllerTest.java+137 −0 added@@ -0,0 +1,137 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import javax.servlet.ServletContext; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.vaadin.flow.server.connect.EndpointNameChecker; +import com.vaadin.flow.server.connect.ExplicitNullableTypeChecker; +import com.vaadin.flow.server.connect.VaadinConnectController; +import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebMvcTest +@Import({VaadinConnectEndpoints.class, MyRestController.class}) +public class EndpointWithRestControllerTest { + + private MockMvc mockMvcForEndpoint; + + @Autowired + private MockMvc mockMvcForRest; + + + @Autowired + private ApplicationContext applicationContext; + + @Before + public void setUp() { + mockMvcForEndpoint = MockMvcBuilders.standaloneSetup(new VaadinConnectController( + null, mock(VaadinConnectAccessChecker.class), + mock(EndpointNameChecker.class), + mock(ExplicitNullableTypeChecker.class), + applicationContext)) + .build(); + Assert.assertNotEquals(null, applicationContext); + } + + @Test + //https://github.com/vaadin/flow/issues/8010 + public void shouldNotExposePrivateAndProtectedFields_when_CallingFromRestAPIs() + throws Exception { + String result = mockMvcForRest.perform(get("/api/get") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + assertEquals("{\"name\":\"Bond\"}", result); + } + + @Test + //https://github.com/vaadin/flow/issues/8034 + public void should_BeAbleToSerializePrivateFieldsOfABean_when_CallingFromConnectEndPoint() { + try { + String result = callEndpointMethod("getBeanWithPrivateFields"); + assertEquals("{\"codeNumber\":\"007\",\"name\":\"Bond\",\"firstName\":\"James\"}", result); + } catch (Exception e) { + fail("failed to serialize a bean with private fields"); + } + } + + @Test + //https://github.com/vaadin/flow/issues/8034 + public void should_BeAbleToSerializeABeanWithZonedDateTimeField() { + try { + String result = callEndpointMethod("getBeanWithZonedDateTimeField"); + assertNotNull(result); + assertNotEquals("", result); + assertNotEquals("{\"message\":\"Failed to serialize endpoint 'VaadinConnectTypeConversionEndpoints' method 'getBeanWithZonedDateTimeField' response. Double check method's return type or specify a custom mapper bean with qualifier 'vaadinEndpointMapper'\"}", result); + } catch (Exception e) { + fail("failed to serialize a bean with ZonedDateTime field"); + } + } + + @Test + //https://github.com/vaadin/flow/issues/8067 + public void should_RepsectJacksonAnnotation_when_serializeBean() throws Exception { + String result = callEndpointMethod("getBeanWithJacksonAnnotation"); + assertEquals("{\"name\":null,\"rating\":2,\"bookId\":null}", result); + } + + @Test + /** + * this requires jackson-datatype-jsr310, which is added as a test scope dependency. + * jackson-datatype-jsr310 is provided in spring-boot-starter-web, which is part of + * vaadin-spring-boot-starter + */ + public void should_serializeLocalTimeInExpectedFormat_when_UsingSpringBoot() throws Exception{ + String result = callEndpointMethod("getLocalTime"); + assertEquals("\"08:00:00\"", result); + } + + private String callEndpointMethod(String methodName) throws Exception { + String endpointName = VaadinConnectEndpoints.class.getSimpleName(); + String requestUrl = String.format("/%s/%s", endpointName, methodName); + RequestBuilder requestBuilder = MockMvcRequestBuilders.post(requestUrl) + .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) + .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + + return mockMvcForEndpoint.perform(requestBuilder).andReturn().getResponse().getContentAsString(); + } +} +
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/MixedRestAndEndpointApplication.java+23 −0 added@@ -0,0 +1,23 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MixedRestAndEndpointApplication { + // Test application to provide test context for the integration tests +}
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/MyRestController.java+32 −0 added@@ -0,0 +1,32 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.vaadin.flow.server.connect.rest.BeanWithPrivateFields; + +@RestController +public class MyRestController { + + @GetMapping("api/get") + public BeanWithPrivateFields read() { + return new BeanWithPrivateFields(); + } + +}
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/VaadinConnectEndpoints.java+53 −0 added@@ -0,0 +1,53 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import java.time.LocalTime; +import java.time.ZonedDateTime; + +import com.vaadin.flow.server.connect.Endpoint; + +@Endpoint +public class VaadinConnectEndpoints { + + public BeanWithZonedDateTimeField getBeanWithZonedDateTimeField(){ + return new BeanWithZonedDateTimeField(); + } + + public BeanWithPrivateFields getBeanWithPrivateFields(){ + return new BeanWithPrivateFields(); + } + + public BeanWithJacksonAnnotation getBeanWithJacksonAnnotation(){ + return new BeanWithJacksonAnnotation(); + } + + public LocalTime getLocalTime(){ + return LocalTime.of(8, 0, 0); + } + + public static class BeanWithZonedDateTimeField { + private ZonedDateTime zonedDateTime = ZonedDateTime.now(); + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + } +}
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/ArrayConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class ArrayConversionIT extends BaseTypeConversionIT { +public class ArrayConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToArrayInt_When_ReceiveArrayInt() {
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/BaseTypeConversionTest.java+1 −1 renamed@@ -40,7 +40,7 @@ @RunWith(SpringRunner.class) @WebMvcTest @Import(VaadinConnectTypeConversionEndpoints.class) -public abstract class BaseTypeConversionIT { +public abstract class BaseTypeConversionTest { private MockMvc mockMvc;
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/BeanConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class BeanConversionIT extends BaseTypeConversionIT { +public class BeanConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToBean_When_ReceiveBeanObject() {
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/BooleanConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class BooleanConversionIT extends BaseTypeConversionIT { +public class BooleanConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToBoolean_When_ReceiveTrueOrFalse() { assertEqualExpectedValueWhenCallingMethod("revertBoolean", "true",
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/ByteConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class ByteConversionIT extends BaseTypeConversionIT { +public class ByteConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertNumberToByte_When_ReceiveNumber() { assertEqualExpectedValueWhenCallingMethod("addOneByte", "1", "2");
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/CharacterConversionTest.java+1 −1 renamed@@ -19,7 +19,7 @@ import org.junit.Test; import org.springframework.mock.web.MockHttpServletResponse; -public class CharacterConversionIT extends BaseTypeConversionIT { +public class CharacterConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToChar_When_ReceiveASingleCharOrNumber() throws Exception {
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/CollectionConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class CollectionConversionIT extends BaseTypeConversionIT { +public class CollectionConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToIntegerCollection_When_ReceiveNumberArray() {
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/DateTimeConversionTest.java+1 −4 renamed@@ -15,10 +15,9 @@ */ package com.vaadin.flow.server.connect.typeconversion; -import org.junit.Ignore; import org.junit.Test; -public class DateTimeConversionIT extends BaseTypeConversionIT { +public class DateTimeConversionTest extends BaseTypeConversionTest { // region date tests @Test @@ -58,7 +57,6 @@ public void should_ConvertToNullForDate_When_ReceiveANull() { // format @Test - @Ignore("Failing after moved to flow") public void should_ConvertToLocalDate_When_ReceiveALocalDate() { String inputDate = "\"2019-12-13\""; String expectedTimestamp = "\"2019-12-14\""; @@ -77,7 +75,6 @@ public void should_FailToConvertToLocalDate_When_ReceiveWrongFormat() { // LocalDate uses java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME as // default format @Test - @Ignore("Failing after moved to flow") public void should_ConvertToLocalDateTime_When_ReceiveALocalDateTime() { String inputDate = "\"2019-12-13T12:12:12\""; String expectedTimestamp = "\"2019-12-14T13:12:12\"";
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/DoubleConversionTest.java+1 −1 renamed@@ -19,7 +19,7 @@ import org.junit.Test; import org.springframework.mock.web.MockHttpServletResponse; -public class DoubleConversionIT extends BaseTypeConversionIT { +public class DoubleConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToDouble_When_ReceiveANumber() { assertCallMethodWithExpectedDoubleValue("addOneDouble", "1", "2.0");
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/EnumConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class EnumConversionIT extends BaseTypeConversionIT { +public class EnumConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToEnum_When_ReceiveStringWithSameName() {
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/FloatConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class FloatConversionIT extends BaseTypeConversionIT { +public class FloatConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToFloat_When_ReceiveANumber() { assertEqualExpectedValueWhenCallingMethod("addOneFloat", "1", "2.0");
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/IntegerConversionTest.java+9 −10 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class IntegerConversionIT extends BaseTypeConversionIT { +public class IntegerConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertNumberToInt_When_ReceiveNumberAsNumber() { @@ -45,19 +45,18 @@ public void should_ConvertNumberToInt_When_ReceiveNumberAsString() { } @Test - public void should_HandleOverflowInteger_When_ReceiveOverflowNumber() { + public void should_FailToConvertOverflowInteger_When_ReceiveOverflowNumber() { String overflowInputInteger = "2147483648"; - assertEqualExpectedValueWhenCallingMethod("addOneInt", - overflowInputInteger, String.valueOf(Integer.MIN_VALUE + 1)); - assertEqualExpectedValueWhenCallingMethod("addOneIntBoxed", - overflowInputInteger, String.valueOf(Integer.MIN_VALUE + 1)); + assert400ResponseWhenCallingMethod("addOneInt", + overflowInputInteger); + assert400ResponseWhenCallingMethod("addOneIntBoxed", + overflowInputInteger); String underflowInputInteger = "-2147483649"; // underflow will become MAX, then +1 in the method => MIN - assertEqualExpectedValueWhenCallingMethod("addOneInt", - underflowInputInteger, String.valueOf(Integer.MIN_VALUE)); - assertEqualExpectedValueWhenCallingMethod("addOneIntBoxed", - underflowInputInteger, String.valueOf(Integer.MIN_VALUE)); + assert400ResponseWhenCallingMethod("addOneInt", underflowInputInteger); + assert400ResponseWhenCallingMethod("addOneIntBoxed", + underflowInputInteger); } @Test
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/LongConversionTest.java+6 −10 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class LongConversionIT extends BaseTypeConversionIT { +public class LongConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToLong_When_ReceiveANumber() { @@ -78,18 +78,14 @@ public void should_FailToConvertToLong_When_ReceiveDecimalAsString() { } @Test - public void should_HandleOverflowLong_When_ReceiveANumberOverflowOrUnderflow() { + public void should_FailToConvertToLong_When_ReceiveANumberOverflowOrUnderflow() { String overflowLong = "9223372036854775808"; // 2^63 - assertEqualExpectedValueWhenCallingMethod("addOneLong", overflowLong, - String.valueOf(Long.MIN_VALUE + 1)); - assertEqualExpectedValueWhenCallingMethod("addOneLongBoxed", - overflowLong, String.valueOf(Long.MIN_VALUE + 1)); + assert400ResponseWhenCallingMethod("addOneLong", overflowLong); + assert400ResponseWhenCallingMethod("addOneLongBoxed", overflowLong); String underflowLong = "-9223372036854775809"; // -2^63-1 - assertEqualExpectedValueWhenCallingMethod("addOneLong", underflowLong, - String.valueOf(Long.MIN_VALUE)); - assertEqualExpectedValueWhenCallingMethod("addOneLongBoxed", - underflowLong, String.valueOf(Long.MIN_VALUE)); + assert400ResponseWhenCallingMethod("addOneLong", underflowLong); + assert400ResponseWhenCallingMethod("addOneLongBoxed", underflowLong); } @Test
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/MapConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class MapConversionIT extends BaseTypeConversionIT { +public class MapConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToMapOfString_When_ReceiveMapOfString() {
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/ShortConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class ShortConversionIT extends BaseTypeConversionIT { +public class ShortConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToShort_When_ReceiveANumber() { assertEqualExpectedValueWhenCallingMethod("addOneShort", "1", "2");
flow-server/src/test/java/com/vaadin/flow/server/connect/typeconversion/StringConversionTest.java+1 −1 renamed@@ -17,7 +17,7 @@ import org.junit.Test; -public class StringConversionIT extends BaseTypeConversionIT { +public class StringConversionTest extends BaseTypeConversionTest { @Test public void should_ConvertToString_When_ReceiveAString() {
flow-server/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerConfigurationTest.java+0 −70 removed@@ -1,70 +0,0 @@ -package com.vaadin.flow.server.connect; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Collections; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.junit.Test; -import org.springframework.boot.autoconfigure.jackson.JacksonProperties; -import org.springframework.context.ApplicationContext; - -public class VaadinConnectControllerConfigurationTest { - - @Test - public void should_NotOverrideVisibility_When_JacksonPropertiesProvideVisibility() { - ApplicationContext contextMock = mock(ApplicationContext.class); - VaadinEndpointProperties endpointPropertiesMock = mock(VaadinEndpointProperties.class); - VaadinConnectControllerConfiguration configuration = new VaadinConnectControllerConfiguration(endpointPropertiesMock); - - JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); - when(contextMock.getBean(JacksonProperties.class)) - .thenReturn(mockJacksonProperties); - when(mockJacksonProperties.getVisibility()) - .thenReturn(Collections.singletonMap(PropertyAccessor.ALL, - JsonAutoDetect.Visibility.PUBLIC_ONLY)); - - ObjectMapper objectMapper = configuration.vaadinEndpointMapper(contextMock); - - verify(contextMock, times(1)).getBean(JacksonProperties.class); - - try { - String result = objectMapper.writeValueAsString(new Entity()); - assertEquals("{\"name\":\"Bond\"}", result); - } catch (Exception e) { - fail("Failed to write an entity"); - } - - } - - public class Entity { - @SuppressWarnings("unused") - private String codeNumber = "007"; - private String name = "Bond"; - private String firstName = "James"; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - protected String getFirstName() { - return firstName; - } - - protected void setFirstName(String firstName) { - this.firstName = firstName; - } - } -}
flow-server/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerTest.java+47 −4 modified@@ -37,6 +37,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import com.vaadin.flow.server.MockVaadinServletService; import com.vaadin.flow.server.VaadinService; @@ -586,7 +587,6 @@ public MyCustomException() { } @Test - @Ignore("requires mockito version with plugin for final classes") public void should_Return500_When_MapperFailsToSerializeResponse() throws Exception { ObjectMapper mapperMock = mock(ObjectMapper.class); @@ -629,7 +629,6 @@ public void should_Return500_When_MapperFailsToSerializeResponse() } @Test - @Ignore("requires mockito version with plugin for final classes") public void should_ThrowException_When_MapperFailsToSerializeEverything() throws Exception { ObjectMapper mapperMock = mock(ObjectMapper.class); @@ -791,11 +790,19 @@ public void should_UseCustomEndpointName_When_EndpointClassIsProxied() { public void should_Never_UseSpringObjectMapper() { ApplicationContext contextMock = mock(ApplicationContext.class); ObjectMapper mockSpringObjectMapper = mock(ObjectMapper.class); + ObjectMapper mockOwnObjectMapper = mock(ObjectMapper.class); + Jackson2ObjectMapperBuilder mockObjectMapperBuilder = mock(Jackson2ObjectMapperBuilder.class); JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); when(contextMock.getBean(ObjectMapper.class)) .thenReturn(mockSpringObjectMapper); when(contextMock.getBean(JacksonProperties.class)) .thenReturn(mockJacksonProperties); + when(contextMock.getBean(Jackson2ObjectMapperBuilder.class)) + .thenReturn(mockObjectMapperBuilder); + when(mockObjectMapperBuilder.createXmlMapper(false)) + .thenReturn(mockObjectMapperBuilder); + when(mockObjectMapperBuilder.build()) + .thenReturn(mockOwnObjectMapper); when(mockJacksonProperties.getVisibility()) .thenReturn(Collections.emptyMap()); new VaadinConnectController(null, @@ -804,9 +811,45 @@ public void should_Never_UseSpringObjectMapper() { mock(ExplicitNullableTypeChecker.class), contextMock); verify(contextMock, never()).getBean(ObjectMapper.class); - verify(mockSpringObjectMapper, never()).setVisibility( + verify(contextMock, times(1)).getBean(Jackson2ObjectMapperBuilder.class); + verify(contextMock, times(1)).getBean(JacksonProperties.class); + verify(mockOwnObjectMapper, times(1)).setVisibility( + PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + } + + @Test + public void should_NotOverrideVisibility_When_JacksonPropertiesProvideVisibility() { + ApplicationContext contextMock = mock(ApplicationContext.class); + ObjectMapper mockDefaultObjectMapper = mock(ObjectMapper.class); + ObjectMapper mockOwnObjectMapper = mock(ObjectMapper.class); + Jackson2ObjectMapperBuilder mockObjectMapperBuilder = mock(Jackson2ObjectMapperBuilder.class); + JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); + when(contextMock.getBean(ObjectMapper.class)) + .thenReturn(mockDefaultObjectMapper); + when(contextMock.getBean(JacksonProperties.class)) + .thenReturn(mockJacksonProperties); + when(contextMock.getBean(Jackson2ObjectMapperBuilder.class)) + .thenReturn(mockObjectMapperBuilder); + when(mockObjectMapperBuilder.createXmlMapper(false)) + .thenReturn(mockObjectMapperBuilder); + when(mockObjectMapperBuilder.build()) + .thenReturn(mockOwnObjectMapper); + when(mockJacksonProperties.getVisibility()) + .thenReturn(Collections.singletonMap(PropertyAccessor.ALL, + JsonAutoDetect.Visibility.PUBLIC_ONLY)); + new VaadinConnectController(null, + mock(VaadinConnectAccessChecker.class), + mock(EndpointNameChecker.class), + mock(ExplicitNullableTypeChecker.class), + contextMock); + + verify(contextMock, never()).getBean(ObjectMapper.class); + verify(contextMock, times(1)).getBean(Jackson2ObjectMapperBuilder.class); + verify(mockDefaultObjectMapper, never()).setVisibility( + PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + verify(mockOwnObjectMapper, never()).setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - verify(contextMock, never()).getBean(JacksonProperties.class); + verify(contextMock, times(1)).getBean(JacksonProperties.class); } @Test
e9e27c3a249dinherit the default behaviour of spring for internal object mapper (#8051)
9 files changed · +305 −101
flow-server/src/main/java/com/vaadin/flow/server/connect/VaadinConnectControllerConfiguration.java+0 −28 modified@@ -20,23 +20,15 @@ import com.vaadin.flow.server.frontend.FrontendUtils; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jackson.JacksonProperties; import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.databind.ObjectMapper; import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker; -import static com.vaadin.flow.server.connect.VaadinConnectController.VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER; - /** * A configuration class for customizing the {@link VaadinConnectController} * class. @@ -146,24 +138,4 @@ public VaadinConnectAccessChecker accessChecker() { public ExplicitNullableTypeChecker typeChecker() { return new ExplicitNullableTypeChecker(); } - - /** - * Registers a {@link ObjectMapper} bean instance. - * - * @param context - * Spring application context - * @return the object mapper for endpoint. - */ - @Bean - @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) - public ObjectMapper vaadinEndpointMapper(ApplicationContext context) { - ObjectMapper objectMapper = new ObjectMapper(); - JacksonProperties jacksonProperties = context - .getBean(JacksonProperties.class); - if (jacksonProperties.getVisibility().isEmpty()) { - objectMapper.setVisibility(PropertyAccessor.ALL, - JsonAutoDetect.Visibility.ANY); - } - return objectMapper; - } }
flow-server/src/main/java/com/vaadin/flow/server/connect/VaadinConnectController.java+21 −2 modified@@ -41,18 +41,23 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jackson.JacksonProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.ClassUtils; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -132,13 +137,16 @@ public class VaadinConnectController { * The servlet context for the controller. */ public VaadinConnectController( - @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) ObjectMapper vaadinEndpointMapper, + @Autowired(required = false) @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) + ObjectMapper vaadinEndpointMapper, VaadinConnectAccessChecker accessChecker, EndpointNameChecker endpointNameChecker, ExplicitNullableTypeChecker explicitNullableTypeChecker, ApplicationContext context, ServletContext servletContext) { - this.vaadinEndpointMapper = vaadinEndpointMapper; + this.vaadinEndpointMapper = vaadinEndpointMapper != null + ? vaadinEndpointMapper + : createVaadinConnectObjectMapper(context); this.accessChecker = accessChecker; this.explicitNullableTypeChecker = explicitNullableTypeChecker; @@ -153,6 +161,17 @@ public VaadinConnectController( } } + private ObjectMapper createVaadinConnectObjectMapper(ApplicationContext context) { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); + JacksonProperties jacksonProperties = context + .getBean(JacksonProperties.class); + if (jacksonProperties.getVisibility().isEmpty()) { + objectMapper.setVisibility(PropertyAccessor.ALL, + JsonAutoDetect.Visibility.ANY); + } + return objectMapper; + } + private DeploymentConfiguration createDeploymentConfiguration( ServletContext ctx) { if (ctx.getServletRegistrations().isEmpty()) {
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/BeanWithPrivateFields.java+40 −0 added@@ -0,0 +1,40 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +public class BeanWithPrivateFields { + @SuppressWarnings("unused") + private String codeNumber = "007"; + private String name = "Bond"; + private String firstName = "James"; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + protected String getFirstName() { + return firstName; + } + + protected void setFirstName(String firstName) { + this.firstName = firstName; + } +}
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/EndpointWithRestControllerTest.java+119 −0 added@@ -0,0 +1,119 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import javax.servlet.ServletContext; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.vaadin.flow.server.connect.EndpointNameChecker; +import com.vaadin.flow.server.connect.ExplicitNullableTypeChecker; +import com.vaadin.flow.server.connect.VaadinConnectController; +import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebMvcTest +@Import({VaadinConnectEndpoints.class, MyRestController.class}) +public class EndpointWithRestControllerTest { + + private MockMvc mockMvcForEndpoint; + + @Autowired + private MockMvc mockMvcForRest; + + + @Autowired + private ApplicationContext applicationContext; + + @Before + public void setUp() { + mockMvcForEndpoint = MockMvcBuilders.standaloneSetup(new VaadinConnectController( + null, mock(VaadinConnectAccessChecker.class), + mock(EndpointNameChecker.class), + mock(ExplicitNullableTypeChecker.class), + applicationContext, + mock(ServletContext.class))) + .build(); + Assert.assertNotEquals(null, applicationContext); + } + + @Test + //https://github.com/vaadin/flow/issues/8010 + public void shouldNotExposePrivateAndProtectedFields_when_CallingFromRestAPIs() + throws Exception { + String result = mockMvcForRest.perform(get("/api/get") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + Assert.assertEquals("{\"name\":\"Bond\"}", result); + } + + @Test + //https://github.com/vaadin/flow/issues/8034 + public void should_BeAbleToSerializePrivateFieldsOfABean_when_CallingFromConnectEndPoint() { + try { + String result = callEndpointMethod("getBeanWithPrivateFields"); + Assert.assertEquals("{\"codeNumber\":\"007\",\"name\":\"Bond\",\"firstName\":\"James\"}", result); + } catch (Exception e) { + fail("failed to serialize a bean with private fields"); + } + } + + @Test + //https://github.com/vaadin/flow/issues/8034 + public void should_BeAbleToSerializeABeanWithZonedDateTimeField() { + try { + String result = callEndpointMethod("getBeanWithZonedDateTimeField"); + assertNotNull(result); + assertNotEquals("", result); + assertNotEquals("{\"message\":\"Failed to serialize endpoint 'VaadinConnectTypeConversionEndpoints' method 'getBeanWithZonedDateTimeField' response. Double check method's return type or specify a custom mapper bean with qualifier 'vaadinEndpointMapper'\"}", result); + } catch (Exception e) { + fail("failed to serialize a bean with ZonedDateTime field"); + } + } + + private String callEndpointMethod(String methodName) throws Exception { + String endpointName = VaadinConnectEndpoints.class.getSimpleName(); + String requestUrl = String.format("/%s/%s", endpointName, methodName); + RequestBuilder requestBuilder = MockMvcRequestBuilders.post(requestUrl) + .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) + .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + + return mockMvcForEndpoint.perform(requestBuilder).andReturn().getResponse().getContentAsString(); + } +} +
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/MixedRestAndEndpointApplication.java+23 −0 added@@ -0,0 +1,23 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MixedRestAndEndpointApplication { + // Test application to provide test context for the integration tests +}
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/MyRestController.java+32 −0 added@@ -0,0 +1,32 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.vaadin.flow.server.connect.rest.BeanWithPrivateFields; + +@RestController +public class MyRestController { + + @GetMapping("api/get") + public BeanWithPrivateFields read() { + return new BeanWithPrivateFields(); + } + +}
flow-server/src/test/java/com/vaadin/flow/server/connect/rest/VaadinConnectEndpoints.java+44 −0 added@@ -0,0 +1,44 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.server.connect.rest; + +import java.time.ZonedDateTime; + +import com.vaadin.flow.server.connect.Endpoint; + +@Endpoint +public class VaadinConnectEndpoints { + + public BeanWithZonedDateTimeField getBeanWithZonedDateTimeField(){ + return new BeanWithZonedDateTimeField(); + } + + public BeanWithPrivateFields getBeanWithPrivateFields(){ + return new BeanWithPrivateFields(); + } + + public static class BeanWithZonedDateTimeField { + private ZonedDateTime zonedDateTime = ZonedDateTime.now(); + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + } +}
flow-server/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerConfigurationTest.java+0 −70 removed@@ -1,70 +0,0 @@ -package com.vaadin.flow.server.connect; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Collections; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.junit.Test; -import org.springframework.boot.autoconfigure.jackson.JacksonProperties; -import org.springframework.context.ApplicationContext; - -public class VaadinConnectControllerConfigurationTest { - - @Test - public void should_NotOverrideVisibility_When_JacksonPropertiesProvideVisibility() { - ApplicationContext contextMock = mock(ApplicationContext.class); - VaadinEndpointProperties endpointPropertiesMock = mock(VaadinEndpointProperties.class); - VaadinConnectControllerConfiguration configuration = new VaadinConnectControllerConfiguration(endpointPropertiesMock); - - JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); - when(contextMock.getBean(JacksonProperties.class)) - .thenReturn(mockJacksonProperties); - when(mockJacksonProperties.getVisibility()) - .thenReturn(Collections.singletonMap(PropertyAccessor.ALL, - JsonAutoDetect.Visibility.PUBLIC_ONLY)); - - ObjectMapper objectMapper = configuration.vaadinEndpointMapper(contextMock); - - verify(contextMock, times(1)).getBean(JacksonProperties.class); - - try { - String result = objectMapper.writeValueAsString(new Entity()); - assertEquals("{\"name\":\"Bond\"}", result); - } catch (Exception e) { - fail("Failed to write an entity"); - } - - } - - public class Entity { - @SuppressWarnings("unused") - private String codeNumber = "007"; - private String name = "Bond"; - private String firstName = "James"; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - protected String getFirstName() { - return firstName; - } - - protected void setFirstName(String firstName) { - this.firstName = firstName; - } - } -}
flow-server/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerTest.java+26 −1 modified@@ -813,9 +813,34 @@ public void should_Never_UseSpringObjectMapper() { mock(ServletContext.class)); verify(contextMock, never()).getBean(ObjectMapper.class); + verify(contextMock, times(1)).getBean(JacksonProperties.class); verify(mockSpringObjectMapper, never()).setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - verify(contextMock, never()).getBean(JacksonProperties.class); + } + + @Test + public void should_NotOverrideVisibility_When_JacksonPropertiesProvideVisibility() { + ApplicationContext contextMock = mock(ApplicationContext.class); + ObjectMapper mockDefaultObjectMapper = mock(ObjectMapper.class); + JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); + when(contextMock.getBean(ObjectMapper.class)) + .thenReturn(mockDefaultObjectMapper); + when(contextMock.getBean(JacksonProperties.class)) + .thenReturn(mockJacksonProperties); + when(mockJacksonProperties.getVisibility()) + .thenReturn(Collections.singletonMap(PropertyAccessor.ALL, + JsonAutoDetect.Visibility.PUBLIC_ONLY)); + new VaadinConnectController(null, + mock(VaadinConnectAccessChecker.class), + mock(EndpointNameChecker.class), + mock(ExplicitNullableTypeChecker.class), + contextMock, + mock(ServletContext.class)); + + verify(contextMock, never()).getBean(ObjectMapper.class); + verify(mockDefaultObjectMapper, never()).setVisibility( + PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + verify(contextMock, times(1)).getBean(JacksonProperties.class); } @Test
7222c96b1c5d[Security Fix]Use Vaadin's own ObjectMapper instead of the one from Spring (#8016)
4 files changed · +108 −76
flow-server/src/main/java/com/vaadin/flow/server/connect/VaadinConnectControllerConfiguration.java+29 −0 modified@@ -19,15 +19,24 @@ import java.lang.reflect.Method; import com.vaadin.flow.server.frontend.FrontendUtils; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jackson.JacksonProperties; import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.databind.ObjectMapper; import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker; +import static com.vaadin.flow.server.connect.VaadinConnectController.VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER; + /** * A configuration class for customizing the {@link VaadinConnectController} * class. @@ -137,4 +146,24 @@ public VaadinConnectAccessChecker accessChecker() { public ExplicitNullableTypeChecker typeChecker() { return new ExplicitNullableTypeChecker(); } + + /** + * Registers a {@link ObjectMapper} bean instance. + * + * @param context + * Spring application context + * @return the object mapper for endpoint. + */ + @Bean + @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) + public ObjectMapper vaadinEndpointMapper(ApplicationContext context) { + ObjectMapper objectMapper = new ObjectMapper(); + JacksonProperties jacksonProperties = context + .getBean(JacksonProperties.class); + if (jacksonProperties.getVisibility().isEmpty()) { + objectMapper.setVisibility(PropertyAccessor.ALL, + JsonAutoDetect.Visibility.ANY); + } + return objectMapper; + } }
flow-server/src/main/java/com/vaadin/flow/server/connect/VaadinConnectController.java+2 −27 modified@@ -41,17 +41,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jackson.JacksonProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; @@ -136,15 +132,13 @@ public class VaadinConnectController { * The servlet context for the controller. */ public VaadinConnectController( - @Autowired(required = false) @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) ObjectMapper vaadinEndpointMapper, + @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) ObjectMapper vaadinEndpointMapper, VaadinConnectAccessChecker accessChecker, EndpointNameChecker endpointNameChecker, ExplicitNullableTypeChecker explicitNullableTypeChecker, ApplicationContext context, ServletContext servletContext) { - this.vaadinEndpointMapper = vaadinEndpointMapper != null - ? vaadinEndpointMapper - : getDefaultObjectMapper(context); + this.vaadinEndpointMapper = vaadinEndpointMapper; this.accessChecker = accessChecker; this.explicitNullableTypeChecker = explicitNullableTypeChecker; @@ -206,25 +200,6 @@ void validateEndpointBean(EndpointNameChecker endpointNameChecker, new VaadinEndpointData(endpointBean, beanType.getMethods())); } - private ObjectMapper getDefaultObjectMapper(ApplicationContext context) { - try { - ObjectMapper objectMapper = context.getBean(ObjectMapper.class); - JacksonProperties jacksonProperties = context - .getBean(JacksonProperties.class); - if (jacksonProperties.getVisibility().isEmpty()) { - objectMapper.setVisibility(PropertyAccessor.ALL, - JsonAutoDetect.Visibility.ANY); - } - return objectMapper; - } catch (Exception e) { - throw new IllegalStateException(String.format( - "Auto configured jackson object mapper is not found." - + "Please define your own object mapper with '@Qualifier(%s)' or " - + "make sure that the auto configured jackson object mapper is available.", - VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER), e); - } - } - /** * Captures and processes the Vaadin Connect requests. * <p>
flow-server/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerConfigurationTest.java+70 −0 added@@ -0,0 +1,70 @@ +package com.vaadin.flow.server.connect; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.junit.Test; +import org.springframework.boot.autoconfigure.jackson.JacksonProperties; +import org.springframework.context.ApplicationContext; + +public class VaadinConnectControllerConfigurationTest { + + @Test + public void should_NotOverrideVisibility_When_JacksonPropertiesProvideVisibility() { + ApplicationContext contextMock = mock(ApplicationContext.class); + VaadinEndpointProperties endpointPropertiesMock = mock(VaadinEndpointProperties.class); + VaadinConnectControllerConfiguration configuration = new VaadinConnectControllerConfiguration(endpointPropertiesMock); + + JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); + when(contextMock.getBean(JacksonProperties.class)) + .thenReturn(mockJacksonProperties); + when(mockJacksonProperties.getVisibility()) + .thenReturn(Collections.singletonMap(PropertyAccessor.ALL, + JsonAutoDetect.Visibility.PUBLIC_ONLY)); + + ObjectMapper objectMapper = configuration.vaadinEndpointMapper(contextMock); + + verify(contextMock, times(1)).getBean(JacksonProperties.class); + + try { + String result = objectMapper.writeValueAsString(new Entity()); + assertEquals("{\"name\":\"Bond\"}", result); + } catch (Exception e) { + fail("Failed to write an entity"); + } + + } + + public class Entity { + @SuppressWarnings("unused") + private String codeNumber = "007"; + private String name = "Bond"; + private String firstName = "James"; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + protected String getFirstName() { + return firstName; + } + + protected void setFirstName(String firstName) { + this.firstName = firstName; + } + } +}
flow-server/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerTest.java+7 −49 modified@@ -34,7 +34,6 @@ import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.jackson.JacksonProperties; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; @@ -57,6 +56,7 @@ import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.only; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -795,12 +795,12 @@ public void should_UseCustomEndpointName_When_EndpointClassIsProxied() { } @Test - public void should_UseDefaultObjectMapper_When_NoneIsProvided() { + public void should_Never_UseSpringObjectMapper() { ApplicationContext contextMock = mock(ApplicationContext.class); - ObjectMapper mockDefaultObjectMapper = mock(ObjectMapper.class); + ObjectMapper mockSpringObjectMapper = mock(ObjectMapper.class); JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); when(contextMock.getBean(ObjectMapper.class)) - .thenReturn(mockDefaultObjectMapper); + .thenReturn(mockSpringObjectMapper); when(contextMock.getBean(JacksonProperties.class)) .thenReturn(mockJacksonProperties); when(mockJacksonProperties.getVisibility()) @@ -812,52 +812,10 @@ public void should_UseDefaultObjectMapper_When_NoneIsProvided() { contextMock, mock(ServletContext.class)); - verify(contextMock, times(1)).getBean(ObjectMapper.class); - verify(mockDefaultObjectMapper, times(1)).setVisibility( + verify(contextMock, never()).getBean(ObjectMapper.class); + verify(mockSpringObjectMapper, never()).setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - verify(contextMock, times(1)).getBean(JacksonProperties.class); - } - - @Test - public void should_NotOverrideVisibility_When_JacksonPropertiesProvideVisibility() { - ApplicationContext contextMock = mock(ApplicationContext.class); - ObjectMapper mockDefaultObjectMapper = mock(ObjectMapper.class); - JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); - when(contextMock.getBean(ObjectMapper.class)) - .thenReturn(mockDefaultObjectMapper); - when(contextMock.getBean(JacksonProperties.class)) - .thenReturn(mockJacksonProperties); - when(mockJacksonProperties.getVisibility()) - .thenReturn(Collections.singletonMap(PropertyAccessor.ALL, - JsonAutoDetect.Visibility.PUBLIC_ONLY)); - new VaadinConnectController(null, - mock(VaadinConnectAccessChecker.class), - mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - contextMock, - mock(ServletContext.class)); - - verify(contextMock, times(1)).getBean(ObjectMapper.class); - verify(mockDefaultObjectMapper, times(0)).setVisibility( - PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - verify(contextMock, times(1)).getBean(JacksonProperties.class); - } - - @Test - public void should_ThrowError_When_DefaultObjectMapperIsNotFound() { - ApplicationContext contextMock = mock(ApplicationContext.class); - when(contextMock.getBean(ObjectMapper.class)) - .thenThrow(new NoSuchBeanDefinitionException("Bean not found")); - - exception.expect(IllegalStateException.class); - exception.expectMessage("object mapper"); - - new VaadinConnectController(null, - mock(VaadinConnectAccessChecker.class), - mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - contextMock, - mock(ServletContext.class)); + verify(contextMock, never()).getBean(JacksonProperties.class); } @Test
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
6- github.com/advisories/GHSA-rjww-2x8v-m9v9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-36319ghsaADVISORY
- github.com/vaadin/flow/pull/8016ghsax_refsource_MISCWEB
- github.com/vaadin/flow/pull/8051ghsax_refsource_MISCWEB
- github.com/vaadin/flow/security/advisories/GHSA-rjww-2x8v-m9v9ghsaWEB
- vaadin.com/security/cve-2020-36319ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.