Merge pull request #8472 from YevhenBondarenko/kv-validator
kv validator improvements
This commit is contained in:
commit
b3e4e2adcb
@ -43,6 +43,7 @@ import org.thingsboard.server.common.msg.queue.TbCallback;
|
|||||||
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
|
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
|
||||||
import org.thingsboard.server.dao.attributes.AttributesService;
|
import org.thingsboard.server.dao.attributes.AttributesService;
|
||||||
import org.thingsboard.server.dao.timeseries.TimeseriesService;
|
import org.thingsboard.server.dao.timeseries.TimeseriesService;
|
||||||
|
import org.thingsboard.server.dao.util.KvUtils;
|
||||||
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
|
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
|
||||||
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
|
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
|
||||||
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
|
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
|
||||||
@ -134,6 +135,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
|
|||||||
checkInternalEntity(entityId);
|
checkInternalEntity(entityId);
|
||||||
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
|
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
|
||||||
if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
|
if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
|
||||||
|
KvUtils.validate(ts);
|
||||||
if (saveLatest) {
|
if (saveLatest) {
|
||||||
saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
|
saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -30,12 +30,10 @@ import java.util.Optional;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class NoXssValidator implements ConstraintValidator<NoXss, Object> {
|
public class NoXssValidator implements ConstraintValidator<NoXss, Object> {
|
||||||
private static final AntiSamy xssChecker = new AntiSamy();
|
private static final AntiSamy xssChecker = new AntiSamy();
|
||||||
private static Policy xssPolicy;
|
private static final Policy xssPolicy;
|
||||||
|
|
||||||
@Override
|
static {
|
||||||
public void initialize(NoXss constraintAnnotation) {
|
xssPolicy = Optional.ofNullable(NoXssValidator.class.getClassLoader().getResourceAsStream("xss-policy.xml"))
|
||||||
if (xssPolicy == null) {
|
|
||||||
xssPolicy = Optional.ofNullable(getClass().getClassLoader().getResourceAsStream("xss-policy.xml"))
|
|
||||||
.map(inputStream -> {
|
.map(inputStream -> {
|
||||||
try {
|
try {
|
||||||
return Policy.getInstance(inputStream);
|
return Policy.getInstance(inputStream);
|
||||||
@ -45,7 +43,6 @@ public class NoXssValidator implements ConstraintValidator<NoXss, Object> {
|
|||||||
})
|
})
|
||||||
.orElseThrow(() -> new IllegalStateException("XSS policy file not found"));
|
.orElseThrow(() -> new IllegalStateException("XSS policy file not found"));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
|
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
|
||||||
@ -55,10 +52,13 @@ public class NoXssValidator implements ConstraintValidator<NoXss, Object> {
|
|||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return isValid(stringValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValid(String stringValue) {
|
||||||
if (stringValue.isEmpty()) {
|
if (stringValue.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return xssChecker.scan(stringValue, xssPolicy).getNumberOfErrors() == 0;
|
return xssChecker.scan(stringValue, xssPolicy).getNumberOfErrors() == 0;
|
||||||
} catch (ScanException | PolicyException e) {
|
} catch (ScanException | PolicyException e) {
|
||||||
|
|||||||
@ -42,7 +42,6 @@ import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
|
|||||||
import org.thingsboard.server.dao.entityview.EntityViewService;
|
import org.thingsboard.server.dao.entityview.EntityViewService;
|
||||||
import org.thingsboard.server.dao.exception.IncorrectParameterException;
|
import org.thingsboard.server.dao.exception.IncorrectParameterException;
|
||||||
import org.thingsboard.server.dao.service.Validator;
|
import org.thingsboard.server.dao.service.Validator;
|
||||||
import org.thingsboard.server.dao.util.KvUtils;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -156,7 +155,6 @@ public class BaseTimeseriesService implements TimeseriesService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) {
|
public ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) {
|
||||||
KvUtils.validate(tsKvEntry);
|
|
||||||
validate(entityId);
|
validate(entityId);
|
||||||
List<ListenableFuture<Integer>> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY);
|
List<ListenableFuture<Integer>> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY);
|
||||||
saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, 0L);
|
saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, 0L);
|
||||||
@ -174,7 +172,6 @@ public class BaseTimeseriesService implements TimeseriesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture<Integer> doSave(TenantId tenantId, EntityId entityId, List<TsKvEntry> tsKvEntries, long ttl, boolean saveLatest) {
|
private ListenableFuture<Integer> doSave(TenantId tenantId, EntityId entityId, List<TsKvEntry> tsKvEntries, long ttl, boolean saveLatest) {
|
||||||
KvUtils.validate(tsKvEntries);
|
|
||||||
int inserts = saveLatest ? INSERTS_PER_ENTRY : INSERTS_PER_ENTRY_WITHOUT_LATEST;
|
int inserts = saveLatest ? INSERTS_PER_ENTRY : INSERTS_PER_ENTRY_WITHOUT_LATEST;
|
||||||
List<ListenableFuture<Integer>> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * inserts);
|
List<ListenableFuture<Integer>> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * inserts);
|
||||||
for (TsKvEntry tsKvEntry : tsKvEntries) {
|
for (TsKvEntry tsKvEntry : tsKvEntries) {
|
||||||
@ -189,7 +186,6 @@ public class BaseTimeseriesService implements TimeseriesService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<List<Void>> saveLatest(TenantId tenantId, EntityId entityId, List<TsKvEntry> tsKvEntries) {
|
public ListenableFuture<List<Void>> saveLatest(TenantId tenantId, EntityId entityId, List<TsKvEntry> tsKvEntries) {
|
||||||
KvUtils.validate(tsKvEntries);
|
|
||||||
List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size());
|
List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size());
|
||||||
for (TsKvEntry tsKvEntry : tsKvEntries) {
|
for (TsKvEntry tsKvEntry : tsKvEntries) {
|
||||||
futures.add(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry));
|
futures.add(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry));
|
||||||
|
|||||||
@ -15,13 +15,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.dao.util;
|
package org.thingsboard.server.dao.util;
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import org.thingsboard.server.common.data.kv.KvEntry;
|
import org.thingsboard.server.common.data.kv.KvEntry;
|
||||||
|
import org.thingsboard.server.dao.exception.DataValidationException;
|
||||||
import org.thingsboard.server.dao.exception.IncorrectParameterException;
|
import org.thingsboard.server.dao.exception.IncorrectParameterException;
|
||||||
import org.thingsboard.server.dao.service.ConstraintValidator;
|
import org.thingsboard.server.dao.service.NoXssValidator;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class KvUtils {
|
public class KvUtils {
|
||||||
|
|
||||||
|
private static final Cache<String, Boolean> validatedKeys;
|
||||||
|
|
||||||
|
static {
|
||||||
|
validatedKeys = Caffeine.newBuilder()
|
||||||
|
.weakKeys()
|
||||||
|
.expireAfterAccess(60, TimeUnit.MINUTES)
|
||||||
|
.maximumSize(100000).build();
|
||||||
|
}
|
||||||
|
|
||||||
public static void validate(List<? extends KvEntry> tsKvEntries) {
|
public static void validate(List<? extends KvEntry> tsKvEntries) {
|
||||||
tsKvEntries.forEach(KvUtils::validate);
|
tsKvEntries.forEach(KvUtils::validate);
|
||||||
}
|
}
|
||||||
@ -30,6 +44,24 @@ public class KvUtils {
|
|||||||
if (tsKvEntry == null) {
|
if (tsKvEntry == null) {
|
||||||
throw new IncorrectParameterException("Key value entry can't be null");
|
throw new IncorrectParameterException("Key value entry can't be null");
|
||||||
}
|
}
|
||||||
ConstraintValidator.validateFields(tsKvEntry);
|
|
||||||
|
String key = tsKvEntry.getKey();
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
throw new DataValidationException("Key can't be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validatedKeys.getIfPresent(key) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NoXssValidator.isValid(key)) {
|
||||||
|
throw new DataValidationException("Validation error: key is malformed");
|
||||||
|
}
|
||||||
|
if (key.length() > 255) {
|
||||||
|
throw new DataValidationException("Validation error: key length must be equal or less than 255");
|
||||||
|
}
|
||||||
|
|
||||||
|
validatedKeys.put(key, Boolean.TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user