From 5bf0278c32e8e799cfd9b885184d7dd82518bbbd Mon Sep 17 00:00:00 2001 From: Levente Gal Date: Thu, 18 Jan 2024 11:25:47 +0200 Subject: [PATCH] #12841 Add validation to the national health ID field --- .../de/symeda/sormas/api/i18n/Strings.java | 1 + .../symeda/sormas/api/i18n/Validations.java | 1 + .../api/utils}/luxembourg/CheckDigitLuhn.java | 4 +- .../utils}/luxembourg/CheckDigitVerhoeff.java | 4 +- .../LuxembourgNationalHealthIdValidator.java | 67 ++++++++++++++++ .../src/main/resources/strings.properties | 1 + .../src/main/resources/validations.properties | 3 +- .../externalemail/ExternalEmailFacadeEjb.java | 76 +++++++++---------- .../luxembourg/NationalHealthIdValidator.java | 72 ------------------ .../ExternalEmailFacadeEjbTest.java | 2 +- .../NationalHealthIdValidatorTest.java | 66 ---------------- ...xembourgNationalHealthIdValidatorTest.java | 55 ++++++++++++++ .../sormas/ui/person/PersonEditForm.java | 28 ++++++- .../sormas/ui/utils/ValidationUtils.java | 25 +++++- 14 files changed, 217 insertions(+), 188 deletions(-) rename {sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail => sormas-api/src/main/java/de/symeda/sormas/api/utils}/luxembourg/CheckDigitLuhn.java (95%) rename {sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail => sormas-api/src/main/java/de/symeda/sormas/api/utils}/luxembourg/CheckDigitVerhoeff.java (98%) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/LuxembourgNationalHealthIdValidator.java delete mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/NationalHealthIdValidator.java delete mode 100644 sormas-backend/src/test/java/de/symeda/sormas/backend/externalemail/luxembourg/NationalHealthIdValidatorTest.java create mode 100644 sormas-backend/src/test/java/de/symeda/sormas/backend/util/luxembourg/LuxembourgNationalHealthIdValidatorTest.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java index c47d7446ce9..fadd698da1f 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java @@ -1397,6 +1397,7 @@ public interface Strings { String messagePersonListAddedAsEventPerticipants = "messagePersonListAddedAsEventPerticipants"; String messagePersonMergedAddressDescription = "messagePersonMergedAddressDescription"; String messagePersonMergeNoEventParticipantRights = "messagePersonMergeNoEventParticipantRights"; + String messagePersonNationalHealthIdInvalid = "messagePersonNationalHealthIdInvalid"; String messagePersonSaved = "messagePersonSaved"; String messagePersonSavedClassificationChanged = "messagePersonSavedClassificationChanged"; String messagePickEventParticipantsIncompleteSelection = "messagePickEventParticipantsIncompleteSelection"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Validations.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Validations.java index 5fd5571e404..29d9dfc8231 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Validations.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Validations.java @@ -144,6 +144,7 @@ public interface Validations { String importUnexpectedError = "importUnexpectedError"; String importWrongDataTypeError = "importWrongDataTypeError"; String infrastructureDataLocked = "infrastructureDataLocked"; + String invalidNationalHealthId = "invalidNationalHealthId"; String investigationStatusUnclassifiedCase = "investigationStatusUnclassifiedCase"; String jurisdictionChangeUserAssignment = "jurisdictionChangeUserAssignment"; String latitudeBetween = "latitudeBetween"; diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/CheckDigitLuhn.java b/sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/CheckDigitLuhn.java similarity index 95% rename from sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/CheckDigitLuhn.java rename to sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/CheckDigitLuhn.java index 698b79ce1b3..a9ba98dde08 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/CheckDigitLuhn.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/CheckDigitLuhn.java @@ -1,6 +1,6 @@ /* * SORMAS® - Surveillance Outbreak Response Management & Analysis System - * Copyright © 2016-2023 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package de.symeda.sormas.backend.externalemail.luxembourg; +package de.symeda.sormas.api.utils.luxembourg; /** * Apply Luhn algorithm to compute check digit diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/CheckDigitVerhoeff.java b/sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/CheckDigitVerhoeff.java similarity index 98% rename from sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/CheckDigitVerhoeff.java rename to sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/CheckDigitVerhoeff.java index c34cc70d58d..88620dcd123 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/CheckDigitVerhoeff.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/CheckDigitVerhoeff.java @@ -1,6 +1,6 @@ /* * SORMAS® - Surveillance Outbreak Response Management & Analysis System - * Copyright © 2016-2023 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package de.symeda.sormas.backend.externalemail.luxembourg; +package de.symeda.sormas.api.utils.luxembourg; /** * Apply Verhoeff algorithm to compute check digit diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/LuxembourgNationalHealthIdValidator.java b/sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/LuxembourgNationalHealthIdValidator.java new file mode 100644 index 00000000000..a4d6fb87cf5 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/utils/luxembourg/LuxembourgNationalHealthIdValidator.java @@ -0,0 +1,67 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.utils.luxembourg; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LuxembourgNationalHealthIdValidator { + + /** + * - AAAA = année de naissance + * - MM = mois de naissance + * - JJ = jour de naissance + * - XXX = numéro aléatoire unique par date de naissance + * - C1 = numéro de contrôle calculé sur AAAAMMJJXXX suivant l’algorithme LUHN 10 + * - C2 = numéro de contrôle calculé sur AAAAMMJJXXX suivant l’algorithme VERHOEFF + */ + private static final Pattern NATIONAL_HEALTH_ID_PATTERN = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})(\\d{3})(\\d)(\\d)"); + + public static boolean isValid(String nationalHealthId, Integer birthdateYYYY, Integer birthdateMM, Integer birthdateDD) { + if (nationalHealthId == null) { + return false; + } + + Matcher patternMatcher = NATIONAL_HEALTH_ID_PATTERN.matcher(nationalHealthId); + if (!patternMatcher.matches()) { + return false; + } + + String yyyy = patternMatcher.group(1); + String mm = patternMatcher.group(2); + String dd = patternMatcher.group(3); + String xxx = patternMatcher.group(4); + String c1 = patternMatcher.group(5); + String c2 = patternMatcher.group(6); + + if (isNullOrEquals(birthdateYYYY, Integer.parseInt(yyyy)) + && isNullOrEquals(birthdateMM, Integer.parseInt(mm)) + && isNullOrEquals(birthdateDD, Integer.parseInt(dd))) { + String iNumber = yyyy + mm + dd + xxx; + + if (CheckDigitLuhn.checkDigit(iNumber + c1) && CheckDigitVerhoeff.checkDigit(iNumber + c2)) { + return true; + } + } + + return false; + } + + private static boolean isNullOrEquals(Integer personBirthdateFieldValue, int yyyy) { + return personBirthdateFieldValue == null || personBirthdateFieldValue == yyyy; + } + +} diff --git a/sormas-api/src/main/resources/strings.properties b/sormas-api/src/main/resources/strings.properties index 8496db6f8aa..4c5d537912f 100644 --- a/sormas-api/src/main/resources/strings.properties +++ b/sormas-api/src/main/resources/strings.properties @@ -1498,6 +1498,7 @@ messageExternalEmailNoAttachments=No attachments messageCustomizableEnumValueSaved = Customizable enum value saved messageExternalEmailAttachmentPassword=Please use this password to open the documents sent to you via email from SORMAS: %s messageExternalEmailAttachmentNotAvailableInfo=Attaching documents is disabled because encryption would not be possible. To encrypt documents, the person needs to have either a national health ID specified, or a primary mobile phone number set with SMS sending set up on this system. +messagePersonNationalHealthIdInvalid=The entered national health ID does not seem to be correct # Notifications notificationCaseClassificationChanged = The classification of case %s has changed to %s. notificationCaseInvestigationDone = The investigation of case %s has been done. diff --git a/sormas-api/src/main/resources/validations.properties b/sormas-api/src/main/resources/validations.properties index 5e92dd5e38c..2f55e0e2a24 100644 --- a/sormas-api/src/main/resources/validations.properties +++ b/sormas-api/src/main/resources/validations.properties @@ -291,4 +291,5 @@ customizableEnumValueAllowedCharacters = Value may only consist of uppercase let customizableEnumValueEmptyTranslations = Please select languages and enter captions for all translations in the list. customizableEnumValueDuplicateLanguage = Please only add one translation per language. customizableEnumValueDuplicateValue = The value %s already exists for data type %s. Enum values have to be unique. -attachedDocumentNotRelatedToEntity=The attached document is not related to the entity. \ No newline at end of file +attachedDocumentNotRelatedToEntity=The attached document is not related to the entity. +invalidNationalHealthId=This value does not seem to be a correct national health ID \ No newline at end of file diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/ExternalEmailFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/ExternalEmailFacadeEjb.java index aba01f432fe..3bd6cfe6825 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/ExternalEmailFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/ExternalEmailFacadeEjb.java @@ -60,6 +60,7 @@ import de.symeda.sormas.api.travelentry.TravelEntryReferenceDto; import de.symeda.sormas.api.utils.DataHelper.Pair; import de.symeda.sormas.api.utils.ValidationRuntimeException; +import de.symeda.sormas.api.utils.luxembourg.LuxembourgNationalHealthIdValidator; import de.symeda.sormas.backend.caze.CaseService; import de.symeda.sormas.backend.common.ConfigFacadeEjb.ConfigFacadeEjbLocal; import de.symeda.sormas.backend.common.messaging.EmailService; @@ -75,7 +76,6 @@ import de.symeda.sormas.backend.document.DocumentService; import de.symeda.sormas.backend.document.DocumentStorageService; import de.symeda.sormas.backend.event.EventParticipantService; -import de.symeda.sormas.backend.externalemail.luxembourg.NationalHealthIdValidator; import de.symeda.sormas.backend.manualmessagelog.ManualMessageLog; import de.symeda.sormas.backend.manualmessagelog.ManualMessageLogService; import de.symeda.sormas.backend.person.Person; @@ -151,8 +151,8 @@ public void sendEmail(@Valid ExternalEmailOptionsDto options) throws DocumentTem User currentUser = userService.getCurrentUser(); DocumentTemplateEntities documentEntities = templateEntitiesBuilder.resolveEntities( - new RootEntities().addReference(options.getRootEntityType(), options.getRootEntityReference()) - .addEntity(RootEntityType.ROOT_USER, currentUser)); + new RootEntities().addReference(options.getRootEntityType(), options.getRootEntityReference()) + .addEntity(RootEntityType.ROOT_USER, currentUser)); PersonReferenceDto personRef = (PersonReferenceDto) documentEntities.getEntity(RootEntityType.ROOT_PERSON); Person person = personService.getByReferenceDto(personRef); @@ -176,7 +176,7 @@ public void sendEmail(@Valid ExternalEmailOptionsDto options) throws DocumentTem } String generatedText = - documentTemplateFacade.generateDocumentTxtFromEntities(options.getDocumentWorkflow(), options.getTemplateName(), documentEntities, null); + documentTemplateFacade.generateDocumentTxtFromEntities(options.getDocumentWorkflow(), options.getTemplateName(), documentEntities, null); EmailTemplateTexts emailTexts = splitTemplateContent(generatedText); try { @@ -184,10 +184,10 @@ public void sendEmail(@Valid ExternalEmailOptionsDto options) throws DocumentTem if (passwordType == PasswordType.RANDOM) { messagingService.sendManualMessage( - person, - null, - String.format(I18nProperties.getString(Strings.messageExternalEmailAttachmentPassword), password), - MessageType.SMS); + person, + null, + String.format(I18nProperties.getString(Strings.messageExternalEmailAttachmentPassword), password), + MessageType.SMS); } } catch (MessagingException | NotificationDeliveryFailedException e) { logger.error("Error sending email", e); @@ -201,9 +201,9 @@ public void sendEmail(@Valid ExternalEmailOptionsDto options) throws DocumentTem private static void validateAttachedDocuments(List sormasDocuments, ExternalEmailOptionsDto options) { DocumentRelatedEntityType documentRelatedEntityType = DOCUMENT_WORKFLOW_DOCUMENT_RELATION_MAPPING.get(options.getDocumentWorkflow()); if (sormasDocuments.stream() - .anyMatch( - d -> d.getRelatedEntityType() != documentRelatedEntityType - && !Objects.equals(d.getRelatedEntityUuid(), options.getRootEntityReference().getUuid()))) { + .anyMatch( + d -> d.getRelatedEntityType() != documentRelatedEntityType + && !Objects.equals(d.getRelatedEntityUuid(), options.getRootEntityReference().getUuid()))) { throw new ValidationRuntimeException(I18nProperties.getValidationError(Validations.attachedDocumentNotRelatedToEntity)); } } @@ -238,30 +238,30 @@ private Pair getPassword(Person person) throws ExternalEma PasswordType passwordType = getApplicablePasswordType(person); switch (passwordType) { - case HEALTH_ID: - return new Pair<>(person.getNationalHealthId(), passwordType); - case RANDOM: - return new Pair<>(generateRandomPassword(), passwordType); - case NONE: - default: - throw new ExternalEmailException(I18nProperties.getString(Strings.errorExternalEmailAttachmentCannotEncrypt)); + case HEALTH_ID: + return new Pair<>(person.getNationalHealthId(), passwordType); + case RANDOM: + return new Pair<>(generateRandomPassword(), passwordType); + case NONE: + default: + throw new ExternalEmailException(I18nProperties.getString(Strings.errorExternalEmailAttachmentCannotEncrypt)); } } private static String generateRandomPassword() { return new RandomStringGenerator.Builder().withinRange( - new char[]{ - 'a', - 'z'}, - new char[]{ - 'A', - 'Z'}, - new char[]{ - '2', - '9'}) - .filteredBy(codePoint -> !"lIO".contains(String.valueOf((char) codePoint))) - .build() - .generate(ATTACHMENT_PASSWORD_LENGTH); + new char[] { + 'a', + 'z' }, + new char[] { + 'A', + 'Z' }, + new char[] { + '2', + '9' }) + .filteredBy(codePoint -> !"lIO".contains(String.valueOf((char) codePoint))) + .build() + .generate(ATTACHMENT_PASSWORD_LENGTH); } private static void validateOptions(ExternalEmailOptionsDto options) { @@ -272,10 +272,10 @@ private static void validateOptions(ExternalEmailOptionsDto options) { } private ManualMessageLog createMessageLog( - ExternalEmailOptionsDto options, - PersonReferenceDto personRef, - User currentUser, - List attachedDocuments) { + ExternalEmailOptionsDto options, + PersonReferenceDto personRef, + User currentUser, + List attachedDocuments) { ManualMessageLog log = new ManualMessageLog(); log.setMessageType(MessageType.EMAIL); @@ -290,10 +290,10 @@ private ManualMessageLog createMessageLog( log.setCaze(caseService.getByReferenceDto(getRootEntityReference(options, RootEntityType.ROOT_CASE, CaseReferenceDto.class))); log.setContact(contactService.getByReferenceDto(getRootEntityReference(options, RootEntityType.ROOT_CONTACT, ContactReferenceDto.class))); log.setEventParticipant( - eventParticipantService - .getByReferenceDto(getRootEntityReference(options, RootEntityType.ROOT_EVENT_PARTICIPANT, EventParticipantReferenceDto.class))); + eventParticipantService + .getByReferenceDto(getRootEntityReference(options, RootEntityType.ROOT_EVENT_PARTICIPANT, EventParticipantReferenceDto.class))); log.setTravelEntry( - travelEntryService.getByReferenceDto(getRootEntityReference(options, RootEntityType.ROOT_TRAVEL_ENTRY, TravelEntryReferenceDto.class))); + travelEntryService.getByReferenceDto(getRootEntityReference(options, RootEntityType.ROOT_TRAVEL_ENTRY, TravelEntryReferenceDto.class))); return log; } @@ -312,7 +312,7 @@ private static boolean isValidLuxembourgNationalHealthId(String nationalHealthId return false; } - return NationalHealthIdValidator.isValid(nationalHealthId, person); + return LuxembourgNationalHealthIdValidator.isValid(nationalHealthId, person.getBirthdateYYYY(), person.getBirthdateMM(), person.getBirthdateDD()); } @Stateless diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/NationalHealthIdValidator.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/NationalHealthIdValidator.java deleted file mode 100644 index cbb3e995f4e..00000000000 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalemail/luxembourg/NationalHealthIdValidator.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SORMAS® - Surveillance Outbreak Response Management & Analysis System - * Copyright © 2016-2023 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package de.symeda.sormas.backend.externalemail.luxembourg; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import de.symeda.sormas.backend.person.Person; - -public class NationalHealthIdValidator { - - /** - * - AAAA = année de naissance - * - MM = mois de naissance - * - JJ = jour de naissance - * - XXX = numéro aléatoire unique par date de naissance - * - C1 = numéro de contrôle calculé sur AAAAMMJJXXX suivant l’algorithme LUHN 10 - * - C2 = numéro de contrôle calculé sur AAAAMMJJXXX suivant l’algorithme VERHOEFF - */ - private static final Pattern NATIONAL_HEALTH_ID_PATTERN = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})(\\d{3})(\\d)(\\d)"); - - public static boolean isValid(String nationalHealthId, Person person) { - if (nationalHealthId == null) { - return false; - } - - Matcher patternMatcher = NATIONAL_HEALTH_ID_PATTERN.matcher(nationalHealthId); - if (!patternMatcher.matches()) { - return false; - } - - String yyyy = patternMatcher.group(1); - String mm = patternMatcher.group(2); - String dd = patternMatcher.group(3); - String xxx = patternMatcher.group(4); - String c1 = patternMatcher.group(5); - String c2 = patternMatcher.group(6); - - if (isNullOrEquals(person.getBirthdateYYYY(), Integer.parseInt(yyyy)) - && isNullOrEquals(person.getBirthdateMM(), Integer.parseInt(mm)) - && isNullOrEquals(person.getBirthdateDD(), Integer.parseInt(dd))) { - String iNumber = yyyy + mm + dd + xxx; - - if (CheckDigitLuhn.checkDigit(iNumber + c1) && CheckDigitVerhoeff.checkDigit(iNumber + c2)) { - return true; - } - } - - return false; - } - - private static boolean isNullOrEquals(Integer personBirthdateFieldValue, int yyyy) { - return personBirthdateFieldValue == null || personBirthdateFieldValue == yyyy; - } - -} diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/externalemail/ExternalEmailFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/externalemail/ExternalEmailFacadeEjbTest.java index f5f60e6eb76..bf07b5b8738 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/externalemail/ExternalEmailFacadeEjbTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/externalemail/ExternalEmailFacadeEjbTest.java @@ -16,7 +16,7 @@ package de.symeda.sormas.backend.externalemail; import static de.symeda.sormas.backend.docgeneration.TemplateTestUtil.updateLineSeparatorsBasedOnOS; -import static de.symeda.sormas.backend.externalemail.luxembourg.NationalHealthIdValidatorTest.VALID_LU_NATIONAL_HEALTH_ID; +import static de.symeda.sormas.backend.util.luxembourg.LuxembourgNationalHealthIdValidatorTest.VALID_LU_NATIONAL_HEALTH_ID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/externalemail/luxembourg/NationalHealthIdValidatorTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/externalemail/luxembourg/NationalHealthIdValidatorTest.java deleted file mode 100644 index cf4d55517c7..00000000000 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/externalemail/luxembourg/NationalHealthIdValidatorTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SORMAS® - Surveillance Outbreak Response Management & Analysis System - * Copyright © 2016-2023 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package de.symeda.sormas.backend.externalemail.luxembourg; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.Test; - -import de.symeda.sormas.backend.person.Person; - -public class NationalHealthIdValidatorTest { - - public static final String VALID_LU_NATIONAL_HEALTH_ID = "1980010145728"; - - @Test - public void testIsValid() { - assertThat(NationalHealthIdValidator.isValid("19800101", createPerson(1980, 1, 1)), is(false)); - assertThat(NationalHealthIdValidator.isValid("1980Jan0145728", createPerson(1980, 1, 1)), is(false)); - assertThat(NationalHealthIdValidator.isValid("1980Ja0145728", createPerson(1980, 1, 1)), is(false)); - - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(1980, 1, 1)), is(true)); - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(null, 1, 1)), is(true)); - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(1980, 1, null)), is(true)); - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(1980, null, 1)), is(true)); - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(1980, null, null)), is(true)); - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(null, null, null)), is(true)); - - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(1981, 1, 1)), is(false)); - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(1980, 2, 1)), is(false)); - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(1980, 1, 2)), is(false)); - assertThat(NationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, createPerson(1981, 1, null)), is(false)); - - assertThat(NationalHealthIdValidator.isValid("1980010145628", createPerson(1980, 1, 1)), is(false)); - assertThat(NationalHealthIdValidator.isValid("1980010145718", createPerson(1980, 1, 1)), is(false)); - assertThat(NationalHealthIdValidator.isValid("1980010145723", createPerson(1980, 1, 1)), is(false)); - assertThat(NationalHealthIdValidator.isValid("1980010345728", createPerson(1980, 1, null)), is(false)); - } - - @NotNull - private static Person createPerson(Integer birthdateYYYY, Integer birthdateMM, Integer birthdateDD) { - Person person = new Person(); - person.setBirthdateYYYY(birthdateYYYY); - person.setBirthdateMM(birthdateMM); - person.setBirthdateDD(birthdateDD); - - return person; - } -} diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/util/luxembourg/LuxembourgNationalHealthIdValidatorTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/util/luxembourg/LuxembourgNationalHealthIdValidatorTest.java new file mode 100644 index 00000000000..9955063e320 --- /dev/null +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/util/luxembourg/LuxembourgNationalHealthIdValidatorTest.java @@ -0,0 +1,55 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.backend.util.luxembourg; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import de.symeda.sormas.api.utils.luxembourg.LuxembourgNationalHealthIdValidator; + +public class LuxembourgNationalHealthIdValidatorTest { + + public static final String VALID_LU_NATIONAL_HEALTH_ID = "1980010145728"; + + @Test + public void testIsValid() { + assertThat(LuxembourgNationalHealthIdValidator.isValid("19800101", 1980, 1, 1), is(false)); + assertThat(LuxembourgNationalHealthIdValidator.isValid("1980Jan0145728", 1980, 1, 1), is(false)); + assertThat(LuxembourgNationalHealthIdValidator.isValid("1980Ja0145728", 1980, 1, 1), is(false)); + + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, 1980, 1, 1), is(true)); + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, null, 1, 1), is(true)); + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, 1980, 1, null), is(true)); + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, 1980, null, 1), is(true)); + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, 1980, null, null), is(true)); + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, null, null, null), is(true)); + + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, 1981, 1, 1), is(false)); + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, 1980, 2, 1), is(false)); + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, 1980, 1, 2), is(false)); + assertThat(LuxembourgNationalHealthIdValidator.isValid(VALID_LU_NATIONAL_HEALTH_ID, 1981, 1, null), is(false)); + + assertThat(LuxembourgNationalHealthIdValidator.isValid("1980010145628", 1980, 1, 1), is(false)); + assertThat(LuxembourgNationalHealthIdValidator.isValid("1980010145718", 1980, 1, 1), is(false)); + assertThat(LuxembourgNationalHealthIdValidator.isValid("1980010145723", 1980, 1, 1), is(false)); + assertThat(LuxembourgNationalHealthIdValidator.isValid("1980010345728", 1980, 1, null), is(false)); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/person/PersonEditForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/person/PersonEditForm.java index 744498cc7b7..03a3235ab23 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/person/PersonEditForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/person/PersonEditForm.java @@ -37,6 +37,7 @@ import org.apache.commons.lang3.StringUtils; +import com.vaadin.shared.ui.ErrorLevel; import com.vaadin.ui.CustomLayout; import com.vaadin.ui.Label; import com.vaadin.v7.data.Item; @@ -49,6 +50,7 @@ import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; +import de.symeda.sormas.api.CountryHelper; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.caze.CaseDataDto; @@ -79,6 +81,7 @@ import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.api.utils.fieldvisibility.checkers.CountryFieldVisibilityChecker; +import de.symeda.sormas.api.utils.luxembourg.LuxembourgNationalHealthIdValidator; import de.symeda.sormas.ui.ControllerProvider; import de.symeda.sormas.ui.UserProvider; import de.symeda.sormas.ui.location.LocationEditForm; @@ -103,6 +106,7 @@ public class PersonEditForm extends AbstractEditForm { private static final String ADDRESSES_HEADER = "addressesHeader"; private static final String CONTACT_INFORMATION_HEADER = "contactInformationHeader"; private static final String EXTERNAL_TOKEN_WARNING_LOC = "externalTokenWarningLoc"; + private static final String NATIONAL_HEALTH_ID_WARNING_LABEL = "nationalHealthIdWarningLoc"; private static final String GENERAL_COMMENT_LOC = "generalCommentLoc"; //@formatter:off private static final String HTML_LAYOUT = @@ -134,6 +138,7 @@ public class PersonEditForm extends AbstractEditForm { oneOfTwoCol(PersonDto.BURIAL_PLACE_DESCRIPTION) ) + fluidRowLocs(PersonDto.PASSPORT_NUMBER, PersonDto.NATIONAL_HEALTH_ID) + + fluidRowLocs("", NATIONAL_HEALTH_ID_WARNING_LABEL) + fluidRowLocs(PersonDto.EXTERNAL_ID, PersonDto.EXTERNAL_TOKEN) + fluidRowLocs(PersonDto.INTERNAL_TOKEN, EXTERNAL_TOKEN_WARNING_LOC) + @@ -366,7 +371,13 @@ protected void addFields() { addInfrastructureField(PersonDto.BIRTH_COUNTRY).addItems(countries); addInfrastructureField(PersonDto.CITIZENSHIP).addItems(countries); - addFields(PersonDto.PASSPORT_NUMBER, PersonDto.NATIONAL_HEALTH_ID); + addField(PersonDto.PASSPORT_NUMBER); + + TextField nationalHealthIdField = addField(PersonDto.NATIONAL_HEALTH_ID); + Label nationalHealthIdWarningLabel = new Label(I18nProperties.getString(Strings.messagePersonNationalHealthIdInvalid)); + nationalHealthIdWarningLabel.addStyleNames(VSPACE_3, LABEL_WHITE_SPACE_NORMAL); + getContent().addComponent(nationalHealthIdWarningLabel, NATIONAL_HEALTH_ID_WARNING_LABEL); + Field externalId = addField(PersonDto.EXTERNAL_ID); if (FacadeProvider.getExternalSurveillanceToolFacade().isFeatureEnabled()) { externalId.setEnabled(false); @@ -566,13 +577,24 @@ protected void addFields() { burialDate.setValidationVisible(!burialDate.isValid()); }); - addValueChangeListener((e) -> { + addValueChangeListener(e -> { ValidationUtils.initComponentErrorValidator( externalTokenField, getValue().getExternalToken(), Validations.duplicateExternalToken, externalTokenWarningLabel, - (externalToken) -> FacadeProvider.getPersonFacade().doesExternalTokenExist(externalToken, getValue().getUuid())); + externalToken -> FacadeProvider.getPersonFacade().doesExternalTokenExist(externalToken, getValue().getUuid())); + + if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + ValidationUtils.initComponentErrorValidator( + nationalHealthIdField, + getValue().getNationalHealthId(), + Validations.invalidNationalHealthId, + nationalHealthIdWarningLabel, + nationalHealthId -> !LuxembourgNationalHealthIdValidator + .isValid(nationalHealthId, getValue().getBirthdateYYYY(), getValue().getBirthdateMM(), getValue().getBirthdateDD()), + ErrorLevel.WARNING); + } personContactDetailsField.setThisPerson((PersonDto) e.getProperty().getValue()); }); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/ValidationUtils.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/ValidationUtils.java index fd9a40c07bc..5df3c1fae8c 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/ValidationUtils.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/ValidationUtils.java @@ -20,7 +20,9 @@ import org.apache.commons.lang3.StringUtils; import com.vaadin.server.UserError; +import com.vaadin.shared.ui.ErrorLevel; import com.vaadin.ui.Label; +import com.vaadin.v7.data.Property; import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.i18n.I18nProperties; @@ -36,13 +38,28 @@ public static void initComponentErrorValidator( String validationMessageTag, Label warningLabel, Function exists) { + initComponentErrorValidator(field, initialFieldValue, validationMessageTag, warningLabel, exists, ErrorLevel.ERROR); + } + + public static void initComponentErrorValidator( + TextField field, + String initialFieldValue, + String validationMessageTag, + Label warningLabel, + Function isInvlaid, + ErrorLevel errorLevel) { if (field == null) { return; } + // already initialized + if (field.getData() != null && Property.ValueChangeListener.class.isAssignableFrom(field.getData().getClass())) { + return; + } + Function validateExternalToken = (String value) -> { - if (field.isVisible() && !StringUtils.isEmpty(value) && exists.apply(value)) { - field.setComponentError(new UserError(I18nProperties.getValidationError(validationMessageTag))); + if (field.isVisible() && !StringUtils.isEmpty(value) && isInvlaid.apply(value)) { + field.setComponentError(new UserError(I18nProperties.getValidationError(validationMessageTag), null, errorLevel)); warningLabel.setVisible(true); } else { field.setComponentError(null); @@ -53,6 +70,8 @@ public static void initComponentErrorValidator( }; validateExternalToken.apply(initialFieldValue); - field.addValueChangeListener(f -> validateExternalToken.apply((String) f.getProperty().getValue())); + Property.ValueChangeListener valueChangeListener = f -> validateExternalToken.apply((String) f.getProperty().getValue()); + field.addValueChangeListener(valueChangeListener); + field.setData(valueChangeListener); } }