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 extends Payload>[] 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));
+ }
+}