From 797ffe6c509de6db1e95393f7327b12e0ea958e7 Mon Sep 17 00:00:00 2001
From: Machac
";
+ private static final String PAYLOAD_CODE_PRE = "Cell System.out.println()line1\nline2
";
+ private static final String PAYLOAD_PLAIN = "Hello plain text";
+
+ @Autowired
+ private TestHelper testHelper;
+
+ @Autowired
+ private IPetriNetService petriNetService;
+
+ @Autowired
+ private IWorkflowService workflowService;
+
+ @Autowired
+ private TaskRepository taskRepository;
+
+ @Autowired
+ private CaseRepository caseRepository;
+
+ @Autowired
+ private DataService dataService;
+
+ @Autowired
+ private SuperCreator superCreator;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @BeforeEach
+ void before() {
+ testHelper.truncateDbs();
+ }
+
+ @Test
+ void offModeShouldStoreRawScriptPayload() throws Exception {
+ assertStoredEquals("text_off", PAYLOAD_SCRIPT_BOLD, PAYLOAD_SCRIPT_BOLD);
+ }
+
+ @Test
+ void offModeShouldStoreRawImgOnErrorPayload() throws Exception {
+ assertStoredEquals("text_off", PAYLOAD_IMG_ONERROR, PAYLOAD_IMG_ONERROR);
+ }
+
+ @Test
+ void offModeShouldStoreRawJavascriptHrefPayload() throws Exception {
+ assertStoredEquals("text_off", PAYLOAD_JS_LINK, PAYLOAD_JS_LINK);
+ }
+
+ @Test
+ void offModeShouldKeepPlainTextUntouched() throws Exception {
+ assertStoredEquals("text_off", PAYLOAD_PLAIN, PAYLOAD_PLAIN);
+ }
+
+ @Test
+ void plainDefaultShouldStripHtmlFromScriptPayload() throws Exception {
+ assertStoredEquals("text_plain_default", PAYLOAD_SCRIPT_BOLD, "Hello");
+ }
+
+ @Test
+ void plainDefaultShouldRemoveImgPayload() throws Exception {
+ assertStoredEquals("text_plain_default", PAYLOAD_IMG_ONERROR, "");
+ }
+
+ @Test
+ void plainDefaultShouldKeepPlainTextUntouched() throws Exception {
+ assertStoredEquals("text_plain_default", PAYLOAD_PLAIN, PAYLOAD_PLAIN);
+ }
+
+ @Test
+ void plainTextSanitizeShouldStripHtml() throws Exception {
+ assertStoredEquals("text_plain_sanitize", PAYLOAD_SCRIPT_BOLD, "Hello");
+ }
+
+ @Test
+ void plainTextSanitizeShouldKeepPlainTextUntouched() throws Exception {
+ assertStoredEquals("text_plain_sanitize", "Hello world 123", "Hello world 123");
+ }
+
+ @Test
+ void plainTextSanitizeShouldRemoveImgTagAndKeepNoText() throws Exception {
+ assertStoredEquals("text_plain_sanitize", PAYLOAD_IMG_ONERROR, "");
+ }
+
+ @Test
+ void plainTextSanitizeShouldRemoveImgTagAndKeepText() throws Exception {
+ assertStoredEquals("text_plain_sanitize", "test", "test");
+ }
+
+ @Test
+ void plainTextRejectShouldThrowExceptionForBoldHtml() throws Exception {
+ assertRejected("text_plain_reject", "Hello");
+ }
+
+ @Test
+ void plainTextRejectShouldThrowExceptionForScriptPayload() throws Exception {
+ assertRejected("text_plain_reject", PAYLOAD_SCRIPT_BOLD);
+ }
+
+ @Test
+ void plainTextRejectShouldThrowExceptionForImgPayload() throws Exception {
+ assertRejected("text_plain_reject", PAYLOAD_IMG_ONERROR);
+ }
+
+ @Test
+ void plainTextRejectShouldNotThrowWhenValueIsAlreadyClean() throws Exception {
+ assertStoredEquals("text_plain_reject", "Hello clean", "Hello clean");
+ }
+
+ @Test
+ void safeHtmlShouldKeepSafeFormattingAndRemoveScript() throws Exception {
+ assertStoredEquals("text_safe_html", PAYLOAD_SCRIPT_BOLD, "Hello");
+ }
+
+ @Test
+ void safeHtmlShouldRemoveImgOnError() throws Exception {
+ Case useCase = createCase();
+ Task task = findTask(useCase);
+
+ dataService.setData(task, textValue("text_safe_html", PAYLOAD_IMG_ONERROR));
+
+ String value = getStringValue(getCase(useCase.getStringId()), "text_safe_html");
+ assertFalse(value.contains("
Hello
Hello
Hello
")); + assertTrue(value.contains("| Cell |
"));
+ assertTrue(value.contains("
| Cell |
| Cell |
"));
+ assertTrue(value.contains(" dataService.setData(task, textValue(fieldId, input))
+ );
+
+ assertTrue(ex.getMessage().contains("unsafe content"));
+ }
+
+ private Case createCase() throws IOException, MissingPetriNetMetaDataException {
+ ImportPetriNetEventOutcome importOutcome = petriNetService.importPetriNet(
+ new FileInputStream(XML_PATH),
+ VersionType.MAJOR,
+ superCreator.getLoggedSuper()
+ );
+
+ PetriNet net = importOutcome.getNet();
+ assertNotNull(net);
+
+ CreateCaseEventOutcome caseOutcome = workflowService.createCase(
+ net.getStringId(),
+ "Sanitization test case",
+ "color",
+ superCreator.getLoggedSuper()
+ );
+
+ assertNotNull(caseOutcome);
+ assertNotNull(caseOutcome.getCase());
+ return caseOutcome.getCase();
+ }
+
+ private Task findTask(Case useCase) {
+ return taskRepository.findAll()
+ .stream()
+ .filter(task -> useCase.getStringId().equals(task.getCaseId()))
+ .filter(task -> task.getTitle() != null && TASK_TITLE.equals(task.getTitle().getDefaultValue()))
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("Task not found"));
+ }
+
+ private Case getCase(String caseId) {
+ return caseRepository.findById(caseId)
+ .orElseThrow(() -> new AssertionError("Case not found"));
+ }
+
+ private String getStringValue(Case useCase, String fieldId) {
+ DataField dataField = useCase.getDataField(fieldId);
+ assertNotNull(dataField, "Field [" + fieldId + "] not found");
+ return (String) dataField.getValue();
+ }
+
+ private ObjectNode textValue(String fieldId, String value) {
+ ObjectNode root = objectMapper.createObjectNode();
+ ObjectNode fieldNode = objectMapper.createObjectNode();
+ fieldNode.put("type", "text");
+ fieldNode.put("value", value);
+ root.set(fieldId, fieldNode);
+ return root;
+ }
+
+ private ObjectNode textValueWithNull(String fieldId) {
+ ObjectNode root = objectMapper.createObjectNode();
+ ObjectNode fieldNode = objectMapper.createObjectNode();
+ fieldNode.put("type", "text");
+ fieldNode.putNull("value");
+ root.set(fieldId, fieldNode);
+ return root;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/netgrif/application/engine/workflow/service/FieldSanitizationServiceTest.java b/src/test/java/com/netgrif/application/engine/workflow/service/FieldSanitizationServiceTest.java
new file mode 100644
index 00000000000..fda41288c40
--- /dev/null
+++ b/src/test/java/com/netgrif/application/engine/workflow/service/FieldSanitizationServiceTest.java
@@ -0,0 +1,332 @@
+package com.netgrif.application.engine.workflow.service;
+
+import com.netgrif.application.engine.workflow.service.sanitization.FieldSanitizationService;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import com.netgrif.application.engine.petrinet.domain.Component;
+import com.netgrif.application.engine.petrinet.domain.dataset.TextField;
+import org.mockito.Mockito;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+
+@Slf4j
+@SpringBootTest
+@ActiveProfiles({"test"})
+@ExtendWith(SpringExtension.class)
+public class FieldSanitizationServiceTest {
+
+ private FieldSanitizationService service;
+
+ @BeforeEach
+ void setUp() {
+ service = new FieldSanitizationService();
+ }
+
+ @Test
+ void shouldReturnNullWhenValueIsNull() {
+ TextField field = new TextField();
+ String result = service.sanitize(null, field);
+ assertNull(result);
+ }
+
+ @Test
+ void shouldReturnSameValueWhenInputIsPlainText() {
+ TextField field = new TextField();
+ String input = "Hello world 123";
+ String result = service.sanitize(input, field);
+ assertEquals("Hello world 123", result);
+ }
+
+ @Test
+ void shouldStripHtmlTagsWhenDefaultModeIsPlainText() {
+ TextField field = new TextField();
+ String input = "Hello world";
+ String result = service.sanitize(input, field);
+ assertEquals("Hello world", result);
+ assertNotEquals(input, result);
+ }
+
+ @Test
+ void shouldStripDangerousAttributesWhenDefaultModeIsPlainText() {
+ TextField field = new TextField();
+ String input = "
test";
+ String result = service.sanitize(input, field);
+ assertEquals("test", result);
+ }
+
+ @Test
+ void shouldNotSanitizeWhenModeIsOff() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+ when(component.getProperties()).thenReturn(
+ Map.of(FieldSanitizationService.SANITIZATION_MODE_KEY, "OFF")
+ );
+ field.setComponent(component);
+ String input = "Hello";
+ String result = service.sanitize(input, field);
+ assertEquals(input, result);
+ }
+
+ @Test
+ void shouldResolveModeCaseInsensitively() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+
+ when(component.getProperties()).thenReturn(
+ Map.of(FieldSanitizationService.SANITIZATION_MODE_KEY, "oFf")
+ );
+
+ field.setComponent(component);
+
+ String input = "Hello";
+ String result = service.sanitize(input, field);
+
+ assertEquals(input, result);
+ }
+
+ @Test
+ void shouldUsePlainTextModeWhenComponentIsNull() {
+ TextField field = new TextField();
+ field.setComponent(null);
+ String input = "Hello";
+ String result = service.sanitize(input, field);
+ assertEquals("Hello", result);
+ }
+
+ @Test
+ void shouldUsePlainTextModeWhenComponentPropertiesAreNull() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+ when(component.getProperties()).thenReturn(null);
+ field.setComponent(component);
+ String input = "Hello";
+ String result = service.sanitize(input, field);
+ assertEquals("Hello", result);
+ }
+
+ @Test
+ void shouldUsePlainTextModeWhenSanitizationModePropertyIsMissing() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+ when(component.getProperties()).thenReturn(Map.of("otherKey", "true"));
+ field.setComponent(component);
+ String input = "text";
+ String result = service.sanitize(input, field);
+ assertEquals("text", result);
+ }
+
+ @Test
+ void shouldKeepSafeFormattingWhenModeIsSafeHtml() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+
+ when(component.getProperties()).thenReturn(
+ Map.of(FieldSanitizationService.SANITIZATION_MODE_KEY, "SAFE_HTML")
+ );
+
+ field.setComponent(component);
+
+ String input = "Hello world";
+ String result = service.sanitize(input, field);
+
+ assertEquals("Hello world", result);
+ }
+
+ @Test
+ void shouldRemoveScriptWhenModeIsSafeHtml() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+
+ when(component.getProperties()).thenReturn(
+ Map.of(FieldSanitizationService.SANITIZATION_MODE_KEY, "SAFE_HTML")
+ );
+
+ field.setComponent(component);
+
+ String input = "Hello";
+ String result = service.sanitize(input, field);
+
+ assertEquals("Hello", result);
+ }
+
+ @Test
+ void shouldRemoveJavascriptHrefWhenModeIsSafeHtml() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+
+ when(component.getProperties()).thenReturn(
+ Map.of(FieldSanitizationService.SANITIZATION_MODE_KEY, "SAFE_HTML")
+ );
+
+ field.setComponent(component);
+
+ String input = "click";
+ String result = service.sanitize(input, field);
+
+ assertNotEquals(input, result);
+ assertFalse(result.contains("javascript:"));
+ assertTrue(result.contains("click"));
+ }
+
+
+
+ @Test
+ void shouldRemoveImgTagWhenModeIsSafeHtml() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+
+ when(component.getProperties()).thenReturn(
+ Map.of(FieldSanitizationService.SANITIZATION_MODE_KEY, "SAFE_HTML")
+ );
+
+ field.setComponent(component);
+
+ String input = "
test";
+ String result = service.sanitize(input, field);
+
+ assertEquals("test", result);
+ }
+
+ @Test
+ void shouldThrowExceptionWhenActionIsRejectAndPlainTextModeChangesContent() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+
+ when(component.getProperties()).thenReturn(
+ Map.of(
+ FieldSanitizationService.SANITIZATION_MODE_KEY, "PLAIN_TEXT",
+ FieldSanitizationService.SANITIZATION_ACTION_KEY, "REJECT"
+ )
+ );
+ field.setComponent(component);
+ String input = "Hello";
+
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> service.sanitize(input, field)
+ );
+
+ assertEquals("Unsafe content detected in field [null]", exception.getMessage());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenActionIsRejectAndSafeHtmlModeChangesContent() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+
+ when(component.getProperties()).thenReturn(
+ Map.of(
+ FieldSanitizationService.SANITIZATION_MODE_KEY, "SAFE_HTML",
+ FieldSanitizationService.SANITIZATION_ACTION_KEY, "REJECT"
+ )
+ );
+
+ field.setComponent(component);
+
+ String input = "Hello";
+
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> service.sanitize(input, field)
+ );
+
+ assertEquals("Unsafe content detected in field [null]", exception.getMessage());
+ }
+
+ @Test
+ void shouldNotThrowExceptionWhenActionIsSanitize() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+ when(component.getProperties()).thenReturn(
+ Map.of(
+ FieldSanitizationService.SANITIZATION_MODE_KEY, "PLAIN_TEXT",
+ FieldSanitizationService.SANITIZATION_ACTION_KEY, "SANITIZE"
+ )
+ );
+ field.setComponent(component);
+ String input = "Hello";
+ String result = service.sanitize(input, field);
+ assertEquals("Hello", result);
+ }
+
+ @Test
+ void shouldUseSanitizeActionWhenActionPropertyIsMissing() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+
+ when(component.getProperties()).thenReturn(
+ Map.of(FieldSanitizationService.SANITIZATION_MODE_KEY, "PLAIN_TEXT")
+ );
+
+ field.setComponent(component);
+ String input = "Hello";
+ String result = service.sanitize(input, field);
+ assertEquals("Hello", result);
+ }
+
+ @Test
+ void shouldResolveActionCaseInsensitively() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+ when(component.getProperties()).thenReturn(
+ Map.of(
+ FieldSanitizationService.SANITIZATION_MODE_KEY, "PLAIN_TEXT",
+ FieldSanitizationService.SANITIZATION_ACTION_KEY, "rEjEcT"
+ )
+ );
+ field.setComponent(component);
+
+ String input = "Hello";
+
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> service.sanitize(input, field)
+ );
+
+ assertEquals("Unsafe content detected in field [null]", exception.getMessage());
+ }
+
+ @Test
+ void shouldNotThrowWhenActionIsRejectButContentIsAlreadyClean() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+ when(component.getProperties()).thenReturn(
+ Map.of(
+ FieldSanitizationService.SANITIZATION_MODE_KEY, "PLAIN_TEXT",
+ FieldSanitizationService.SANITIZATION_ACTION_KEY, "REJECT"
+ )
+ );
+ field.setComponent(component);
+
+ String input = "Hello world";
+ String result = service.sanitize(input, field);
+
+ assertEquals("Hello world", result);
+ }
+
+ @Test
+ void shouldIgnoreRejectActionWhenModeIsOff() {
+ TextField field = new TextField();
+ Component component = Mockito.mock(Component.class);
+ when(component.getProperties()).thenReturn(
+ Map.of(
+ FieldSanitizationService.SANITIZATION_MODE_KEY, "OFF",
+ FieldSanitizationService.SANITIZATION_ACTION_KEY, "REJECT"
+ )
+ );
+ field.setComponent(component);
+
+ String input = "Hello";
+ String result = service.sanitize(input, field);
+
+ assertEquals(input, result);
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/data_text_validation.xml b/src/test/resources/data_text_validation.xml
index cf90b3a361e..0c2c165283f 100644
--- a/src/test/resources/data_text_validation.xml
+++ b/src/test/resources/data_text_validation.xml
@@ -1,4 +1,4 @@
-F
+
test";
- String result = service.sanitize(input, field);
- assertEquals("test", result);
- }
-
@Test
void shouldNotSanitizeWhenModeIsOff() {
TextField field = new TextField();
@@ -93,34 +76,34 @@ void shouldResolveModeCaseInsensitively() {
}
@Test
- void shouldUsePlainTextModeWhenComponentIsNull() {
+ void shouldUseOffModeWhenComponentIsNull() {
TextField field = new TextField();
field.setComponent(null);
String input = "Hello";
String result = service.sanitize(input, field);
- assertEquals("Hello", result);
+ assertEquals("Hello", result);
}
@Test
- void shouldUsePlainTextModeWhenComponentPropertiesAreNull() {
+ void shouldUseOffModeWhenComponentPropertiesAreNull() {
TextField field = new TextField();
Component component = Mockito.mock(Component.class);
when(component.getProperties()).thenReturn(null);
field.setComponent(component);
String input = "Hello";
String result = service.sanitize(input, field);
- assertEquals("Hello", result);
+ assertEquals("Hello", result);
}
@Test
- void shouldUsePlainTextModeWhenSanitizationModePropertyIsMissing() {
+ void shouldUseOffModeWhenSanitizationModePropertyIsMissing() {
TextField field = new TextField();
Component component = Mockito.mock(Component.class);
when(component.getProperties()).thenReturn(Map.of("otherKey", "true"));
field.setComponent(component);
String input = "text";
String result = service.sanitize(input, field);
- assertEquals("text", result);
+ assertEquals("text", result);
}
@Test