Skip to content

Commit

Permalink
Wrong length validation of event title field (#7841)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
holotsvan authored Nov 30, 2024
1 parent db6f12a commit ec6cc4f
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 1 deletion.
2 changes: 2 additions & 0 deletions core/src/main/java/greencity/controller/EventController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -51,6 +52,7 @@
import org.springframework.web.multipart.MultipartFile;
import static greencity.constant.SwaggerExampleModel.UPDATE_EVENT;

@Slf4j
@Validated
@RestController
@RequestMapping("/events")
Expand Down
6 changes: 6 additions & 0 deletions service-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,11 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.18.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
24 changes: 24 additions & 0 deletions service-api/src/main/java/greencity/annotations/DecodedSize.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,7 +22,7 @@
public class AddEventDtoRequest {
@NotEmpty
@NotBlank
@Size(min = 1, max = 70)
@DecodedSize(min = 1, max = 70)
private String title;

@NotEmpty
Expand Down
Original file line number Diff line number Diff line change
@@ -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<DecodedSize, String> {
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;
}
}
Original file line number Diff line number Diff line change
@@ -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&lt;&gt;&amp;', 0, 9, true",
"'a&lt;&gt;&amp;', 0, 3, false",
"'Hello &lt;world&gt;', 0, 20, true",
"'Hello &lt;world&gt;', 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));
}
}

0 comments on commit ec6cc4f

Please sign in to comment.