Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.netgrif</groupId>
<artifactId>application-engine</artifactId>
<version>6.4.2</version>
<version>6.4.3-SNAPSHOT</version>
<packaging>jar</packaging>

<name>NETGRIF Application Engine</name>
Expand Down Expand Up @@ -233,6 +233,11 @@
<version>0.9.1</version>
</dependency>

<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20260313.1</version>
</dependency>

<!-- Session -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.netgrif.application.engine.petrinet.domain.Component;
import com.netgrif.application.engine.petrinet.domain.*;
import com.netgrif.application.engine.petrinet.domain.dataset.*;
import com.netgrif.application.engine.petrinet.domain.dataset.TextField;
import com.netgrif.application.engine.petrinet.domain.dataset.logic.ChangedField;
import com.netgrif.application.engine.petrinet.domain.dataset.logic.FieldBehavior;
import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.FieldActionsRunner;
Expand All @@ -32,10 +33,7 @@
import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.GetDataEventOutcome;
import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.GetDataGroupsEventOutcome;
import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.SetDataEventOutcome;
import com.netgrif.application.engine.workflow.service.interfaces.IDataService;
import com.netgrif.application.engine.workflow.service.interfaces.IEventService;
import com.netgrif.application.engine.workflow.service.interfaces.ITaskService;
import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService;
import com.netgrif.application.engine.workflow.service.interfaces.*;
import com.netgrif.application.engine.workflow.web.responsebodies.DataFieldsResource;
import com.netgrif.application.engine.workflow.web.responsebodies.LocalisedField;
import com.querydsl.core.types.Predicate;
Expand Down Expand Up @@ -100,6 +98,9 @@ public class DataService implements IDataService {
@Autowired
protected IValidationService validation;

@Autowired
protected IFieldSanitizationService sanitizationService;

@Autowired
private StorageResolverService storageResolverService;

Expand Down Expand Up @@ -279,6 +280,8 @@ public SetDataEventOutcome setData(Task task, ObjectNode values, Map<String, Str
ChangedField changedField = new ChangedField();
changedField.setId(fieldId);
Object newValue = parseFieldsValues(entry.getValue(), dataField, task.getStringId());
newValue = sanitizeValueIfNeeded(useCase, task, field, newValue);
Comment thread
Retoocs marked this conversation as resolved.

if (entry.getValue().has("value") || getFieldTypeFromNode((ObjectNode) entry.getValue()).equals("button")) {
dataField.setValue(newValue);
changedField.addAttribute("value", newValue);
Expand Down Expand Up @@ -693,7 +696,7 @@ public SetDataEventOutcome saveFiles(String taskId, String fieldId, MultipartFil

private List<EventOutcome> getChangedFieldByFileFieldContainer(String fieldId, Task referencingTask, Case useCase, Map<String, String> params) {
List<EventOutcome> outcomes = new ArrayList<>();
outcomes.addAll( resolveDataEvents(useCase.getPetriNet().getField(fieldId).get(), DataEventType.SET,
outcomes.addAll(resolveDataEvents(useCase.getPetriNet().getField(fieldId).get(), DataEventType.SET,
EventPhase.PRE, useCase, referencingTask, params));
outcomes.addAll(resolveDataEvents(useCase.getPetriNet().getField(fieldId).get(), DataEventType.SET,
EventPhase.POST, useCase, referencingTask, params));
Expand Down Expand Up @@ -1157,4 +1160,37 @@ public void validateCaseRefValue(List<String> value, List<String> allowedNets) t
// }
// }

}
protected Object sanitizeValueIfNeeded(Case useCase, Task task, Field<?> field, Object value) {
if (!(value instanceof String) || !(field instanceof TextField)) {
return value;
}

return sanitizationService.sanitize((String) value, resolveFieldForSanitization(useCase, task, field));
}

protected Field<?> resolveFieldForSanitization(Case useCase, Task task, Field<?> originalField) {
Field<?> field = originalField.clone();
field.setComponent(resolveSanitizationComponent(useCase, task, originalField));
return field;
}

protected Component resolveSanitizationComponent(Case useCase, Task task, Field<?> field) {
DataField dataField = useCase.getDataField(field.getStringId());
if (dataField == null) {
return field.getComponent();
}

if (task != null && dataField.getDataRefComponents() != null) {
Component dataRefComponent = dataField.getDataRefComponents().get(task.getTransitionId());
if (dataRefComponent != null) {
return dataRefComponent;
}
}

if (dataField.getComponent() != null) {
return dataField.getComponent();
}

return field.getComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.netgrif.application.engine.workflow.service.interfaces;


import com.netgrif.application.engine.petrinet.domain.dataset.Field;

public interface IFieldSanitizationService {

String sanitize(String value, Field<?> field);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.netgrif.application.engine.workflow.service.sanitization;

import com.netgrif.application.engine.petrinet.domain.Component;
import com.netgrif.application.engine.petrinet.domain.dataset.Field;
import com.netgrif.application.engine.workflow.service.interfaces.IFieldSanitizationService;
import lombok.extern.slf4j.Slf4j;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;
import org.springframework.stereotype.Service;

import java.util.Map;

@Slf4j
@Service
public class FieldSanitizationService implements IFieldSanitizationService {

public static final String SANITIZATION_MODE_KEY = "sanitizationMode";
public static final String SANITIZATION_ACTION_KEY = "sanitizationAction";

private static final PolicyFactory PLAIN_TEXT_POLICY =
new HtmlPolicyBuilder().toFactory();

private static final PolicyFactory SAFE_HTML_BASIC_POLICY =
Comment thread
Retoocs marked this conversation as resolved.
new HtmlPolicyBuilder()
.allowElements("b", "i", "u", "em", "strong", "s", "span", "br")
.toFactory();

private static final PolicyFactory SAFE_HTML_LINKS_ONLY_POLICY =
new HtmlPolicyBuilder()
.allowElements("a")
.allowAttributes("href").onElements("a")
.allowUrlProtocols("http", "https", "mailto")
.requireRelNofollowOnLinks()
.toFactory();

private static final PolicyFactory SAFE_HTML_NO_LINKS_POLICY =
Sanitizers.FORMATTING.and(Sanitizers.BLOCKS);

private static final PolicyFactory SAFE_HTML_POLICY =
Sanitizers.FORMATTING
.and(Sanitizers.LINKS)
.and(Sanitizers.BLOCKS);

private static final PolicyFactory SAFE_HTML_RELAXED_POLICY =
Sanitizers.FORMATTING
.and(Sanitizers.LINKS)
.and(Sanitizers.BLOCKS)
.and(Sanitizers.TABLES)
.and(new HtmlPolicyBuilder()
.allowElements("code", "pre", "span")
.toFactory());

private static final PolicyFactory DISABLE_JAVASCRIPT_POLICY =
new HtmlPolicyBuilder()
.allowElements(
"a", "abbr", "b", "blockquote", "br", "caption", "code", "col", "colgroup",
"dd", "del", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6",
"hr", "i", "img", "ins", "li", "ol", "p", "pre", "s", "small", "span",
"strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead",
"tr", "u", "ul"
)
.allowWithoutAttributes(
"abbr", "b", "blockquote", "br", "caption", "code", "dd", "del", "div", "dl",
"dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "ins",
"li", "ol", "p", "pre", "s", "small", "span", "strong", "sub", "sup",
"table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul"
)
.allowAttributes("href").onElements("a")
.allowAttributes("src", "alt", "title").onElements("img")
.allowAttributes("colspan", "rowspan").onElements("td", "th")
.allowUrlProtocols("http", "https", "mailto")
.requireRelNofollowOnLinks()
.toFactory();

@Override
public String sanitize(String value, Field<?> field) {
if (value == null) {
return null;
}

Component component = field.getComponent();
SanitizationMode mode = SanitizationMode.from(getProperty(component, SANITIZATION_MODE_KEY));
SanitizationAction action = SanitizationAction.from(getProperty(component, SANITIZATION_ACTION_KEY));

if (mode == SanitizationMode.OFF) {
log.debug("Sanitization mode OFF for field [{}]", field.getStringId());
return value;
}

String sanitized = resolvePolicy(mode).sanitize(value);

if (!value.equals(sanitized)) {
log.warn("Content was modified by sanitizer for field [{}] (mode={}, action={})",
field.getStringId(), mode, action);
if (action == SanitizationAction.REJECT) {
throw new IllegalArgumentException(
"Field [" + field.getStringId() + "] contains unsafe content " +
"and the configured action is REJECT."
);
}
}

return sanitized;
}

protected PolicyFactory resolvePolicy(SanitizationMode mode) {
switch (mode) {
case SAFE_HTML:
return SAFE_HTML_POLICY;
case SAFE_HTML_BASIC:
return SAFE_HTML_BASIC_POLICY;
case SAFE_HTML_LINKS_ONLY:
return SAFE_HTML_LINKS_ONLY_POLICY;
case SAFE_HTML_NO_LINKS:
return SAFE_HTML_NO_LINKS_POLICY;
case SAFE_HTML_RELAXED:
return SAFE_HTML_RELAXED_POLICY;
case PLAIN_TEXT:
return PLAIN_TEXT_POLICY;
case DISABLE_JAVASCRIPT:
default:
return DISABLE_JAVASCRIPT_POLICY;
}
}

protected String getProperty(Component component, String key) {
if (component == null) {
return null;
}

Map<String, String> properties = component.getProperties();
if (properties == null) {
return null;
}

return properties.get(key);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.netgrif.application.engine.workflow.service.sanitization;

public enum SanitizationAction {
SANITIZE,
REJECT;

static SanitizationAction from(String value) {
if (value == null || value.isBlank()) {
return SANITIZE;
}

for (SanitizationAction action : values()) {
if (action.name().equalsIgnoreCase(value)) {
return action;
}
}

return SANITIZE;
Comment thread
machacjozef marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.netgrif.application.engine.workflow.service.sanitization;

public enum SanitizationMode {

OFF,
PLAIN_TEXT,
SAFE_HTML,
SAFE_HTML_BASIC,
SAFE_HTML_LINKS_ONLY,
SAFE_HTML_NO_LINKS,
DISABLE_JAVASCRIPT,
SAFE_HTML_RELAXED;

public static SanitizationMode from(String value) {
if (value == null || value.isBlank()) {
return OFF;
}

for (SanitizationMode mode : values()) {
if (mode.name().equalsIgnoreCase(value)) {
return mode;
}
}

return OFF;
}
}
Loading
Loading