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,6 +76,7 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
76 import org.thingsboard.server.common.data.kv.TsKvEntry; 76 import org.thingsboard.server.common.data.kv.TsKvEntry;
77 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; 77 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
78 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 78 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  79 +import org.thingsboard.server.dao.service.ConstraintValidator;
79 import org.thingsboard.server.dao.timeseries.TimeseriesService; 80 import org.thingsboard.server.dao.timeseries.TimeseriesService;
80 import org.thingsboard.server.queue.util.TbCoreComponent; 81 import org.thingsboard.server.queue.util.TbCoreComponent;
81 import org.thingsboard.server.service.security.AccessValidator; 82 import org.thingsboard.server.service.security.AccessValidator;
@@ -184,7 +185,11 @@ public class TelemetryController extends BaseController { @@ -184,7 +185,11 @@ public class TelemetryController extends BaseController {
184 public DeferredResult<ResponseEntity> getAttributeKeys( 185 public DeferredResult<ResponseEntity> getAttributeKeys(
185 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, 186 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
186 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { 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 @ApiOperation(value = "Get all attribute keys by scope (getAttributeKeysByScope)", 195 @ApiOperation(value = "Get all attribute keys by scope (getAttributeKeysByScope)",
@@ -201,8 +206,12 @@ public class TelemetryController extends BaseController { @@ -201,8 +206,12 @@ public class TelemetryController extends BaseController {
201 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, 206 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
202 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, 207 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
203 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException { 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 @ApiOperation(value = "Get attributes (getAttributes)", 217 @ApiOperation(value = "Get attributes (getAttributes)",
@@ -220,9 +229,13 @@ public class TelemetryController extends BaseController { @@ -220,9 +229,13 @@ public class TelemetryController extends BaseController {
220 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, 229 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
221 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, 230 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
222 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { 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,9 +257,13 @@ public class TelemetryController extends BaseController {
244 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, 257 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
245 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, 258 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
246 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { 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 @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)", 269 @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)",
@@ -259,8 +276,12 @@ public class TelemetryController extends BaseController { @@ -259,8 +276,12 @@ public class TelemetryController extends BaseController {
259 public DeferredResult<ResponseEntity> getTimeseriesKeys( 276 public DeferredResult<ResponseEntity> getTimeseriesKeys(
260 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, 277 @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
261 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { 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 @ApiOperation(value = "Get latest time-series value (getLatestTimeseries)", 287 @ApiOperation(value = "Get latest time-series value (getLatestTimeseries)",
@@ -285,9 +306,13 @@ public class TelemetryController extends BaseController { @@ -285,9 +306,13 @@ public class TelemetryController extends BaseController {
285 @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr, 306 @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr,
286 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) 307 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION)
287 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { 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 @ApiOperation(value = "Get time-series data (getTimeseries)", 318 @ApiOperation(value = "Get time-series data (getTimeseries)",
@@ -324,15 +349,19 @@ public class TelemetryController extends BaseController { @@ -324,15 +349,19 @@ public class TelemetryController extends BaseController {
324 @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy, 349 @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy,
325 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) 350 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION)
326 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { 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 @ApiOperation(value = "Save device attributes (saveDeviceAttributes)", 367 @ApiOperation(value = "Save device attributes (saveDeviceAttributes)",
@@ -356,8 +385,12 @@ public class TelemetryController extends BaseController { @@ -356,8 +385,12 @@ public class TelemetryController extends BaseController {
356 @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr, 385 @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
357 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, 386 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
358 @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { 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 @ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)", 396 @ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)",
@@ -380,8 +413,12 @@ public class TelemetryController extends BaseController { @@ -380,8 +413,12 @@ public class TelemetryController extends BaseController {
380 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, 413 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
381 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, 414 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
382 @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { 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 @ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)", 424 @ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)",
@@ -404,8 +441,12 @@ public class TelemetryController extends BaseController { @@ -404,8 +441,12 @@ public class TelemetryController extends BaseController {
404 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, 441 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
405 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, 442 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
406 @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { 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,8 +470,12 @@ public class TelemetryController extends BaseController {
429 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, 470 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
430 @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope, 471 @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope,
431 @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException { 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 @ApiOperation(value = "Save or update time-series data with TTL (saveEntityTelemetryWithTTL)", 481 @ApiOperation(value = "Save or update time-series data with TTL (saveEntityTelemetryWithTTL)",
@@ -455,8 +500,12 @@ public class TelemetryController extends BaseController { @@ -455,8 +500,12 @@ public class TelemetryController extends BaseController {
455 @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope, 500 @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope,
456 @ApiParam(value = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl, 501 @ApiParam(value = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl,
457 @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException { 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 @ApiOperation(value = "Delete entity time-series data (deleteEntityTimeseries)", 511 @ApiOperation(value = "Delete entity time-series data (deleteEntityTimeseries)",
@@ -489,8 +538,12 @@ public class TelemetryController extends BaseController { @@ -489,8 +538,12 @@ public class TelemetryController extends BaseController {
489 @RequestParam(name = "endTs", required = false) Long endTs, 538 @RequestParam(name = "endTs", required = false) Long endTs,
490 @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.") 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 @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException { 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 private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys, 549 private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys,
@@ -556,8 +609,12 @@ public class TelemetryController extends BaseController { @@ -556,8 +609,12 @@ public class TelemetryController extends BaseController {
556 @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr, 609 @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
557 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, 610 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
558 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { 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 @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)", 620 @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)",
@@ -580,8 +637,12 @@ public class TelemetryController extends BaseController { @@ -580,8 +637,12 @@ public class TelemetryController extends BaseController {
580 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, 637 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
581 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, 638 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
582 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { 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 private DeferredResult<ResponseEntity> deleteAttributes(EntityId entityIdSrc, String scope, String keysStr) throws ThingsboardException { 648 private DeferredResult<ResponseEntity> deleteAttributes(EntityId entityIdSrc, String scope, String keysStr) throws ThingsboardException {
@@ -627,6 +688,7 @@ public class TelemetryController extends BaseController { @@ -627,6 +688,7 @@ public class TelemetryController extends BaseController {
627 } 688 }
628 if (json.isObject()) { 689 if (json.isObject()) {
629 List<AttributeKvEntry> attributes = extractRequestAttributes(json); 690 List<AttributeKvEntry> attributes = extractRequestAttributes(json);
  691 + attributes.forEach(ConstraintValidator::validateFields);
630 if (attributes.isEmpty()) { 692 if (attributes.isEmpty()) {
631 return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); 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,6 +100,20 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
100 } 100 }
101 101
102 @Test 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 public void testUpdateAssetFromDifferentTenant() throws Exception { 117 public void testUpdateAssetFromDifferentTenant() throws Exception {
104 Asset asset = new Asset(); 118 Asset asset = new Asset();
105 asset.setName("My asset"); 119 asset.setName("My asset");
@@ -91,6 +91,28 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @@ -91,6 +91,28 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
91 } 91 }
92 92
93 @Test 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 public void testUpdateCustomerFromDifferentTenant() throws Exception { 116 public void testUpdateCustomerFromDifferentTenant() throws Exception {
95 Customer customer = new Customer(); 117 Customer customer = new Customer();
96 customer.setTitle("My customer"); 118 customer.setTitle("My customer");
@@ -94,6 +94,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -94,6 +94,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
94 } 94 }
95 95
96 @Test 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 public void testUpdateDashboardFromDifferentTenant() throws Exception { 104 public void testUpdateDashboardFromDifferentTenant() throws Exception {
98 Dashboard dashboard = new Dashboard(); 105 Dashboard dashboard = new Dashboard();
99 dashboard.setTitle("My dashboard"); 106 dashboard.setTitle("My dashboard");
@@ -115,6 +115,20 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -115,6 +115,20 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
115 } 115 }
116 116
117 @Test 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 public void testUpdateDeviceFromDifferentTenant() throws Exception { 132 public void testUpdateDeviceFromDifferentTenant() throws Exception {
119 Device device = new Device(); 133 Device device = new Device();
120 device.setName("My device"); 134 device.setName("My device");
@@ -22,6 +22,7 @@ import com.google.protobuf.DynamicMessage; @@ -22,6 +22,7 @@ import com.google.protobuf.DynamicMessage;
22 import com.google.protobuf.InvalidProtocolBufferException; 22 import com.google.protobuf.InvalidProtocolBufferException;
23 import com.google.protobuf.util.JsonFormat; 23 import com.google.protobuf.util.JsonFormat;
24 import com.squareup.wire.schema.internal.parser.ProtoFileElement; 24 import com.squareup.wire.schema.internal.parser.ProtoFileElement;
  25 +import org.apache.commons.lang3.RandomStringUtils;
25 import org.junit.After; 26 import org.junit.After;
26 import org.junit.Assert; 27 import org.junit.Assert;
27 import org.junit.Before; 28 import org.junit.Before;
@@ -110,6 +111,12 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @@ -110,6 +111,12 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
110 } 111 }
111 112
112 @Test 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 public void testFindDeviceProfileById() throws Exception { 120 public void testFindDeviceProfileById() throws Exception {
114 DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); 121 DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null);
115 DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); 122 DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
@@ -48,6 +48,7 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; @@ -48,6 +48,7 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
48 import java.util.ArrayList; 48 import java.util.ArrayList;
49 import java.util.Collections; 49 import java.util.Collections;
50 import java.util.List; 50 import java.util.List;
  51 +import java.util.Random;
51 52
52 import static org.hamcrest.Matchers.containsString; 53 import static org.hamcrest.Matchers.containsString;
53 import static org.hamcrest.Matchers.nullValue; 54 import static org.hamcrest.Matchers.nullValue;
@@ -113,6 +114,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { @@ -113,6 +114,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
113 } 114 }
114 115
115 @Test 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 public void testFindEdgeById() throws Exception { 129 public void testFindEdgeById() throws Exception {
117 Edge edge = constructEdge("My edge", "default"); 130 Edge edge = constructEdge("My edge", "default");
118 Edge savedEdge = doPost("/api/edge", edge, Edge.class); 131 Edge savedEdge = doPost("/api/edge", edge, Edge.class);
@@ -133,6 +133,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -133,6 +133,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
133 assertEquals(foundEntityView.getKeys(), telemetry); 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 @Test 145 @Test
138 public void testUpdateEntityViewFromDifferentTenant() throws Exception { 146 public void testUpdateEntityViewFromDifferentTenant() throws Exception {
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
18 import com.fasterxml.jackson.core.type.TypeReference; 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 import org.junit.After; 20 import org.junit.After;
20 import org.junit.Assert; 21 import org.junit.Assert;
21 import org.junit.Before; 22 import org.junit.Before;
@@ -40,6 +41,7 @@ import java.util.ArrayList; @@ -40,6 +41,7 @@ import java.util.ArrayList;
40 import java.util.Collections; 41 import java.util.Collections;
41 import java.util.List; 42 import java.util.List;
42 43
  44 +import static org.hamcrest.Matchers.containsString;
43 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 45 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
44 import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; 46 import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
45 47
@@ -118,6 +120,24 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -118,6 +120,24 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
118 } 120 }
119 121
120 @Test 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 public void testSaveFirmwareData() throws Exception { 141 public void testSaveFirmwareData() throws Exception {
122 SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); 142 SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
123 firmwareInfo.setDeviceProfileId(deviceProfileId); 143 firmwareInfo.setDeviceProfileId(deviceProfileId);
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
18 import com.fasterxml.jackson.core.type.TypeReference; 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 import org.junit.After; 20 import org.junit.After;
20 import org.junit.Assert; 21 import org.junit.Assert;
21 import org.junit.Before; 22 import org.junit.Before;
@@ -33,6 +34,7 @@ import java.util.ArrayList; @@ -33,6 +34,7 @@ import java.util.ArrayList;
33 import java.util.Collections; 34 import java.util.Collections;
34 import java.util.List; 35 import java.util.List;
35 36
  37 +import static org.hamcrest.Matchers.containsString;
36 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 38 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
37 39
38 public abstract class BaseRuleChainControllerTest extends AbstractControllerTest { 40 public abstract class BaseRuleChainControllerTest extends AbstractControllerTest {
@@ -85,6 +87,13 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest @@ -85,6 +87,13 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
85 } 87 }
86 88
87 @Test 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 public void testFindRuleChainById() throws Exception { 97 public void testFindRuleChainById() throws Exception {
89 RuleChain ruleChain = new RuleChain(); 98 RuleChain ruleChain = new RuleChain();
90 ruleChain.setName("RuleChain"); 99 ruleChain.setName("RuleChain");
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
18 import com.fasterxml.jackson.core.type.TypeReference; 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 import org.junit.After; 20 import org.junit.After;
20 import org.junit.Assert; 21 import org.junit.Assert;
21 import org.junit.Before; 22 import org.junit.Before;
@@ -33,6 +34,7 @@ import java.util.ArrayList; @@ -33,6 +34,7 @@ import java.util.ArrayList;
33 import java.util.Collections; 34 import java.util.Collections;
34 import java.util.List; 35 import java.util.List;
35 36
  37 +import static org.hamcrest.Matchers.containsString;
36 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 38 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
37 39
38 public abstract class BaseTbResourceControllerTest extends AbstractControllerTest { 40 public abstract class BaseTbResourceControllerTest extends AbstractControllerTest {
@@ -99,6 +101,16 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes @@ -99,6 +101,16 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes
99 } 101 }
100 102
101 @Test 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 public void testUpdateTbResourceFromDifferentTenant() throws Exception { 114 public void testUpdateTbResourceFromDifferentTenant() throws Exception {
103 TbResource resource = new TbResource(); 115 TbResource resource = new TbResource();
104 resource.setResourceType(ResourceType.JKS); 116 resource.setResourceType(ResourceType.JKS);
@@ -52,6 +52,14 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @@ -52,6 +52,14 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
52 doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) 52 doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
53 .andExpect(status().isOk()); 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 @Test 64 @Test
57 public void testFindTenantById() throws Exception { 65 public void testFindTenantById() throws Exception {
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
18 import com.fasterxml.jackson.core.type.TypeReference; 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 import org.junit.After; 20 import org.junit.After;
20 import org.junit.Assert; 21 import org.junit.Assert;
21 import org.junit.Test; 22 import org.junit.Test;
@@ -75,6 +76,13 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController @@ -75,6 +76,13 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController
75 } 76 }
76 77
77 @Test 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 public void testFindTenantProfileById() throws Exception { 86 public void testFindTenantProfileById() throws Exception {
79 loginSysAdmin(); 87 loginSysAdmin();
80 TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"); 88 TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
@@ -106,6 +106,28 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -106,6 +106,28 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
106 } 106 }
107 107
108 @Test 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 public void testUpdateUserFromDifferentTenant() throws Exception { 131 public void testUpdateUserFromDifferentTenant() throws Exception {
110 loginSysAdmin(); 132 loginSysAdmin();
111 Tenant tenant = new Tenant(); 133 Tenant tenant = new Tenant();
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
18 import com.fasterxml.jackson.core.type.TypeReference; 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 import org.junit.After; 20 import org.junit.After;
20 import org.junit.Assert; 21 import org.junit.Assert;
21 import org.junit.Before; 22 import org.junit.Before;
@@ -88,6 +89,13 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController @@ -88,6 +89,13 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
88 Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); 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 @Test 99 @Test
92 public void testUpdateWidgetsBundleFromDifferentTenant() throws Exception { 100 public void testUpdateWidgetsBundleFromDifferentTenant() throws Exception {
93 WidgetsBundle widgetsBundle = new WidgetsBundle(); 101 WidgetsBundle widgetsBundle = new WidgetsBundle();
@@ -17,30 +17,36 @@ package org.thingsboard.server.common.data; @@ -17,30 +17,36 @@ package org.thingsboard.server.common.data;
17 17
18 import lombok.EqualsAndHashCode; 18 import lombok.EqualsAndHashCode;
19 import org.thingsboard.server.common.data.id.UUIDBased; 19 import org.thingsboard.server.common.data.id.UUIDBased;
  20 +import org.thingsboard.server.common.data.validation.Length;
20 import org.thingsboard.server.common.data.validation.NoXss; 21 import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 @EqualsAndHashCode(callSuper = true) 23 @EqualsAndHashCode(callSuper = true)
23 public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName { 24 public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
24 - 25 +
25 private static final long serialVersionUID = 5047448057830660988L; 26 private static final long serialVersionUID = 5047448057830660988L;
26 27
  28 + @Length(fieldName = "country")
27 @NoXss 29 @NoXss
28 protected String country; 30 protected String country;
  31 + @Length(fieldName = "state")
29 @NoXss 32 @NoXss
30 protected String state; 33 protected String state;
  34 + @Length(fieldName = "city")
31 @NoXss 35 @NoXss
32 protected String city; 36 protected String city;
33 @NoXss 37 @NoXss
34 protected String address; 38 protected String address;
35 @NoXss 39 @NoXss
36 protected String address2; 40 protected String address2;
  41 + @Length(fieldName = "zip or postal code")
37 @NoXss 42 @NoXss
38 protected String zip; 43 protected String zip;
  44 + @Length(fieldName = "phone")
39 @NoXss 45 @NoXss
40 protected String phone; 46 protected String phone;
41 @NoXss 47 @NoXss
42 protected String email; 48 protected String email;
43 - 49 +
44 public ContactBased() { 50 public ContactBased() {
45 super(); 51 super();
46 } 52 }
@@ -48,7 +54,7 @@ public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedW @@ -48,7 +54,7 @@ public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedW
48 public ContactBased(I id) { 54 public ContactBased(I id) {
49 super(id); 55 super(id);
50 } 56 }
51 - 57 +
52 public ContactBased(ContactBased<I> contact) { 58 public ContactBased(ContactBased<I> contact) {
53 super(contact); 59 super(contact);
54 this.country = contact.getCountry(); 60 this.country = contact.getCountry();
@@ -22,13 +22,15 @@ import com.fasterxml.jackson.databind.JsonNode; @@ -22,13 +22,15 @@ import com.fasterxml.jackson.databind.JsonNode;
22 import io.swagger.annotations.ApiModelProperty; 22 import io.swagger.annotations.ApiModelProperty;
23 import org.thingsboard.server.common.data.id.CustomerId; 23 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.TenantId; 24 import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.data.validation.Length;
25 import org.thingsboard.server.common.data.validation.NoXss; 26 import org.thingsboard.server.common.data.validation.NoXss;
26 27
27 public class Customer extends ContactBased<CustomerId> implements HasTenantId { 28 public class Customer extends ContactBased<CustomerId> implements HasTenantId {
28 - 29 +
29 private static final long serialVersionUID = -1599722990298929275L; 30 private static final long serialVersionUID = -1599722990298929275L;
30 31
31 @NoXss 32 @NoXss
  33 + @Length(fieldName = "title")
32 @ApiModelProperty(position = 3, value = "Title of the customer", example = "Company A") 34 @ApiModelProperty(position = 3, value = "Title of the customer", example = "Company A")
33 private String title; 35 private String title;
34 @ApiModelProperty(position = 5, required = true, value = "JSON object with Tenant Id") 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,7 +43,7 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId {
41 public Customer(CustomerId id) { 43 public Customer(CustomerId id) {
42 super(id); 44 super(id);
43 } 45 }
44 - 46 +
45 public Customer(Customer customer) { 47 public Customer(Customer customer) {
46 super(customer); 48 super(customer);
47 this.tenantId = customer.getTenantId(); 49 this.tenantId = customer.getTenantId();
@@ -21,6 +21,7 @@ import io.swagger.annotations.ApiModelProperty; @@ -21,6 +21,7 @@ import io.swagger.annotations.ApiModelProperty;
21 import org.thingsboard.server.common.data.id.CustomerId; 21 import org.thingsboard.server.common.data.id.CustomerId;
22 import org.thingsboard.server.common.data.id.DashboardId; 22 import org.thingsboard.server.common.data.id.DashboardId;
23 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.validation.Length;
24 import org.thingsboard.server.common.data.validation.NoXss; 25 import org.thingsboard.server.common.data.validation.NoXss;
25 26
26 import javax.validation.Valid; 27 import javax.validation.Valid;
@@ -32,6 +33,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -32,6 +33,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
32 33
33 private TenantId tenantId; 34 private TenantId tenantId;
34 @NoXss 35 @NoXss
  36 + @Length(fieldName = "title")
35 private String title; 37 private String title;
36 private String image; 38 private String image;
37 @Valid 39 @Valid
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
28 import org.thingsboard.server.common.data.id.DeviceProfileId; 28 import org.thingsboard.server.common.data.id.DeviceProfileId;
29 import org.thingsboard.server.common.data.id.OtaPackageId; 29 import org.thingsboard.server.common.data.id.OtaPackageId;
30 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 import org.thingsboard.server.common.data.validation.NoXss; 32 import org.thingsboard.server.common.data.validation.NoXss;
32 33
33 import java.io.ByteArrayInputStream; 34 import java.io.ByteArrayInputStream;
@@ -44,10 +45,13 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -44,10 +45,13 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
44 private TenantId tenantId; 45 private TenantId tenantId;
45 private CustomerId customerId; 46 private CustomerId customerId;
46 @NoXss 47 @NoXss
  48 + @Length(fieldName = "name")
47 private String name; 49 private String name;
48 @NoXss 50 @NoXss
  51 + @Length(fieldName = "type")
49 private String type; 52 private String type;
50 @NoXss 53 @NoXss
  54 + @Length(fieldName = "label")
51 private String label; 55 private String label;
52 private DeviceProfileId deviceProfileId; 56 private DeviceProfileId deviceProfileId;
53 private transient DeviceData deviceData; 57 private transient DeviceData deviceData;
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
28 import org.thingsboard.server.common.data.id.OtaPackageId; 28 import org.thingsboard.server.common.data.id.OtaPackageId;
29 import org.thingsboard.server.common.data.id.RuleChainId; 29 import org.thingsboard.server.common.data.id.RuleChainId;
30 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 import org.thingsboard.server.common.data.validation.NoXss; 32 import org.thingsboard.server.common.data.validation.NoXss;
32 33
33 import javax.validation.Valid; 34 import javax.validation.Valid;
@@ -47,6 +48,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H @@ -47,6 +48,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
47 @ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", readOnly = true) 48 @ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", readOnly = true)
48 private TenantId tenantId; 49 private TenantId tenantId;
49 @NoXss 50 @NoXss
  51 + @Length(fieldName = "name")
50 @ApiModelProperty(position = 4, value = "Unique Device Profile Name in scope of Tenant.", example = "Moisture Sensor") 52 @ApiModelProperty(position = 4, value = "Unique Device Profile Name in scope of Tenant.", example = "Moisture Sensor")
51 private String name; 53 private String name;
52 @NoXss 54 @NoXss
@@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.EntityId; @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.EntityId;
25 import org.thingsboard.server.common.data.id.EntityViewId; 25 import org.thingsboard.server.common.data.id.EntityViewId;
26 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
27 import org.thingsboard.server.common.data.objects.TelemetryEntityView; 27 import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  28 +import org.thingsboard.server.common.data.validation.Length;
28 import org.thingsboard.server.common.data.validation.NoXss; 29 import org.thingsboard.server.common.data.validation.NoXss;
29 30
30 /** 31 /**
@@ -44,9 +45,11 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId> @@ -44,9 +45,11 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
44 private TenantId tenantId; 45 private TenantId tenantId;
45 private CustomerId customerId; 46 private CustomerId customerId;
46 @NoXss 47 @NoXss
  48 + @Length(fieldName = "name")
47 @ApiModelProperty(position = 5, required = true, value = "Entity View name", example = "A4B72CCDFF33") 49 @ApiModelProperty(position = 5, required = true, value = "Entity View name", example = "A4B72CCDFF33")
48 private String name; 50 private String name;
49 @NoXss 51 @NoXss
  52 + @Length(fieldName = "type")
50 @ApiModelProperty(position = 6, required = true, value = "Device Profile Name", example = "Temperature Sensor") 53 @ApiModelProperty(position = 6, required = true, value = "Device Profile Name", example = "Temperature Sensor")
51 private String type; 54 private String type;
52 @ApiModelProperty(position = 8, required = true, value = "Set of telemetry and attribute keys to expose via Entity View.") 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,6 +27,10 @@ import org.thingsboard.server.common.data.id.OtaPackageId;
27 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
28 import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; 28 import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
29 import org.thingsboard.server.common.data.ota.OtaPackageType; 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 @ApiModel 35 @ApiModel
32 @Slf4j 36 @Slf4j
@@ -42,16 +46,26 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage @@ -42,16 +46,26 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
42 private DeviceProfileId deviceProfileId; 46 private DeviceProfileId deviceProfileId;
43 @ApiModelProperty(position = 5, value = "OTA Package type.", example = "FIRMWARE", readOnly = true) 47 @ApiModelProperty(position = 5, value = "OTA Package type.", example = "FIRMWARE", readOnly = true)
44 private OtaPackageType type; 48 private OtaPackageType type;
  49 + @Length(fieldName = "title")
  50 + @NoXss
45 @ApiModelProperty(position = 6, value = "OTA Package title.", example = "fw", readOnly = true) 51 @ApiModelProperty(position = 6, value = "OTA Package title.", example = "fw", readOnly = true)
46 private String title; 52 private String title;
  53 + @Length(fieldName = "version")
  54 + @NoXss
47 @ApiModelProperty(position = 7, value = "OTA Package version.", example = "1.0", readOnly = true) 55 @ApiModelProperty(position = 7, value = "OTA Package version.", example = "1.0", readOnly = true)
48 private String version; 56 private String version;
  57 + @Length(fieldName = "tag")
  58 + @NoXss
49 @ApiModelProperty(position = 8, value = "OTA Package tag.", example = "fw_1.0", readOnly = true) 59 @ApiModelProperty(position = 8, value = "OTA Package tag.", example = "fw_1.0", readOnly = true)
50 private String tag; 60 private String tag;
  61 + @Length(fieldName = "url")
  62 + @NoXss
51 @ApiModelProperty(position = 9, value = "OTA Package url.", example = "http://thingsboard.org/fw/1", readOnly = true) 63 @ApiModelProperty(position = 9, value = "OTA Package url.", example = "http://thingsboard.org/fw/1", readOnly = true)
52 private String url; 64 private String url;
53 @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) 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 private boolean hasData; 66 private boolean hasData;
  67 + @Length(fieldName = "file name")
  68 + @NoXss
55 @ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", readOnly = true) 69 @ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", readOnly = true)
56 private String fileName; 70 private String fileName;
57 @ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", readOnly = true) 71 @ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", readOnly = true)
@@ -20,6 +20,7 @@ import lombok.Data; @@ -20,6 +20,7 @@ import lombok.Data;
20 import lombok.EqualsAndHashCode; 20 import lombok.EqualsAndHashCode;
21 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
22 import org.thingsboard.server.common.data.id.TbResourceId; 22 import org.thingsboard.server.common.data.id.TbResourceId;
  23 +import org.thingsboard.server.common.data.validation.Length;
23 import org.thingsboard.server.common.data.validation.NoXss; 24 import org.thingsboard.server.common.data.validation.NoXss;
24 25
25 @Slf4j 26 @Slf4j
@@ -30,6 +31,7 @@ public class TbResource extends TbResourceInfo { @@ -30,6 +31,7 @@ public class TbResource extends TbResourceInfo {
30 private static final long serialVersionUID = 7379609705527272306L; 31 private static final long serialVersionUID = 7379609705527272306L;
31 32
32 @NoXss 33 @NoXss
  34 + @Length(fieldName = "file name")
33 @ApiModelProperty(position = 8, value = "Resource file name.", example = "19.xml", readOnly = true) 35 @ApiModelProperty(position = 8, value = "Resource file name.", example = "19.xml", readOnly = true)
34 private String fileName; 36 private String fileName;
35 37
@@ -23,6 +23,7 @@ import lombok.EqualsAndHashCode; @@ -23,6 +23,7 @@ import lombok.EqualsAndHashCode;
23 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
24 import org.thingsboard.server.common.data.id.TbResourceId; 24 import org.thingsboard.server.common.data.id.TbResourceId;
25 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.validation.Length;
26 import org.thingsboard.server.common.data.validation.NoXss; 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 @ApiModel 29 @ApiModel
@@ -36,6 +37,7 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has @@ -36,6 +37,7 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
36 @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", readOnly = true) 37 @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", readOnly = true)
37 private TenantId tenantId; 38 private TenantId tenantId;
38 @NoXss 39 @NoXss
  40 + @Length(fieldName = "title")
39 @ApiModelProperty(position = 4, value = "Resource title.", example = "BinaryAppDataContainer id=19 v1.0") 41 @ApiModelProperty(position = 4, value = "Resource title.", example = "BinaryAppDataContainer id=19 v1.0")
40 private String title; 42 private String title;
41 @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true) 43 @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true)
@@ -23,6 +23,7 @@ import io.swagger.annotations.ApiModelProperty; @@ -23,6 +23,7 @@ import io.swagger.annotations.ApiModelProperty;
23 import lombok.EqualsAndHashCode; 23 import lombok.EqualsAndHashCode;
24 import org.thingsboard.server.common.data.id.TenantId; 24 import org.thingsboard.server.common.data.id.TenantId;
25 import org.thingsboard.server.common.data.id.TenantProfileId; 25 import org.thingsboard.server.common.data.id.TenantProfileId;
  26 +import org.thingsboard.server.common.data.validation.Length;
26 import org.thingsboard.server.common.data.validation.NoXss; 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 @ApiModel 29 @ApiModel
@@ -31,6 +32,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId { @@ -31,6 +32,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
31 32
32 private static final long serialVersionUID = 8057243243859922101L; 33 private static final long serialVersionUID = 8057243243859922101L;
33 34
  35 + @Length(fieldName = "title")
34 @NoXss 36 @NoXss
35 @ApiModelProperty(position = 3, value = "Title of the tenant", example = "Company A") 37 @ApiModelProperty(position = 3, value = "Title of the tenant", example = "Company A")
36 private String title; 38 private String title;
@@ -48,7 +50,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId { @@ -48,7 +50,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
48 public Tenant(TenantId id) { 50 public Tenant(TenantId id) {
49 super(id); 51 super(id);
50 } 52 }
51 - 53 +
52 public Tenant(Tenant tenant) { 54 public Tenant(Tenant tenant) {
53 super(tenant); 55 super(tenant);
54 this.title = tenant.getTitle(); 56 this.title = tenant.getTitle();
@@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j; @@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
25 import org.thingsboard.server.common.data.id.TenantProfileId; 25 import org.thingsboard.server.common.data.id.TenantProfileId;
26 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; 26 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
27 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; 27 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
  28 +import org.thingsboard.server.common.data.validation.Length;
28 import org.thingsboard.server.common.data.validation.NoXss; 29 import org.thingsboard.server.common.data.validation.NoXss;
29 30
30 import java.io.ByteArrayInputStream; 31 import java.io.ByteArrayInputStream;
@@ -42,6 +43,7 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H @@ -42,6 +43,7 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H
42 private static final long serialVersionUID = 3021989561267192281L; 43 private static final long serialVersionUID = 3021989561267192281L;
43 44
44 @NoXss 45 @NoXss
  46 + @Length(fieldName = "name")
45 @ApiModelProperty(position = 3, value = "Name of the tenant profile", example = "High Priority Tenants") 47 @ApiModelProperty(position = 3, value = "Name of the tenant profile", example = "High Priority Tenants")
46 private String name; 48 private String name;
47 @NoXss 49 @NoXss
@@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.EntityId; @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.EntityId;
26 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
27 import org.thingsboard.server.common.data.id.UserId; 27 import org.thingsboard.server.common.data.id.UserId;
28 import org.thingsboard.server.common.data.security.Authority; 28 import org.thingsboard.server.common.data.security.Authority;
29 - 29 +import org.thingsboard.server.common.data.validation.Length;
30 import org.thingsboard.server.common.data.validation.NoXss; 30 import org.thingsboard.server.common.data.validation.NoXss;
31 31
32 @ApiModel 32 @ApiModel
@@ -40,8 +40,10 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H @@ -40,8 +40,10 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
40 private String email; 40 private String email;
41 private Authority authority; 41 private Authority authority;
42 @NoXss 42 @NoXss
  43 + @Length(fieldName = "first name")
43 private String firstName; 44 private String firstName;
44 @NoXss 45 @NoXss
  46 + @Length(fieldName = "last name")
45 private String lastName; 47 private String lastName;
46 48
47 public User() { 49 public User() {
@@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.AlarmId; @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
30 import org.thingsboard.server.common.data.id.CustomerId; 30 import org.thingsboard.server.common.data.id.CustomerId;
31 import org.thingsboard.server.common.data.id.EntityId; 31 import org.thingsboard.server.common.data.id.EntityId;
32 import org.thingsboard.server.common.data.id.TenantId; 32 import org.thingsboard.server.common.data.id.TenantId;
  33 +import org.thingsboard.server.common.data.validation.Length;
33 34
34 import java.util.List; 35 import java.util.List;
35 36
@@ -49,6 +50,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha @@ -49,6 +50,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
49 private CustomerId customerId; 50 private CustomerId customerId;
50 51
51 @ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") 52 @ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
  53 + @Length(fieldName = "type")
52 private String type; 54 private String type;
53 @ApiModelProperty(position = 7, required = true, value = "JSON object with alarm originator id") 55 @ApiModelProperty(position = 7, required = true, value = "JSON object with alarm originator id")
54 private EntityId originator; 56 private EntityId originator;
@@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
26 import org.thingsboard.server.common.data.id.AssetId; 26 import org.thingsboard.server.common.data.id.AssetId;
27 import org.thingsboard.server.common.data.id.CustomerId; 27 import org.thingsboard.server.common.data.id.CustomerId;
28 import org.thingsboard.server.common.data.id.TenantId; 28 import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.validation.Length;
29 import org.thingsboard.server.common.data.validation.NoXss; 30 import org.thingsboard.server.common.data.validation.NoXss;
30 31
31 import java.util.Optional; 32 import java.util.Optional;
@@ -39,10 +40,13 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements @@ -39,10 +40,13 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
39 private TenantId tenantId; 40 private TenantId tenantId;
40 private CustomerId customerId; 41 private CustomerId customerId;
41 @NoXss 42 @NoXss
  43 + @Length(fieldName = "name")
42 private String name; 44 private String name;
43 @NoXss 45 @NoXss
  46 + @Length(fieldName = "type")
44 private String type; 47 private String type;
45 @NoXss 48 @NoXss
  49 + @Length(fieldName = "label")
46 private String label; 50 private String label;
47 51
48 public Asset() { 52 public Asset() {
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
28 import org.thingsboard.server.common.data.id.EdgeId; 28 import org.thingsboard.server.common.data.id.EdgeId;
29 import org.thingsboard.server.common.data.id.RuleChainId; 29 import org.thingsboard.server.common.data.id.RuleChainId;
30 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 32
32 @ApiModel 33 @ApiModel
33 @EqualsAndHashCode(callSuper = true) 34 @EqualsAndHashCode(callSuper = true)
@@ -40,8 +41,11 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H @@ -40,8 +41,11 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
40 private TenantId tenantId; 41 private TenantId tenantId;
41 private CustomerId customerId; 42 private CustomerId customerId;
42 private RuleChainId rootRuleChainId; 43 private RuleChainId rootRuleChainId;
  44 + @Length(fieldName = "name")
43 private String name; 45 private String name;
  46 + @Length(fieldName = "type")
44 private String type; 47 private String type;
  48 + @Length(fieldName = "label")
45 private String label; 49 private String label;
46 private String routingKey; 50 private String routingKey;
47 private String secret; 51 private String secret;
@@ -15,8 +15,7 @@ @@ -15,8 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.kv; 16 package org.thingsboard.server.common.data.kv;
17 17
18 -import com.fasterxml.jackson.databind.JsonNode;  
19 - 18 +import javax.validation.Valid;
20 import java.util.Optional; 19 import java.util.Optional;
21 20
22 /** 21 /**
@@ -27,6 +26,7 @@ public class BaseAttributeKvEntry implements AttributeKvEntry { @@ -27,6 +26,7 @@ public class BaseAttributeKvEntry implements AttributeKvEntry {
27 private static final long serialVersionUID = -6460767583563159407L; 26 private static final long serialVersionUID = -6460767583563159407L;
28 27
29 private final long lastUpdateTs; 28 private final long lastUpdateTs;
  29 + @Valid
30 private final KvEntry kv; 30 private final KvEntry kv;
31 31
32 public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs) { 32 public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs) {
@@ -15,11 +15,14 @@ @@ -15,11 +15,14 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.kv; 16 package org.thingsboard.server.common.data.kv;
17 17
  18 +import org.thingsboard.server.common.data.validation.Length;
  19 +
18 import java.util.Objects; 20 import java.util.Objects;
19 import java.util.Optional; 21 import java.util.Optional;
20 22
21 public abstract class BasicKvEntry implements KvEntry { 23 public abstract class BasicKvEntry implements KvEntry {
22 24
  25 + @Length(fieldName = "attribute key")
23 private final String key; 26 private final String key;
24 27
25 protected BasicKvEntry(String key) { 28 protected BasicKvEntry(String key) {
@@ -21,6 +21,7 @@ import java.util.Optional; @@ -21,6 +21,7 @@ import java.util.Optional;
21 public class StringDataEntry extends BasicKvEntry { 21 public class StringDataEntry extends BasicKvEntry {
22 22
23 private static final long serialVersionUID = 1L; 23 private static final long serialVersionUID = 1L;
  24 +
24 private final String value; 25 private final String value;
25 26
26 public StringDataEntry(String key, String value) { 27 public StringDataEntry(String key, String value) {
@@ -22,6 +22,7 @@ import io.swagger.annotations.ApiModelProperty; @@ -22,6 +22,7 @@ import io.swagger.annotations.ApiModelProperty;
22 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
23 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; 23 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
24 import org.thingsboard.server.common.data.id.EntityId; 24 import org.thingsboard.server.common.data.id.EntityId;
  25 +import org.thingsboard.server.common.data.validation.Length;
25 26
26 import java.io.Serializable; 27 import java.io.Serializable;
27 28
@@ -37,6 +38,7 @@ public class EntityRelation implements Serializable { @@ -37,6 +38,7 @@ public class EntityRelation implements Serializable {
37 38
38 private EntityId from; 39 private EntityId from;
39 private EntityId to; 40 private EntityId to;
  41 + @Length(fieldName = "type")
40 private String type; 42 private String type;
41 private RelationTypeGroup typeGroup; 43 private RelationTypeGroup typeGroup;
42 private transient JsonNode additionalInfo; 44 private transient JsonNode additionalInfo;
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
28 import org.thingsboard.server.common.data.id.RuleChainId; 28 import org.thingsboard.server.common.data.id.RuleChainId;
29 import org.thingsboard.server.common.data.id.RuleNodeId; 29 import org.thingsboard.server.common.data.id.RuleNodeId;
30 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 import org.thingsboard.server.common.data.validation.NoXss; 32 import org.thingsboard.server.common.data.validation.NoXss;
32 33
33 @ApiModel 34 @ApiModel
@@ -41,6 +42,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im @@ -41,6 +42,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
41 @ApiModelProperty(position = 3, required = true, value = "JSON object with Tenant Id.", readOnly = true) 42 @ApiModelProperty(position = 3, required = true, value = "JSON object with Tenant Id.", readOnly = true)
42 private TenantId tenantId; 43 private TenantId tenantId;
43 @NoXss 44 @NoXss
  45 + @Length(fieldName = "name")
44 @ApiModelProperty(position = 4, required = true, value = "Rule Chain name", example = "Humidity data processing") 46 @ApiModelProperty(position = 4, required = true, value = "Rule Chain name", example = "Humidity data processing")
45 private String name; 47 private String name;
46 @ApiModelProperty(position = 5, value = "Rule Chain type. 'EDGE' rule chains are processing messages on the edge devices only.", example = "A4B72CCDFF33") 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,6 +26,7 @@ import org.thingsboard.server.common.data.HasName;
26 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; 26 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
27 import org.thingsboard.server.common.data.id.RuleChainId; 27 import org.thingsboard.server.common.data.id.RuleChainId;
28 import org.thingsboard.server.common.data.id.RuleNodeId; 28 import org.thingsboard.server.common.data.id.RuleNodeId;
  29 +import org.thingsboard.server.common.data.validation.Length;
29 30
30 @ApiModel 31 @ApiModel
31 @Data 32 @Data
@@ -37,8 +38,10 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl @@ -37,8 +38,10 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
37 38
38 @ApiModelProperty(position = 3, value = "JSON object with the Rule Chain Id. ", readOnly = true) 39 @ApiModelProperty(position = 3, value = "JSON object with the Rule Chain Id. ", readOnly = true)
39 private RuleChainId ruleChainId; 40 private RuleChainId ruleChainId;
  41 + @Length(fieldName = "type")
40 @ApiModelProperty(position = 4, value = "Full Java Class Name of the rule node implementation. ", example = "com.mycompany.iot.rule.engine.ProcessingNode") 42 @ApiModelProperty(position = 4, value = "Full Java Class Name of the rule node implementation. ", example = "com.mycompany.iot.rule.engine.ProcessingNode")
41 private String type; 43 private String type;
  44 + @Length(fieldName = "name")
42 @ApiModelProperty(position = 5, value = "User defined name of the rule node. Used on UI and for logging. ", example = "Process sensor reading") 45 @ApiModelProperty(position = 5, value = "User defined name of the rule node. Used on UI and for logging. ", example = "Process sensor reading")
43 private String name; 46 private String name;
44 @ApiModelProperty(position = 6, value = "Enable/disable debug. ", example = "false") 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,10 +23,9 @@ import org.thingsboard.server.common.data.HasTenantId;
23 import org.thingsboard.server.common.data.SearchTextBased; 23 import org.thingsboard.server.common.data.SearchTextBased;
24 import org.thingsboard.server.common.data.id.TenantId; 24 import org.thingsboard.server.common.data.id.TenantId;
25 import org.thingsboard.server.common.data.id.WidgetsBundleId; 25 import org.thingsboard.server.common.data.id.WidgetsBundleId;
  26 +import org.thingsboard.server.common.data.validation.Length;
26 import org.thingsboard.server.common.data.validation.NoXss; 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 -import java.util.Arrays;  
29 -  
30 @ApiModel 29 @ApiModel
31 public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasTenantId { 30 public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasTenantId {
32 31
@@ -38,12 +37,14 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H @@ -38,12 +37,14 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
38 private TenantId tenantId; 37 private TenantId tenantId;
39 38
40 @NoXss 39 @NoXss
  40 + @Length(fieldName = "alias")
41 @Getter 41 @Getter
42 @Setter 42 @Setter
43 @ApiModelProperty(position = 4, value = "Unique alias that is used in widget types as a reference widget bundle", readOnly = true) 43 @ApiModelProperty(position = 4, value = "Unique alias that is used in widget types as a reference widget bundle", readOnly = true)
44 private String alias; 44 private String alias;
45 45
46 @NoXss 46 @NoXss
  47 + @Length(fieldName = "title")
47 @Getter 48 @Getter
48 @Setter 49 @Setter
49 @ApiModelProperty(position = 5, value = "Title used in search and UI", readOnly = true) 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,6 +56,7 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
55 private String image; 56 private String image;
56 57
57 @NoXss 58 @NoXss
  59 + @Length(fieldName = "description")
58 @Getter 60 @Getter
59 @Setter 61 @Setter
60 @ApiModelProperty(position = 7, value = "Description", readOnly = true) 62 @ApiModelProperty(position = 7, value = "Description", readOnly = true)
@@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
41 import org.thingsboard.server.common.data.relation.RelationsSearchParameters; 41 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
42 import org.thingsboard.server.dao.entity.EntityService; 42 import org.thingsboard.server.dao.entity.EntityService;
43 import org.thingsboard.server.dao.exception.DataValidationException; 43 import org.thingsboard.server.dao.exception.DataValidationException;
  44 +import org.thingsboard.server.dao.service.ConstraintValidator;
44 45
45 import javax.annotation.Nullable; 46 import javax.annotation.Nullable;
46 import java.util.ArrayList; 47 import java.util.ArrayList;
@@ -559,6 +560,7 @@ public class BaseRelationService implements RelationService { @@ -559,6 +560,7 @@ public class BaseRelationService implements RelationService {
559 if (relation == null) { 560 if (relation == null) {
560 throw new DataValidationException("Relation type should be specified!"); 561 throw new DataValidationException("Relation type should be specified!");
561 } 562 }
  563 + ConstraintValidator.validateFields(relation);
562 validate(relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup()); 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,51 +17,32 @@ package org.thingsboard.server.dao.service;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 import lombok.extern.slf4j.Slf4j; 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 import org.thingsboard.server.common.data.BaseData; 20 import org.thingsboard.server.common.data.BaseData;
24 import org.thingsboard.server.common.data.EntityType; 21 import org.thingsboard.server.common.data.EntityType;
25 import org.thingsboard.server.common.data.id.TenantId; 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 import org.thingsboard.server.dao.TenantEntityDao; 23 import org.thingsboard.server.dao.TenantEntityDao;
29 import org.thingsboard.server.dao.TenantEntityWithDataDao; 24 import org.thingsboard.server.dao.TenantEntityWithDataDao;
30 import org.thingsboard.server.dao.exception.DataValidationException; 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 import java.util.HashSet; 27 import java.util.HashSet;
36 import java.util.Iterator; 28 import java.util.Iterator;
37 -import java.util.List;  
38 import java.util.Set; 29 import java.util.Set;
39 import java.util.function.Function; 30 import java.util.function.Function;
40 import java.util.regex.Matcher; 31 import java.util.regex.Matcher;
41 import java.util.regex.Pattern; 32 import java.util.regex.Pattern;
42 -import java.util.stream.Collectors;  
43 33
44 @Slf4j 34 @Slf4j
45 public abstract class DataValidator<D extends BaseData<?>> { 35 public abstract class DataValidator<D extends BaseData<?>> {
46 private static final Pattern EMAIL_PATTERN = 36 private static final Pattern EMAIL_PATTERN =
47 Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE); 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 public void validate(D data, Function<D, TenantId> tenantIdFunction) { 39 public void validate(D data, Function<D, TenantId> tenantIdFunction) {
56 try { 40 try {
57 if (data == null) { 41 if (data == null) {
58 throw new DataValidationException("Data object can't be null!"); 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 TenantId tenantId = tenantIdFunction.apply(data); 47 TenantId tenantId = tenantIdFunction.apply(data);
67 validateDataImpl(tenantId, data); 48 validateDataImpl(tenantId, data);
@@ -104,14 +85,6 @@ public abstract class DataValidator<D extends BaseData<?>> { @@ -104,14 +85,6 @@ public abstract class DataValidator<D extends BaseData<?>> {
104 return emailMatcher.matches(); 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 protected void validateNumberOfEntitiesPerTenant(TenantId tenantId, 88 protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
116 TenantEntityDao tenantEntityDao, 89 TenantEntityDao tenantEntityDao,
117 long maxEntities, 90 long maxEntities,
@@ -126,10 +99,10 @@ public abstract class DataValidator<D extends BaseData<?>> { @@ -126,10 +99,10 @@ public abstract class DataValidator<D extends BaseData<?>> {
126 } 99 }
127 100
128 protected void validateMaxSumDataSizePerTenant(TenantId tenantId, 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 if (maxSumDataSize > 0) { 106 if (maxSumDataSize > 0) {
134 if (dataDao.sumDataSizeByTenantId(tenantId) + currentDataSize > maxSumDataSize) { 107 if (dataDao.sumDataSizeByTenantId(tenantId) + currentDataSize > maxSumDataSize) {
135 throw new DataValidationException(String.format("Failed to create the %s, files size limit is exhausted %d bytes!", 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,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,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageLink;
38 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; 38 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
39 import org.thingsboard.server.dao.exception.DataValidationException; 39 import org.thingsboard.server.dao.exception.DataValidationException;
40 40
  41 +import javax.validation.ValidationException;
41 import java.nio.ByteBuffer; 42 import java.nio.ByteBuffer;
42 import java.util.ArrayList; 43 import java.util.ArrayList;
43 import java.util.Collections; 44 import java.util.Collections;
@@ -673,8 +674,8 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -673,8 +674,8 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
673 firmwareInfo.setUrl(URL); 674 firmwareInfo.setUrl(URL);
674 firmwareInfo.setTenantId(tenantId); 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 otaPackageService.saveOtaPackageInfo(firmwareInfo, true); 680 otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
680 } 681 }
@@ -689,7 +690,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -689,7 +690,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
689 firmwareInfo.setTitle(TITLE); 690 firmwareInfo.setTitle(TITLE);
690 691
691 firmwareInfo.setVersion(RandomStringUtils.random(257)); 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 otaPackageService.saveOtaPackageInfo(firmwareInfo, true); 695 otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
695 } 696 }
@@ -36,6 +36,9 @@ @@ -36,6 +36,9 @@
36 <mat-error *ngIf="attributeFormGroup.get('key').hasError('required')"> 36 <mat-error *ngIf="attributeFormGroup.get('key').hasError('required')">
37 {{ 'attribute.key-required' | translate }} 37 {{ 'attribute.key-required' | translate }}
38 </mat-error> 38 </mat-error>
  39 + <mat-error *ngIf="attributeFormGroup.get('key').hasError('maxlength')">
  40 + {{ 'attribute.key-max-length' | translate }}
  41 + </mat-error>
39 </mat-form-field> 42 </mat-form-field>
40 <tb-value-input 43 <tb-value-input
41 formControlName="value" 44 formControlName="value"
@@ -56,7 +56,7 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia @@ -56,7 +56,7 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
56 56
57 ngOnInit(): void { 57 ngOnInit(): void {
58 this.attributeFormGroup = this.fb.group({ 58 this.attributeFormGroup = this.fb.group({
59 - key: ['', [Validators.required]], 59 + key: ['', [Validators.required, Validators.maxLength(255)]],
60 value: [null, [Validators.required]] 60 value: [null, [Validators.required]]
61 }); 61 });
62 } 62 }
@@ -37,15 +37,15 @@ export abstract class ContactBasedComponent<T extends ContactBased<HasId>> exten @@ -37,15 +37,15 @@ export abstract class ContactBasedComponent<T extends ContactBased<HasId>> exten
37 37
38 buildForm(entity: T): FormGroup { 38 buildForm(entity: T): FormGroup {
39 const entityForm = this.buildEntityForm(entity); 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 entityForm.addControl('zip', this.fb.control(entity ? entity.zip : '', 43 entityForm.addControl('zip', this.fb.control(entity ? entity.zip : '',
44 this.zipValidators(entity ? entity.country : '') 44 this.zipValidators(entity ? entity.country : '')
45 )); 45 ));
46 entityForm.addControl('address', this.fb.control(entity ? entity.address : '', [])); 46 entityForm.addControl('address', this.fb.control(entity ? entity.address : '', []));
47 entityForm.addControl('address2', this.fb.control(entity ? entity.address2 : '', [])); 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 entityForm.addControl('email', this.fb.control(entity ? entity.email : '', [Validators.email])); 49 entityForm.addControl('email', this.fb.control(entity ? entity.email : '', [Validators.email]));
50 return entityForm; 50 return entityForm;
51 } 51 }
@@ -41,6 +41,9 @@ @@ -41,6 +41,9 @@
41 <mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('required')"> 41 <mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('required')">
42 {{ 'device-profile.name-required' | translate }} 42 {{ 'device-profile.name-required' | translate }}
43 </mat-error> 43 </mat-error>
  44 + <mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('maxlength')">
  45 + {{ 'device-profile.name-max-length' | translate }}
  46 + </mat-error>
44 </mat-form-field> 47 </mat-form-field>
45 <tb-rule-chain-autocomplete 48 <tb-rule-chain-autocomplete
46 labelText="device-profile.default-rule-chain" 49 labelText="device-profile.default-rule-chain"
@@ -105,7 +105,7 @@ export class AddDeviceProfileDialogComponent extends @@ -105,7 +105,7 @@ export class AddDeviceProfileDialogComponent extends
105 super(store, router, dialogRef); 105 super(store, router, dialogRef);
106 this.deviceProfileDetailsFormGroup = this.fb.group( 106 this.deviceProfileDetailsFormGroup = this.fb.group(
107 { 107 {
108 - name: [data.deviceProfileName, [Validators.required]], 108 + name: [data.deviceProfileName, [Validators.required, Validators.maxLength(255)]],
109 type: [DeviceProfileType.DEFAULT, [Validators.required]], 109 type: [DeviceProfileType.DEFAULT, [Validators.required]],
110 image: [null, []], 110 image: [null, []],
111 defaultRuleChainId: [null, []], 111 defaultRuleChainId: [null, []],
@@ -54,6 +54,9 @@ @@ -54,6 +54,9 @@
54 <mat-error *ngIf="entityForm.get('name').hasError('required')"> 54 <mat-error *ngIf="entityForm.get('name').hasError('required')">
55 {{ 'device-profile.name-required' | translate }} 55 {{ 'device-profile.name-required' | translate }}
56 </mat-error> 56 </mat-error>
  57 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  58 + {{ 'device-profile.name-max-length' | translate }}
  59 + </mat-error>
57 </mat-form-field> 60 </mat-form-field>
58 <tb-rule-chain-autocomplete 61 <tb-rule-chain-autocomplete
59 labelText="device-profile.default-rule-chain" 62 labelText="device-profile.default-rule-chain"
@@ -103,7 +103,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -103,7 +103,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
103 }; 103 };
104 const form = this.fb.group( 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 type: [entity ? entity.type : null, [Validators.required]], 107 type: [entity ? entity.type : null, [Validators.required]],
108 image: [entity ? entity.image : null], 108 image: [entity ? entity.image : null],
109 transportType: [entity ? entity.transportType : null, [Validators.required]], 109 transportType: [entity ? entity.transportType : null, [Validators.required]],
@@ -48,6 +48,9 @@ @@ -48,6 +48,9 @@
48 <mat-error *ngIf="entityForm.get('name').hasError('required')"> 48 <mat-error *ngIf="entityForm.get('name').hasError('required')">
49 {{ 'tenant-profile.name-required' | translate }} 49 {{ 'tenant-profile.name-required' | translate }}
50 </mat-error> 50 </mat-error>
  51 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  52 + {{ 'tenant-profile.name-max-length' | translate }}
  53 + </mat-error>
51 </mat-form-field> 54 </mat-form-field>
52 <div fxLayout="column"> 55 <div fxLayout="column">
53 <mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore"> 56 <mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore">
@@ -59,7 +59,7 @@ export class TenantProfileComponent extends EntityComponent<TenantProfile> { @@ -59,7 +59,7 @@ export class TenantProfileComponent extends EntityComponent<TenantProfile> {
59 buildForm(entity: TenantProfile): FormGroup { 59 buildForm(entity: TenantProfile): FormGroup {
60 return this.fb.group( 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 isolatedTbCore: [entity ? entity.isolatedTbCore : false, []], 63 isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
64 isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], 64 isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
65 profileData: [entity && !this.isAdd ? entity.profileData : { 65 profileData: [entity && !this.isAdd ? entity.profileData : {
@@ -44,10 +44,16 @@ @@ -44,10 +44,16 @@
44 <mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('required')"> 44 <mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('required')">
45 {{ 'device.name-required' | translate }} 45 {{ 'device.name-required' | translate }}
46 </mat-error> 46 </mat-error>
  47 + <mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('maxlength')">
  48 + {{ 'device.name-max-length' | translate }}
  49 + </mat-error>
47 </mat-form-field> 50 </mat-form-field>
48 <mat-form-field class="mat-block"> 51 <mat-form-field class="mat-block">
49 <mat-label translate>device.label</mat-label> 52 <mat-label translate>device.label</mat-label>
50 <input matInput formControlName="label"> 53 <input matInput formControlName="label">
  54 + <mat-error *ngIf="deviceWizardFormGroup.get('label').hasError('maxlength')">
  55 + {{ 'device.label-max-length' | translate }}
  56 + </mat-error>
51 </mat-form-field> 57 </mat-form-field>
52 <div fxLayout="row" fxLayoutGap="16px"> 58 <div fxLayout="row" fxLayoutGap="16px">
53 <mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around"> 59 <mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around">
@@ -105,8 +105,8 @@ export class DeviceWizardDialogComponent extends @@ -105,8 +105,8 @@ export class DeviceWizardDialogComponent extends
105 private fb: FormBuilder) { 105 private fb: FormBuilder) {
106 super(store, router, dialogRef); 106 super(store, router, dialogRef);
107 this.deviceWizardFormGroup = this.fb.group({ 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 gateway: [false], 110 gateway: [false],
111 overwriteActivityTime: [false], 111 overwriteActivityTime: [false],
112 addProfileType: [0], 112 addProfileType: [0],
@@ -56,6 +56,9 @@ @@ -56,6 +56,9 @@
56 <mat-error *ngIf="entityForm.get('title').hasError('required')"> 56 <mat-error *ngIf="entityForm.get('title').hasError('required')">
57 {{ 'resource.title-required' | translate }} 57 {{ 'resource.title-required' | translate }}
58 </mat-error> 58 </mat-error>
  59 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  60 + {{ 'resource.title-max-length' | translate }}
  61 + </mat-error>
59 </mat-form-field> 62 </mat-form-field>
60 <tb-file-input *ngIf="isAdd" 63 <tb-file-input *ngIf="isAdd"
61 formControlName="data" 64 formControlName="data"
@@ -29,7 +29,7 @@ import { @@ -29,7 +29,7 @@ import {
29 ResourceTypeMIMETypes, 29 ResourceTypeMIMETypes,
30 ResourceTypeTranslationMap 30 ResourceTypeTranslationMap
31 } from '@shared/models/resource.models'; 31 } from '@shared/models/resource.models';
32 -import { pairwise, startWith, takeUntil } from 'rxjs/operators'; 32 +import {filter, pairwise, startWith, takeUntil} from 'rxjs/operators';
33 import { ActionNotificationShow } from '@core/notification/notification.actions'; 33 import { ActionNotificationShow } from '@core/notification/notification.actions';
34 34
35 @Component({ 35 @Component({
@@ -57,16 +57,14 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme @@ -57,16 +57,14 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
57 super.ngOnInit(); 57 super.ngOnInit();
58 this.entityForm.get('resourceType').valueChanges.pipe( 58 this.entityForm.get('resourceType').valueChanges.pipe(
59 startWith(ResourceType.LWM2M_MODEL), 59 startWith(ResourceType.LWM2M_MODEL),
60 - pairwise(), 60 + filter(() => this.isAdd),
61 takeUntil(this.destroy$) 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 if (type === this.resourceType.LWM2M_MODEL) { 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 this.entityForm.patchValue({ 69 this.entityForm.patchValue({
72 data: null, 70 data: null,
@@ -92,11 +90,8 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme @@ -92,11 +90,8 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
92 buildForm(entity: Resource): FormGroup { 90 buildForm(entity: Resource): FormGroup {
93 const form = this.fb.group( 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 fileName: [entity ? entity.fileName : null, [Validators.required]], 95 fileName: [entity ? entity.fileName : null, [Validators.required]],
101 } 96 }
102 ); 97 );
@@ -76,6 +76,9 @@ @@ -76,6 +76,9 @@
76 <mat-error *ngIf="entityForm.get('name').hasError('required')"> 76 <mat-error *ngIf="entityForm.get('name').hasError('required')">
77 {{ 'asset.name-required' | translate }} 77 {{ 'asset.name-required' | translate }}
78 </mat-error> 78 </mat-error>
  79 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  80 + {{ 'asset.name-max-length' | translate }}
  81 + </mat-error>
79 </mat-form-field> 82 </mat-form-field>
80 <tb-entity-subtype-autocomplete 83 <tb-entity-subtype-autocomplete
81 formControlName="type" 84 formControlName="type"
@@ -86,6 +89,9 @@ @@ -86,6 +89,9 @@
86 <mat-form-field class="mat-block"> 89 <mat-form-field class="mat-block">
87 <mat-label translate>asset.label</mat-label> 90 <mat-label translate>asset.label</mat-label>
88 <input matInput formControlName="label"> 91 <input matInput formControlName="label">
  92 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  93 + {{ 'asset.label-max-length' | translate }}
  94 + </mat-error>
89 </mat-form-field> 95 </mat-form-field>
90 <div formGroupName="additionalInfo"> 96 <div formGroupName="additionalInfo">
91 <mat-form-field class="mat-block"> 97 <mat-form-field class="mat-block">
@@ -66,9 +66,9 @@ export class AssetComponent extends EntityComponent<AssetInfo> { @@ -66,9 +66,9 @@ export class AssetComponent extends EntityComponent<AssetInfo> {
66 buildForm(entity: AssetInfo): FormGroup { 66 buildForm(entity: AssetInfo): FormGroup {
67 return this.fb.group( 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 additionalInfo: this.fb.group( 72 additionalInfo: this.fb.group(
73 { 73 {
74 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], 74 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
@@ -73,6 +73,9 @@ @@ -73,6 +73,9 @@
73 <mat-error *ngIf="entityForm.get('title').hasError('required')"> 73 <mat-error *ngIf="entityForm.get('title').hasError('required')">
74 {{ 'customer.title-required' | translate }} 74 {{ 'customer.title-required' | translate }}
75 </mat-error> 75 </mat-error>
  76 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  77 + {{ 'customer.title-max-length' | translate }}
  78 + </mat-error>
76 </mat-form-field> 79 </mat-form-field>
77 <div formGroupName="additionalInfo" fxLayout="column"> 80 <div formGroupName="additionalInfo" fxLayout="column">
78 <mat-form-field class="mat-block"> 81 <mat-form-field class="mat-block">
@@ -58,7 +58,7 @@ export class CustomerComponent extends ContactBasedComponent<Customer> { @@ -58,7 +58,7 @@ export class CustomerComponent extends ContactBasedComponent<Customer> {
58 buildEntityForm(entity: Customer): FormGroup { 58 buildEntityForm(entity: Customer): FormGroup {
59 return this.fb.group( 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 additionalInfo: this.fb.group( 62 additionalInfo: this.fb.group(
63 { 63 {
64 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], 64 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
@@ -104,6 +104,9 @@ @@ -104,6 +104,9 @@
104 <mat-error *ngIf="entityForm.get('title').hasError('required')"> 104 <mat-error *ngIf="entityForm.get('title').hasError('required')">
105 {{ 'dashboard.title-required' | translate }} 105 {{ 'dashboard.title-required' | translate }}
106 </mat-error> 106 </mat-error>
  107 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  108 + {{ 'dashboard.title-max-length' | translate }}
  109 + </mat-error>
107 </mat-form-field> 110 </mat-form-field>
108 <div formGroupName="configuration" fxLayout="column"> 111 <div formGroupName="configuration" fxLayout="column">
109 <mat-form-field class="mat-block"> 112 <mat-form-field class="mat-block">
@@ -80,7 +80,7 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> { @@ -80,7 +80,7 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
80 this.updateFields(entity); 80 this.updateFields(entity);
81 return this.fb.group( 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 image: [entity ? entity.image : null], 84 image: [entity ? entity.image : null],
85 mobileHide: [entity ? entity.mobileHide : false], 85 mobileHide: [entity ? entity.mobileHide : false],
86 mobileOrder: [entity ? entity.mobileOrder : null, [Validators.pattern(/^-?[0-9]+$/)]], 86 mobileOrder: [entity ? entity.mobileOrder : null, [Validators.pattern(/^-?[0-9]+$/)]],
@@ -89,6 +89,9 @@ @@ -89,6 +89,9 @@
89 <mat-error *ngIf="entityForm.get('name').hasError('required')"> 89 <mat-error *ngIf="entityForm.get('name').hasError('required')">
90 {{ 'device.name-required' | translate }} 90 {{ 'device.name-required' | translate }}
91 </mat-error> 91 </mat-error>
  92 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  93 + {{ 'device.name-max-length' | translate }}
  94 + </mat-error>
92 </mat-form-field> 95 </mat-form-field>
93 <tb-device-profile-autocomplete 96 <tb-device-profile-autocomplete
94 [selectDefaultProfile]="isAdd" 97 [selectDefaultProfile]="isAdd"
@@ -100,6 +103,9 @@ @@ -100,6 +103,9 @@
100 <mat-form-field class="mat-block"> 103 <mat-form-field class="mat-block">
101 <mat-label translate>device.label</mat-label> 104 <mat-label translate>device.label</mat-label>
102 <input matInput formControlName="label"> 105 <input matInput formControlName="label">
  106 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  107 + {{ 'device.label-max-length' | translate }}
  108 + </mat-error>
103 </mat-form-field> 109 </mat-form-field>
104 <tb-ota-package-autocomplete 110 <tb-ota-package-autocomplete
105 [useFullEntityId]="true" 111 [useFullEntityId]="true"
@@ -82,11 +82,11 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { @@ -82,11 +82,11 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
82 buildForm(entity: DeviceInfo): FormGroup { 82 buildForm(entity: DeviceInfo): FormGroup {
83 const form = this.fb.group( 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 deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]], 86 deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]],
87 firmwareId: [entity ? entity.firmwareId : null], 87 firmwareId: [entity ? entity.firmwareId : null],
88 softwareId: [entity ? entity.softwareId : null], 88 softwareId: [entity ? entity.softwareId : null],
89 - label: [entity ? entity.label : ''], 89 + label: [entity ? entity.label : '', [Validators.maxLength(255)]],
90 deviceData: [entity ? entity.deviceData : null, [Validators.required]], 90 deviceData: [entity ? entity.deviceData : null, [Validators.required]],
91 additionalInfo: this.fb.group( 91 additionalInfo: this.fb.group(
92 { 92 {
@@ -126,6 +126,9 @@ @@ -126,6 +126,9 @@
126 <mat-error *ngIf="entityForm.get('name').hasError('required')"> 126 <mat-error *ngIf="entityForm.get('name').hasError('required')">
127 {{ 'edge.name-required' | translate }} 127 {{ 'edge.name-required' | translate }}
128 </mat-error> 128 </mat-error>
  129 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  130 + {{ 'edge.name-max-length' | translate }}
  131 + </mat-error>
129 </mat-form-field> 132 </mat-form-field>
130 <tb-entity-subtype-autocomplete 133 <tb-entity-subtype-autocomplete
131 formControlName="type" 134 formControlName="type"
@@ -140,6 +143,9 @@ @@ -140,6 +143,9 @@
140 <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('required')"> 143 <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('required')">
141 {{ 'edge.edge-license-key-required' | translate }} 144 {{ 'edge.edge-license-key-required' | translate }}
142 </mat-error> 145 </mat-error>
  146 + <mat-error *ngIf="entityForm.get('type').hasError('maxlength')">
  147 + {{ 'edge.type-max-length' | translate }}
  148 + </mat-error>
143 </mat-form-field> 149 </mat-form-field>
144 </div> 150 </div>
145 <div [fxShow]="edgeScope !== 'customer_user'"> 151 <div [fxShow]="edgeScope !== 'customer_user'">
@@ -179,6 +185,9 @@ @@ -179,6 +185,9 @@
179 <mat-form-field class="mat-block"> 185 <mat-form-field class="mat-block">
180 <mat-label translate>edge.label</mat-label> 186 <mat-label translate>edge.label</mat-label>
181 <input matInput formControlName="label"> 187 <input matInput formControlName="label">
  188 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  189 + {{ 'edge.label-max-length' | translate }}
  190 + </mat-error>
182 </mat-form-field> 191 </mat-form-field>
183 <div formGroupName="additionalInfo" fxLayout="column"> 192 <div formGroupName="additionalInfo" fxLayout="column">
184 <mat-form-field class="mat-block"> 193 <mat-form-field class="mat-block">
@@ -70,9 +70,9 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> { @@ -70,9 +70,9 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> {
70 buildForm(entity: EdgeInfo): FormGroup { 70 buildForm(entity: EdgeInfo): FormGroup {
71 const form = this.fb.group( 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 cloudEndpoint: [null, [Validators.required]], 76 cloudEndpoint: [null, [Validators.required]],
77 edgeLicenseKey: ['', [Validators.required]], 77 edgeLicenseKey: ['', [Validators.required]],
78 routingKey: this.fb.control({value: entity ? entity.routingKey : null, disabled: true}), 78 routingKey: this.fb.control({value: entity ? entity.routingKey : null, disabled: true}),
@@ -76,6 +76,9 @@ @@ -76,6 +76,9 @@
76 <mat-error *ngIf="entityForm.get('name').hasError('required')"> 76 <mat-error *ngIf="entityForm.get('name').hasError('required')">
77 {{ 'entity-view.name-required' | translate }} 77 {{ 'entity-view.name-required' | translate }}
78 </mat-error> 78 </mat-error>
  79 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  80 + {{ 'entity-view.name-max-length' | translate }}
  81 + </mat-error>
79 </mat-form-field> 82 </mat-form-field>
80 <tb-entity-subtype-autocomplete 83 <tb-entity-subtype-autocomplete
81 formControlName="type" 84 formControlName="type"
@@ -81,8 +81,8 @@ export class EntityViewComponent extends EntityComponent<EntityViewInfo> { @@ -81,8 +81,8 @@ export class EntityViewComponent extends EntityComponent<EntityViewInfo> {
81 buildForm(entity: EntityViewInfo): FormGroup { 81 buildForm(entity: EntityViewInfo): FormGroup {
82 return this.fb.group( 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 entityId: [entity ? entity.entityId : null, [Validators.required]], 86 entityId: [entity ? entity.entityId : null, [Validators.required]],
87 startTimeMs: [entity ? entity.startTimeMs : null], 87 startTimeMs: [entity ? entity.startTimeMs : null],
88 endTimeMs: [entity ? entity.endTimeMs : null], 88 endTimeMs: [entity ? entity.endTimeMs : null],
@@ -65,6 +65,9 @@ @@ -65,6 +65,9 @@
65 <mat-error *ngIf="entityForm.get('title').hasError('required')"> 65 <mat-error *ngIf="entityForm.get('title').hasError('required')">
66 {{ 'ota-update.title-required' | translate }} 66 {{ 'ota-update.title-required' | translate }}
67 </mat-error> 67 </mat-error>
  68 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  69 + {{ 'ota-update.title-max-length' | translate }}
  70 + </mat-error>
68 </mat-form-field> 71 </mat-form-field>
69 <mat-form-field class="mat-block" fxFlex> 72 <mat-form-field class="mat-block" fxFlex>
70 <mat-label translate>ota-update.version</mat-label> 73 <mat-label translate>ota-update.version</mat-label>
@@ -72,6 +75,9 @@ @@ -72,6 +75,9 @@
72 <mat-error *ngIf="entityForm.get('version').hasError('required')"> 75 <mat-error *ngIf="entityForm.get('version').hasError('required')">
73 {{ 'ota-update.version-required' | translate }} 76 {{ 'ota-update.version-required' | translate }}
74 </mat-error> 77 </mat-error>
  78 + <mat-error *ngIf="entityForm.get('version').hasError('maxlength')">
  79 + {{ 'ota-update.version-max-length' | translate }}
  80 + </mat-error>
75 </mat-form-field> 81 </mat-form-field>
76 </div> 82 </div>
77 <mat-form-field class="mat-block" fxFlex style="margin-bottom: 8px"> 83 <mat-form-field class="mat-block" fxFlex style="margin-bottom: 8px">
@@ -116,7 +122,8 @@ @@ -116,7 +122,8 @@
116 </mat-checkbox> 122 </mat-checkbox>
117 </section> 123 </section>
118 <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayoutGap.sm="8px" 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 <mat-form-field class="mat-block" fxFlex="33"> 127 <mat-form-field class="mat-block" fxFlex="33">
121 <mat-label translate>ota-update.checksum-algorithm</mat-label> 128 <mat-label translate>ota-update.checksum-algorithm</mat-label>
122 <mat-select formControlName="checksumAlgorithm"> 129 <mat-select formControlName="checksumAlgorithm">
@@ -154,7 +161,8 @@ @@ -154,7 +161,8 @@
154 <input matInput formControlName="url" 161 <input matInput formControlName="url"
155 type="text" 162 type="text"
156 [required]="entityForm.get('isURL').value"> 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 ota-update.direct-url-required 166 ota-update.direct-url-required
159 </mat-error> 167 </mat-error>
160 </mat-form-field> 168 </mat-form-field>
@@ -84,6 +84,9 @@ @@ -84,6 +84,9 @@
84 <mat-error *ngIf="entityForm.get('name').hasError('required')"> 84 <mat-error *ngIf="entityForm.get('name').hasError('required')">
85 {{ 'rulechain.name-required' | translate }} 85 {{ 'rulechain.name-required' | translate }}
86 </mat-error> 86 </mat-error>
  87 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  88 + {{ 'rulechain.name-max-length' | translate }}
  89 + </mat-error>
87 </mat-form-field> 90 </mat-form-field>
88 <mat-checkbox fxFlex formControlName="debugMode" style="padding-bottom: 16px;"> 91 <mat-checkbox fxFlex formControlName="debugMode" style="padding-bottom: 16px;">
89 {{ 'rulechain.debug-mode' | translate }} 92 {{ 'rulechain.debug-mode' | translate }}
@@ -58,7 +58,7 @@ export class RuleChainComponent extends EntityComponent<RuleChain> { @@ -58,7 +58,7 @@ export class RuleChainComponent extends EntityComponent<RuleChain> {
58 buildForm(entity: RuleChain): FormGroup { 58 buildForm(entity: RuleChain): FormGroup {
59 return this.fb.group( 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 debugMode: [entity ? entity.debugMode : false], 62 debugMode: [entity ? entity.debugMode : false],
63 additionalInfo: this.fb.group( 63 additionalInfo: this.fb.group(
64 { 64 {
@@ -48,6 +48,9 @@ @@ -48,6 +48,9 @@
48 <mat-error *ngIf="entityForm.get('title').hasError('required')"> 48 <mat-error *ngIf="entityForm.get('title').hasError('required')">
49 {{ 'tenant.title-required' | translate }} 49 {{ 'tenant.title-required' | translate }}
50 </mat-error> 50 </mat-error>
  51 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  52 + {{ 'tenant.title-max-length' | translate }}
  53 + </mat-error>
51 </mat-form-field> 54 </mat-form-field>
52 <tb-tenant-profile-autocomplete 55 <tb-tenant-profile-autocomplete
53 [selectDefaultProfile]="isAdd" 56 [selectDefaultProfile]="isAdd"
@@ -52,7 +52,7 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> { @@ -52,7 +52,7 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
52 buildEntityForm(entity: TenantInfo): FormGroup { 52 buildEntityForm(entity: TenantInfo): FormGroup {
53 return this.fb.group( 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 tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]], 56 tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]],
57 additionalInfo: this.fb.group( 57 additionalInfo: this.fb.group(
58 { 58 {
@@ -44,6 +44,9 @@ @@ -44,6 +44,9 @@
44 <mat-error *ngIf="entityForm.get('title').hasError('required')"> 44 <mat-error *ngIf="entityForm.get('title').hasError('required')">
45 {{ 'widgets-bundle.title-required' | translate }} 45 {{ 'widgets-bundle.title-required' | translate }}
46 </mat-error> 46 </mat-error>
  47 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  48 + {{ 'widgets-bundle.title-max-length' | translate }}
  49 + </mat-error>
47 </mat-form-field> 50 </mat-form-field>
48 <tb-image-input fxFlex 51 <tb-image-input fxFlex
49 label="{{'widgets-bundle.image-preview' | translate}}" 52 label="{{'widgets-bundle.image-preview' | translate}}"
@@ -48,7 +48,7 @@ export class WidgetsBundleComponent extends EntityComponent<WidgetsBundle> { @@ -48,7 +48,7 @@ export class WidgetsBundleComponent extends EntityComponent<WidgetsBundle> {
48 buildForm(entity: WidgetsBundle): FormGroup { 48 buildForm(entity: WidgetsBundle): FormGroup {
49 return this.fb.group( 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 image: [entity ? entity.image : ''], 52 image: [entity ? entity.image : ''],
53 description: [entity ? entity.description : '', Validators.maxLength(255)] 53 description: [entity ? entity.description : '', Validators.maxLength(255)]
54 } 54 }
@@ -28,10 +28,16 @@ @@ -28,10 +28,16 @@
28 <mat-form-field class="mat-block"> 28 <mat-form-field class="mat-block">
29 <mat-label translate>contact.city</mat-label> 29 <mat-label translate>contact.city</mat-label>
30 <input matInput formControlName="city"> 30 <input matInput formControlName="city">
  31 + <mat-error *ngIf="parentForm.get('city').hasError('maxlength')">
  32 + {{ 'contact.city-max-length' | translate }}
  33 + </mat-error>
31 </mat-form-field> 34 </mat-form-field>
32 <mat-form-field class="mat-block"> 35 <mat-form-field class="mat-block">
33 <mat-label translate>contact.state</mat-label> 36 <mat-label translate>contact.state</mat-label>
34 <input matInput formControlName="state"> 37 <input matInput formControlName="state">
  38 + <mat-error *ngIf="parentForm.get('state').hasError('maxlength')">
  39 + {{ 'contact.state-max-length' | translate }}
  40 + </mat-error>
35 </mat-form-field> 41 </mat-form-field>
36 <mat-form-field class="mat-block"> 42 <mat-form-field class="mat-block">
37 <mat-label translate>contact.postal-code</mat-label> 43 <mat-label translate>contact.postal-code</mat-label>
@@ -52,6 +58,9 @@ @@ -52,6 +58,9 @@
52 <mat-form-field class="mat-block"> 58 <mat-form-field class="mat-block">
53 <mat-label translate>contact.phone</mat-label> 59 <mat-label translate>contact.phone</mat-label>
54 <input matInput formControlName="phone"> 60 <input matInput formControlName="phone">
  61 + <mat-error *ngIf="parentForm.get('phone').hasError('maxlength')">
  62 + {{ 'contact.phone-max-length' | translate }}
  63 + </mat-error>
55 </mat-form-field> 64 </mat-form-field>
56 <mat-form-field class="mat-block"> 65 <mat-form-field class="mat-block">
57 <mat-label translate>contact.email</mat-label> 66 <mat-label translate>contact.email</mat-label>
@@ -40,4 +40,7 @@ @@ -40,4 +40,7 @@
40 <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('required')"> 40 <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('required')">
41 {{ entitySubtypeRequiredText | translate }} 41 {{ entitySubtypeRequiredText | translate }}
42 </mat-error> 42 </mat-error>
  43 + <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('maxlength')">
  44 + {{ entitySubtypeMaxLength | translate }}
  45 + </mat-error>
43 </mat-form-field> 46 </mat-form-field>
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; 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 import { Observable, of, Subscription, throwError } from 'rxjs'; 19 import { Observable, of, Subscription, throwError } from 'rxjs';
20 import { 20 import {
21 catchError, 21 catchError,
@@ -58,9 +58,11 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -58,9 +58,11 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
58 entityType: EntityType; 58 entityType: EntityType;
59 59
60 private requiredValue: boolean; 60 private requiredValue: boolean;
  61 +
61 get required(): boolean { 62 get required(): boolean {
62 return this.requiredValue; 63 return this.requiredValue;
63 } 64 }
  65 +
64 @Input() 66 @Input()
65 set required(value: boolean) { 67 set required(value: boolean) {
66 this.requiredValue = coerceBooleanProperty(value); 68 this.requiredValue = coerceBooleanProperty(value);
@@ -74,6 +76,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -74,6 +76,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
74 selectEntitySubtypeText: string; 76 selectEntitySubtypeText: string;
75 entitySubtypeText: string; 77 entitySubtypeText: string;
76 entitySubtypeRequiredText: string; 78 entitySubtypeRequiredText: string;
  79 + entitySubtypeMaxLength: string;
77 80
78 filteredSubTypes: Observable<Array<string>>; 81 filteredSubTypes: Observable<Array<string>>;
79 82
@@ -96,7 +99,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -96,7 +99,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
96 private entityViewService: EntityViewService, 99 private entityViewService: EntityViewService,
97 private fb: FormBuilder) { 100 private fb: FormBuilder) {
98 this.subTypeFormGroup = this.fb.group({ 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,6 +117,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
114 this.selectEntitySubtypeText = 'asset.select-asset-type'; 117 this.selectEntitySubtypeText = 'asset.select-asset-type';
115 this.entitySubtypeText = 'asset.asset-type'; 118 this.entitySubtypeText = 'asset.asset-type';
116 this.entitySubtypeRequiredText = 'asset.asset-type-required'; 119 this.entitySubtypeRequiredText = 'asset.asset-type-required';
  120 + this.entitySubtypeMaxLength = 'asset.asset-type-max-length';
117 this.broadcastSubscription = this.broadcast.on('assetSaved', () => { 121 this.broadcastSubscription = this.broadcast.on('assetSaved', () => {
118 this.subTypes = null; 122 this.subTypes = null;
119 }); 123 });
@@ -122,6 +126,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -122,6 +126,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
122 this.selectEntitySubtypeText = 'device.select-device-type'; 126 this.selectEntitySubtypeText = 'device.select-device-type';
123 this.entitySubtypeText = 'device.device-type'; 127 this.entitySubtypeText = 'device.device-type';
124 this.entitySubtypeRequiredText = 'device.device-type-required'; 128 this.entitySubtypeRequiredText = 'device.device-type-required';
  129 + this.entitySubtypeMaxLength = 'device.device-type-max-length';
125 this.broadcastSubscription = this.broadcast.on('deviceSaved', () => { 130 this.broadcastSubscription = this.broadcast.on('deviceSaved', () => {
126 this.subTypes = null; 131 this.subTypes = null;
127 }); 132 });
@@ -130,6 +135,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -130,6 +135,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
130 this.selectEntitySubtypeText = 'edge.select-edge-type'; 135 this.selectEntitySubtypeText = 'edge.select-edge-type';
131 this.entitySubtypeText = 'edge.edge-type'; 136 this.entitySubtypeText = 'edge.edge-type';
132 this.entitySubtypeRequiredText = 'edge.edge-type-required'; 137 this.entitySubtypeRequiredText = 'edge.edge-type-required';
  138 + this.entitySubtypeMaxLength = 'edge.type-max-length';
133 this.broadcastSubscription = this.broadcast.on('edgeSaved', () => { 139 this.broadcastSubscription = this.broadcast.on('edgeSaved', () => {
134 this.subTypes = null; 140 this.subTypes = null;
135 }); 141 });
@@ -138,6 +144,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -138,6 +144,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
138 this.selectEntitySubtypeText = 'entity-view.select-entity-view-type'; 144 this.selectEntitySubtypeText = 'entity-view.select-entity-view-type';
139 this.entitySubtypeText = 'entity-view.entity-view-type'; 145 this.entitySubtypeText = 'entity-view.entity-view-type';
140 this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required'; 146 this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required';
  147 + this.entitySubtypeMaxLength = 'entity-view.type-max-length'
141 this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => { 148 this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => {
142 this.subTypes = null; 149 this.subTypes = null;
143 }); 150 });
@@ -149,7 +156,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -149,7 +156,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
149 debounceTime(150), 156 debounceTime(150),
150 distinctUntilChanged(), 157 distinctUntilChanged(),
151 tap(value => { 158 tap(value => {
152 - this.updateView(value); 159 + this.updateView(value);
153 }), 160 }),
154 // startWith<string | EntitySubtype>(''), 161 // startWith<string | EntitySubtype>(''),
155 map(value => value ? value : ''), 162 map(value => value ? value : ''),
@@ -203,7 +210,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @@ -203,7 +210,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
203 fetchSubTypes(searchText?: string, strictMatch: boolean = false): Observable<Array<string>> { 210 fetchSubTypes(searchText?: string, strictMatch: boolean = false): Observable<Array<string>> {
204 this.searchText = searchText; 211 this.searchText = searchText;
205 return this.getSubTypes().pipe( 212 return this.getSubTypes().pipe(
206 - map(subTypes => subTypes.filter( subType => { 213 + map(subTypes => subTypes.filter(subType => {
207 if (strictMatch) { 214 if (strictMatch) {
208 return searchText ? subType === searchText : false; 215 return searchText ? subType === searchText : false;
209 } else { 216 } else {
@@ -39,4 +39,7 @@ @@ -39,4 +39,7 @@
39 <mat-error *ngIf="relationTypeFormGroup.get('relationType').hasError('required')"> 39 <mat-error *ngIf="relationTypeFormGroup.get('relationType').hasError('required')">
40 {{ 'relation.relation-type-required' | translate }} 40 {{ 'relation.relation-type-required' | translate }}
41 </mat-error> 41 </mat-error>
  42 + <mat-error *ngIf="relationTypeFormGroup.get('relationType').hasError('maxlength')">
  43 + {{ 'relation.relation-type-max-length' | translate }}
  44 + </mat-error>
42 </mat-form-field> 45 </mat-form-field>
@@ -68,7 +68,7 @@ export class RelationTypeAutocompleteComponent implements ControlValueAccessor, @@ -68,7 +68,7 @@ export class RelationTypeAutocompleteComponent implements ControlValueAccessor,
68 public translate: TranslateService, 68 public translate: TranslateService,
69 private fb: FormBuilder) { 69 private fb: FormBuilder) {
70 this.relationTypeFormGroup = this.fb.group({ 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,6 +370,7 @@
370 "management": "Asset management", 370 "management": "Asset management",
371 "view-assets": "View Assets", 371 "view-assets": "View Assets",
372 "add": "Add Asset", 372 "add": "Add Asset",
  373 + "asset-type-max-length": "Asset type should be less than 256",
373 "assign-to-customer": "Assign to customer", 374 "assign-to-customer": "Assign to customer",
374 "assign-asset-to-customer": "Assign Asset(s) To Customer", 375 "assign-asset-to-customer": "Assign Asset(s) To Customer",
375 "assign-asset-to-customer-text": "Please select the assets to assign to the customer", 376 "assign-asset-to-customer-text": "Please select the assets to assign to the customer",
@@ -392,6 +393,8 @@ @@ -392,6 +393,8 @@
392 "asset-types": "Asset types", 393 "asset-types": "Asset types",
393 "name": "Name", 394 "name": "Name",
394 "name-required": "Name is required.", 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 "description": "Description", 398 "description": "Description",
396 "type": "Type", 399 "type": "Type",
397 "type-required": "Type is required.", 400 "type-required": "Type is required.",
@@ -451,6 +454,7 @@ @@ -451,6 +454,7 @@
451 "scope-shared": "Shared attributes", 454 "scope-shared": "Shared attributes",
452 "add": "Add attribute", 455 "add": "Add attribute",
453 "key": "Key", 456 "key": "Key",
  457 + "key-max-length": "Key should be less than 256",
454 "last-update-time": "Last update time", 458 "last-update-time": "Last update time",
455 "key-required": "Attribute key is required.", 459 "key-required": "Attribute key is required.",
456 "value": "Value", 460 "value": "Value",
@@ -592,7 +596,10 @@ @@ -592,7 +596,10 @@
592 "address2": "Address 2", 596 "address2": "Address 2",
593 "phone": "Phone", 597 "phone": "Phone",
594 "email": "Email", 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 "common": { 604 "common": {
598 "username": "Username", 605 "username": "Username",
@@ -648,6 +655,7 @@ @@ -648,6 +655,7 @@
648 "manage-dashboards": "Manage dashboards", 655 "manage-dashboards": "Manage dashboards",
649 "title": "Title", 656 "title": "Title",
650 "title-required": "Title is required.", 657 "title-required": "Title is required.",
  658 + "title-max-length": "Title should be less than 256",
651 "description": "Description", 659 "description": "Description",
652 "details": "Details", 660 "details": "Details",
653 "events": "Events", 661 "events": "Events",
@@ -705,6 +713,7 @@ @@ -705,6 +713,7 @@
705 "select-widget-subtitle": "List of available widget types", 713 "select-widget-subtitle": "List of available widget types",
706 "delete": "Delete dashboard", 714 "delete": "Delete dashboard",
707 "title-required": "Title is required.", 715 "title-required": "Title is required.",
  716 + "title-max-length": "Title should be less than 256",
708 "description": "Description", 717 "description": "Description",
709 "details": "Details", 718 "details": "Details",
710 "dashboard-details": "Dashboard details", 719 "dashboard-details": "Dashboard details",
@@ -891,6 +900,7 @@ @@ -891,6 +900,7 @@
891 "management": "Device management", 900 "management": "Device management",
892 "view-devices": "View Devices", 901 "view-devices": "View Devices",
893 "device-alias": "Device alias", 902 "device-alias": "Device alias",
  903 + "device-type-max-length": "Device type should be less than 256",
894 "aliases": "Device aliases", 904 "aliases": "Device aliases",
895 "no-alias-matching": "'{{alias}}' not found.", 905 "no-alias-matching": "'{{alias}}' not found.",
896 "no-aliases-found": "No aliases found.", 906 "no-aliases-found": "No aliases found.",
@@ -1003,6 +1013,8 @@ @@ -1003,6 +1013,8 @@
1003 "device-types": "Device types", 1013 "device-types": "Device types",
1004 "name": "Name", 1014 "name": "Name",
1005 "name-required": "Name is required.", 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 "description": "Description", 1018 "description": "Description",
1007 "label": "Label", 1019 "label": "Label",
1008 "events": "Events", 1020 "events": "Events",
@@ -1055,6 +1067,7 @@ @@ -1055,6 +1067,7 @@
1055 "set-default": "Make device profile default", 1067 "set-default": "Make device profile default",
1056 "delete": "Delete device profile", 1068 "delete": "Delete device profile",
1057 "copyId": "Copy device profile Id", 1069 "copyId": "Copy device profile Id",
  1070 + "name-max-length": "Name should be less than 256",
1058 "new-device-profile-name": "Device profile name", 1071 "new-device-profile-name": "Device profile name",
1059 "new-device-profile-name-required": "Device profile name is required.", 1072 "new-device-profile-name-required": "Device profile name is required.",
1060 "name": "Name", 1073 "name": "Name",
@@ -1416,6 +1429,9 @@ @@ -1416,6 +1429,9 @@
1416 "edge": "Edge", 1429 "edge": "Edge",
1417 "edge-instances": "Edge instances", 1430 "edge-instances": "Edge instances",
1418 "edge-file": "Edge file", 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 "management": "Edge management", 1435 "management": "Edge management",
1420 "no-edges-matching": "No edges matching '{{entity}}' were found.", 1436 "no-edges-matching": "No edges matching '{{entity}}' were found.",
1421 "add": "Add Edge", 1437 "add": "Add Edge",
@@ -1750,6 +1766,8 @@ @@ -1750,6 +1766,8 @@
1750 "created-time": "Created time", 1766 "created-time": "Created time",
1751 "name": "Name", 1767 "name": "Name",
1752 "name-required": "Name is required.", 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 "description": "Description", 1771 "description": "Description",
1754 "events": "Events", 1772 "events": "Events",
1755 "details": "Details", 1773 "details": "Details",
@@ -2374,6 +2392,7 @@ @@ -2374,6 +2392,7 @@
2374 "selected-package": "{ count, plural, 1 {1 package} other {# packages} } selected", 2392 "selected-package": "{ count, plural, 1 {1 package} other {# packages} } selected",
2375 "title": "Title", 2393 "title": "Title",
2376 "title-required": "Title is required.", 2394 "title-required": "Title is required.",
  2395 + "title-max-length": "Title should be less than 256",
2377 "types": { 2396 "types": {
2378 "firmware": "Firmware", 2397 "firmware": "Firmware",
2379 "software": "Software" 2398 "software": "Software"
@@ -2384,6 +2403,7 @@ @@ -2384,6 +2403,7 @@
2384 "version-required": "Version is required.", 2403 "version-required": "Version is required.",
2385 "version-tag": "Version Tag", 2404 "version-tag": "Version Tag",
2386 "version-tag-hint": "Custom tag should match the package version reported by your device.", 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 "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." 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 "position": { 2409 "position": {
@@ -2426,6 +2446,7 @@ @@ -2426,6 +2446,7 @@
2426 "delete": "Delete relation", 2446 "delete": "Delete relation",
2427 "relation-type": "Relation type", 2447 "relation-type": "Relation type",
2428 "relation-type-required": "Relation type is required.", 2448 "relation-type-required": "Relation type is required.",
  2449 + "relation-type-max-length": "Relation type should be less than 256",
2429 "any-relation-type": "Any type", 2450 "any-relation-type": "Any type",
2430 "add": "Add relation", 2451 "add": "Add relation",
2431 "edit": "Edit relation", 2452 "edit": "Edit relation",
@@ -2470,7 +2491,8 @@ @@ -2470,7 +2491,8 @@
2470 "selected-resources": "{ count, plural, 1 {1 resource} other {# resources} } selected", 2491 "selected-resources": "{ count, plural, 1 {1 resource} other {# resources} } selected",
2471 "system": "System", 2492 "system": "System",
2472 "title": "Title", 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 "rulechain": { 2497 "rulechain": {
2476 "rulechain": "Rule chain", 2498 "rulechain": "Rule chain",
@@ -2479,6 +2501,7 @@ @@ -2479,6 +2501,7 @@
2479 "delete": "Delete rule chain", 2501 "delete": "Delete rule chain",
2480 "name": "Name", 2502 "name": "Name",
2481 "name-required": "Name is required.", 2503 "name-required": "Name is required.",
  2504 + "name-max-length": "Name should be less than 256",
2482 "description": "Description", 2505 "description": "Description",
2483 "add": "Add Rule Chain", 2506 "add": "Add Rule Chain",
2484 "set-root": "Make rule chain root", 2507 "set-root": "Make rule chain root",
@@ -2621,6 +2644,7 @@ @@ -2621,6 +2644,7 @@
2621 "add-tenant-text": "Add new tenant", 2644 "add-tenant-text": "Add new tenant",
2622 "no-tenants-text": "No tenants found", 2645 "no-tenants-text": "No tenants found",
2623 "tenant-details": "Tenant details", 2646 "tenant-details": "Tenant details",
  2647 + "title-max-length": "Title should be less than 256",
2624 "delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?", 2648 "delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?",
2625 "delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.", 2649 "delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.",
2626 "delete-tenants-title": "Are you sure you want to delete { count, plural, 1 {1 tenant} other {# tenants} }?", 2650 "delete-tenants-title": "Are you sure you want to delete { count, plural, 1 {1 tenant} other {# tenants} }?",
@@ -2650,6 +2674,7 @@ @@ -2650,6 +2674,7 @@
2650 "edit": "Edit tenant profile", 2674 "edit": "Edit tenant profile",
2651 "tenant-profile-details": "Tenant profile details", 2675 "tenant-profile-details": "Tenant profile details",
2652 "no-tenant-profiles-text": "No tenant profiles found", 2676 "no-tenant-profiles-text": "No tenant profiles found",
  2677 + "name-max-length": "Name should be less than 256",
2653 "search": "Search tenant profiles", 2678 "search": "Search tenant profiles",
2654 "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected", 2679 "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected",
2655 "no-tenant-profiles-matching": "No tenant profile matching '{{entity}}' were found.", 2680 "no-tenant-profiles-matching": "No tenant profile matching '{{entity}}' were found.",
@@ -2997,6 +3022,7 @@ @@ -2997,6 +3022,7 @@
2997 "delete": "Delete widgets bundle", 3022 "delete": "Delete widgets bundle",
2998 "title": "Title", 3023 "title": "Title",
2999 "title-required": "Title is required.", 3024 "title-required": "Title is required.",
  3025 + "title-max-length": "Title should be less than 256",
3000 "description": "Description", 3026 "description": "Description",
3001 "image-preview": "Image preview", 3027 "image-preview": "Image preview",
3002 "add-widgets-bundle-text": "Add new widgets bundle", 3028 "add-widgets-bundle-text": "Add new widgets bundle",