Commit 71bed873b01bffc8107c6d6bdd68f9a7fbd64176

Authored by Andrew Shvayka
Committed by GitHub
2 parents de1a56a4 fe5215c7

Merge pull request #5166 from ViacheslavKlimov/fields-validation

[3.3.2] Fields length validation
Showing 81 changed files with 662 additions and 141 deletions
... ... @@ -76,6 +76,7 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
76 76 import org.thingsboard.server.common.data.kv.TsKvEntry;
77 77 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
78 78 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  79 +import org.thingsboard.server.dao.service.ConstraintValidator;
79 80 import org.thingsboard.server.dao.timeseries.TimeseriesService;
80 81 import org.thingsboard.server.queue.util.TbCoreComponent;
81 82 import org.thingsboard.server.service.security.AccessValidator;
... ... @@ -184,7 +185,11 @@ public class TelemetryController extends BaseController {
184 185 public DeferredResult<ResponseEntity> getAttributeKeys(
185 186 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
186 187 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
187   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback);
  188 + try {
  189 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback);
  190 + } catch (Exception e) {
  191 + throw handleException(e);
  192 + }
188 193 }
189 194
190 195 @ApiOperation(value = "Get all attribute keys by scope (getAttributeKeysByScope)",
... ... @@ -201,8 +206,12 @@ public class TelemetryController extends BaseController {
201 206 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
202 207 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
203 208 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException {
204   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
205   - (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope));
  209 + try {
  210 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
  211 + (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope));
  212 + } catch (Exception e) {
  213 + throw handleException(e);
  214 + }
206 215 }
207 216
208 217 @ApiOperation(value = "Get attributes (getAttributes)",
... ... @@ -220,9 +229,13 @@ public class TelemetryController extends BaseController {
220 229 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
221 230 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
222 231 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
223   - SecurityUser user = getCurrentUser();
224   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
225   - (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
  232 + try {
  233 + SecurityUser user = getCurrentUser();
  234 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
  235 + (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
  236 + } catch (Exception e) {
  237 + throw handleException(e);
  238 + }
226 239 }
227 240
228 241
... ... @@ -244,9 +257,13 @@ public class TelemetryController extends BaseController {
244 257 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
245 258 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
246 259 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
247   - SecurityUser user = getCurrentUser();
248   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
249   - (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr));
  260 + try {
  261 + SecurityUser user = getCurrentUser();
  262 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
  263 + (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr));
  264 + } catch (Exception e) {
  265 + throw handleException(e);
  266 + }
250 267 }
251 268
252 269 @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)",
... ... @@ -259,8 +276,12 @@ public class TelemetryController extends BaseController {
259 276 public DeferredResult<ResponseEntity> getTimeseriesKeys(
260 277 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
261 278 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
262   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
263   - (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor()));
  279 + try {
  280 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
  281 + (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor()));
  282 + } catch (Exception e) {
  283 + throw handleException(e);
  284 + }
264 285 }
265 286
266 287 @ApiOperation(value = "Get latest time-series value (getLatestTimeseries)",
... ... @@ -285,9 +306,13 @@ public class TelemetryController extends BaseController {
285 306 @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr,
286 307 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION)
287 308 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
288   - SecurityUser user = getCurrentUser();
289   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
290   - (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes));
  309 + try {
  310 + SecurityUser user = getCurrentUser();
  311 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
  312 + (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes));
  313 + } catch (Exception e) {
  314 + throw handleException(e);
  315 + }
291 316 }
292 317
293 318 @ApiOperation(value = "Get time-series data (getTimeseries)",
... ... @@ -324,15 +349,19 @@ public class TelemetryController extends BaseController {
324 349 @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy,
325 350 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION)
326 351 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
327   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
328   - (result, tenantId, entityId) -> {
329   - // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
330   - Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
331   - List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
332   - .collect(Collectors.toList());
333   -
334   - Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
335   - });
  352 + try {
  353 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
  354 + (result, tenantId, entityId) -> {
  355 + // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
  356 + Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
  357 + List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
  358 + .collect(Collectors.toList());
  359 +
  360 + Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
  361 + });
  362 + } catch (Exception e) {
  363 + throw handleException(e);
  364 + }
336 365 }
337 366
338 367 @ApiOperation(value = "Save device attributes (saveDeviceAttributes)",
... ... @@ -356,8 +385,12 @@ public class TelemetryController extends BaseController {
356 385 @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
357 386 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
358 387 @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
359   - EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
360   - return saveAttributes(getTenantId(), entityId, scope, request);
  388 + try {
  389 + EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
  390 + return saveAttributes(getTenantId(), entityId, scope, request);
  391 + } catch (Exception e) {
  392 + throw handleException(e);
  393 + }
361 394 }
362 395
363 396 @ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)",
... ... @@ -380,8 +413,12 @@ public class TelemetryController extends BaseController {
380 413 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
381 414 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
382 415 @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
383   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
384   - return saveAttributes(getTenantId(), entityId, scope, request);
  416 + try {
  417 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  418 + return saveAttributes(getTenantId(), entityId, scope, request);
  419 + } catch (Exception e) {
  420 + throw handleException(e);
  421 + }
385 422 }
386 423
387 424 @ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)",
... ... @@ -404,8 +441,12 @@ public class TelemetryController extends BaseController {
404 441 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
405 442 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
406 443 @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
407   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
408   - return saveAttributes(getTenantId(), entityId, scope, request);
  444 + try {
  445 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  446 + return saveAttributes(getTenantId(), entityId, scope, request);
  447 + } catch (Exception e) {
  448 + throw handleException(e);
  449 + }
409 450 }
410 451
411 452
... ... @@ -429,8 +470,12 @@ public class TelemetryController extends BaseController {
429 470 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
430 471 @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope,
431 472 @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
432   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
433   - return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
  473 + try {
  474 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  475 + return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
  476 + } catch (Exception e) {
  477 + throw handleException(e);
  478 + }
434 479 }
435 480
436 481 @ApiOperation(value = "Save or update time-series data with TTL (saveEntityTelemetryWithTTL)",
... ... @@ -455,8 +500,12 @@ public class TelemetryController extends BaseController {
455 500 @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope,
456 501 @ApiParam(value = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl,
457 502 @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
458   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
459   - return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
  503 + try {
  504 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  505 + return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
  506 + } catch (Exception e) {
  507 + throw handleException(e);
  508 + }
460 509 }
461 510
462 511 @ApiOperation(value = "Delete entity time-series data (deleteEntityTimeseries)",
... ... @@ -489,8 +538,12 @@ public class TelemetryController extends BaseController {
489 538 @RequestParam(name = "endTs", required = false) Long endTs,
490 539 @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.")
491 540 @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException {
492   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
493   - return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted);
  541 + try {
  542 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  543 + return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted);
  544 + } catch (Exception e) {
  545 + throw handleException(e);
  546 + }
494 547 }
495 548
496 549 private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys,
... ... @@ -556,8 +609,12 @@ public class TelemetryController extends BaseController {
556 609 @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
557 610 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
558 611 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
559   - EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
560   - return deleteAttributes(entityId, scope, keysStr);
  612 + try {
  613 + EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
  614 + return deleteAttributes(entityId, scope, keysStr);
  615 + } catch (Exception e) {
  616 + throw handleException(e);
  617 + }
561 618 }
562 619
563 620 @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)",
... ... @@ -580,8 +637,12 @@ public class TelemetryController extends BaseController {
580 637 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
581 638 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
582 639 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
583   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
584   - return deleteAttributes(entityId, scope, keysStr);
  640 + try {
  641 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  642 + return deleteAttributes(entityId, scope, keysStr);
  643 + } catch (Exception e) {
  644 + throw handleException(e);
  645 + }
585 646 }
586 647
587 648 private DeferredResult<ResponseEntity> deleteAttributes(EntityId entityIdSrc, String scope, String keysStr) throws ThingsboardException {
... ... @@ -627,6 +688,7 @@ public class TelemetryController extends BaseController {
627 688 }
628 689 if (json.isObject()) {
629 690 List<AttributeKvEntry> attributes = extractRequestAttributes(json);
  691 + attributes.forEach(ConstraintValidator::validateFields);
630 692 if (attributes.isEmpty()) {
631 693 return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
632 694 }
... ...
... ... @@ -100,6 +100,20 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
100 100 }
101 101
102 102 @Test
  103 + public void testSaveAssetWithViolationOfLengthValidation() throws Exception {
  104 + Asset asset = new Asset();
  105 + asset.setName(RandomStringUtils.randomAlphabetic(300));
  106 + asset.setType("default");
  107 + doPost("/api/asset", asset).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
  108 + asset.setName("Normal name");
  109 + asset.setType(RandomStringUtils.randomAlphabetic(300));
  110 + doPost("/api/asset", asset).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
  111 + asset.setType("default");
  112 + asset.setLabel(RandomStringUtils.randomAlphabetic(300));
  113 + doPost("/api/asset", asset).andExpect(statusReason(containsString("length of label must be equal or less than 255")));
  114 + }
  115 +
  116 + @Test
103 117 public void testUpdateAssetFromDifferentTenant() throws Exception {
104 118 Asset asset = new Asset();
105 119 asset.setName("My asset");
... ...
... ... @@ -91,6 +91,28 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
91 91 }
92 92
93 93 @Test
  94 + public void testSaveCustomerWithViolationOfValidation() throws Exception {
  95 + Customer customer = new Customer();
  96 + customer.setTitle(RandomStringUtils.randomAlphabetic(300));
  97 + doPost("/api/customer", customer).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
  98 + customer.setTitle("Normal title");
  99 + customer.setCity(RandomStringUtils.randomAlphabetic(300));
  100 + doPost("/api/customer", customer).andExpect(statusReason(containsString("length of city must be equal or less than 255")));
  101 + customer.setCity("Normal city");
  102 + customer.setCountry(RandomStringUtils.randomAlphabetic(300));
  103 + doPost("/api/customer", customer).andExpect(statusReason(containsString("length of country must be equal or less than 255")));
  104 + customer.setCountry("Ukraine");
  105 + customer.setPhone(RandomStringUtils.randomAlphabetic(300));
  106 + doPost("/api/customer", customer).andExpect(statusReason(containsString("length of phone must be equal or less than 255")));
  107 + customer.setPhone("+3892555554512");
  108 + customer.setState(RandomStringUtils.randomAlphabetic(300));
  109 + doPost("/api/customer", customer).andExpect(statusReason(containsString("length of state must be equal or less than 255")));
  110 + customer.setState("Normal state");
  111 + customer.setZip(RandomStringUtils.randomAlphabetic(300));
  112 + doPost("/api/customer", customer).andExpect(statusReason(containsString("length of zip or postal code must be equal or less than 255")));
  113 + }
  114 +
  115 + @Test
94 116 public void testUpdateCustomerFromDifferentTenant() throws Exception {
95 117 Customer customer = new Customer();
96 118 customer.setTitle("My customer");
... ...
... ... @@ -94,6 +94,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
94 94 }
95 95
96 96 @Test
  97 + public void testSaveDashboardInfoWithViolationOfValidation() throws Exception {
  98 + Dashboard dashboard = new Dashboard();
  99 + dashboard.setTitle(RandomStringUtils.randomAlphabetic(300));
  100 + doPost("/api/dashboard", dashboard).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
  101 + }
  102 +
  103 + @Test
97 104 public void testUpdateDashboardFromDifferentTenant() throws Exception {
98 105 Dashboard dashboard = new Dashboard();
99 106 dashboard.setTitle("My dashboard");
... ...
... ... @@ -115,6 +115,20 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
115 115 }
116 116
117 117 @Test
  118 + public void saveDeviceWithViolationOfValidation() throws Exception {
  119 + Device device = new Device();
  120 + device.setName(RandomStringUtils.randomAlphabetic(300));
  121 + device.setType("default");
  122 + doPost("/api/device", device).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
  123 + device.setName("Normal Name");
  124 + device.setType(RandomStringUtils.randomAlphabetic(300));
  125 + doPost("/api/device", device).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
  126 + device.setType("Normal type");
  127 + device.setLabel(RandomStringUtils.randomAlphabetic(300));
  128 + doPost("/api/device", device).andExpect(statusReason(containsString("length of label must be equal or less than 255")));
  129 + }
  130 +
  131 + @Test
118 132 public void testUpdateDeviceFromDifferentTenant() throws Exception {
119 133 Device device = new Device();
120 134 device.setName("My device");
... ...
... ... @@ -22,6 +22,7 @@ import com.google.protobuf.DynamicMessage;
22 22 import com.google.protobuf.InvalidProtocolBufferException;
23 23 import com.google.protobuf.util.JsonFormat;
24 24 import com.squareup.wire.schema.internal.parser.ProtoFileElement;
  25 +import org.apache.commons.lang3.RandomStringUtils;
25 26 import org.junit.After;
26 27 import org.junit.Assert;
27 28 import org.junit.Before;
... ... @@ -110,6 +111,12 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
110 111 }
111 112
112 113 @Test
  114 + public void saveDeviceProfileWithViolationOfValidation() throws Exception {
  115 + doPost("/api/deviceProfile", this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300), null))
  116 + .andExpect(statusReason(containsString("length of name must be equal or less than 255")));
  117 + }
  118 +
  119 + @Test
113 120 public void testFindDeviceProfileById() throws Exception {
114 121 DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null);
115 122 DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
... ...
... ... @@ -48,6 +48,7 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
48 48 import java.util.ArrayList;
49 49 import java.util.Collections;
50 50 import java.util.List;
  51 +import java.util.Random;
51 52
52 53 import static org.hamcrest.Matchers.containsString;
53 54 import static org.hamcrest.Matchers.nullValue;
... ... @@ -113,6 +114,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
113 114 }
114 115
115 116 @Test
  117 + public void testSaveEdgeWithViolationOfLengthValidation() throws Exception {
  118 + Edge edge = constructEdge(RandomStringUtils.randomAlphabetic(300), "default");
  119 + doPost("/api/edge", edge).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
  120 + edge.setName("normal name");
  121 + edge.setType(RandomStringUtils.randomAlphabetic(300));
  122 + doPost("/api/edge", edge).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
  123 + edge.setType("normal type");
  124 + edge.setLabel(RandomStringUtils.randomAlphabetic(300));
  125 + doPost("/api/edge", edge).andExpect(statusReason(containsString("length of label must be equal or less than 255")));
  126 + }
  127 +
  128 + @Test
116 129 public void testFindEdgeById() throws Exception {
117 130 Edge edge = constructEdge("My edge", "default");
118 131 Edge savedEdge = doPost("/api/edge", edge, Edge.class);
... ...
... ... @@ -133,6 +133,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
133 133 assertEquals(foundEntityView.getKeys(), telemetry);
134 134 }
135 135
  136 + @Test
  137 + public void testSaveEntityViewWithViolationOfValidation() throws Exception {
  138 + EntityView entityView = createEntityView(RandomStringUtils.randomAlphabetic(300), 0, 0);
  139 + doPost("/api/entityView", entityView).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
  140 + entityView.setName("Normal name");
  141 + entityView.setType(RandomStringUtils.randomAlphabetic(300));
  142 + doPost("/api/entityView", entityView).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
  143 + }
136 144
137 145 @Test
138 146 public void testUpdateEntityViewFromDifferentTenant() throws Exception {
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 20 import org.junit.After;
20 21 import org.junit.Assert;
21 22 import org.junit.Before;
... ... @@ -40,6 +41,7 @@ import java.util.ArrayList;
40 41 import java.util.Collections;
41 42 import java.util.List;
42 43
  44 +import static org.hamcrest.Matchers.containsString;
43 45 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
44 46 import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
45 47
... ... @@ -118,6 +120,24 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
118 120 }
119 121
120 122 @Test
  123 + public void saveOtaPackageInfoWithViolationOfLengthValidation() throws Exception {
  124 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
  125 + firmwareInfo.setDeviceProfileId(deviceProfileId);
  126 + firmwareInfo.setType(FIRMWARE);
  127 + firmwareInfo.setTitle(RandomStringUtils.randomAlphabetic(300));
  128 + firmwareInfo.setVersion(VERSION);
  129 + firmwareInfo.setUsesUrl(false);
  130 + doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
  131 + firmwareInfo.setTitle(TITLE);
  132 + firmwareInfo.setVersion(RandomStringUtils.randomAlphabetic(300));
  133 + doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of version must be equal or less than 255")));
  134 + firmwareInfo.setVersion(VERSION);
  135 + firmwareInfo.setUsesUrl(true);
  136 + firmwareInfo.setUrl(RandomStringUtils.randomAlphabetic(300));
  137 + doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of url must be equal or less than 255")));
  138 + }
  139 +
  140 + @Test
121 141 public void testSaveFirmwareData() throws Exception {
122 142 SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
123 143 firmwareInfo.setDeviceProfileId(deviceProfileId);
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 20 import org.junit.After;
20 21 import org.junit.Assert;
21 22 import org.junit.Before;
... ... @@ -33,6 +34,7 @@ import java.util.ArrayList;
33 34 import java.util.Collections;
34 35 import java.util.List;
35 36
  37 +import static org.hamcrest.Matchers.containsString;
36 38 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
37 39
38 40 public abstract class BaseRuleChainControllerTest extends AbstractControllerTest {
... ... @@ -85,6 +87,13 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
85 87 }
86 88
87 89 @Test
  90 + public void testSaveRuleChainWithViolationOfLengthValidation() throws Exception {
  91 + RuleChain ruleChain = new RuleChain();
  92 + ruleChain.setName(RandomStringUtils.randomAlphabetic(300));
  93 + doPost("/api/ruleChain", ruleChain).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
  94 + }
  95 +
  96 + @Test
88 97 public void testFindRuleChainById() throws Exception {
89 98 RuleChain ruleChain = new RuleChain();
90 99 ruleChain.setName("RuleChain");
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 20 import org.junit.After;
20 21 import org.junit.Assert;
21 22 import org.junit.Before;
... ... @@ -33,6 +34,7 @@ import java.util.ArrayList;
33 34 import java.util.Collections;
34 35 import java.util.List;
35 36
  37 +import static org.hamcrest.Matchers.containsString;
36 38 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
37 39
38 40 public abstract class BaseTbResourceControllerTest extends AbstractControllerTest {
... ... @@ -99,6 +101,16 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes
99 101 }
100 102
101 103 @Test
  104 + public void saveResourceInfoWithViolationOfLengthValidation() throws Exception {
  105 + TbResource resource = new TbResource();
  106 + resource.setResourceType(ResourceType.JKS);
  107 + resource.setTitle(RandomStringUtils.randomAlphabetic(300));
  108 + resource.setFileName(DEFAULT_FILE_NAME);
  109 + resource.setData("Test Data");
  110 + doPost("/api/resource", resource).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
  111 + }
  112 +
  113 + @Test
102 114 public void testUpdateTbResourceFromDifferentTenant() throws Exception {
103 115 TbResource resource = new TbResource();
104 116 resource.setResourceType(ResourceType.JKS);
... ...
... ... @@ -52,6 +52,14 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
52 52 doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
53 53 .andExpect(status().isOk());
54 54 }
  55 +
  56 + @Test
  57 + public void testSaveTenantWithViolationOfValidation() throws Exception {
  58 + loginSysAdmin();
  59 + Tenant tenant = new Tenant();
  60 + tenant.setTitle(RandomStringUtils.randomAlphanumeric(300));
  61 + doPost("/api/tenant", tenant).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
  62 + }
55 63
56 64 @Test
57 65 public void testFindTenantById() throws Exception {
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 20 import org.junit.After;
20 21 import org.junit.Assert;
21 22 import org.junit.Test;
... ... @@ -75,6 +76,13 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController
75 76 }
76 77
77 78 @Test
  79 + public void testSaveTenantProfileWithViolationOfLengthValidation() throws Exception {
  80 + loginSysAdmin();
  81 + TenantProfile tenantProfile = this.createTenantProfile(RandomStringUtils.randomAlphabetic(300));
  82 + doPost("/api/tenantProfile", tenantProfile).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
  83 + }
  84 +
  85 + @Test
78 86 public void testFindTenantProfileById() throws Exception {
79 87 loginSysAdmin();
80 88 TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
... ...
... ... @@ -106,6 +106,28 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
106 106 }
107 107
108 108 @Test
  109 + public void testSaveUserWithViolationOfFiledValidation() throws Exception {
  110 + loginSysAdmin();
  111 +
  112 + Tenant tenant = new Tenant();
  113 + tenant.setTitle("My tenant");
  114 + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  115 + Assert.assertNotNull(savedTenant);
  116 +
  117 + String email = "tenant2@thingsboard.org";
  118 + User user = new User();
  119 + user.setAuthority(Authority.TENANT_ADMIN);
  120 + user.setTenantId(savedTenant.getId());
  121 + user.setEmail(email);
  122 + user.setFirstName(RandomStringUtils.randomAlphabetic(300));
  123 + user.setLastName("Downs");
  124 + doPost("/api/user", user).andExpect(statusReason(containsString("Validation error: length of first name must be equal or less than 255")));
  125 + user.setFirstName("Normal name");
  126 + user.setLastName(RandomStringUtils.randomAlphabetic(300));
  127 + doPost("/api/user", user).andExpect(statusReason(containsString("length of last name must be equal or less than 255")));
  128 + }
  129 +
  130 + @Test
109 131 public void testUpdateUserFromDifferentTenant() throws Exception {
110 132 loginSysAdmin();
111 133 Tenant tenant = new Tenant();
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 20 import org.junit.After;
20 21 import org.junit.Assert;
21 22 import org.junit.Before;
... ... @@ -88,6 +89,13 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
88 89 Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle());
89 90 }
90 91
  92 + @Test
  93 + public void testSaveWidgetBundleWithViolationOfLengthValidation() throws Exception {
  94 + WidgetsBundle widgetsBundle = new WidgetsBundle();
  95 + widgetsBundle.setTitle(RandomStringUtils.randomAlphabetic(300));
  96 + doPost("/api/widgetsBundle", widgetsBundle).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
  97 + }
  98 +
91 99 @Test
92 100 public void testUpdateWidgetsBundleFromDifferentTenant() throws Exception {
93 101 WidgetsBundle widgetsBundle = new WidgetsBundle();
... ...
... ... @@ -17,30 +17,36 @@ 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.Length;
20 21 import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 23 @EqualsAndHashCode(callSuper = true)
23 24 public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
24   -
  25 +
25 26 private static final long serialVersionUID = 5047448057830660988L;
26 27
  28 + @Length(fieldName = "country")
27 29 @NoXss
28 30 protected String country;
  31 + @Length(fieldName = "state")
29 32 @NoXss
30 33 protected String state;
  34 + @Length(fieldName = "city")
31 35 @NoXss
32 36 protected String city;
33 37 @NoXss
34 38 protected String address;
35 39 @NoXss
36 40 protected String address2;
  41 + @Length(fieldName = "zip or postal code")
37 42 @NoXss
38 43 protected String zip;
  44 + @Length(fieldName = "phone")
39 45 @NoXss
40 46 protected String phone;
41 47 @NoXss
42 48 protected String email;
43   -
  49 +
44 50 public ContactBased() {
45 51 super();
46 52 }
... ... @@ -48,7 +54,7 @@ public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedW
48 54 public ContactBased(I id) {
49 55 super(id);
50 56 }
51   -
  57 +
52 58 public ContactBased(ContactBased<I> contact) {
53 59 super(contact);
54 60 this.country = contact.getCountry();
... ...
... ... @@ -22,13 +22,15 @@ import com.fasterxml.jackson.databind.JsonNode;
22 22 import io.swagger.annotations.ApiModelProperty;
23 23 import org.thingsboard.server.common.data.id.CustomerId;
24 24 import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.data.validation.Length;
25 26 import org.thingsboard.server.common.data.validation.NoXss;
26 27
27 28 public class Customer extends ContactBased<CustomerId> implements HasTenantId {
28   -
  29 +
29 30 private static final long serialVersionUID = -1599722990298929275L;
30 31
31 32 @NoXss
  33 + @Length(fieldName = "title")
32 34 @ApiModelProperty(position = 3, value = "Title of the customer", example = "Company A")
33 35 private String title;
34 36 @ApiModelProperty(position = 5, required = true, value = "JSON object with Tenant Id")
... ... @@ -41,7 +43,7 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId {
41 43 public Customer(CustomerId id) {
42 44 super(id);
43 45 }
44   -
  46 +
45 47 public Customer(Customer customer) {
46 48 super(customer);
47 49 this.tenantId = customer.getTenantId();
... ...
... ... @@ -21,6 +21,7 @@ import io.swagger.annotations.ApiModelProperty;
21 21 import org.thingsboard.server.common.data.id.CustomerId;
22 22 import org.thingsboard.server.common.data.id.DashboardId;
23 23 import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.validation.Length;
24 25 import org.thingsboard.server.common.data.validation.NoXss;
25 26
26 27 import javax.validation.Valid;
... ... @@ -32,6 +33,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
32 33
33 34 private TenantId tenantId;
34 35 @NoXss
  36 + @Length(fieldName = "title")
35 37 private String title;
36 38 private String image;
37 39 @Valid
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
28 28 import org.thingsboard.server.common.data.id.DeviceProfileId;
29 29 import org.thingsboard.server.common.data.id.OtaPackageId;
30 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 32 import org.thingsboard.server.common.data.validation.NoXss;
32 33
33 34 import java.io.ByteArrayInputStream;
... ... @@ -44,10 +45,13 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
44 45 private TenantId tenantId;
45 46 private CustomerId customerId;
46 47 @NoXss
  48 + @Length(fieldName = "name")
47 49 private String name;
48 50 @NoXss
  51 + @Length(fieldName = "type")
49 52 private String type;
50 53 @NoXss
  54 + @Length(fieldName = "label")
51 55 private String label;
52 56 private DeviceProfileId deviceProfileId;
53 57 private transient DeviceData deviceData;
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
28 28 import org.thingsboard.server.common.data.id.OtaPackageId;
29 29 import org.thingsboard.server.common.data.id.RuleChainId;
30 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 32 import org.thingsboard.server.common.data.validation.NoXss;
32 33
33 34 import javax.validation.Valid;
... ... @@ -47,6 +48,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
47 48 @ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", readOnly = true)
48 49 private TenantId tenantId;
49 50 @NoXss
  51 + @Length(fieldName = "name")
50 52 @ApiModelProperty(position = 4, value = "Unique Device Profile Name in scope of Tenant.", example = "Moisture Sensor")
51 53 private String name;
52 54 @NoXss
... ...
... ... @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.EntityId;
25 25 import org.thingsboard.server.common.data.id.EntityViewId;
26 26 import org.thingsboard.server.common.data.id.TenantId;
27 27 import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  28 +import org.thingsboard.server.common.data.validation.Length;
28 29 import org.thingsboard.server.common.data.validation.NoXss;
29 30
30 31 /**
... ... @@ -44,9 +45,11 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
44 45 private TenantId tenantId;
45 46 private CustomerId customerId;
46 47 @NoXss
  48 + @Length(fieldName = "name")
47 49 @ApiModelProperty(position = 5, required = true, value = "Entity View name", example = "A4B72CCDFF33")
48 50 private String name;
49 51 @NoXss
  52 + @Length(fieldName = "type")
50 53 @ApiModelProperty(position = 6, required = true, value = "Device Profile Name", example = "Temperature Sensor")
51 54 private String type;
52 55 @ApiModelProperty(position = 8, required = true, value = "Set of telemetry and attribute keys to expose via Entity View.")
... ...
... ... @@ -27,6 +27,10 @@ import org.thingsboard.server.common.data.id.OtaPackageId;
27 27 import org.thingsboard.server.common.data.id.TenantId;
28 28 import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
29 29 import org.thingsboard.server.common.data.ota.OtaPackageType;
  30 +import org.thingsboard.server.common.data.validation.Length;
  31 +import org.thingsboard.server.common.data.validation.NoXss;
  32 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  33 +import org.thingsboard.server.common.data.ota.OtaPackageType;
30 34
31 35 @ApiModel
32 36 @Slf4j
... ... @@ -42,16 +46,26 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
42 46 private DeviceProfileId deviceProfileId;
43 47 @ApiModelProperty(position = 5, value = "OTA Package type.", example = "FIRMWARE", readOnly = true)
44 48 private OtaPackageType type;
  49 + @Length(fieldName = "title")
  50 + @NoXss
45 51 @ApiModelProperty(position = 6, value = "OTA Package title.", example = "fw", readOnly = true)
46 52 private String title;
  53 + @Length(fieldName = "version")
  54 + @NoXss
47 55 @ApiModelProperty(position = 7, value = "OTA Package version.", example = "1.0", readOnly = true)
48 56 private String version;
  57 + @Length(fieldName = "tag")
  58 + @NoXss
49 59 @ApiModelProperty(position = 8, value = "OTA Package tag.", example = "fw_1.0", readOnly = true)
50 60 private String tag;
  61 + @Length(fieldName = "url")
  62 + @NoXss
51 63 @ApiModelProperty(position = 9, value = "OTA Package url.", example = "http://thingsboard.org/fw/1", readOnly = true)
52 64 private String url;
53 65 @ApiModelProperty(position = 10, value = "Indicates OTA Package 'has data'. Field is returned from DB ('true' if data exists or url is set). If OTA Package 'has data' is 'false' we can not assign the OTA Package to the Device or Device Profile.", example = "true", readOnly = true)
54 66 private boolean hasData;
  67 + @Length(fieldName = "file name")
  68 + @NoXss
55 69 @ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", readOnly = true)
56 70 private String fileName;
57 71 @ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", readOnly = true)
... ...
... ... @@ -20,6 +20,7 @@ import lombok.Data;
20 20 import lombok.EqualsAndHashCode;
21 21 import lombok.extern.slf4j.Slf4j;
22 22 import org.thingsboard.server.common.data.id.TbResourceId;
  23 +import org.thingsboard.server.common.data.validation.Length;
23 24 import org.thingsboard.server.common.data.validation.NoXss;
24 25
25 26 @Slf4j
... ... @@ -30,6 +31,7 @@ public class TbResource extends TbResourceInfo {
30 31 private static final long serialVersionUID = 7379609705527272306L;
31 32
32 33 @NoXss
  34 + @Length(fieldName = "file name")
33 35 @ApiModelProperty(position = 8, value = "Resource file name.", example = "19.xml", readOnly = true)
34 36 private String fileName;
35 37
... ...
... ... @@ -23,6 +23,7 @@ import lombok.EqualsAndHashCode;
23 23 import lombok.extern.slf4j.Slf4j;
24 24 import org.thingsboard.server.common.data.id.TbResourceId;
25 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.validation.Length;
26 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 29 @ApiModel
... ... @@ -36,6 +37,7 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
36 37 @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", readOnly = true)
37 38 private TenantId tenantId;
38 39 @NoXss
  40 + @Length(fieldName = "title")
39 41 @ApiModelProperty(position = 4, value = "Resource title.", example = "BinaryAppDataContainer id=19 v1.0")
40 42 private String title;
41 43 @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true)
... ...
... ... @@ -23,6 +23,7 @@ import io.swagger.annotations.ApiModelProperty;
23 23 import lombok.EqualsAndHashCode;
24 24 import org.thingsboard.server.common.data.id.TenantId;
25 25 import org.thingsboard.server.common.data.id.TenantProfileId;
  26 +import org.thingsboard.server.common.data.validation.Length;
26 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 29 @ApiModel
... ... @@ -31,6 +32,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
31 32
32 33 private static final long serialVersionUID = 8057243243859922101L;
33 34
  35 + @Length(fieldName = "title")
34 36 @NoXss
35 37 @ApiModelProperty(position = 3, value = "Title of the tenant", example = "Company A")
36 38 private String title;
... ... @@ -48,7 +50,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
48 50 public Tenant(TenantId id) {
49 51 super(id);
50 52 }
51   -
  53 +
52 54 public Tenant(Tenant tenant) {
53 55 super(tenant);
54 56 this.title = tenant.getTitle();
... ...
... ... @@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
25 25 import org.thingsboard.server.common.data.id.TenantProfileId;
26 26 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
27 27 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
  28 +import org.thingsboard.server.common.data.validation.Length;
28 29 import org.thingsboard.server.common.data.validation.NoXss;
29 30
30 31 import java.io.ByteArrayInputStream;
... ... @@ -42,6 +43,7 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H
42 43 private static final long serialVersionUID = 3021989561267192281L;
43 44
44 45 @NoXss
  46 + @Length(fieldName = "name")
45 47 @ApiModelProperty(position = 3, value = "Name of the tenant profile", example = "High Priority Tenants")
46 48 private String name;
47 49 @NoXss
... ...
... ... @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.EntityId;
26 26 import org.thingsboard.server.common.data.id.TenantId;
27 27 import org.thingsboard.server.common.data.id.UserId;
28 28 import org.thingsboard.server.common.data.security.Authority;
29   -
  29 +import org.thingsboard.server.common.data.validation.Length;
30 30 import org.thingsboard.server.common.data.validation.NoXss;
31 31
32 32 @ApiModel
... ... @@ -40,8 +40,10 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
40 40 private String email;
41 41 private Authority authority;
42 42 @NoXss
  43 + @Length(fieldName = "first name")
43 44 private String firstName;
44 45 @NoXss
  46 + @Length(fieldName = "last name")
45 47 private String lastName;
46 48
47 49 public User() {
... ...
... ... @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
30 30 import org.thingsboard.server.common.data.id.CustomerId;
31 31 import org.thingsboard.server.common.data.id.EntityId;
32 32 import org.thingsboard.server.common.data.id.TenantId;
  33 +import org.thingsboard.server.common.data.validation.Length;
33 34
34 35 import java.util.List;
35 36
... ... @@ -49,6 +50,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
49 50 private CustomerId customerId;
50 51
51 52 @ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
  53 + @Length(fieldName = "type")
52 54 private String type;
53 55 @ApiModelProperty(position = 7, required = true, value = "JSON object with alarm originator id")
54 56 private EntityId originator;
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
26 26 import org.thingsboard.server.common.data.id.AssetId;
27 27 import org.thingsboard.server.common.data.id.CustomerId;
28 28 import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.validation.Length;
29 30 import org.thingsboard.server.common.data.validation.NoXss;
30 31
31 32 import java.util.Optional;
... ... @@ -39,10 +40,13 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
39 40 private TenantId tenantId;
40 41 private CustomerId customerId;
41 42 @NoXss
  43 + @Length(fieldName = "name")
42 44 private String name;
43 45 @NoXss
  46 + @Length(fieldName = "type")
44 47 private String type;
45 48 @NoXss
  49 + @Length(fieldName = "label")
46 50 private String label;
47 51
48 52 public Asset() {
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
28 28 import org.thingsboard.server.common.data.id.EdgeId;
29 29 import org.thingsboard.server.common.data.id.RuleChainId;
30 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 32
32 33 @ApiModel
33 34 @EqualsAndHashCode(callSuper = true)
... ... @@ -40,8 +41,11 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
40 41 private TenantId tenantId;
41 42 private CustomerId customerId;
42 43 private RuleChainId rootRuleChainId;
  44 + @Length(fieldName = "name")
43 45 private String name;
  46 + @Length(fieldName = "type")
44 47 private String type;
  48 + @Length(fieldName = "label")
45 49 private String label;
46 50 private String routingKey;
47 51 private String secret;
... ...
... ... @@ -15,8 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.common.data.kv;
17 17
18   -import com.fasterxml.jackson.databind.JsonNode;
19   -
  18 +import javax.validation.Valid;
20 19 import java.util.Optional;
21 20
22 21 /**
... ... @@ -27,6 +26,7 @@ public class BaseAttributeKvEntry implements AttributeKvEntry {
27 26 private static final long serialVersionUID = -6460767583563159407L;
28 27
29 28 private final long lastUpdateTs;
  29 + @Valid
30 30 private final KvEntry kv;
31 31
32 32 public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs) {
... ...
... ... @@ -15,11 +15,14 @@
15 15 */
16 16 package org.thingsboard.server.common.data.kv;
17 17
  18 +import org.thingsboard.server.common.data.validation.Length;
  19 +
18 20 import java.util.Objects;
19 21 import java.util.Optional;
20 22
21 23 public abstract class BasicKvEntry implements KvEntry {
22 24
  25 + @Length(fieldName = "attribute key")
23 26 private final String key;
24 27
25 28 protected BasicKvEntry(String key) {
... ...
... ... @@ -21,6 +21,7 @@ import java.util.Optional;
21 21 public class StringDataEntry extends BasicKvEntry {
22 22
23 23 private static final long serialVersionUID = 1L;
  24 +
24 25 private final String value;
25 26
26 27 public StringDataEntry(String key, String value) {
... ...
... ... @@ -22,6 +22,7 @@ import io.swagger.annotations.ApiModelProperty;
22 22 import lombok.extern.slf4j.Slf4j;
23 23 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
24 24 import org.thingsboard.server.common.data.id.EntityId;
  25 +import org.thingsboard.server.common.data.validation.Length;
25 26
26 27 import java.io.Serializable;
27 28
... ... @@ -37,6 +38,7 @@ public class EntityRelation implements Serializable {
37 38
38 39 private EntityId from;
39 40 private EntityId to;
  41 + @Length(fieldName = "type")
40 42 private String type;
41 43 private RelationTypeGroup typeGroup;
42 44 private transient JsonNode additionalInfo;
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
28 28 import org.thingsboard.server.common.data.id.RuleChainId;
29 29 import org.thingsboard.server.common.data.id.RuleNodeId;
30 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 32 import org.thingsboard.server.common.data.validation.NoXss;
32 33
33 34 @ApiModel
... ... @@ -41,6 +42,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
41 42 @ApiModelProperty(position = 3, required = true, value = "JSON object with Tenant Id.", readOnly = true)
42 43 private TenantId tenantId;
43 44 @NoXss
  45 + @Length(fieldName = "name")
44 46 @ApiModelProperty(position = 4, required = true, value = "Rule Chain name", example = "Humidity data processing")
45 47 private String name;
46 48 @ApiModelProperty(position = 5, value = "Rule Chain type. 'EDGE' rule chains are processing messages on the edge devices only.", example = "A4B72CCDFF33")
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.HasName;
26 26 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
27 27 import org.thingsboard.server.common.data.id.RuleChainId;
28 28 import org.thingsboard.server.common.data.id.RuleNodeId;
  29 +import org.thingsboard.server.common.data.validation.Length;
29 30
30 31 @ApiModel
31 32 @Data
... ... @@ -37,8 +38,10 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
37 38
38 39 @ApiModelProperty(position = 3, value = "JSON object with the Rule Chain Id. ", readOnly = true)
39 40 private RuleChainId ruleChainId;
  41 + @Length(fieldName = "type")
40 42 @ApiModelProperty(position = 4, value = "Full Java Class Name of the rule node implementation. ", example = "com.mycompany.iot.rule.engine.ProcessingNode")
41 43 private String type;
  44 + @Length(fieldName = "name")
42 45 @ApiModelProperty(position = 5, value = "User defined name of the rule node. Used on UI and for logging. ", example = "Process sensor reading")
43 46 private String name;
44 47 @ApiModelProperty(position = 6, value = "Enable/disable debug. ", example = "false")
... ...
  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 Length {
  29 + String message() default "length of {fieldName} must be equal or less than {max}";
  30 +
  31 + String fieldName();
  32 +
  33 + int max() default 255;
  34 +
  35 + Class<?>[] groups() default {};
  36 +
  37 + Class<? extends Payload>[] payload() default {};
  38 +}
... ...
... ... @@ -23,10 +23,9 @@ import org.thingsboard.server.common.data.HasTenantId;
23 23 import org.thingsboard.server.common.data.SearchTextBased;
24 24 import org.thingsboard.server.common.data.id.TenantId;
25 25 import org.thingsboard.server.common.data.id.WidgetsBundleId;
  26 +import org.thingsboard.server.common.data.validation.Length;
26 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28
28   -import java.util.Arrays;
29   -
30 29 @ApiModel
31 30 public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasTenantId {
32 31
... ... @@ -38,12 +37,14 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
38 37 private TenantId tenantId;
39 38
40 39 @NoXss
  40 + @Length(fieldName = "alias")
41 41 @Getter
42 42 @Setter
43 43 @ApiModelProperty(position = 4, value = "Unique alias that is used in widget types as a reference widget bundle", readOnly = true)
44 44 private String alias;
45 45
46 46 @NoXss
  47 + @Length(fieldName = "title")
47 48 @Getter
48 49 @Setter
49 50 @ApiModelProperty(position = 5, value = "Title used in search and UI", readOnly = true)
... ... @@ -55,6 +56,7 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
55 56 private String image;
56 57
57 58 @NoXss
  59 + @Length(fieldName = "description")
58 60 @Getter
59 61 @Setter
60 62 @ApiModelProperty(position = 7, value = "Description", readOnly = true)
... ...
... ... @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
41 41 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
42 42 import org.thingsboard.server.dao.entity.EntityService;
43 43 import org.thingsboard.server.dao.exception.DataValidationException;
  44 +import org.thingsboard.server.dao.service.ConstraintValidator;
44 45
45 46 import javax.annotation.Nullable;
46 47 import java.util.ArrayList;
... ... @@ -559,6 +560,7 @@ public class BaseRelationService implements RelationService {
559 560 if (relation == null) {
560 561 throw new DataValidationException("Relation type should be specified!");
561 562 }
  563 + ConstraintValidator.validateFields(relation);
562 564 validate(relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup());
563 565 }
564 566
... ...
  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 lombok.extern.slf4j.Slf4j;
  19 +import org.hibernate.validator.HibernateValidator;
  20 +import org.hibernate.validator.HibernateValidatorConfiguration;
  21 +import org.hibernate.validator.cfg.ConstraintMapping;
  22 +import org.thingsboard.server.common.data.validation.Length;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
  24 +
  25 +import javax.validation.ConstraintViolation;
  26 +import javax.validation.Validation;
  27 +import javax.validation.ValidationException;
  28 +import javax.validation.Validator;
  29 +import java.util.List;
  30 +import java.util.Set;
  31 +import java.util.stream.Collectors;
  32 +
  33 +@Slf4j
  34 +public class ConstraintValidator {
  35 +
  36 + private static Validator fieldsValidator;
  37 +
  38 + static {
  39 + initializeValidators();
  40 + }
  41 +
  42 + public static void validateFields(Object data) {
  43 + Set<ConstraintViolation<Object>> constraintsViolations = fieldsValidator.validate(data);
  44 + List<String> validationErrors = constraintsViolations.stream()
  45 + .map(ConstraintViolation::getMessage)
  46 + .distinct()
  47 + .collect(Collectors.toList());
  48 + if (!validationErrors.isEmpty()) {
  49 + throw new ValidationException("Validation error: " + String.join(", ", validationErrors));
  50 + }
  51 + }
  52 +
  53 + private static void initializeValidators() {
  54 + HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
  55 +
  56 + ConstraintMapping constraintMapping = validatorConfiguration.createConstraintMapping();
  57 + constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
  58 + constraintMapping.constraintDefinition(Length.class).validatedBy(StringLengthValidator.class);
  59 + validatorConfiguration.addMapping(constraintMapping);
  60 +
  61 + fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
  62 + }
  63 +}
... ...
... ... @@ -17,51 +17,32 @@ 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;
23 20 import org.thingsboard.server.common.data.BaseData;
24 21 import org.thingsboard.server.common.data.EntityType;
25 22 import org.thingsboard.server.common.data.id.TenantId;
26   -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
27   -import org.thingsboard.server.common.data.validation.NoXss;
28 23 import org.thingsboard.server.dao.TenantEntityDao;
29 24 import org.thingsboard.server.dao.TenantEntityWithDataDao;
30 25 import org.thingsboard.server.dao.exception.DataValidationException;
31 26
32   -import javax.validation.ConstraintViolation;
33   -import javax.validation.Validation;
34   -import javax.validation.Validator;
35 27 import java.util.HashSet;
36 28 import java.util.Iterator;
37   -import java.util.List;
38 29 import java.util.Set;
39 30 import java.util.function.Function;
40 31 import java.util.regex.Matcher;
41 32 import java.util.regex.Pattern;
42   -import java.util.stream.Collectors;
43 33
44 34 @Slf4j
45 35 public abstract class DataValidator<D extends BaseData<?>> {
46 36 private static final Pattern EMAIL_PATTERN =
47 37 Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
48 38
49   - private static Validator fieldsValidator;
50   -
51   - static {
52   - initializeFieldsValidator();
53   - }
54   -
55 39 public void validate(D data, Function<D, TenantId> tenantIdFunction) {
56 40 try {
57 41 if (data == null) {
58 42 throw new DataValidationException("Data object can't be null!");
59 43 }
60 44
61   - List<String> validationErrors = validateFields(data);
62   - if (!validationErrors.isEmpty()) {
63   - throw new IllegalArgumentException("Validation error: " + String.join(", ", validationErrors));
64   - }
  45 + ConstraintValidator.validateFields(data);
65 46
66 47 TenantId tenantId = tenantIdFunction.apply(data);
67 48 validateDataImpl(tenantId, data);
... ... @@ -104,14 +85,6 @@ public abstract class DataValidator<D extends BaseData<?>> {
104 85 return emailMatcher.matches();
105 86 }
106 87
107   - private List<String> validateFields(D data) {
108   - Set<ConstraintViolation<D>> constraintsViolations = fieldsValidator.validate(data);
109   - return constraintsViolations.stream()
110   - .map(ConstraintViolation::getMessage)
111   - .distinct()
112   - .collect(Collectors.toList());
113   - }
114   -
115 88 protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
116 89 TenantEntityDao tenantEntityDao,
117 90 long maxEntities,
... ... @@ -126,10 +99,10 @@ public abstract class DataValidator<D extends BaseData<?>> {
126 99 }
127 100
128 101 protected void validateMaxSumDataSizePerTenant(TenantId tenantId,
129   - TenantEntityWithDataDao dataDao,
130   - long maxSumDataSize,
131   - long currentDataSize,
132   - EntityType entityType) {
  102 + TenantEntityWithDataDao dataDao,
  103 + long maxSumDataSize,
  104 + long currentDataSize,
  105 + EntityType entityType) {
133 106 if (maxSumDataSize > 0) {
134 107 if (dataDao.sumDataSizeByTenantId(tenantId) + currentDataSize > maxSumDataSize) {
135 108 throw new DataValidationException(String.format("Failed to create the %s, files size limit is exhausted %d bytes!",
... ... @@ -156,12 +129,4 @@ public abstract class DataValidator<D extends BaseData<?>> {
156 129 }
157 130 }
158 131
159   - private static void initializeFieldsValidator() {
160   - HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
161   - ConstraintMapping constraintMapping = validatorConfiguration.createConstraintMapping();
162   - constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
163   - validatorConfiguration.addMapping(constraintMapping);
164   -
165   - fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
166   - }
167 132 }
... ...
  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 lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.StringUtils;
  20 +import org.thingsboard.server.common.data.validation.Length;
  21 +
  22 +import javax.validation.ConstraintValidator;
  23 +import javax.validation.ConstraintValidatorContext;
  24 +
  25 +@Slf4j
  26 +public class StringLengthValidator implements ConstraintValidator<Length, String> {
  27 + private int max;
  28 +
  29 + @Override
  30 + public boolean isValid(String value, ConstraintValidatorContext context) {
  31 + if (StringUtils.isEmpty(value)) {
  32 + return true;
  33 + }
  34 + return value.length() <= max;
  35 + }
  36 +
  37 + @Override
  38 + public void initialize(Length constraintAnnotation) {
  39 + this.max = constraintAnnotation.max();
  40 + }
  41 +}
... ...
... ... @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageLink;
38 38 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
39 39 import org.thingsboard.server.dao.exception.DataValidationException;
40 40
  41 +import javax.validation.ValidationException;
41 42 import java.nio.ByteBuffer;
42 43 import java.util.ArrayList;
43 44 import java.util.Collections;
... ... @@ -673,8 +674,8 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
673 674 firmwareInfo.setUrl(URL);
674 675 firmwareInfo.setTenantId(tenantId);
675 676
676   - thrown.expect(DataValidationException.class);
677   - thrown.expectMessage("The length of title should be equal or shorter than 255");
  677 + thrown.expect(ValidationException.class);
  678 + thrown.expectMessage("length of title must be equal or less than 255");
678 679
679 680 otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
680 681 }
... ... @@ -689,7 +690,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
689 690 firmwareInfo.setTitle(TITLE);
690 691
691 692 firmwareInfo.setVersion(RandomStringUtils.random(257));
692   - thrown.expectMessage("The length of version should be equal or shorter than 255");
  693 + thrown.expectMessage("length of version must be equal or less than 255");
693 694
694 695 otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
695 696 }
... ...
... ... @@ -36,6 +36,9 @@
36 36 <mat-error *ngIf="attributeFormGroup.get('key').hasError('required')">
37 37 {{ 'attribute.key-required' | translate }}
38 38 </mat-error>
  39 + <mat-error *ngIf="attributeFormGroup.get('key').hasError('maxlength')">
  40 + {{ 'attribute.key-max-length' | translate }}
  41 + </mat-error>
39 42 </mat-form-field>
40 43 <tb-value-input
41 44 formControlName="value"
... ...
... ... @@ -56,7 +56,7 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
56 56
57 57 ngOnInit(): void {
58 58 this.attributeFormGroup = this.fb.group({
59   - key: ['', [Validators.required]],
  59 + key: ['', [Validators.required, Validators.maxLength(255)]],
60 60 value: [null, [Validators.required]]
61 61 });
62 62 }
... ...
... ... @@ -37,15 +37,15 @@ export abstract class ContactBasedComponent<T extends ContactBased<HasId>> exten
37 37
38 38 buildForm(entity: T): FormGroup {
39 39 const entityForm = this.buildEntityForm(entity);
40   - entityForm.addControl('country', this.fb.control(entity ? entity.country : '', []));
41   - entityForm.addControl('city', this.fb.control(entity ? entity.city : '', []));
42   - entityForm.addControl('state', this.fb.control(entity ? entity.state : '', []));
  40 + entityForm.addControl('country', this.fb.control(entity ? entity.country : '', [Validators.maxLength(255)]));
  41 + entityForm.addControl('city', this.fb.control(entity ? entity.city : '', [Validators.maxLength(255)]));
  42 + entityForm.addControl('state', this.fb.control(entity ? entity.state : '', [Validators.maxLength(255)]));
43 43 entityForm.addControl('zip', this.fb.control(entity ? entity.zip : '',
44 44 this.zipValidators(entity ? entity.country : '')
45 45 ));
46 46 entityForm.addControl('address', this.fb.control(entity ? entity.address : '', []));
47 47 entityForm.addControl('address2', this.fb.control(entity ? entity.address2 : '', []));
48   - entityForm.addControl('phone', this.fb.control(entity ? entity.phone : '', []));
  48 + entityForm.addControl('phone', this.fb.control(entity ? entity.phone : '', [Validators.maxLength(255)]));
49 49 entityForm.addControl('email', this.fb.control(entity ? entity.email : '', [Validators.email]));
50 50 return entityForm;
51 51 }
... ...
... ... @@ -41,6 +41,9 @@
41 41 <mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('required')">
42 42 {{ 'device-profile.name-required' | translate }}
43 43 </mat-error>
  44 + <mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('maxlength')">
  45 + {{ 'device-profile.name-max-length' | translate }}
  46 + </mat-error>
44 47 </mat-form-field>
45 48 <tb-rule-chain-autocomplete
46 49 labelText="device-profile.default-rule-chain"
... ...
... ... @@ -105,7 +105,7 @@ export class AddDeviceProfileDialogComponent extends
105 105 super(store, router, dialogRef);
106 106 this.deviceProfileDetailsFormGroup = this.fb.group(
107 107 {
108   - name: [data.deviceProfileName, [Validators.required]],
  108 + name: [data.deviceProfileName, [Validators.required, Validators.maxLength(255)]],
109 109 type: [DeviceProfileType.DEFAULT, [Validators.required]],
110 110 image: [null, []],
111 111 defaultRuleChainId: [null, []],
... ...
... ... @@ -54,6 +54,9 @@
54 54 <mat-error *ngIf="entityForm.get('name').hasError('required')">
55 55 {{ 'device-profile.name-required' | translate }}
56 56 </mat-error>
  57 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  58 + {{ 'device-profile.name-max-length' | translate }}
  59 + </mat-error>
57 60 </mat-form-field>
58 61 <tb-rule-chain-autocomplete
59 62 labelText="device-profile.default-rule-chain"
... ...
... ... @@ -103,7 +103,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
103 103 };
104 104 const form = this.fb.group(
105 105 {
106   - name: [entity ? entity.name : '', [Validators.required]],
  106 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
107 107 type: [entity ? entity.type : null, [Validators.required]],
108 108 image: [entity ? entity.image : null],
109 109 transportType: [entity ? entity.transportType : null, [Validators.required]],
... ...
... ... @@ -48,6 +48,9 @@
48 48 <mat-error *ngIf="entityForm.get('name').hasError('required')">
49 49 {{ 'tenant-profile.name-required' | translate }}
50 50 </mat-error>
  51 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  52 + {{ 'tenant-profile.name-max-length' | translate }}
  53 + </mat-error>
51 54 </mat-form-field>
52 55 <div fxLayout="column">
53 56 <mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore">
... ...
... ... @@ -59,7 +59,7 @@ export class TenantProfileComponent extends EntityComponent<TenantProfile> {
59 59 buildForm(entity: TenantProfile): FormGroup {
60 60 return this.fb.group(
61 61 {
62   - name: [entity ? entity.name : '', [Validators.required]],
  62 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
63 63 isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
64 64 isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
65 65 profileData: [entity && !this.isAdd ? entity.profileData : {
... ...
... ... @@ -44,10 +44,16 @@
44 44 <mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('required')">
45 45 {{ 'device.name-required' | translate }}
46 46 </mat-error>
  47 + <mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('maxlength')">
  48 + {{ 'device.name-max-length' | translate }}
  49 + </mat-error>
47 50 </mat-form-field>
48 51 <mat-form-field class="mat-block">
49 52 <mat-label translate>device.label</mat-label>
50 53 <input matInput formControlName="label">
  54 + <mat-error *ngIf="deviceWizardFormGroup.get('label').hasError('maxlength')">
  55 + {{ 'device.label-max-length' | translate }}
  56 + </mat-error>
51 57 </mat-form-field>
52 58 <div fxLayout="row" fxLayoutGap="16px">
53 59 <mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around">
... ...
... ... @@ -105,8 +105,8 @@ export class DeviceWizardDialogComponent extends
105 105 private fb: FormBuilder) {
106 106 super(store, router, dialogRef);
107 107 this.deviceWizardFormGroup = this.fb.group({
108   - name: ['', Validators.required],
109   - label: [''],
  108 + name: ['', [Validators.required, Validators.maxLength(255)]],
  109 + label: ['', Validators.maxLength(255)],
110 110 gateway: [false],
111 111 overwriteActivityTime: [false],
112 112 addProfileType: [0],
... ...
... ... @@ -56,6 +56,9 @@
56 56 <mat-error *ngIf="entityForm.get('title').hasError('required')">
57 57 {{ 'resource.title-required' | translate }}
58 58 </mat-error>
  59 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  60 + {{ 'resource.title-max-length' | translate }}
  61 + </mat-error>
59 62 </mat-form-field>
60 63 <tb-file-input *ngIf="isAdd"
61 64 formControlName="data"
... ...
... ... @@ -29,7 +29,7 @@ import {
29 29 ResourceTypeMIMETypes,
30 30 ResourceTypeTranslationMap
31 31 } from '@shared/models/resource.models';
32   -import { pairwise, startWith, takeUntil } from 'rxjs/operators';
  32 +import {filter, pairwise, startWith, takeUntil} from 'rxjs/operators';
33 33 import { ActionNotificationShow } from '@core/notification/notification.actions';
34 34
35 35 @Component({
... ... @@ -57,16 +57,14 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
57 57 super.ngOnInit();
58 58 this.entityForm.get('resourceType').valueChanges.pipe(
59 59 startWith(ResourceType.LWM2M_MODEL),
60   - pairwise(),
  60 + filter(() => this.isAdd),
61 61 takeUntil(this.destroy$)
62   - ).subscribe(([previousType, type]) => {
63   - if (previousType === this.resourceType.LWM2M_MODEL) {
64   - this.entityForm.get('title').setValidators(Validators.required);
65   - this.entityForm.get('title').updateValueAndValidity({emitEvent: false});
66   - }
  62 + ).subscribe((type) => {
67 63 if (type === this.resourceType.LWM2M_MODEL) {
68   - this.entityForm.get('title').clearValidators();
69   - this.entityForm.get('title').updateValueAndValidity({emitEvent: false});
  64 + this.entityForm.get('title').disable({emitEvent: false});
  65 + this.entityForm.patchValue({title: ''}, {emitEvent: false});
  66 + } else {
  67 + this.entityForm.get('title').enable({emitEvent: false})
70 68 }
71 69 this.entityForm.patchValue({
72 70 data: null,
... ... @@ -92,11 +90,8 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
92 90 buildForm(entity: Resource): FormGroup {
93 91 const form = this.fb.group(
94 92 {
95   - title: [entity ? entity.title : '', []],
96   - resourceType: [{
97   - value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL,
98   - disabled: !this.isAdd
99   - }, [Validators.required]],
  93 + title: [entity ? entity.title : "", [Validators.required, Validators.maxLength(255)]],
  94 + resourceType: [entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, [Validators.required]],
100 95 fileName: [entity ? entity.fileName : null, [Validators.required]],
101 96 }
102 97 );
... ...
... ... @@ -76,6 +76,9 @@
76 76 <mat-error *ngIf="entityForm.get('name').hasError('required')">
77 77 {{ 'asset.name-required' | translate }}
78 78 </mat-error>
  79 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  80 + {{ 'asset.name-max-length' | translate }}
  81 + </mat-error>
79 82 </mat-form-field>
80 83 <tb-entity-subtype-autocomplete
81 84 formControlName="type"
... ... @@ -86,6 +89,9 @@
86 89 <mat-form-field class="mat-block">
87 90 <mat-label translate>asset.label</mat-label>
88 91 <input matInput formControlName="label">
  92 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  93 + {{ 'asset.label-max-length' | translate }}
  94 + </mat-error>
89 95 </mat-form-field>
90 96 <div formGroupName="additionalInfo">
91 97 <mat-form-field class="mat-block">
... ...
... ... @@ -66,9 +66,9 @@ export class AssetComponent extends EntityComponent<AssetInfo> {
66 66 buildForm(entity: AssetInfo): FormGroup {
67 67 return this.fb.group(
68 68 {
69   - name: [entity ? entity.name : '', [Validators.required]],
70   - type: [entity ? entity.type : null, [Validators.required]],
71   - label: [entity ? entity.label : ''],
  69 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
  70 + type: [entity ? entity.type : null, [Validators.required, Validators.maxLength(255)]],
  71 + label: [entity ? entity.label : '', Validators.maxLength(255)],
72 72 additionalInfo: this.fb.group(
73 73 {
74 74 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
... ...
... ... @@ -73,6 +73,9 @@
73 73 <mat-error *ngIf="entityForm.get('title').hasError('required')">
74 74 {{ 'customer.title-required' | translate }}
75 75 </mat-error>
  76 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  77 + {{ 'customer.title-max-length' | translate }}
  78 + </mat-error>
76 79 </mat-form-field>
77 80 <div formGroupName="additionalInfo" fxLayout="column">
78 81 <mat-form-field class="mat-block">
... ...
... ... @@ -58,7 +58,7 @@ export class CustomerComponent extends ContactBasedComponent<Customer> {
58 58 buildEntityForm(entity: Customer): FormGroup {
59 59 return this.fb.group(
60 60 {
61   - title: [entity ? entity.title : '', [Validators.required]],
  61 + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
62 62 additionalInfo: this.fb.group(
63 63 {
64 64 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
... ...
... ... @@ -104,6 +104,9 @@
104 104 <mat-error *ngIf="entityForm.get('title').hasError('required')">
105 105 {{ 'dashboard.title-required' | translate }}
106 106 </mat-error>
  107 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  108 + {{ 'dashboard.title-max-length' | translate }}
  109 + </mat-error>
107 110 </mat-form-field>
108 111 <div formGroupName="configuration" fxLayout="column">
109 112 <mat-form-field class="mat-block">
... ...
... ... @@ -80,7 +80,7 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
80 80 this.updateFields(entity);
81 81 return this.fb.group(
82 82 {
83   - title: [entity ? entity.title : '', [Validators.required]],
  83 + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
84 84 image: [entity ? entity.image : null],
85 85 mobileHide: [entity ? entity.mobileHide : false],
86 86 mobileOrder: [entity ? entity.mobileOrder : null, [Validators.pattern(/^-?[0-9]+$/)]],
... ...
... ... @@ -89,6 +89,9 @@
89 89 <mat-error *ngIf="entityForm.get('name').hasError('required')">
90 90 {{ 'device.name-required' | translate }}
91 91 </mat-error>
  92 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  93 + {{ 'device.name-max-length' | translate }}
  94 + </mat-error>
92 95 </mat-form-field>
93 96 <tb-device-profile-autocomplete
94 97 [selectDefaultProfile]="isAdd"
... ... @@ -100,6 +103,9 @@
100 103 <mat-form-field class="mat-block">
101 104 <mat-label translate>device.label</mat-label>
102 105 <input matInput formControlName="label">
  106 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  107 + {{ 'device.label-max-length' | translate }}
  108 + </mat-error>
103 109 </mat-form-field>
104 110 <tb-ota-package-autocomplete
105 111 [useFullEntityId]="true"
... ...
... ... @@ -82,11 +82,11 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
82 82 buildForm(entity: DeviceInfo): FormGroup {
83 83 const form = this.fb.group(
84 84 {
85   - name: [entity ? entity.name : '', [Validators.required]],
  85 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
86 86 deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]],
87 87 firmwareId: [entity ? entity.firmwareId : null],
88 88 softwareId: [entity ? entity.softwareId : null],
89   - label: [entity ? entity.label : ''],
  89 + label: [entity ? entity.label : '', [Validators.maxLength(255)]],
90 90 deviceData: [entity ? entity.deviceData : null, [Validators.required]],
91 91 additionalInfo: this.fb.group(
92 92 {
... ...
... ... @@ -126,6 +126,9 @@
126 126 <mat-error *ngIf="entityForm.get('name').hasError('required')">
127 127 {{ 'edge.name-required' | translate }}
128 128 </mat-error>
  129 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  130 + {{ 'edge.name-max-length' | translate }}
  131 + </mat-error>
129 132 </mat-form-field>
130 133 <tb-entity-subtype-autocomplete
131 134 formControlName="type"
... ... @@ -140,6 +143,9 @@
140 143 <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('required')">
141 144 {{ 'edge.edge-license-key-required' | translate }}
142 145 </mat-error>
  146 + <mat-error *ngIf="entityForm.get('type').hasError('maxlength')">
  147 + {{ 'edge.type-max-length' | translate }}
  148 + </mat-error>
143 149 </mat-form-field>
144 150 </div>
145 151 <div [fxShow]="edgeScope !== 'customer_user'">
... ... @@ -179,6 +185,9 @@
179 185 <mat-form-field class="mat-block">
180 186 <mat-label translate>edge.label</mat-label>
181 187 <input matInput formControlName="label">
  188 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  189 + {{ 'edge.label-max-length' | translate }}
  190 + </mat-error>
182 191 </mat-form-field>
183 192 <div formGroupName="additionalInfo" fxLayout="column">
184 193 <mat-form-field class="mat-block">
... ...
... ... @@ -70,9 +70,9 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> {
70 70 buildForm(entity: EdgeInfo): FormGroup {
71 71 const form = this.fb.group(
72 72 {
73   - name: [entity ? entity.name : '', [Validators.required]],
74   - type: [entity?.type ? entity.type : 'default', [Validators.required]],
75   - label: [entity ? entity.label : ''],
  73 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
  74 + type: [entity?.type ? entity.type : 'default', [Validators.required, Validators.maxLength(255)]],
  75 + label: [entity ? entity.label : '', Validators.maxLength(255)],
76 76 cloudEndpoint: [null, [Validators.required]],
77 77 edgeLicenseKey: ['', [Validators.required]],
78 78 routingKey: this.fb.control({value: entity ? entity.routingKey : null, disabled: true}),
... ...
... ... @@ -76,6 +76,9 @@
76 76 <mat-error *ngIf="entityForm.get('name').hasError('required')">
77 77 {{ 'entity-view.name-required' | translate }}
78 78 </mat-error>
  79 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  80 + {{ 'entity-view.name-max-length' | translate }}
  81 + </mat-error>
79 82 </mat-form-field>
80 83 <tb-entity-subtype-autocomplete
81 84 formControlName="type"
... ...
... ... @@ -81,8 +81,8 @@ export class EntityViewComponent extends EntityComponent<EntityViewInfo> {
81 81 buildForm(entity: EntityViewInfo): FormGroup {
82 82 return this.fb.group(
83 83 {
84   - name: [entity ? entity.name : '', [Validators.required]],
85   - type: [entity ? entity.type : null, [Validators.required]],
  84 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
  85 + type: [entity ? entity.type : null, Validators.required],
86 86 entityId: [entity ? entity.entityId : null, [Validators.required]],
87 87 startTimeMs: [entity ? entity.startTimeMs : null],
88 88 endTimeMs: [entity ? entity.endTimeMs : null],
... ...
... ... @@ -65,6 +65,9 @@
65 65 <mat-error *ngIf="entityForm.get('title').hasError('required')">
66 66 {{ 'ota-update.title-required' | translate }}
67 67 </mat-error>
  68 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  69 + {{ 'ota-update.title-max-length' | translate }}
  70 + </mat-error>
68 71 </mat-form-field>
69 72 <mat-form-field class="mat-block" fxFlex>
70 73 <mat-label translate>ota-update.version</mat-label>
... ... @@ -72,6 +75,9 @@
72 75 <mat-error *ngIf="entityForm.get('version').hasError('required')">
73 76 {{ 'ota-update.version-required' | translate }}
74 77 </mat-error>
  78 + <mat-error *ngIf="entityForm.get('version').hasError('maxlength')">
  79 + {{ 'ota-update.version-max-length' | translate }}
  80 + </mat-error>
75 81 </mat-form-field>
76 82 </div>
77 83 <mat-form-field class="mat-block" fxFlex style="margin-bottom: 8px">
... ... @@ -116,7 +122,8 @@
116 122 </mat-checkbox>
117 123 </section>
118 124 <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayoutGap.sm="8px"
119   - fxLayout.xs="column" fxLayout.md="column" *ngIf="!(isAdd && this.entityForm.get('generateChecksum').value)">
  125 + fxLayout.xs="column" fxLayout.md="column"
  126 + *ngIf="!(isAdd && this.entityForm.get('generateChecksum').value)">
120 127 <mat-form-field class="mat-block" fxFlex="33">
121 128 <mat-label translate>ota-update.checksum-algorithm</mat-label>
122 129 <mat-select formControlName="checksumAlgorithm">
... ... @@ -154,7 +161,8 @@
154 161 <input matInput formControlName="url"
155 162 type="text"
156 163 [required]="entityForm.get('isURL').value">
157   - <mat-error *ngIf="entityForm.get('url').hasError('required') || entityForm.get('url').hasError('pattern')" translate>
  164 + <mat-error *ngIf="entityForm.get('url').hasError('required') || entityForm.get('url').hasError('pattern')"
  165 + translate>
158 166 ota-update.direct-url-required
159 167 </mat-error>
160 168 </mat-form-field>
... ...
... ... @@ -84,6 +84,9 @@
84 84 <mat-error *ngIf="entityForm.get('name').hasError('required')">
85 85 {{ 'rulechain.name-required' | translate }}
86 86 </mat-error>
  87 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  88 + {{ 'rulechain.name-max-length' | translate }}
  89 + </mat-error>
87 90 </mat-form-field>
88 91 <mat-checkbox fxFlex formControlName="debugMode" style="padding-bottom: 16px;">
89 92 {{ 'rulechain.debug-mode' | translate }}
... ...
... ... @@ -58,7 +58,7 @@ export class RuleChainComponent extends EntityComponent<RuleChain> {
58 58 buildForm(entity: RuleChain): FormGroup {
59 59 return this.fb.group(
60 60 {
61   - name: [entity ? entity.name : '', [Validators.required]],
  61 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
62 62 debugMode: [entity ? entity.debugMode : false],
63 63 additionalInfo: this.fb.group(
64 64 {
... ...
... ... @@ -48,6 +48,9 @@
48 48 <mat-error *ngIf="entityForm.get('title').hasError('required')">
49 49 {{ 'tenant.title-required' | translate }}
50 50 </mat-error>
  51 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  52 + {{ 'tenant.title-max-length' | translate }}
  53 + </mat-error>
51 54 </mat-form-field>
52 55 <tb-tenant-profile-autocomplete
53 56 [selectDefaultProfile]="isAdd"
... ...
... ... @@ -52,7 +52,7 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
52 52 buildEntityForm(entity: TenantInfo): FormGroup {
53 53 return this.fb.group(
54 54 {
55   - title: [entity ? entity.title : '', [Validators.required]],
  55 + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
56 56 tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]],
57 57 additionalInfo: this.fb.group(
58 58 {
... ...
... ... @@ -44,6 +44,9 @@
44 44 <mat-error *ngIf="entityForm.get('title').hasError('required')">
45 45 {{ 'widgets-bundle.title-required' | translate }}
46 46 </mat-error>
  47 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  48 + {{ 'widgets-bundle.title-max-length' | translate }}
  49 + </mat-error>
47 50 </mat-form-field>
48 51 <tb-image-input fxFlex
49 52 label="{{'widgets-bundle.image-preview' | translate}}"
... ...
... ... @@ -48,7 +48,7 @@ export class WidgetsBundleComponent extends EntityComponent<WidgetsBundle> {
48 48 buildForm(entity: WidgetsBundle): FormGroup {
49 49 return this.fb.group(
50 50 {
51   - title: [entity ? entity.title : '', [Validators.required]],
  51 + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
52 52 image: [entity ? entity.image : ''],
53 53 description: [entity ? entity.description : '', Validators.maxLength(255)]
54 54 }
... ...
... ... @@ -28,10 +28,16 @@
28 28 <mat-form-field class="mat-block">
29 29 <mat-label translate>contact.city</mat-label>
30 30 <input matInput formControlName="city">
  31 + <mat-error *ngIf="parentForm.get('city').hasError('maxlength')">
  32 + {{ 'contact.city-max-length' | translate }}
  33 + </mat-error>
31 34 </mat-form-field>
32 35 <mat-form-field class="mat-block">
33 36 <mat-label translate>contact.state</mat-label>
34 37 <input matInput formControlName="state">
  38 + <mat-error *ngIf="parentForm.get('state').hasError('maxlength')">
  39 + {{ 'contact.state-max-length' | translate }}
  40 + </mat-error>
35 41 </mat-form-field>
36 42 <mat-form-field class="mat-block">
37 43 <mat-label translate>contact.postal-code</mat-label>
... ... @@ -52,6 +58,9 @@
52 58 <mat-form-field class="mat-block">
53 59 <mat-label translate>contact.phone</mat-label>
54 60 <input matInput formControlName="phone">
  61 + <mat-error *ngIf="parentForm.get('phone').hasError('maxlength')">
  62 + {{ 'contact.phone-max-length' | translate }}
  63 + </mat-error>
55 64 </mat-form-field>
56 65 <mat-form-field class="mat-block">
57 66 <mat-label translate>contact.email</mat-label>
... ...
... ... @@ -40,4 +40,7 @@
40 40 <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('required')">
41 41 {{ entitySubtypeRequiredText | translate }}
42 42 </mat-error>
  43 + <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('maxlength')">
  44 + {{ entitySubtypeMaxLength | translate }}
  45 + </mat-error>
43 46 </mat-form-field>
... ...
... ... @@ -15,7 +15,7 @@
15 15 ///
16 16
17 17 import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
18   -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
19 19 import { Observable, of, Subscription, throwError } from 'rxjs';
20 20 import {
21 21 catchError,
... ... @@ -58,9 +58,11 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
58 58 entityType: EntityType;
59 59
60 60 private requiredValue: boolean;
  61 +
61 62 get required(): boolean {
62 63 return this.requiredValue;
63 64 }
  65 +
64 66 @Input()
65 67 set required(value: boolean) {
66 68 this.requiredValue = coerceBooleanProperty(value);
... ... @@ -74,6 +76,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
74 76 selectEntitySubtypeText: string;
75 77 entitySubtypeText: string;
76 78 entitySubtypeRequiredText: string;
  79 + entitySubtypeMaxLength: string;
77 80
78 81 filteredSubTypes: Observable<Array<string>>;
79 82
... ... @@ -96,7 +99,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
96 99 private entityViewService: EntityViewService,
97 100 private fb: FormBuilder) {
98 101 this.subTypeFormGroup = this.fb.group({
99   - subType: [null]
  102 + subType: [null, Validators.maxLength(255)]
100 103 });
101 104 }
102 105
... ... @@ -114,6 +117,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
114 117 this.selectEntitySubtypeText = 'asset.select-asset-type';
115 118 this.entitySubtypeText = 'asset.asset-type';
116 119 this.entitySubtypeRequiredText = 'asset.asset-type-required';
  120 + this.entitySubtypeMaxLength = 'asset.asset-type-max-length';
117 121 this.broadcastSubscription = this.broadcast.on('assetSaved', () => {
118 122 this.subTypes = null;
119 123 });
... ... @@ -122,6 +126,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
122 126 this.selectEntitySubtypeText = 'device.select-device-type';
123 127 this.entitySubtypeText = 'device.device-type';
124 128 this.entitySubtypeRequiredText = 'device.device-type-required';
  129 + this.entitySubtypeMaxLength = 'device.device-type-max-length';
125 130 this.broadcastSubscription = this.broadcast.on('deviceSaved', () => {
126 131 this.subTypes = null;
127 132 });
... ... @@ -130,6 +135,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
130 135 this.selectEntitySubtypeText = 'edge.select-edge-type';
131 136 this.entitySubtypeText = 'edge.edge-type';
132 137 this.entitySubtypeRequiredText = 'edge.edge-type-required';
  138 + this.entitySubtypeMaxLength = 'edge.type-max-length';
133 139 this.broadcastSubscription = this.broadcast.on('edgeSaved', () => {
134 140 this.subTypes = null;
135 141 });
... ... @@ -138,6 +144,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
138 144 this.selectEntitySubtypeText = 'entity-view.select-entity-view-type';
139 145 this.entitySubtypeText = 'entity-view.entity-view-type';
140 146 this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required';
  147 + this.entitySubtypeMaxLength = 'entity-view.type-max-length'
141 148 this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => {
142 149 this.subTypes = null;
143 150 });
... ... @@ -149,7 +156,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
149 156 debounceTime(150),
150 157 distinctUntilChanged(),
151 158 tap(value => {
152   - this.updateView(value);
  159 + this.updateView(value);
153 160 }),
154 161 // startWith<string | EntitySubtype>(''),
155 162 map(value => value ? value : ''),
... ... @@ -203,7 +210,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
203 210 fetchSubTypes(searchText?: string, strictMatch: boolean = false): Observable<Array<string>> {
204 211 this.searchText = searchText;
205 212 return this.getSubTypes().pipe(
206   - map(subTypes => subTypes.filter( subType => {
  213 + map(subTypes => subTypes.filter(subType => {
207 214 if (strictMatch) {
208 215 return searchText ? subType === searchText : false;
209 216 } else {
... ...
... ... @@ -39,4 +39,7 @@
39 39 <mat-error *ngIf="relationTypeFormGroup.get('relationType').hasError('required')">
40 40 {{ 'relation.relation-type-required' | translate }}
41 41 </mat-error>
  42 + <mat-error *ngIf="relationTypeFormGroup.get('relationType').hasError('maxlength')">
  43 + {{ 'relation.relation-type-max-length' | translate }}
  44 + </mat-error>
42 45 </mat-form-field>
... ...
... ... @@ -68,7 +68,7 @@ export class RelationTypeAutocompleteComponent implements ControlValueAccessor,
68 68 public translate: TranslateService,
69 69 private fb: FormBuilder) {
70 70 this.relationTypeFormGroup = this.fb.group({
71   - relationType: [null, this.required ? [Validators.required] : []]
  71 + relationType: [null, this.required ? [Validators.required, Validators.maxLength(255)] : [Validators.maxLength(255)]]
72 72 });
73 73 }
74 74
... ...
... ... @@ -370,6 +370,7 @@
370 370 "management": "Asset management",
371 371 "view-assets": "View Assets",
372 372 "add": "Add Asset",
  373 + "asset-type-max-length": "Asset type should be less than 256",
373 374 "assign-to-customer": "Assign to customer",
374 375 "assign-asset-to-customer": "Assign Asset(s) To Customer",
375 376 "assign-asset-to-customer-text": "Please select the assets to assign to the customer",
... ... @@ -392,6 +393,8 @@
392 393 "asset-types": "Asset types",
393 394 "name": "Name",
394 395 "name-required": "Name is required.",
  396 + "name-max-length": "Name should be less than 256",
  397 + "label-max-length": "Label should be less than 256",
395 398 "description": "Description",
396 399 "type": "Type",
397 400 "type-required": "Type is required.",
... ... @@ -451,6 +454,7 @@
451 454 "scope-shared": "Shared attributes",
452 455 "add": "Add attribute",
453 456 "key": "Key",
  457 + "key-max-length": "Key should be less than 256",
454 458 "last-update-time": "Last update time",
455 459 "key-required": "Attribute key is required.",
456 460 "value": "Value",
... ... @@ -592,7 +596,10 @@
592 596 "address2": "Address 2",
593 597 "phone": "Phone",
594 598 "email": "Email",
595   - "no-address": "No address"
  599 + "no-address": "No address",
  600 + "state-max-length": "State length should be less than 256",
  601 + "phone-max-length": "Phone number should be less than 256",
  602 + "city-max-length": "Specified city should be less than 256"
596 603 },
597 604 "common": {
598 605 "username": "Username",
... ... @@ -648,6 +655,7 @@
648 655 "manage-dashboards": "Manage dashboards",
649 656 "title": "Title",
650 657 "title-required": "Title is required.",
  658 + "title-max-length": "Title should be less than 256",
651 659 "description": "Description",
652 660 "details": "Details",
653 661 "events": "Events",
... ... @@ -705,6 +713,7 @@
705 713 "select-widget-subtitle": "List of available widget types",
706 714 "delete": "Delete dashboard",
707 715 "title-required": "Title is required.",
  716 + "title-max-length": "Title should be less than 256",
708 717 "description": "Description",
709 718 "details": "Details",
710 719 "dashboard-details": "Dashboard details",
... ... @@ -891,6 +900,7 @@
891 900 "management": "Device management",
892 901 "view-devices": "View Devices",
893 902 "device-alias": "Device alias",
  903 + "device-type-max-length": "Device type should be less than 256",
894 904 "aliases": "Device aliases",
895 905 "no-alias-matching": "'{{alias}}' not found.",
896 906 "no-aliases-found": "No aliases found.",
... ... @@ -1003,6 +1013,8 @@
1003 1013 "device-types": "Device types",
1004 1014 "name": "Name",
1005 1015 "name-required": "Name is required.",
  1016 + "name-max-length": "Name should be less than 256",
  1017 + "label-max-length": "Label should be less than 256",
1006 1018 "description": "Description",
1007 1019 "label": "Label",
1008 1020 "events": "Events",
... ... @@ -1055,6 +1067,7 @@
1055 1067 "set-default": "Make device profile default",
1056 1068 "delete": "Delete device profile",
1057 1069 "copyId": "Copy device profile Id",
  1070 + "name-max-length": "Name should be less than 256",
1058 1071 "new-device-profile-name": "Device profile name",
1059 1072 "new-device-profile-name-required": "Device profile name is required.",
1060 1073 "name": "Name",
... ... @@ -1416,6 +1429,9 @@
1416 1429 "edge": "Edge",
1417 1430 "edge-instances": "Edge instances",
1418 1431 "edge-file": "Edge file",
  1432 + "name-max-length": "Name should be less than 256",
  1433 + "label-max-length": "Label should be less than 256",
  1434 + "type-max-length": "Type should be less than 256",
1419 1435 "management": "Edge management",
1420 1436 "no-edges-matching": "No edges matching '{{entity}}' were found.",
1421 1437 "add": "Add Edge",
... ... @@ -1750,6 +1766,8 @@
1750 1766 "created-time": "Created time",
1751 1767 "name": "Name",
1752 1768 "name-required": "Name is required.",
  1769 + "name-max-length": "Name should be less than 256",
  1770 + "type-max-length": "Entity view type should be less than 256",
1753 1771 "description": "Description",
1754 1772 "events": "Events",
1755 1773 "details": "Details",
... ... @@ -2374,6 +2392,7 @@
2374 2392 "selected-package": "{ count, plural, 1 {1 package} other {# packages} } selected",
2375 2393 "title": "Title",
2376 2394 "title-required": "Title is required.",
  2395 + "title-max-length": "Title should be less than 256",
2377 2396 "types": {
2378 2397 "firmware": "Firmware",
2379 2398 "software": "Software"
... ... @@ -2384,6 +2403,7 @@
2384 2403 "version-required": "Version is required.",
2385 2404 "version-tag": "Version Tag",
2386 2405 "version-tag-hint": "Custom tag should match the package version reported by your device.",
  2406 + "version-max-length": "Version should be less than 256",
2387 2407 "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type."
2388 2408 },
2389 2409 "position": {
... ... @@ -2426,6 +2446,7 @@
2426 2446 "delete": "Delete relation",
2427 2447 "relation-type": "Relation type",
2428 2448 "relation-type-required": "Relation type is required.",
  2449 + "relation-type-max-length": "Relation type should be less than 256",
2429 2450 "any-relation-type": "Any type",
2430 2451 "add": "Add relation",
2431 2452 "edit": "Edit relation",
... ... @@ -2470,7 +2491,8 @@
2470 2491 "selected-resources": "{ count, plural, 1 {1 resource} other {# resources} } selected",
2471 2492 "system": "System",
2472 2493 "title": "Title",
2473   - "title-required": "Title is required."
  2494 + "title-required": "Title is required.",
  2495 + "title-max-length": "Title should be less than 256"
2474 2496 },
2475 2497 "rulechain": {
2476 2498 "rulechain": "Rule chain",
... ... @@ -2479,6 +2501,7 @@
2479 2501 "delete": "Delete rule chain",
2480 2502 "name": "Name",
2481 2503 "name-required": "Name is required.",
  2504 + "name-max-length": "Name should be less than 256",
2482 2505 "description": "Description",
2483 2506 "add": "Add Rule Chain",
2484 2507 "set-root": "Make rule chain root",
... ... @@ -2621,6 +2644,7 @@
2621 2644 "add-tenant-text": "Add new tenant",
2622 2645 "no-tenants-text": "No tenants found",
2623 2646 "tenant-details": "Tenant details",
  2647 + "title-max-length": "Title should be less than 256",
2624 2648 "delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?",
2625 2649 "delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.",
2626 2650 "delete-tenants-title": "Are you sure you want to delete { count, plural, 1 {1 tenant} other {# tenants} }?",
... ... @@ -2650,6 +2674,7 @@
2650 2674 "edit": "Edit tenant profile",
2651 2675 "tenant-profile-details": "Tenant profile details",
2652 2676 "no-tenant-profiles-text": "No tenant profiles found",
  2677 + "name-max-length": "Name should be less than 256",
2653 2678 "search": "Search tenant profiles",
2654 2679 "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected",
2655 2680 "no-tenant-profiles-matching": "No tenant profile matching '{{entity}}' were found.",
... ... @@ -2997,6 +3022,7 @@
2997 3022 "delete": "Delete widgets bundle",
2998 3023 "title": "Title",
2999 3024 "title-required": "Title is required.",
  3025 + "title-max-length": "Title should be less than 256",
3000 3026 "description": "Description",
3001 3027 "image-preview": "Image preview",
3002 3028 "add-widgets-bundle-text": "Add new widgets bundle",
... ...