From e926854911524d7063ecfecd65cd7fd68028a8fb Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 13 Apr 2023 12:53:59 +0300 Subject: [PATCH] NoXss and length validation for notification entities --- .../server/controller/BaseController.java | 22 ++++++---- .../DefaultNotificationCenter.java | 18 +++++--- .../channels/EmailNotificationChannel.java | 5 +-- .../channels/NotificationChannel.java | 2 +- .../channels/SlackNotificationChannel.java | 10 +++-- .../channels/SmsNotificationChannel.java | 6 ++- .../notification/rule/NotificationRule.java | 2 + .../targets/NotificationTarget.java | 2 + .../targets/NotificationTargetConfig.java | 2 + ...ailDeliveryMethodNotificationTemplate.java | 4 ++ .../template/NotificationTemplate.java | 2 + .../template/NotificationText.java | 30 ------------- ...ackDeliveryMethodNotificationTemplate.java | 7 +++ ...SmsDeliveryMethodNotificationTemplate.java | 9 ++++ ...WebDeliveryMethodNotificationTemplate.java | 15 +++++++ .../server/common/data/validation/Length.java | 4 +- .../server/common/data/validation/NoXss.java | 6 ++- .../dao/service/ConstraintValidator.java | 44 +++++++++++-------- 18 files changed, 115 insertions(+), 75 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 34f065d500..8a892b91a9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -103,7 +103,6 @@ import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.common.data.settings.UserDashboardAction; import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; @@ -130,12 +129,12 @@ import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; -import org.thingsboard.server.dao.user.UserSettingsService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; @@ -164,7 +163,9 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletResponse; +import javax.validation.ConstraintViolation; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -395,16 +396,19 @@ public abstract class BaseController { * Handles validation error for controller method arguments annotated with @{@link javax.validation.Valid} * */ @ExceptionHandler(MethodArgumentNotValidException.class) - public void handleValidationError(MethodArgumentNotValidException e, HttpServletResponse response) { - String errorMessage = "Validation error: " + e.getFieldErrors().stream() + public void handleValidationError(MethodArgumentNotValidException validationError, HttpServletResponse response) { + List> constraintsViolations = validationError.getFieldErrors().stream() .map(fieldError -> { - String property = fieldError.getField(); - if (property.equals("valid") || StringUtils.endsWith(property, ".valid")) { // when custom @AssertTrue is used - property = ""; + try { + return (ConstraintViolation) fieldError.unwrap(ConstraintViolation.class); + } catch (Exception e) { + log.warn("FieldError source is not of type ConstraintViolation"); + return null; // should not happen } - return (!property.isEmpty() ? (property + " ") : "") + fieldError.getDefaultMessage(); }) - .collect(Collectors.joining(", ")); + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String errorMessage = "Validation error: " + ConstraintValidator.getErrorMessage(constraintsViolations); ThingsboardException thingsboardException = new ThingsboardException(errorMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS); handleControllerException(thingsboardException, response); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index fa7e973843..d297f70a7f 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -125,8 +125,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple NotificationRuleId ruleId = request.getRuleId(); notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { if (!template.isEnabled()) return; - if (!channels.get(deliveryMethod).check(tenantId)) { - throw new IllegalArgumentException("Unable to send notification via " + deliveryMethod.getName() + ": not configured or not working"); + try { + channels.get(deliveryMethod).check(tenantId); + } catch (Exception e) { + throw new IllegalArgumentException(e.getMessage()); } if (ruleId == null) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { @@ -341,14 +343,20 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple @Override public Set getAvailableDeliveryMethods(TenantId tenantId) { return channels.values().stream() - .filter(channel -> channel.check(tenantId)) + .filter(channel -> { + try { + channel.check(tenantId); + return true; + } catch (Exception e) { + return false; + } + }) .map(NotificationChannel::getDeliveryMethod) .collect(Collectors.toSet()); } @Override - public boolean check(TenantId tenantId) { - return true; + public void check(TenantId tenantId) throws Exception { } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java index 61e4c528dc..f5bbc57954 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java @@ -48,12 +48,11 @@ public class EmailNotificationChannel implements NotificationChannel sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx); - boolean check(TenantId tenantId); + void check(TenantId tenantId) throws Exception; NotificationDeliveryMethod getDeliveryMethod(); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java index 7ff48d4cb9..46afbd7270 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java @@ -22,12 +22,12 @@ import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; -import org.thingsboard.server.dao.notification.NotificationSettingsService; -import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; +import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; +import org.thingsboard.server.service.notification.NotificationProcessingContext; @Component @RequiredArgsConstructor @@ -47,9 +47,11 @@ public class SlackNotificationChannel implements NotificationChannel implements Ha private TenantId tenantId; @NotBlank @NoXss + @Length(max = 255, message = "cannot be longer than 255 chars") private String name; @NotNull private NotificationTemplateId templateId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java index 9f267a514d..9a2f9a5306 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; @@ -35,6 +36,7 @@ public class NotificationTarget extends BaseData implement private TenantId tenantId; @NotBlank @NoXss + @Length(max = 255, message = "cannot be longer than 255 chars") private String name; @NotNull @Valid diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java index 2333062f65..231e419fb0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.Data; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @JsonIgnoreProperties(ignoreUnknown = true) @@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss; public abstract class NotificationTargetConfig { @NoXss + @Length(max = 500, message = "cannot be longer than 500 chars") private String description; @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java index bf8aca7913..fe909a104e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java @@ -21,6 +21,8 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.constraints.NotEmpty; @@ -30,6 +32,8 @@ import javax.validation.constraints.NotEmpty; @ToString(callSuper = true) public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { + @NoXss(fieldName = "email subject") + @Length(fieldName = "email subject", max = 250, message = "cannot be longer than 250 chars") @NotEmpty private String subject; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java index 2eaff12a9b..71da2f862b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; @@ -36,6 +37,7 @@ public class NotificationTemplate extends BaseData imple private TenantId tenantId; @NoXss @NotEmpty + @Length(max = 255, message = "cannot be longer than 255 chars") private String name; @NoXss @NotNull diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java deleted file mode 100644 index 6cb0e673a5..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.notification.template; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class NotificationText { - - private String body; - private String subject; - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java index 735eb3660e..8457677308 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java @@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.validation.NoXss; @Data @NoArgsConstructor @@ -31,6 +32,12 @@ public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotif super(other); } + @NoXss(fieldName = "Slack message") + @Override + public String getBody() { + return super.getBody(); + } + @Override public NotificationDeliveryMethod getMethod() { return NotificationDeliveryMethod.SLACK; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java index 55d2582284..7dc2e494f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java @@ -20,6 +20,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; @Data @NoArgsConstructor @@ -31,6 +33,13 @@ public class SmsDeliveryMethodNotificationTemplate extends DeliveryMethodNotific super(other); } + @NoXss(fieldName = "SMS message") + @Length(fieldName = "SMS message", max = 320, message = "cannot be longer than 320 chars") + @Override + public String getBody() { + return super.getBody(); + } + @Override public NotificationDeliveryMethod getMethod() { return NotificationDeliveryMethod.SMS; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java index ac1e0cfd8c..f26ed00568 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java @@ -25,6 +25,8 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.constraints.NotEmpty; import java.util.Optional; @@ -35,6 +37,8 @@ import java.util.Optional; @ToString(callSuper = true) public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { + @NoXss(fieldName = "web notification subject") + @Length(fieldName = "web notification subject", max = 150, message = "cannot be longer than 150 chars") @NotEmpty private String subject; private JsonNode additionalConfig; @@ -45,6 +49,15 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific this.additionalConfig = other.additionalConfig != null ? other.additionalConfig.deepCopy() : null; } + @NoXss(fieldName = "web notification message") + @Length(fieldName = "web notification message", max = 250, message = "cannot be longer than 250 chars") + @Override + public String getBody() { + return super.getBody(); + } + + @NoXss(fieldName = "web notification button text") + @Length(fieldName = "web notification button text", max = 50, message = "cannot be longer than 50 chars") @JsonIgnore public String getButtonText() { return getButtonConfigProperty("text"); @@ -57,6 +70,8 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific }); } + @NoXss(fieldName = "web notification button link") + @Length(fieldName = "web notification button link", max = 300, message = "cannot be longer than 300 chars") @JsonIgnore public String getButtonLink() { return getButtonConfigProperty("link"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java b/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java index 2b412d0ad7..cb7b9b1693 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java @@ -23,12 +23,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) @Constraint(validatedBy = {}) public @interface Length { String message() default "length must be equal or less than {max}"; - String fieldName(); + String fieldName() default ""; int max() default 255; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java b/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java index c99502ed7b..fb7b048f7e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java @@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) @Constraint(validatedBy = {}) public @interface NoXss { + String message() default "is malformed"; + String fieldName() default ""; + Class[] groups() default {}; Class[] payload() default {}; + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java index 41a618959a..b884de0094 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service; import com.google.common.collect.Iterators; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidatorConfiguration; import org.hibernate.validator.cfg.ConstraintMapping; @@ -29,11 +30,13 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.dao.exception.DataValidationException; -import javax.validation.Path; +import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.constraints.AssertTrue; -import java.util.List; +import javax.validation.metadata.ConstraintDescriptor; +import java.util.Collection; +import java.util.Set; import java.util.stream.Collectors; @Slf4j @@ -51,26 +54,31 @@ public class ConstraintValidator { } public static void validateFields(Object data, String errorPrefix) { - List constraintsViolations = getConstraintsViolations(data); + Set> constraintsViolations = fieldsValidator.validate(data); if (!constraintsViolations.isEmpty()) { - throw new DataValidationException(errorPrefix + String.join(", ", constraintsViolations)); + throw new DataValidationException(errorPrefix + getErrorMessage(constraintsViolations)); } } - public static List getConstraintsViolations(Object data) { - return fieldsValidator.validate(data).stream() - .map(constraintViolation -> { - String property; - if (constraintViolation.getConstraintDescriptor().getAttributes().containsKey("fieldName")) { - property = constraintViolation.getConstraintDescriptor().getAttributes().get("fieldName").toString(); - } else { - Path propertyPath = constraintViolation.getPropertyPath(); - property = Iterators.getLast(propertyPath.iterator()).toString(); - } - return property + " " + constraintViolation.getMessage(); - }) - .distinct() - .collect(Collectors.toList()); + public static String getErrorMessage(Collection> constraintsViolations) { + return constraintsViolations.stream() + .map(ConstraintValidator::getErrorMessage) + .distinct().sorted().collect(Collectors.joining(", ")); + } + + public static String getErrorMessage(ConstraintViolation constraintViolation) { + ConstraintDescriptor constraintDescriptor = constraintViolation.getConstraintDescriptor(); + String property = (String) constraintDescriptor.getAttributes().get("fieldName"); + if (StringUtils.isEmpty(property) && !(constraintDescriptor.getAnnotation() instanceof AssertTrue)) { + property = Iterators.getLast(constraintViolation.getPropertyPath().iterator()).toString(); + } + + String error = ""; + if (StringUtils.isNotEmpty(property)) { + error += property + " "; + } + error += constraintViolation.getMessage(); + return error; } private static void initializeValidators() {