Commit e3d0da90be7735d8049bca1bf5c4800bb959bd23

Authored by AndrewVolosytnykhThingsboard
1 parent 4e229ea2

Implement fields validation

Showing 66 changed files with 477 additions and 148 deletions
... ... @@ -26,12 +26,12 @@ import org.springframework.web.bind.annotation.ResponseBody;
26 26 import org.springframework.web.bind.annotation.RestController;
27 27 import org.thingsboard.rule.engine.api.MailService;
28 28 import org.thingsboard.rule.engine.api.SmsService;
29   -import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
30 29 import org.thingsboard.server.common.data.AdminSettings;
31 30 import org.thingsboard.server.common.data.UpdateMessage;
32 31 import org.thingsboard.server.common.data.exception.ThingsboardException;
33 32 import org.thingsboard.server.common.data.id.TenantId;
34 33 import org.thingsboard.server.common.data.security.model.SecuritySettings;
  34 +import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
35 35 import org.thingsboard.server.dao.settings.AdminSettingsService;
36 36 import org.thingsboard.server.queue.util.TbCoreComponent;
37 37 import org.thingsboard.server.service.security.permission.Operation;
... ... @@ -88,6 +88,7 @@ public class AdminController extends BaseController {
88 88 } else if (adminSettings.getKey().equals("sms")) {
89 89 smsService.updateSmsConfiguration();
90 90 }
  91 +
91 92 return adminSettings;
92 93 } catch (Exception e) {
93 94 throw handleException(e);
... ...
... ... @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
34 34 import org.thingsboard.server.common.data.relation.EntityRelationInfo;
35 35 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
36 36 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  37 +import org.thingsboard.server.dao.service.ConstraintValidator;
37 38 import org.thingsboard.server.queue.util.TbCoreComponent;
38 39 import org.thingsboard.server.service.security.permission.Operation;
39 40
... ... @@ -63,6 +64,7 @@ public class EntityRelationController extends BaseController {
63 64 if (relation.getTypeGroup() == null) {
64 65 relation.setTypeGroup(RelationTypeGroup.COMMON);
65 66 }
  67 + ConstraintValidator.validateFields(relation);
66 68 relationService.saveRelation(getTenantId(), relation);
67 69
68 70 logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
... ...
... ... @@ -48,7 +48,6 @@ import org.thingsboard.server.common.data.EntityType;
48 48 import org.thingsboard.server.common.data.TenantProfile;
49 49 import org.thingsboard.server.common.data.audit.ActionType;
50 50 import org.thingsboard.server.common.data.exception.ThingsboardException;
51   -import org.thingsboard.server.common.data.id.CustomerId;
52 51 import org.thingsboard.server.common.data.id.DeviceId;
53 52 import org.thingsboard.server.common.data.id.EntityId;
54 53 import org.thingsboard.server.common.data.id.EntityIdFactory;
... ... @@ -73,6 +72,7 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
73 72 import org.thingsboard.server.common.data.kv.TsKvEntry;
74 73 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
75 74 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  75 +import org.thingsboard.server.dao.service.ConstraintValidator;
76 76 import org.thingsboard.server.dao.timeseries.TimeseriesService;
77 77 import org.thingsboard.server.queue.util.TbCoreComponent;
78 78 import org.thingsboard.server.service.security.AccessValidator;
... ... @@ -138,7 +138,11 @@ public class TelemetryController extends BaseController {
138 138 @ResponseBody
139 139 public DeferredResult<ResponseEntity> getAttributeKeys(
140 140 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
141   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback);
  141 + try {
  142 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback);
  143 + } catch (Exception e) {
  144 + throw handleException(e);
  145 + }
142 146 }
143 147
144 148 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -147,8 +151,12 @@ public class TelemetryController extends BaseController {
147 151 public DeferredResult<ResponseEntity> getAttributeKeysByScope(
148 152 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr
149 153 , @PathVariable("scope") String scope) throws ThingsboardException {
150   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
151   - (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope));
  154 + try {
  155 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
  156 + (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope));
  157 + } catch (Exception e) {
  158 + throw handleException(e);
  159 + }
152 160 }
153 161
154 162 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -157,9 +165,13 @@ public class TelemetryController extends BaseController {
157 165 public DeferredResult<ResponseEntity> getAttributes(
158 166 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
159 167 @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
160   - SecurityUser user = getCurrentUser();
161   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
162   - (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
  168 + try {
  169 + SecurityUser user = getCurrentUser();
  170 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
  171 + (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
  172 + } catch (Exception e) {
  173 + throw handleException(e);
  174 + }
163 175 }
164 176
165 177 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -169,9 +181,13 @@ public class TelemetryController extends BaseController {
169 181 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
170 182 @PathVariable("scope") String scope,
171 183 @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
172   - SecurityUser user = getCurrentUser();
173   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
174   - (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr));
  184 + try {
  185 + SecurityUser user = getCurrentUser();
  186 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
  187 + (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr));
  188 + } catch (Exception e) {
  189 + throw handleException(e);
  190 + }
175 191 }
176 192
177 193 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -179,8 +195,12 @@ public class TelemetryController extends BaseController {
179 195 @ResponseBody
180 196 public DeferredResult<ResponseEntity> getTimeseriesKeys(
181 197 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
182   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
183   - (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor()));
  198 + try {
  199 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
  200 + (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor()));
  201 + } catch (Exception e) {
  202 + throw handleException(e);
  203 + }
184 204 }
185 205
186 206 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -190,10 +210,14 @@ public class TelemetryController extends BaseController {
190 210 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
191 211 @RequestParam(name = "keys", required = false) String keysStr,
192 212 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
193   - SecurityUser user = getCurrentUser();
  213 + try {
  214 + SecurityUser user = getCurrentUser();
194 215
195   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
196   - (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes));
  216 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
  217 + (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes));
  218 + } catch (Exception e) {
  219 + throw handleException(e);
  220 + }
197 221 }
198 222
199 223
... ... @@ -211,15 +235,19 @@ public class TelemetryController extends BaseController {
211 235 @RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
212 236 @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy,
213 237 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
214   - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
215   - (result, tenantId, entityId) -> {
216   - // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
217   - Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
218   - List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
219   - .collect(Collectors.toList());
220   -
221   - Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
222   - });
  238 + try {
  239 + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
  240 + (result, tenantId, entityId) -> {
  241 + // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
  242 + Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
  243 + List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
  244 + .collect(Collectors.toList());
  245 +
  246 + Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
  247 + });
  248 + } catch (Exception e) {
  249 + throw handleException(e);
  250 + }
223 251 }
224 252
225 253 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -227,8 +255,12 @@ public class TelemetryController extends BaseController {
227 255 @ResponseBody
228 256 public DeferredResult<ResponseEntity> saveDeviceAttributes(@PathVariable("deviceId") String deviceIdStr, @PathVariable("scope") String scope,
229 257 @RequestBody JsonNode request) throws ThingsboardException {
230   - EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
231   - return saveAttributes(getTenantId(), entityId, scope, request);
  258 + try {
  259 + EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
  260 + return saveAttributes(getTenantId(), entityId, scope, request);
  261 + } catch (Exception e) {
  262 + throw handleException(e);
  263 + }
232 264 }
233 265
234 266 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -237,8 +269,12 @@ public class TelemetryController extends BaseController {
237 269 public DeferredResult<ResponseEntity> saveEntityAttributesV1(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
238 270 @PathVariable("scope") String scope,
239 271 @RequestBody JsonNode request) throws ThingsboardException {
240   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
241   - return saveAttributes(getTenantId(), entityId, scope, request);
  272 + try {
  273 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  274 + return saveAttributes(getTenantId(), entityId, scope, request);
  275 + } catch (Exception e) {
  276 + throw handleException(e);
  277 + }
242 278 }
243 279
244 280 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -247,8 +283,12 @@ public class TelemetryController extends BaseController {
247 283 public DeferredResult<ResponseEntity> saveEntityAttributesV2(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
248 284 @PathVariable("scope") String scope,
249 285 @RequestBody JsonNode request) throws ThingsboardException {
250   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
251   - return saveAttributes(getTenantId(), entityId, scope, request);
  286 + try {
  287 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  288 + return saveAttributes(getTenantId(), entityId, scope, request);
  289 + } catch (Exception e) {
  290 + throw handleException(e);
  291 + }
252 292 }
253 293
254 294 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -257,8 +297,12 @@ public class TelemetryController extends BaseController {
257 297 public DeferredResult<ResponseEntity> saveEntityTelemetry(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
258 298 @PathVariable("scope") String scope,
259 299 @RequestBody String requestBody) throws ThingsboardException {
260   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
261   - return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
  300 + try {
  301 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  302 + return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
  303 + } catch (Exception e) {
  304 + throw handleException(e);
  305 + }
262 306 }
263 307
264 308 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -267,8 +311,12 @@ public class TelemetryController extends BaseController {
267 311 public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
268 312 @PathVariable("scope") String scope, @PathVariable("ttl") Long ttl,
269 313 @RequestBody String requestBody) throws ThingsboardException {
270   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
271   - return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
  314 + try {
  315 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  316 + return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
  317 + } catch (Exception e) {
  318 + throw handleException(e);
  319 + }
272 320 }
273 321
274 322 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -280,8 +328,12 @@ public class TelemetryController extends BaseController {
280 328 @RequestParam(name = "startTs", required = false) Long startTs,
281 329 @RequestParam(name = "endTs", required = false) Long endTs,
282 330 @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException {
283   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
284   - return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted);
  331 + try {
  332 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  333 + return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted);
  334 + } catch (Exception e) {
  335 + throw handleException(e);
  336 + }
285 337 }
286 338
287 339 private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys,
... ... @@ -335,8 +387,12 @@ public class TelemetryController extends BaseController {
335 387 public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("deviceId") String deviceIdStr,
336 388 @PathVariable("scope") String scope,
337 389 @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
338   - EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
339   - return deleteAttributes(entityId, scope, keysStr);
  390 + try {
  391 + EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
  392 + return deleteAttributes(entityId, scope, keysStr);
  393 + } catch (Exception e) {
  394 + throw handleException(e);
  395 + }
340 396 }
341 397
342 398 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
... ... @@ -345,8 +401,12 @@ public class TelemetryController extends BaseController {
345 401 public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
346 402 @PathVariable("scope") String scope,
347 403 @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
348   - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
349   - return deleteAttributes(entityId, scope, keysStr);
  404 + try {
  405 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  406 + return deleteAttributes(entityId, scope, keysStr);
  407 + } catch (Exception e) {
  408 + throw handleException(e);
  409 + }
350 410 }
351 411
352 412 private DeferredResult<ResponseEntity> deleteAttributes(EntityId entityIdSrc, String scope, String keysStr) throws ThingsboardException {
... ... @@ -392,6 +452,7 @@ public class TelemetryController extends BaseController {
392 452 }
393 453 if (json.isObject()) {
394 454 List<AttributeKvEntry> attributes = extractRequestAttributes(json);
  455 + attributes.forEach(ConstraintValidator::validateFields);
395 456 if (attributes.isEmpty()) {
396 457 return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
397 458 }
... ...
... ... @@ -17,30 +17,36 @@ package org.thingsboard.server.common.data;
17 17
18 18 import lombok.EqualsAndHashCode;
19 19 import org.thingsboard.server.common.data.id.UUIDBased;
  20 +import org.thingsboard.server.common.data.validation.Length;
20 21 import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 23 @EqualsAndHashCode(callSuper = true)
23 24 public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
24   -
  25 +
25 26 private static final long serialVersionUID = 5047448057830660988L;
26 27
  28 + @Length(fieldName = "country")
27 29 @NoXss
28 30 protected String country;
  31 + @Length(fieldName = "state")
29 32 @NoXss
30 33 protected String state;
  34 + @Length(fieldName = "city")
31 35 @NoXss
32 36 protected String city;
33 37 @NoXss
34 38 protected String address;
35 39 @NoXss
36 40 protected String address2;
  41 + @Length(fieldName = "zip or postal code")
37 42 @NoXss
38 43 protected String zip;
  44 + @Length(fieldName = "phone")
39 45 @NoXss
40 46 protected String phone;
41 47 @NoXss
42 48 protected String email;
43   -
  49 +
44 50 public ContactBased() {
45 51 super();
46 52 }
... ... @@ -48,7 +54,7 @@ public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedW
48 54 public ContactBased(I id) {
49 55 super(id);
50 56 }
51   -
  57 +
52 58 public ContactBased(ContactBased<I> contact) {
53 59 super(contact);
54 60 this.country = contact.getCountry();
... ...
... ... @@ -20,13 +20,15 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 20 import com.fasterxml.jackson.annotation.JsonProperty.Access;
21 21 import org.thingsboard.server.common.data.id.CustomerId;
22 22 import org.thingsboard.server.common.data.id.TenantId;
  23 +import org.thingsboard.server.common.data.validation.Length;
23 24 import org.thingsboard.server.common.data.validation.NoXss;
24 25
25 26 public class Customer extends ContactBased<CustomerId> implements HasTenantId {
26   -
  27 +
27 28 private static final long serialVersionUID = -1599722990298929275L;
28 29
29 30 @NoXss
  31 + @Length(fieldName = "title")
30 32 private String title;
31 33 private TenantId tenantId;
32 34
... ... @@ -37,7 +39,7 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId {
37 39 public Customer(CustomerId id) {
38 40 super(id);
39 41 }
40   -
  42 +
41 43 public Customer(Customer customer) {
42 44 super(customer);
43 45 this.tenantId = customer.getTenantId();
... ... @@ -51,7 +53,7 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId {
51 53 public void setTenantId(TenantId tenantId) {
52 54 this.tenantId = tenantId;
53 55 }
54   -
  56 +
55 57 public String getTitle() {
56 58 return title;
57 59 }
... ...
... ... @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
19 19 import org.thingsboard.server.common.data.id.CustomerId;
20 20 import org.thingsboard.server.common.data.id.DashboardId;
21 21 import org.thingsboard.server.common.data.id.TenantId;
  22 +import org.thingsboard.server.common.data.validation.Length;
22 23 import org.thingsboard.server.common.data.validation.NoXss;
23 24
24 25 import javax.validation.Valid;
... ... @@ -29,6 +30,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
29 30
30 31 private TenantId tenantId;
31 32 @NoXss
  33 + @Length(fieldName = "title")
32 34 private String title;
33 35 private String image;
34 36 @Valid
... ...
... ... @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
25 25 import org.thingsboard.server.common.data.id.DeviceProfileId;
26 26 import org.thingsboard.server.common.data.id.OtaPackageId;
27 27 import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.validation.Length;
28 29 import org.thingsboard.server.common.data.validation.NoXss;
29 30
30 31 import java.io.ByteArrayInputStream;
... ... @@ -39,10 +40,13 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
39 40 private TenantId tenantId;
40 41 private CustomerId customerId;
41 42 @NoXss
  43 + @Length(fieldName = "name")
42 44 private String name;
43 45 @NoXss
  46 + @Length(fieldName = "type")
44 47 private String type;
45 48 @NoXss
  49 + @Length(fieldName = "label")
46 50 private String label;
47 51 private DeviceProfileId deviceProfileId;
48 52 private transient DeviceData deviceData;
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
26 26 import org.thingsboard.server.common.data.id.OtaPackageId;
27 27 import org.thingsboard.server.common.data.id.RuleChainId;
28 28 import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.validation.Length;
29 30 import org.thingsboard.server.common.data.validation.NoXss;
30 31
31 32 import javax.validation.Valid;
... ... @@ -41,6 +42,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
41 42
42 43 private TenantId tenantId;
43 44 @NoXss
  45 + @Length(fieldName = "name")
44 46 private String name;
45 47 @NoXss
46 48 private String description;
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId;
23 23 import org.thingsboard.server.common.data.id.EntityViewId;
24 24 import org.thingsboard.server.common.data.id.TenantId;
25 25 import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  26 +import org.thingsboard.server.common.data.validation.Length;
26 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 29 /**
... ... @@ -41,8 +42,10 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
41 42 private TenantId tenantId;
42 43 private CustomerId customerId;
43 44 @NoXss
  45 + @Length(fieldName = "name")
44 46 private String name;
45 47 @NoXss
  48 + @Length(fieldName = "type")
46 49 private String type;
47 50 private TelemetryEntityView keys;
48 51 private long startTimeMs;
... ...
... ... @@ -19,11 +19,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
19 19 import lombok.Data;
20 20 import lombok.EqualsAndHashCode;
21 21 import lombok.extern.slf4j.Slf4j;
22   -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
23   -import org.thingsboard.server.common.data.ota.OtaPackageType;
24 22 import org.thingsboard.server.common.data.id.DeviceProfileId;
25 23 import org.thingsboard.server.common.data.id.OtaPackageId;
26 24 import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  26 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  27 +import org.thingsboard.server.common.data.validation.Length;
27 28
28 29 @Slf4j
29 30 @Data
... ... @@ -35,10 +36,14 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
35 36 private TenantId tenantId;
36 37 private DeviceProfileId deviceProfileId;
37 38 private OtaPackageType type;
  39 + @Length(fieldName = "title")
38 40 private String title;
  41 + @Length(fieldName = "version")
39 42 private String version;
  43 + @Length(fieldName = "url")
40 44 private String url;
41 45 private boolean hasData;
  46 + @Length(fieldName = "file name")
42 47 private String fileName;
43 48 private String contentType;
44 49 private ChecksumAlgorithm checksumAlgorithm;
... ...
... ... @@ -19,6 +19,7 @@ import lombok.Data;
19 19 import lombok.EqualsAndHashCode;
20 20 import lombok.extern.slf4j.Slf4j;
21 21 import org.thingsboard.server.common.data.id.TbResourceId;
  22 +import org.thingsboard.server.common.data.validation.Length;
22 23 import org.thingsboard.server.common.data.validation.NoXss;
23 24
24 25 @Slf4j
... ... @@ -29,6 +30,7 @@ public class TbResource extends TbResourceInfo {
29 30 private static final long serialVersionUID = 7379609705527272306L;
30 31
31 32 @NoXss
  33 + @Length(fieldName = "file name")
32 34 private String fileName;
33 35
34 36 private String data;
... ...
... ... @@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode;
21 21 import lombok.extern.slf4j.Slf4j;
22 22 import org.thingsboard.server.common.data.id.TbResourceId;
23 23 import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.validation.Length;
24 25 import org.thingsboard.server.common.data.validation.NoXss;
25 26
26 27 @Slf4j
... ... @@ -32,6 +33,7 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
32 33
33 34 private TenantId tenantId;
34 35 @NoXss
  36 + @Length(fieldName = "title")
35 37 private String title;
36 38 private ResourceType resourceType;
37 39 private String resourceKey;
... ...
... ... @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 20 import lombok.EqualsAndHashCode;
21 21 import org.thingsboard.server.common.data.id.TenantId;
22 22 import org.thingsboard.server.common.data.id.TenantProfileId;
  23 +import org.thingsboard.server.common.data.validation.Length;
23 24 import org.thingsboard.server.common.data.validation.NoXss;
24 25
25 26 @EqualsAndHashCode(callSuper = true)
... ... @@ -27,8 +28,10 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
27 28
28 29 private static final long serialVersionUID = 8057243243859922101L;
29 30
  31 + @Length(fieldName = "title")
30 32 @NoXss
31 33 private String title;
  34 + @Length(fieldName = "region")
32 35 @NoXss
33 36 private String region;
34 37 private TenantProfileId tenantProfileId;
... ... @@ -40,7 +43,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
40 43 public Tenant(TenantId id) {
41 44 super(id);
42 45 }
43   -
  46 +
44 47 public Tenant(Tenant tenant) {
45 48 super(tenant);
46 49 this.title = tenant.getTitle();
... ...
... ... @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
23 23 import org.thingsboard.server.common.data.id.TenantProfileId;
24 24 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
25 25 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
  26 +import org.thingsboard.server.common.data.validation.Length;
26 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 29 import java.io.ByteArrayInputStream;
... ... @@ -37,6 +38,7 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
37 38 public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName {
38 39
39 40 @NoXss
  41 + @Length(fieldName = "name")
40 42 private String name;
41 43 @NoXss
42 44 private String description;
... ...
... ... @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId;
23 23 import org.thingsboard.server.common.data.id.TenantId;
24 24 import org.thingsboard.server.common.data.id.UserId;
25 25 import org.thingsboard.server.common.data.security.Authority;
26   -
  26 +import org.thingsboard.server.common.data.validation.Length;
27 27 import org.thingsboard.server.common.data.validation.NoXss;
28 28
29 29 @EqualsAndHashCode(callSuper = true)
... ... @@ -36,8 +36,10 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
36 36 private String email;
37 37 private Authority authority;
38 38 @NoXss
  39 + @Length(fieldName = "firs name")
39 40 private String firstName;
40 41 @NoXss
  42 + @Length(fieldName = "last name")
41 43 private String lastName;
42 44
43 45 public User() {
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
28 28 import org.thingsboard.server.common.data.id.CustomerId;
29 29 import org.thingsboard.server.common.data.id.EntityId;
30 30 import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.validation.Length;
31 32
32 33 import java.util.List;
33 34
... ... @@ -41,6 +42,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
41 42
42 43 private TenantId tenantId;
43 44 private CustomerId customerId;
  45 + @Length(fieldName = "type")
44 46 private String type;
45 47 private EntityId originator;
46 48 private AlarmSeverity severity;
... ...
... ... @@ -15,7 +15,6 @@
15 15 */
16 16 package org.thingsboard.server.common.data.edge;
17 17
18   -import com.fasterxml.jackson.databind.JsonNode;
19 18 import lombok.EqualsAndHashCode;
20 19 import lombok.Getter;
21 20 import lombok.Setter;
... ... @@ -28,6 +27,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
28 27 import org.thingsboard.server.common.data.id.EdgeId;
29 28 import org.thingsboard.server.common.data.id.RuleChainId;
30 29 import org.thingsboard.server.common.data.id.TenantId;
  30 +import org.thingsboard.server.common.data.validation.Length;
31 31
32 32 @EqualsAndHashCode(callSuper = true)
33 33 @ToString
... ... @@ -40,8 +40,11 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
40 40 private TenantId tenantId;
41 41 private CustomerId customerId;
42 42 private RuleChainId rootRuleChainId;
  43 + @Length(fieldName = "name")
43 44 private String name;
  45 + @Length(fieldName = "type")
44 46 private String type;
  47 + @Length(fieldName = "label")
45 48 private String label;
46 49 private String routingKey;
47 50 private String secret;
... ...
... ... @@ -15,8 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.common.data.kv;
17 17
18   -import com.fasterxml.jackson.databind.JsonNode;
19   -
  18 +import javax.validation.Valid;
20 19 import java.util.Optional;
21 20
22 21 /**
... ... @@ -25,6 +24,7 @@ import java.util.Optional;
25 24 public class BaseAttributeKvEntry implements AttributeKvEntry {
26 25
27 26 private final long lastUpdateTs;
  27 + @Valid
28 28 private final KvEntry kv;
29 29
30 30 public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs) {
... ...
... ... @@ -15,11 +15,14 @@
15 15 */
16 16 package org.thingsboard.server.common.data.kv;
17 17
  18 +import org.thingsboard.server.common.data.validation.Length;
  19 +
18 20 import java.util.Objects;
19 21 import java.util.Optional;
20 22
21 23 public abstract class BasicKvEntry implements KvEntry {
22 24
  25 + @Length(fieldName = "attribute key")
23 26 private final String key;
24 27
25 28 protected BasicKvEntry(String key) {
... ...
... ... @@ -21,6 +21,7 @@ import java.util.Optional;
21 21 public class StringDataEntry extends BasicKvEntry {
22 22
23 23 private static final long serialVersionUID = 1L;
  24 +
24 25 private final String value;
25 26
26 27 public StringDataEntry(String key, String value) {
... ...
... ... @@ -16,15 +16,12 @@
16 16 package org.thingsboard.server.common.data.relation;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19   -import com.fasterxml.jackson.core.JsonProcessingException;
20 19 import com.fasterxml.jackson.databind.JsonNode;
21   -import com.fasterxml.jackson.databind.ObjectMapper;
22 20 import lombok.extern.slf4j.Slf4j;
23 21 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
24 22 import org.thingsboard.server.common.data.id.EntityId;
  23 +import org.thingsboard.server.common.data.validation.Length;
25 24
26   -import java.io.ByteArrayInputStream;
27   -import java.io.IOException;
28 25 import java.io.Serializable;
29 26
30 27 @Slf4j
... ... @@ -38,6 +35,7 @@ public class EntityRelation implements Serializable {
38 35
39 36 private EntityId from;
40 37 private EntityId to;
  38 + @Length(fieldName = "type")
41 39 private String type;
42 40 private RelationTypeGroup typeGroup;
43 41 private transient JsonNode additionalInfo;
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
26 26 import org.thingsboard.server.common.data.id.RuleChainId;
27 27 import org.thingsboard.server.common.data.id.RuleNodeId;
28 28 import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.validation.Length;
29 30 import org.thingsboard.server.common.data.validation.NoXss;
30 31
31 32 @Data
... ... @@ -37,6 +38,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
37 38
38 39 private TenantId tenantId;
39 40 @NoXss
  41 + @Length(fieldName = "name")
40 42 private String name;
41 43 private RuleChainType type;
42 44 private RuleNodeId firstRuleNodeId;
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.HasName;
24 24 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
25 25 import org.thingsboard.server.common.data.id.RuleChainId;
26 26 import org.thingsboard.server.common.data.id.RuleNodeId;
  27 +import org.thingsboard.server.common.data.validation.Length;
27 28
28 29 @Data
29 30 @EqualsAndHashCode(callSuper = true)
... ... @@ -33,7 +34,9 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
33 34 private static final long serialVersionUID = -5656679015121235465L;
34 35
35 36 private RuleChainId ruleChainId;
  37 + @Length(fieldName = "type")
36 38 private String type;
  39 + @Length(fieldName = "name")
37 40 private String name;
38 41 private boolean debugMode;
39 42 private transient JsonNode configuration;
... ...
  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} should be equals 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 +}
... ...
... ... @@ -19,10 +19,9 @@ import org.thingsboard.server.common.data.HasTenantId;
19 19 import org.thingsboard.server.common.data.SearchTextBased;
20 20 import org.thingsboard.server.common.data.id.TenantId;
21 21 import org.thingsboard.server.common.data.id.WidgetsBundleId;
  22 +import org.thingsboard.server.common.data.validation.Length;
22 23 import org.thingsboard.server.common.data.validation.NoXss;
23 24
24   -import java.util.Arrays;
25   -
26 25 public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasTenantId {
27 26
28 27 private static final long serialVersionUID = -7627368878362410489L;
... ... @@ -31,6 +30,7 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
31 30 @NoXss
32 31 private String alias;
33 32 @NoXss
  33 + @Length(fieldName = "title")
34 34 private String title;
35 35 private String image;
36 36 @NoXss
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.hibernate.validator.HibernateValidator;
  20 +import org.hibernate.validator.HibernateValidatorConfiguration;
  21 +import org.hibernate.validator.cfg.ConstraintMapping;
  22 +import org.thingsboard.server.common.data.validation.Length;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
  24 +
  25 +import javax.validation.ConstraintViolation;
  26 +import javax.validation.Validation;
  27 +import javax.validation.ValidationException;
  28 +import javax.validation.Validator;
  29 +import java.util.List;
  30 +import java.util.Set;
  31 +import java.util.stream.Collectors;
  32 +
  33 +@Slf4j
  34 +public class ConstraintValidator {
  35 +
  36 + private static Validator fieldsValidator;
  37 +
  38 + static {
  39 + initializeValidators();
  40 + }
  41 +
  42 + public static void validateFields(Object data) {
  43 + Set<ConstraintViolation<Object>> constraintsViolations = fieldsValidator.validate(data);
  44 + List<String> validationErrors = constraintsViolations.stream()
  45 + .map(ConstraintViolation::getMessage)
  46 + .distinct()
  47 + .collect(Collectors.toList());
  48 + if (!validationErrors.isEmpty()) {
  49 + throw new ValidationException("Validation error: " + String.join(", ", validationErrors));
  50 + }
  51 + }
  52 +
  53 + private static void initializeValidators() {
  54 + HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
  55 +
  56 + ConstraintMapping constraintMapping = validatorConfiguration.createConstraintMapping();
  57 + constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
  58 + constraintMapping.constraintDefinition(Length.class).validatedBy(StringLengthValidator.class);
  59 + validatorConfiguration.addMapping(constraintMapping);
  60 +
  61 + fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
  62 + }
  63 +}
... ...
... ... @@ -17,51 +17,32 @@ package org.thingsboard.server.dao.service;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.extern.slf4j.Slf4j;
20   -import org.hibernate.validator.HibernateValidator;
21   -import org.hibernate.validator.HibernateValidatorConfiguration;
22   -import org.hibernate.validator.cfg.ConstraintMapping;
23 20 import org.thingsboard.server.common.data.BaseData;
24 21 import org.thingsboard.server.common.data.EntityType;
25 22 import org.thingsboard.server.common.data.id.TenantId;
26   -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
27   -import org.thingsboard.server.common.data.validation.NoXss;
28 23 import org.thingsboard.server.dao.TenantEntityDao;
29 24 import org.thingsboard.server.dao.TenantEntityWithDataDao;
30 25 import org.thingsboard.server.dao.exception.DataValidationException;
31 26
32   -import javax.validation.ConstraintViolation;
33   -import javax.validation.Validation;
34   -import javax.validation.Validator;
35 27 import java.util.HashSet;
36 28 import java.util.Iterator;
37   -import java.util.List;
38 29 import java.util.Set;
39 30 import java.util.function.Function;
40 31 import java.util.regex.Matcher;
41 32 import java.util.regex.Pattern;
42   -import java.util.stream.Collectors;
43 33
44 34 @Slf4j
45 35 public abstract class DataValidator<D extends BaseData<?>> {
46 36 private static final Pattern EMAIL_PATTERN =
47 37 Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
48 38
49   - private static Validator fieldsValidator;
50   -
51   - static {
52   - initializeFieldsValidator();
53   - }
54   -
55 39 public void validate(D data, Function<D, TenantId> tenantIdFunction) {
56 40 try {
57 41 if (data == null) {
58 42 throw new DataValidationException("Data object can't be null!");
59 43 }
60 44
61   - List<String> validationErrors = validateFields(data);
62   - if (!validationErrors.isEmpty()) {
63   - throw new IllegalArgumentException("Validation error: " + String.join(", ", validationErrors));
64   - }
  45 + ConstraintValidator.validateFields(data);
65 46
66 47 TenantId tenantId = tenantIdFunction.apply(data);
67 48 validateDataImpl(tenantId, data);
... ... @@ -104,14 +85,6 @@ public abstract class DataValidator<D extends BaseData<?>> {
104 85 return emailMatcher.matches();
105 86 }
106 87
107   - private List<String> validateFields(D data) {
108   - Set<ConstraintViolation<D>> constraintsViolations = fieldsValidator.validate(data);
109   - return constraintsViolations.stream()
110   - .map(ConstraintViolation::getMessage)
111   - .distinct()
112   - .collect(Collectors.toList());
113   - }
114   -
115 88 protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
116 89 TenantEntityDao tenantEntityDao,
117 90 long maxEntities,
... ... @@ -126,10 +99,10 @@ public abstract class DataValidator<D extends BaseData<?>> {
126 99 }
127 100
128 101 protected void validateMaxSumDataSizePerTenant(TenantId tenantId,
129   - TenantEntityWithDataDao dataDao,
130   - long maxSumDataSize,
131   - long currentDataSize,
132   - EntityType entityType) {
  102 + TenantEntityWithDataDao dataDao,
  103 + long maxSumDataSize,
  104 + long currentDataSize,
  105 + EntityType entityType) {
133 106 if (maxSumDataSize > 0) {
134 107 if (dataDao.sumDataSizeByTenantId(tenantId) + currentDataSize > maxSumDataSize) {
135 108 throw new DataValidationException(String.format("Failed to create the %s, files size limit is exhausted %d bytes!",
... ... @@ -156,12 +129,4 @@ public abstract class DataValidator<D extends BaseData<?>> {
156 129 }
157 130 }
158 131
159   - private static void initializeFieldsValidator() {
160   - HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
161   - ConstraintMapping constraintMapping = validatorConfiguration.createConstraintMapping();
162   - constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
163   - validatorConfiguration.addMapping(constraintMapping);
164   -
165   - fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
166   - }
167 132 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.StringUtils;
  20 +import org.thingsboard.server.common.data.validation.Length;
  21 +
  22 +import javax.validation.ConstraintValidator;
  23 +import javax.validation.ConstraintValidatorContext;
  24 +
  25 +@Slf4j
  26 +public class StringLengthValidator implements ConstraintValidator<Length, String> {
  27 + private int max;
  28 +
  29 + @Override
  30 + public boolean isValid(String value, ConstraintValidatorContext context) {
  31 + if (StringUtils.isEmpty(value)) {
  32 + return true;
  33 + }
  34 + return value.trim().length() <= max;
  35 + }
  36 +
  37 + @Override
  38 + public void initialize(Length constraintAnnotation) {
  39 + this.max = constraintAnnotation.max();
  40 + }
  41 +}
... ...
... ... @@ -36,6 +36,9 @@
36 36 <mat-error *ngIf="attributeFormGroup.get('key').hasError('required')">
37 37 {{ 'attribute.key-required' | translate }}
38 38 </mat-error>
  39 + <mat-error *ngIf="attributeFormGroup.get('key').hasError('maxlength')">
  40 + {{ 'attribute.key-max-length' | translate }}
  41 + </mat-error>
39 42 </mat-form-field>
40 43 <tb-value-input
41 44 formControlName="value"
... ...
... ... @@ -56,7 +56,7 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
56 56
57 57 ngOnInit(): void {
58 58 this.attributeFormGroup = this.fb.group({
59   - key: ['', [Validators.required]],
  59 + key: ['', [Validators.required, Validators.maxLength(255)]],
60 60 value: [null, [Validators.required]]
61 61 });
62 62 }
... ...
... ... @@ -36,15 +36,15 @@ export abstract class ContactBasedComponent<T extends ContactBased<HasId>> exten
36 36
37 37 buildForm(entity: T): FormGroup {
38 38 const entityForm = this.buildEntityForm(entity);
39   - entityForm.addControl('country', this.fb.control(entity ? entity.country : '', []));
40   - entityForm.addControl('city', this.fb.control(entity ? entity.city : '', []));
41   - entityForm.addControl('state', this.fb.control(entity ? entity.state : '', []));
  39 + entityForm.addControl('country', this.fb.control(entity ? entity.country : '', [Validators.maxLength(255)]));
  40 + entityForm.addControl('city', this.fb.control(entity ? entity.city : '', [Validators.maxLength(255)]));
  41 + entityForm.addControl('state', this.fb.control(entity ? entity.state : '', [Validators.maxLength(255)]));
42 42 entityForm.addControl('zip', this.fb.control(entity ? entity.zip : '',
43 43 this.zipValidators(entity ? entity.country : '')
44 44 ));
45 45 entityForm.addControl('address', this.fb.control(entity ? entity.address : '', []));
46 46 entityForm.addControl('address2', this.fb.control(entity ? entity.address2 : '', []));
47   - entityForm.addControl('phone', this.fb.control(entity ? entity.phone : '', []));
  47 + entityForm.addControl('phone', this.fb.control(entity ? entity.phone : '', [Validators.maxLength(255)]));
48 48 entityForm.addControl('email', this.fb.control(entity ? entity.email : '', [Validators.email]));
49 49 return entityForm;
50 50 }
... ...
... ... @@ -41,6 +41,9 @@
41 41 <mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('required')">
42 42 {{ 'device-profile.name-required' | translate }}
43 43 </mat-error>
  44 + <mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('maxlength')">
  45 + {{ 'device-profile.name-max-length' | translate }}
  46 + </mat-error>
44 47 </mat-form-field>
45 48 <tb-rule-chain-autocomplete
46 49 labelText="device-profile.default-rule-chain"
... ...
... ... @@ -105,7 +105,7 @@ export class AddDeviceProfileDialogComponent extends
105 105 super(store, router, dialogRef);
106 106 this.deviceProfileDetailsFormGroup = this.fb.group(
107 107 {
108   - name: [data.deviceProfileName, [Validators.required]],
  108 + name: [data.deviceProfileName, [Validators.required, Validators.maxLength(255)]],
109 109 type: [DeviceProfileType.DEFAULT, [Validators.required]],
110 110 image: [null, []],
111 111 defaultRuleChainId: [null, []],
... ...
... ... @@ -54,6 +54,9 @@
54 54 <mat-error *ngIf="entityForm.get('name').hasError('required')">
55 55 {{ 'device-profile.name-required' | translate }}
56 56 </mat-error>
  57 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  58 + {{ 'device-profile.name-max-length' | translate }}
  59 + </mat-error>
57 60 </mat-form-field>
58 61 <tb-rule-chain-autocomplete
59 62 labelText="device-profile.default-rule-chain"
... ...
... ... @@ -102,7 +102,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
102 102 };
103 103 const form = this.fb.group(
104 104 {
105   - name: [entity ? entity.name : '', [Validators.required]],
  105 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
106 106 type: [entity ? entity.type : null, [Validators.required]],
107 107 image: [entity ? entity.image : null],
108 108 transportType: [entity ? entity.transportType : null, [Validators.required]],
... ...
... ... @@ -48,6 +48,9 @@
48 48 <mat-error *ngIf="entityForm.get('name').hasError('required')">
49 49 {{ 'tenant-profile.name-required' | translate }}
50 50 </mat-error>
  51 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  52 + {{ 'tenant-profile.name-max-length' | translate }}
  53 + </mat-error>
51 54 </mat-form-field>
52 55 <div fxLayout="column">
53 56 <mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore">
... ...
... ... @@ -58,7 +58,7 @@ export class TenantProfileComponent extends EntityComponent<TenantProfile> {
58 58 buildForm(entity: TenantProfile): FormGroup {
59 59 return this.fb.group(
60 60 {
61   - name: [entity ? entity.name : '', [Validators.required]],
  61 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
62 62 isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
63 63 isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
64 64 profileData: [entity && !this.isAdd ? entity.profileData : {
... ...
... ... @@ -44,10 +44,16 @@
44 44 <mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('required')">
45 45 {{ 'device.name-required' | translate }}
46 46 </mat-error>
  47 + <mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('maxlength')">
  48 + {{ 'device.name-max-length' | translate }}
  49 + </mat-error>
47 50 </mat-form-field>
48 51 <mat-form-field class="mat-block">
49 52 <mat-label translate>device.label</mat-label>
50 53 <input matInput formControlName="label">
  54 + <mat-error *ngIf="deviceWizardFormGroup.get('label').hasError('maxlength')">
  55 + {{ 'device.label-max-length' | translate }}
  56 + </mat-error>
51 57 </mat-form-field>
52 58 <div fxLayout="row" fxLayoutGap="16px">
53 59 <mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around">
... ...
... ... @@ -105,8 +105,8 @@ export class DeviceWizardDialogComponent extends
105 105 private fb: FormBuilder) {
106 106 super(store, router, dialogRef);
107 107 this.deviceWizardFormGroup = this.fb.group({
108   - name: ['', Validators.required],
109   - label: [''],
  108 + name: ['', [Validators.required, Validators.maxLength(255)]],
  109 + label: ['', Validators.maxLength(255)],
110 110 gateway: [false],
111 111 overwriteActivityTime: [false],
112 112 addProfileType: [0],
... ...
... ... @@ -56,6 +56,9 @@
56 56 <mat-error *ngIf="entityForm.get('title').hasError('required')">
57 57 {{ 'resource.title-required' | translate }}
58 58 </mat-error>
  59 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  60 + {{ 'resource.title-max-length' | translate }}
  61 + </mat-error>
59 62 </mat-form-field>
60 63 <tb-file-input *ngIf="isAdd"
61 64 formControlName="data"
... ...
... ... @@ -29,7 +29,7 @@ import {
29 29 ResourceTypeMIMETypes,
30 30 ResourceTypeTranslationMap
31 31 } from '@shared/models/resource.models';
32   -import { pairwise, startWith, takeUntil } from 'rxjs/operators';
  32 +import {filter, pairwise, startWith, takeUntil} from 'rxjs/operators';
33 33 import { ActionNotificationShow } from "@core/notification/notification.actions";
34 34
35 35 @Component({
... ... @@ -56,16 +56,14 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
56 56 super.ngOnInit();
57 57 this.entityForm.get('resourceType').valueChanges.pipe(
58 58 startWith(ResourceType.LWM2M_MODEL),
59   - pairwise(),
  59 + filter(() => this.isAdd),
60 60 takeUntil(this.destroy$)
61   - ).subscribe(([previousType, type]) => {
62   - if (previousType === this.resourceType.LWM2M_MODEL) {
63   - this.entityForm.get('title').setValidators(Validators.required);
64   - this.entityForm.get('title').updateValueAndValidity({emitEvent: false});
65   - }
  61 + ).subscribe((type) => {
66 62 if (type === this.resourceType.LWM2M_MODEL) {
67   - this.entityForm.get('title').clearValidators();
68   - this.entityForm.get('title').updateValueAndValidity({emitEvent: false});
  63 + this.entityForm.get('title').disable({emitEvent: false});
  64 + this.entityForm.patchValue({title: ''}, {emitEvent: false});
  65 + } else {
  66 + this.entityForm.get('title').enable({emitEvent: false})
69 67 }
70 68 this.entityForm.patchValue({
71 69 data: null,
... ... @@ -91,11 +89,8 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
91 89 buildForm(entity: Resource): FormGroup {
92 90 const form = this.fb.group(
93 91 {
94   - title: [entity ? entity.title : '', []],
95   - resourceType: [{
96   - value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL,
97   - disabled: !this.isAdd
98   - }, [Validators.required]],
  92 + title: [entity ? entity.title : "", [Validators.required, Validators.maxLength(255)]],
  93 + resourceType: [entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, [Validators.required]],
99 94 fileName: [entity ? entity.fileName : null, [Validators.required]],
100 95 }
101 96 );
... ...
... ... @@ -76,6 +76,9 @@
76 76 <mat-error *ngIf="entityForm.get('name').hasError('required')">
77 77 {{ 'asset.name-required' | translate }}
78 78 </mat-error>
  79 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  80 + {{ 'asset.name-max-length' | translate }}
  81 + </mat-error>
79 82 </mat-form-field>
80 83 <tb-entity-subtype-autocomplete
81 84 formControlName="type"
... ... @@ -86,6 +89,9 @@
86 89 <mat-form-field class="mat-block">
87 90 <mat-label translate>asset.label</mat-label>
88 91 <input matInput formControlName="label">
  92 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  93 + {{ 'asset.label-max-length' | translate }}
  94 + </mat-error>
89 95 </mat-form-field>
90 96 <div formGroupName="additionalInfo">
91 97 <mat-form-field class="mat-block">
... ...
... ... @@ -65,9 +65,9 @@ export class AssetComponent extends EntityComponent<AssetInfo> {
65 65 buildForm(entity: AssetInfo): FormGroup {
66 66 return this.fb.group(
67 67 {
68   - name: [entity ? entity.name : '', [Validators.required]],
69   - type: [entity ? entity.type : null, [Validators.required]],
70   - label: [entity ? entity.label : ''],
  68 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
  69 + type: [entity ? entity.type : null, [Validators.required, Validators.maxLength(255)]],
  70 + label: [entity ? entity.label : '', Validators.maxLength(255)],
71 71 additionalInfo: this.fb.group(
72 72 {
73 73 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
... ...
... ... @@ -73,6 +73,9 @@
73 73 <mat-error *ngIf="entityForm.get('title').hasError('required')">
74 74 {{ 'customer.title-required' | translate }}
75 75 </mat-error>
  76 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  77 + {{ 'customer.title-max-length' | translate }}
  78 + </mat-error>
76 79 </mat-form-field>
77 80 <div formGroupName="additionalInfo" fxLayout="column">
78 81 <mat-form-field class="mat-block">
... ...
... ... @@ -57,7 +57,7 @@ export class CustomerComponent extends ContactBasedComponent<Customer> {
57 57 buildEntityForm(entity: Customer): FormGroup {
58 58 return this.fb.group(
59 59 {
60   - title: [entity ? entity.title : '', [Validators.required]],
  60 + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
61 61 additionalInfo: this.fb.group(
62 62 {
63 63 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
... ...
... ... @@ -104,6 +104,9 @@
104 104 <mat-error *ngIf="entityForm.get('title').hasError('required')">
105 105 {{ 'dashboard.title-required' | translate }}
106 106 </mat-error>
  107 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  108 + {{ 'dashboard.title-max-length' | translate }}
  109 + </mat-error>
107 110 </mat-form-field>
108 111 <tb-image-input fxFlex
109 112 label="{{'dashboard.image' | translate}}"
... ...
... ... @@ -79,7 +79,7 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
79 79 this.updateFields(entity);
80 80 return this.fb.group(
81 81 {
82   - title: [entity ? entity.title : '', [Validators.required]],
  82 + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
83 83 image: [entity ? entity.image : null],
84 84 configuration: this.fb.group(
85 85 {
... ...
... ... @@ -89,6 +89,9 @@
89 89 <mat-error *ngIf="entityForm.get('name').hasError('required')">
90 90 {{ 'device.name-required' | translate }}
91 91 </mat-error>
  92 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  93 + {{ 'device.name-max-length' | translate }}
  94 + </mat-error>
92 95 </mat-form-field>
93 96 <tb-device-profile-autocomplete
94 97 [selectDefaultProfile]="isAdd"
... ... @@ -100,6 +103,9 @@
100 103 <mat-form-field class="mat-block">
101 104 <mat-label translate>device.label</mat-label>
102 105 <input matInput formControlName="label">
  106 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  107 + {{ 'device.label-max-length' | translate }}
  108 + </mat-error>
103 109 </mat-form-field>
104 110 <tb-ota-package-autocomplete
105 111 [useFullEntityId]="true"
... ...
... ... @@ -81,11 +81,11 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
81 81 buildForm(entity: DeviceInfo): FormGroup {
82 82 const form = this.fb.group(
83 83 {
84   - name: [entity ? entity.name : '', [Validators.required]],
  84 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
85 85 deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]],
86 86 firmwareId: [entity ? entity.firmwareId : null],
87 87 softwareId: [entity ? entity.softwareId : null],
88   - label: [entity ? entity.label : ''],
  88 + label: [entity ? entity.label : '', [Validators.maxLength(255)]],
89 89 deviceData: [entity ? entity.deviceData : null, [Validators.required]],
90 90 additionalInfo: this.fb.group(
91 91 {
... ...
... ... @@ -126,6 +126,9 @@
126 126 <mat-error *ngIf="entityForm.get('name').hasError('required')">
127 127 {{ 'edge.name-required' | translate }}
128 128 </mat-error>
  129 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  130 + {{ 'edge.name-max-length' | translate }}
  131 + </mat-error>
129 132 </mat-form-field>
130 133 <tb-entity-subtype-autocomplete
131 134 formControlName="type"
... ... @@ -140,6 +143,9 @@
140 143 <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('required')">
141 144 {{ 'edge.edge-license-key-required' | translate }}
142 145 </mat-error>
  146 + <mat-error *ngIf="entityForm.get('type').hasError('maxlength')">
  147 + {{ 'edge.type-max-length' | translate }}
  148 + </mat-error>
143 149 </mat-form-field>
144 150 </div>
145 151 <div [fxShow]="edgeScope !== 'customer_user'">
... ... @@ -179,6 +185,9 @@
179 185 <mat-form-field class="mat-block">
180 186 <mat-label translate>edge.label</mat-label>
181 187 <input matInput formControlName="label">
  188 + <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
  189 + {{ 'edge.label-max-length' | translate }}
  190 + </mat-error>
182 191 </mat-form-field>
183 192 <div formGroupName="additionalInfo" fxLayout="column">
184 193 <mat-form-field class="mat-block">
... ...
... ... @@ -69,9 +69,9 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> {
69 69 buildForm(entity: EdgeInfo): FormGroup {
70 70 const form = this.fb.group(
71 71 {
72   - name: [entity ? entity.name : '', [Validators.required]],
73   - type: [entity?.type ? entity.type : 'default', [Validators.required]],
74   - label: [entity ? entity.label : ''],
  72 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
  73 + type: [entity?.type ? entity.type : 'default', [Validators.required, Validators.maxLength(255)]],
  74 + label: [entity ? entity.label : '', Validators.maxLength(255)],
75 75 cloudEndpoint: [null, [Validators.required]],
76 76 edgeLicenseKey: ['', [Validators.required]],
77 77 routingKey: this.fb.control({value: entity ? entity.routingKey : null, disabled: true}),
... ...
... ... @@ -76,6 +76,9 @@
76 76 <mat-error *ngIf="entityForm.get('name').hasError('required')">
77 77 {{ 'entity-view.name-required' | translate }}
78 78 </mat-error>
  79 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  80 + {{ 'entity-view.name-max-length' | translate }}
  81 + </mat-error>
79 82 </mat-form-field>
80 83 <tb-entity-subtype-autocomplete
81 84 formControlName="type"
... ...
... ... @@ -80,8 +80,8 @@ export class EntityViewComponent extends EntityComponent<EntityViewInfo> {
80 80 buildForm(entity: EntityViewInfo): FormGroup {
81 81 return this.fb.group(
82 82 {
83   - name: [entity ? entity.name : '', [Validators.required]],
84   - type: [entity ? entity.type : null, [Validators.required]],
  83 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
  84 + type: [entity ? entity.type : null, Validators.required],
85 85 entityId: [entity ? entity.entityId : null, [Validators.required]],
86 86 startTimeMs: [entity ? entity.startTimeMs : null],
87 87 endTimeMs: [entity ? entity.endTimeMs : null],
... ...
... ... @@ -65,6 +65,9 @@
65 65 <mat-error *ngIf="entityForm.get('title').hasError('required')">
66 66 {{ 'ota-update.title-required' | translate }}
67 67 </mat-error>
  68 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  69 + {{ 'ota-update.title-max-length' | translate }}
  70 + </mat-error>
68 71 </mat-form-field>
69 72 <mat-form-field class="mat-block" fxFlex>
70 73 <mat-label translate>ota-update.version</mat-label>
... ... @@ -72,6 +75,9 @@
72 75 <mat-error *ngIf="entityForm.get('version').hasError('required')">
73 76 {{ 'ota-update.version-required' | translate }}
74 77 </mat-error>
  78 + <mat-error *ngIf="entityForm.get('version').hasError('maxlength')">
  79 + {{ 'ota-update.version-max-length' | translate }}
  80 + </mat-error>
75 81 </mat-form-field>
76 82 </div>
77 83 <tb-device-profile-autocomplete
... ... @@ -111,7 +117,8 @@
111 117 </mat-checkbox>
112 118 </section>
113 119 <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayoutGap.sm="8px"
114   - fxLayout.xs="column" fxLayout.md="column" *ngIf="!(isAdd && this.entityForm.get('generateChecksum').value)">
  120 + fxLayout.xs="column" fxLayout.md="column"
  121 + *ngIf="!(isAdd && this.entityForm.get('generateChecksum').value)">
115 122 <mat-form-field class="mat-block" fxFlex="33">
116 123 <mat-label translate>ota-update.checksum-algorithm</mat-label>
117 124 <mat-select formControlName="checksumAlgorithm">
... ... @@ -149,7 +156,8 @@
149 156 <input matInput formControlName="url"
150 157 type="text"
151 158 [required]="entityForm.get('isURL').value">
152   - <mat-error *ngIf="entityForm.get('url').hasError('required') || entityForm.get('url').hasError('pattern')" translate>
  159 + <mat-error *ngIf="entityForm.get('url').hasError('required') || entityForm.get('url').hasError('pattern')"
  160 + translate>
153 161 ota-update.direct-url-required
154 162 </mat-error>
155 163 </mat-form-field>
... ...
... ... @@ -84,6 +84,9 @@
84 84 <mat-error *ngIf="entityForm.get('name').hasError('required')">
85 85 {{ 'rulechain.name-required' | translate }}
86 86 </mat-error>
  87 + <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
  88 + {{ 'rulechain.name-max-length' | translate }}
  89 + </mat-error>
87 90 </mat-form-field>
88 91 <mat-checkbox fxFlex formControlName="debugMode" style="padding-bottom: 16px;">
89 92 {{ 'rulechain.debug-mode' | translate }}
... ...
... ... @@ -57,7 +57,7 @@ export class RuleChainComponent extends EntityComponent<RuleChain> {
57 57 buildForm(entity: RuleChain): FormGroup {
58 58 return this.fb.group(
59 59 {
60   - name: [entity ? entity.name : '', [Validators.required]],
  60 + name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
61 61 debugMode: [entity ? entity.debugMode : false],
62 62 additionalInfo: this.fb.group(
63 63 {
... ...
... ... @@ -48,6 +48,9 @@
48 48 <mat-error *ngIf="entityForm.get('title').hasError('required')">
49 49 {{ 'tenant.title-required' | translate }}
50 50 </mat-error>
  51 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  52 + {{ 'tenant.title-max-length' | translate }}
  53 + </mat-error>
51 54 </mat-form-field>
52 55 <tb-tenant-profile-autocomplete
53 56 [selectDefaultProfile]="isAdd"
... ...
... ... @@ -51,7 +51,7 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
51 51 buildEntityForm(entity: TenantInfo): FormGroup {
52 52 return this.fb.group(
53 53 {
54   - title: [entity ? entity.title : '', [Validators.required]],
  54 + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
55 55 tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]],
56 56 additionalInfo: this.fb.group(
57 57 {
... ...
... ... @@ -44,6 +44,9 @@
44 44 <mat-error *ngIf="entityForm.get('title').hasError('required')">
45 45 {{ 'widgets-bundle.title-required' | translate }}
46 46 </mat-error>
  47 + <mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
  48 + {{ 'widgets-bundle.title-max-length' | translate }}
  49 + </mat-error>
47 50 </mat-form-field>
48 51 <tb-image-input fxFlex
49 52 label="{{'widgets-bundle.image-preview' | translate}}"
... ...
... ... @@ -47,7 +47,7 @@ export class WidgetsBundleComponent extends EntityComponent<WidgetsBundle> {
47 47 buildForm(entity: WidgetsBundle): FormGroup {
48 48 return this.fb.group(
49 49 {
50   - title: [entity ? entity.title : '', [Validators.required]],
  50 + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
51 51 image: [entity ? entity.image : ''],
52 52 description: [entity ? entity.description : '', Validators.maxLength(255)]
53 53 }
... ...
... ... @@ -28,10 +28,16 @@
28 28 <mat-form-field class="mat-block">
29 29 <mat-label translate>contact.city</mat-label>
30 30 <input matInput formControlName="city">
  31 + <mat-error *ngIf="parentForm.get('city').hasError('maxlength')">
  32 + {{ 'contact.city-max-length' | translate }}
  33 + </mat-error>
31 34 </mat-form-field>
32 35 <mat-form-field class="mat-block">
33 36 <mat-label translate>contact.state</mat-label>
34 37 <input matInput formControlName="state">
  38 + <mat-error *ngIf="parentForm.get('state').hasError('maxlength')">
  39 + {{ 'contact.state-max-length' | translate }}
  40 + </mat-error>
35 41 </mat-form-field>
36 42 <mat-form-field class="mat-block">
37 43 <mat-label translate>contact.postal-code</mat-label>
... ... @@ -52,6 +58,9 @@
52 58 <mat-form-field class="mat-block">
53 59 <mat-label translate>contact.phone</mat-label>
54 60 <input matInput formControlName="phone">
  61 + <mat-error *ngIf="parentForm.get('phone').hasError('maxlength')">
  62 + {{ 'contact.phone-max-length' | translate }}
  63 + </mat-error>
55 64 </mat-form-field>
56 65 <mat-form-field class="mat-block">
57 66 <mat-label translate>contact.email</mat-label>
... ...
... ... @@ -40,4 +40,7 @@
40 40 <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('required')">
41 41 {{ entitySubtypeRequiredText | translate }}
42 42 </mat-error>
  43 + <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('maxlength')">
  44 + {{ entitySubtypeMaxLength | translate }}
  45 + </mat-error>
43 46 </mat-form-field>
... ...
... ... @@ -15,7 +15,7 @@
15 15 ///
16 16
17 17 import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
18   -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
19 19 import { Observable, of, Subscription, throwError } from 'rxjs';
20 20 import {
21 21 catchError,
... ... @@ -58,9 +58,11 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
58 58 entityType: EntityType;
59 59
60 60 private requiredValue: boolean;
  61 +
61 62 get required(): boolean {
62 63 return this.requiredValue;
63 64 }
  65 +
64 66 @Input()
65 67 set required(value: boolean) {
66 68 this.requiredValue = coerceBooleanProperty(value);
... ... @@ -74,6 +76,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
74 76 selectEntitySubtypeText: string;
75 77 entitySubtypeText: string;
76 78 entitySubtypeRequiredText: string;
  79 + entitySubtypeMaxLength: string;
77 80
78 81 filteredSubTypes: Observable<Array<string>>;
79 82
... ... @@ -96,7 +99,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
96 99 private entityViewService: EntityViewService,
97 100 private fb: FormBuilder) {
98 101 this.subTypeFormGroup = this.fb.group({
99   - subType: [null]
  102 + subType: [null, Validators.maxLength(255)]
100 103 });
101 104 }
102 105
... ... @@ -114,6 +117,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
114 117 this.selectEntitySubtypeText = 'asset.select-asset-type';
115 118 this.entitySubtypeText = 'asset.asset-type';
116 119 this.entitySubtypeRequiredText = 'asset.asset-type-required';
  120 + this.entitySubtypeMaxLength = 'asset.asset-type-max-length';
117 121 this.broadcastSubscription = this.broadcast.on('assetSaved', () => {
118 122 this.subTypes = null;
119 123 });
... ... @@ -122,6 +126,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
122 126 this.selectEntitySubtypeText = 'device.select-device-type';
123 127 this.entitySubtypeText = 'device.device-type';
124 128 this.entitySubtypeRequiredText = 'device.device-type-required';
  129 + this.entitySubtypeMaxLength = 'device.device-type-max-length';
125 130 this.broadcastSubscription = this.broadcast.on('deviceSaved', () => {
126 131 this.subTypes = null;
127 132 });
... ... @@ -130,6 +135,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
130 135 this.selectEntitySubtypeText = 'edge.select-edge-type';
131 136 this.entitySubtypeText = 'edge.edge-type';
132 137 this.entitySubtypeRequiredText = 'edge.edge-type-required';
  138 + this.entitySubtypeMaxLength = 'edge.type-max-length';
133 139 this.broadcastSubscription = this.broadcast.on('edgeSaved', () => {
134 140 this.subTypes = null;
135 141 });
... ... @@ -138,6 +144,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
138 144 this.selectEntitySubtypeText = 'entity-view.select-entity-view-type';
139 145 this.entitySubtypeText = 'entity-view.entity-view-type';
140 146 this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required';
  147 + this.entitySubtypeMaxLength = 'entity-view.type-max-length'
141 148 this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => {
142 149 this.subTypes = null;
143 150 });
... ... @@ -149,7 +156,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
149 156 debounceTime(150),
150 157 distinctUntilChanged(),
151 158 tap(value => {
152   - this.updateView(value);
  159 + this.updateView(value);
153 160 }),
154 161 // startWith<string | EntitySubtype>(''),
155 162 map(value => value ? value : ''),
... ... @@ -203,7 +210,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
203 210 fetchSubTypes(searchText?: string, strictMatch: boolean = false): Observable<Array<string>> {
204 211 this.searchText = searchText;
205 212 return this.getSubTypes().pipe(
206   - map(subTypes => subTypes.filter( subType => {
  213 + map(subTypes => subTypes.filter(subType => {
207 214 if (strictMatch) {
208 215 return searchText ? subType === searchText : false;
209 216 } else {
... ...
... ... @@ -39,4 +39,7 @@
39 39 <mat-error *ngIf="relationTypeFormGroup.get('relationType').hasError('required')">
40 40 {{ 'relation.relation-type-required' | translate }}
41 41 </mat-error>
  42 + <mat-error *ngIf="relationTypeFormGroup.get('relationType').hasError('maxlength')">
  43 + {{ 'relation.relation-type-max-length' | translate }}
  44 + </mat-error>
42 45 </mat-form-field>
... ...
... ... @@ -68,7 +68,7 @@ export class RelationTypeAutocompleteComponent implements ControlValueAccessor,
68 68 public translate: TranslateService,
69 69 private fb: FormBuilder) {
70 70 this.relationTypeFormGroup = this.fb.group({
71   - relationType: [null, this.required ? [Validators.required] : []]
  71 + relationType: [null, this.required ? [Validators.required, Validators.maxLength(255)] : [Validators.maxLength(255)]]
72 72 });
73 73 }
74 74
... ...
... ... @@ -369,6 +369,7 @@
369 369 "management": "Asset management",
370 370 "view-assets": "View Assets",
371 371 "add": "Add Asset",
  372 + "asset-type-max-length": "Asset type should be less than 256",
372 373 "assign-to-customer": "Assign to customer",
373 374 "assign-asset-to-customer": "Assign Asset(s) To Customer",
374 375 "assign-asset-to-customer-text": "Please select the assets to assign to the customer",
... ... @@ -391,6 +392,8 @@
391 392 "asset-types": "Asset types",
392 393 "name": "Name",
393 394 "name-required": "Name is required.",
  395 + "name-max-length": "Name should be less than 256",
  396 + "label-max-length": "Label should be less than 256",
394 397 "description": "Description",
395 398 "type": "Type",
396 399 "type-required": "Type is required.",
... ... @@ -449,6 +452,7 @@
449 452 "scope-shared": "Shared attributes",
450 453 "add": "Add attribute",
451 454 "key": "Key",
  455 + "key-max-length": "Key should be less than 256",
452 456 "last-update-time": "Last update time",
453 457 "key-required": "Attribute key is required.",
454 458 "value": "Value",
... ... @@ -590,7 +594,10 @@
590 594 "address2": "Address 2",
591 595 "phone": "Phone",
592 596 "email": "Email",
593   - "no-address": "No address"
  597 + "no-address": "No address",
  598 + "state-max-length": "State length should be less than 256",
  599 + "phone-max-length": "Phone number should be less than 256",
  600 + "city-max-length": "Specified city should be less than 256"
594 601 },
595 602 "common": {
596 603 "username": "Username",
... ... @@ -646,6 +653,7 @@
646 653 "manage-dashboards": "Manage dashboards",
647 654 "title": "Title",
648 655 "title-required": "Title is required.",
  656 + "title-max-length": "Title should be less than 256",
649 657 "description": "Description",
650 658 "details": "Details",
651 659 "events": "Events",
... ... @@ -700,6 +708,7 @@
700 708 "select-widget-subtitle": "List of available widget types",
701 709 "delete": "Delete dashboard",
702 710 "title-required": "Title is required.",
  711 + "title-max-length": "Title should be less than 256",
703 712 "description": "Description",
704 713 "details": "Details",
705 714 "dashboard-details": "Dashboard details",
... ... @@ -886,6 +895,7 @@
886 895 "management": "Device management",
887 896 "view-devices": "View Devices",
888 897 "device-alias": "Device alias",
  898 + "device-type-max-length": "Device type should be less than 256",
889 899 "aliases": "Device aliases",
890 900 "no-alias-matching": "'{{alias}}' not found.",
891 901 "no-aliases-found": "No aliases found.",
... ... @@ -998,6 +1008,8 @@
998 1008 "device-types": "Device types",
999 1009 "name": "Name",
1000 1010 "name-required": "Name is required.",
  1011 + "name-max-length": "Name should be less than 256",
  1012 + "label-max-length": "Label should be less than 256",
1001 1013 "description": "Description",
1002 1014 "label": "Label",
1003 1015 "events": "Events",
... ... @@ -1050,6 +1062,7 @@
1050 1062 "set-default": "Make device profile default",
1051 1063 "delete": "Delete device profile",
1052 1064 "copyId": "Copy device profile Id",
  1065 + "name-max-length": "Name should be less than 256",
1053 1066 "new-device-profile-name": "Device profile name",
1054 1067 "new-device-profile-name-required": "Device profile name is required.",
1055 1068 "name": "Name",
... ... @@ -1401,6 +1414,9 @@
1401 1414 "edge": "Edge",
1402 1415 "edge-instances": "Edge instances",
1403 1416 "edge-file": "Edge file",
  1417 + "name-max-length": "Name should be less than 256",
  1418 + "label-max-length": "Label should be less than 256",
  1419 + "type-max-length": "Type should be less than 256",
1404 1420 "management": "Edge management",
1405 1421 "no-edges-matching": "No edges matching '{{entity}}' were found.",
1406 1422 "rulechain-templates": "Rule chain templates",
... ... @@ -1741,6 +1757,8 @@
1741 1757 "created-time": "Created time",
1742 1758 "name": "Name",
1743 1759 "name-required": "Name is required.",
  1760 + "name-max-length": "Name should be less than 256",
  1761 + "type-max-length": "Entity view type should be less than 256",
1744 1762 "description": "Description",
1745 1763 "events": "Events",
1746 1764 "details": "Details",
... ... @@ -2335,12 +2353,14 @@
2335 2353 "selected-package": "{ count, plural, 1 {1 package} other {# packages} } selected",
2336 2354 "title": "Title",
2337 2355 "title-required": "Title is required.",
  2356 + "title-max-length": "Title should be less than 256",
2338 2357 "types": {
2339 2358 "firmware": "Firmware",
2340 2359 "software": "Software"
2341 2360 },
2342 2361 "version": "Version",
2343 2362 "version-required": "Version is required.",
  2363 + "version-max-length": "Version should be less than 256",
2344 2364 "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type."
2345 2365 },
2346 2366 "position": {
... ... @@ -2379,6 +2399,7 @@
2379 2399 "delete": "Delete relation",
2380 2400 "relation-type": "Relation type",
2381 2401 "relation-type-required": "Relation type is required.",
  2402 + "relation-type-max-length": "Relation type should be less than 256",
2382 2403 "any-relation-type": "Any type",
2383 2404 "add": "Add relation",
2384 2405 "edit": "Edit relation",
... ... @@ -2423,7 +2444,8 @@
2423 2444 "selected-resources": "{ count, plural, 1 {1 resource} other {# resources} } selected",
2424 2445 "system": "System",
2425 2446 "title": "Title",
2426   - "title-required": "Title is required."
  2447 + "title-required": "Title is required.",
  2448 + "title-max-length": "Title should be less than 256"
2427 2449 },
2428 2450 "rulechain": {
2429 2451 "rulechain": "Rule chain",
... ... @@ -2432,6 +2454,7 @@
2432 2454 "delete": "Delete rule chain",
2433 2455 "name": "Name",
2434 2456 "name-required": "Name is required.",
  2457 + "name-max-length": "Name should be less than 256",
2435 2458 "description": "Description",
2436 2459 "add": "Add Rule Chain",
2437 2460 "set-root": "Make rule chain root",
... ... @@ -2573,6 +2596,7 @@
2573 2596 "add-tenant-text": "Add new tenant",
2574 2597 "no-tenants-text": "No tenants found",
2575 2598 "tenant-details": "Tenant details",
  2599 + "title-max-length": "Title should be less than 256",
2576 2600 "delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?",
2577 2601 "delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.",
2578 2602 "delete-tenants-title": "Are you sure you want to delete { count, plural, 1 {1 tenant} other {# tenants} }?",
... ... @@ -2602,6 +2626,7 @@
2602 2626 "edit": "Edit tenant profile",
2603 2627 "tenant-profile-details": "Tenant profile details",
2604 2628 "no-tenant-profiles-text": "No tenant profiles found",
  2629 + "name-max-length": "Name should be less than 256",
2605 2630 "search": "Search tenant profiles",
2606 2631 "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected",
2607 2632 "no-tenant-profiles-matching": "No tenant profile matching '{{entity}}' were found.",
... ... @@ -2925,6 +2950,7 @@
2925 2950 "delete": "Delete widgets bundle",
2926 2951 "title": "Title",
2927 2952 "title-required": "Title is required.",
  2953 + "title-max-length": "Title should be less than 256",
2928 2954 "description": "Description",
2929 2955 "image-preview": "Image preview",
2930 2956 "add-widgets-bundle-text": "Add new widgets bundle",
... ...