From ec6cc4f095afdd46cdb24e58dbe636ae068ca45b Mon Sep 17 00:00:00 2001 From: vnglnk <128087718+holotsvan@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:32:16 +0200 Subject: [PATCH] Wrong length validation of event title field (#7841) * add DecodedSize.java annotation * formatter + checkstyle * add parameterized test for validator * fix sonar issue * add test case with null * small fix of null test case in test sources --- .../greencity/controller/EventController.java | 2 + service-api/pom.xml | 6 +++ .../greencity/annotations/DecodedSize.java | 24 +++++++++++ .../dto/event/AddEventDtoRequest.java | 3 +- .../validator/DecodedSizeValidator.java | 29 +++++++++++++ .../validator/DecodedSizeValidatorTest.java | 42 +++++++++++++++++++ 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 service-api/src/main/java/greencity/annotations/DecodedSize.java create mode 100644 service-api/src/main/java/greencity/validator/DecodedSizeValidator.java create mode 100644 service-api/src/test/java/greencity/validator/DecodedSizeValidatorTest.java diff --git a/core/src/main/java/greencity/controller/EventController.java b/core/src/main/java/greencity/controller/EventController.java index a5276be40e..82b3cbcb24 100644 --- a/core/src/main/java/greencity/controller/EventController.java +++ b/core/src/main/java/greencity/controller/EventController.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Set; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -51,6 +52,7 @@ import org.springframework.web.multipart.MultipartFile; import static greencity.constant.SwaggerExampleModel.UPDATE_EVENT; +@Slf4j @Validated @RestController @RequestMapping("/events") diff --git a/service-api/pom.xml b/service-api/pom.xml index e82720f95e..6a23f98318 100644 --- a/service-api/pom.xml +++ b/service-api/pom.xml @@ -102,5 +102,11 @@ springdoc-openapi-starter-webmvc-ui 2.3.0 + + org.jsoup + jsoup + 1.18.1 + compile + diff --git a/service-api/src/main/java/greencity/annotations/DecodedSize.java b/service-api/src/main/java/greencity/annotations/DecodedSize.java new file mode 100644 index 0000000000..9c4caa6925 --- /dev/null +++ b/service-api/src/main/java/greencity/annotations/DecodedSize.java @@ -0,0 +1,24 @@ +package greencity.annotations; + +import greencity.validator.DecodedSizeValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Constraint(validatedBy = DecodedSizeValidator.class) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DecodedSize { + String message() default "Size must be between {min} and {max} after decoding"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + int min() default 0; + + int max() default Integer.MAX_VALUE; +} diff --git a/service-api/src/main/java/greencity/dto/event/AddEventDtoRequest.java b/service-api/src/main/java/greencity/dto/event/AddEventDtoRequest.java index 4bd19997a0..f9c1ca39c6 100644 --- a/service-api/src/main/java/greencity/dto/event/AddEventDtoRequest.java +++ b/service-api/src/main/java/greencity/dto/event/AddEventDtoRequest.java @@ -1,6 +1,7 @@ package greencity.dto.event; import com.fasterxml.jackson.annotation.JsonProperty; +import greencity.annotations.DecodedSize; import jakarta.validation.constraints.NotBlank; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; @@ -21,7 +22,7 @@ public class AddEventDtoRequest { @NotEmpty @NotBlank - @Size(min = 1, max = 70) + @DecodedSize(min = 1, max = 70) private String title; @NotEmpty diff --git a/service-api/src/main/java/greencity/validator/DecodedSizeValidator.java b/service-api/src/main/java/greencity/validator/DecodedSizeValidator.java new file mode 100644 index 0000000000..ffc7f8152f --- /dev/null +++ b/service-api/src/main/java/greencity/validator/DecodedSizeValidator.java @@ -0,0 +1,29 @@ +package greencity.validator; + +import greencity.annotations.DecodedSize; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.jsoup.parser.Parser; + +public class DecodedSizeValidator implements ConstraintValidator { + private int max; + private int min; + + @Override + public void initialize(DecodedSize constraintAnnotation) { + this.max = constraintAnnotation.max(); + this.min = constraintAnnotation.min(); + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + if (value == null) { + return true; + } + + String decodedString = Parser.unescapeEntities(value, true); + int length = decodedString.length(); + + return length >= min && length <= max; + } +} diff --git a/service-api/src/test/java/greencity/validator/DecodedSizeValidatorTest.java b/service-api/src/test/java/greencity/validator/DecodedSizeValidatorTest.java new file mode 100644 index 0000000000..9721dd9257 --- /dev/null +++ b/service-api/src/test/java/greencity/validator/DecodedSizeValidatorTest.java @@ -0,0 +1,42 @@ +package greencity.validator; + +import greencity.annotations.DecodedSize; +import jakarta.validation.ConstraintValidatorContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mockito; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DecodedSizeValidatorTest { + + private final DecodedSizeValidator validator = new DecodedSizeValidator(); + + @ParameterizedTest + @CsvSource({ + "'', 0, 10, true", + "'abc', 1, 3, true", + "'abc', 0, 3, true", + "'abc', 1, 2, false", + "'abc', 2, 4, true", + "'a&b&c', 3, 5, true", + "'a&b&c', 0, 2, false", + "'a<>&', 0, 9, true", + "'a<>&', 0, 3, false", + "'Hello <world>', 0, 20, true", + "'Hello <world>', 0, 12, false", + ", 0, 0, true", + }) + void testDecodedSizeValidator(String input, int min, int max, boolean expectedValidity) { + DecodedSize annotation = Mockito.mock(DecodedSize.class); + Mockito.when(annotation.min()).thenReturn(min); + Mockito.when(annotation.max()).thenReturn(max); + + validator.initialize(annotation); + + boolean isValid = validator.isValid(input, Mockito.mock(ConstraintValidatorContext.class)); + boolean condition = expectedValidity == isValid; + assertTrue(condition, + String.format("For input '%s' with min %d and max %d, expected %b but got %b", input, min, max, + expectedValidity, isValid)); + } +}