diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index 20b76bdc691..9ad8b648bb0 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -88,6 +88,7 @@ public class FineractProperties { private FineractSqlValidationProperties sqlValidation; private FineractInputValidationProperties inputValidation; + private FineractPhoneValidationProperties phoneValidation; private FineractCache cache; @@ -733,6 +734,13 @@ public static class FineractInputValidationPatternProperties { private String pattern; } + @Getter + @Setter + public static class FineractPhoneValidationProperties { + + private String regex = "^\\+?[0-9]{7,15}$"; + } + @Getter @Setter public static class FineractCache { @@ -793,4 +801,10 @@ public static class FineractDefaultValues { private Long officeId; } + + @Getter + @Setter + public static class FineractPhoneValidationProperties { + private String regex = "^\\+?[0-9]{7,15}$"; + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PhoneNumberValidationService.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PhoneNumberValidationService.java new file mode 100644 index 00000000000..deb58263f2a --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PhoneNumberValidationService.java @@ -0,0 +1,25 @@ +package org.apache.fineract.infrastructure.core.service; + +import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PhoneNumberValidationService { + + private final FineractProperties fineractProperties; + + public boolean isValid(String phoneNumber) { + if (phoneNumber == null || phoneNumber.isEmpty()) { + return true; + } + String regex = fineractProperties.getPhoneValidation().getRegex(); + return Pattern.compile(regex).matcher(phoneNumber).matches(); + } + + public String getRegex() { + return fineractProperties.getPhoneValidation().getRegex(); + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/PhoneNumberValidator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/PhoneNumberValidator.java new file mode 100644 index 00000000000..48d0edfc93a --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/PhoneNumberValidator.java @@ -0,0 +1,19 @@ +package org.apache.fineract.infrastructure.core.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.PhoneNumberValidationService; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PhoneNumberValidator implements ConstraintValidator { + + private final PhoneNumberValidationService phoneNumberValidationService; + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return phoneNumberValidationService.isValid(value); + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/ValidPhoneNumber.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/ValidPhoneNumber.java new file mode 100644 index 00000000000..f631a88007a --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/ValidPhoneNumber.java @@ -0,0 +1,23 @@ +package org.apache.fineract.infrastructure.core.validator; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Constraint(validatedBy = PhoneNumberValidator.class) +@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, + ElementType.TYPE_USE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidPhoneNumber { + + String message() default "Invalid phone number"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffCreateRequest.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffCreateRequest.java index ca1264b900f..2a3412c6743 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffCreateRequest.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffCreateRequest.java @@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.apache.fineract.infrastructure.core.validator.ValidPhoneNumber; import org.hibernate.validator.constraints.Length; @Builder @@ -56,7 +56,7 @@ public class StaffCreateRequest implements Serializable { private String emailAddress; @Length(max = 50, message = "{org.apache.fineract.organisation.staff.mobile-no.max}") // @NotBlank(message = "{org.apache.fineract.organisation.staff.mobile-no.not-blank}") - @Pattern(regexp = "^\\+?[0-9]{7,15}$", message = "{org.apache.fineract.organisation.staff.mobile-no.invalid}") + @ValidPhoneNumber(message = "{org.apache.fineract.organisation.staff.mobile-no.invalid}") private String mobileNo; @Builder.Default @JsonProperty("isActive") diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffUpdateRequest.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffUpdateRequest.java index 1817934d01f..87e49d0eb99 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffUpdateRequest.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/data/StaffUpdateRequest.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.Hidden; -import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; import lombok.AllArgsConstructor; @@ -28,6 +27,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.FieldNameConstants; +import org.apache.fineract.infrastructure.core.validator.ValidPhoneNumber; import org.apache.fineract.organisation.staff.validation.StaffForceStatus; import org.hibernate.validator.constraints.Length; @@ -62,7 +62,7 @@ public class StaffUpdateRequest implements Serializable { private String emailAddress; @Length(max = 50, message = "{org.apache.fineract.organisation.staff.mobile-no.max}") // @NotBlank(message = "{org.apache.fineract.organisation.staff.mobile-no.not-blank}") - @Pattern(regexp = "^\\+?[0-9]{7,15}$", message = "{org.apache.fineract.organisation.staff.mobile-no.invalid}") + @ValidPhoneNumber(message = "{org.apache.fineract.organisation.staff.mobile-no.invalid}") private String mobileNo; @JsonProperty("isActive") private Boolean isActive; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java index 546c1051bb0..36900520572 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java @@ -40,6 +40,7 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.PhoneNumberValidationService; import org.apache.fineract.portfolio.client.api.ClientApiConstants; import org.apache.fineract.validation.constraints.DateFormatValidator; import org.springframework.beans.factory.annotation.Autowired; @@ -50,13 +51,15 @@ public final class ClientDataValidator { private final FromJsonHelper fromApiJsonHelper; private final ConfigurationReadPlatformService configurationReadPlatformService; - private static final String MOBILE_NUMBER_REGEX = "^\\+?[0-9]{7,15}$"; + private final PhoneNumberValidationService phoneNumberValidationService; @Autowired public ClientDataValidator(final FromJsonHelper fromApiJsonHelper, - final ConfigurationReadPlatformService configurationReadPlatformService) { + final ConfigurationReadPlatformService configurationReadPlatformService, + final PhoneNumberValidationService phoneNumberValidationService) { this.fromApiJsonHelper = fromApiJsonHelper; this.configurationReadPlatformService = configurationReadPlatformService; + this.phoneNumberValidationService = phoneNumberValidationService; } public void validateForCreate(final String json) { @@ -165,7 +168,7 @@ public void validateForCreate(final String json) { if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) { final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element); baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).ignoreIfNull() - .matchesRegularExpression(MOBILE_NUMBER_REGEX).notExceedingLengthOf(50); + .matchesRegularExpression(phoneNumberValidationService.getRegex()).notExceedingLengthOf(50); } final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(ClientApiConstants.activeParamName, element); @@ -451,7 +454,7 @@ public void validateForUpdate(final String json) { atLeastOneParameterPassedForUpdate = true; final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element); baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).ignoreIfNull() - .matchesRegularExpression(MOBILE_NUMBER_REGEX).notExceedingLengthOf(50); + .matchesRegularExpression(phoneNumberValidationService.getRegex()).notExceedingLengthOf(50); } final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(ClientApiConstants.activeParamName, element); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/client/data/ClientDataValidatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/client/data/ClientDataValidatorTest.java index bcaf9e4f403..b6074111a18 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/client/data/ClientDataValidatorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/client/data/ClientDataValidatorTest.java @@ -28,6 +28,7 @@ import org.apache.fineract.infrastructure.configuration.service.ConfigurationReadPlatformService; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.PhoneNumberValidationService; import org.apache.fineract.portfolio.client.api.ClientApiConstants; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,6 +45,9 @@ class ClientDataValidatorTest { @Mock private ConfigurationReadPlatformService configurationReadPlatformService; + @Mock + private PhoneNumberValidationService phoneNumberValidationService; + private ClientDataValidator validator; @BeforeEach @@ -51,7 +55,8 @@ void setUp() { FromJsonHelper fromApiJsonHelper = new FromJsonHelper(); when(configurationReadPlatformService.retrieveGlobalConfiguration(anyString())) .thenReturn(new GlobalConfigurationPropertyData().setEnabled(false)); - validator = new ClientDataValidator(fromApiJsonHelper, configurationReadPlatformService); + when(phoneNumberValidationService.getRegex()).thenReturn("^\\+?[0-9]{7,15}$"); + validator = new ClientDataValidator(fromApiJsonHelper, configurationReadPlatformService, phoneNumberValidationService); } private static String validMinimalCreateJson(String dateFormat) {