From 9c7f35c7de229145d41b155258e6d5697a9c44b2 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 10 Jul 2024 10:09:39 +0200 Subject: [PATCH] add HsEMailAddressHostingAssetValidator --- .../HostingAssetEntityValidatorRegistry.java | 1 + .../HsEMailAddressHostingAssetValidator.java | 51 ++++++++ .../HsHostingAssetControllerRestTest.java | 28 +++++ ...ingAssetPropsControllerAcceptanceTest.java | 4 +- ...gAssetEntityValidatorRegistryUnitTest.java | 4 +- ...lAddressHostingAssetValidatorUnitTest.java | 114 ++++++++++++++++++ 6 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsEMailAddressHostingAssetValidator.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsEMailAddressHostingAssetValidatorUnitTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidatorRegistry.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidatorRegistry.java index 5b287a14..f44be666 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidatorRegistry.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidatorRegistry.java @@ -25,6 +25,7 @@ public class HostingAssetEntityValidatorRegistry { register(DOMAIN_HTTP_SETUP, new HsDomainHttpSetupHostingAssetValidator()); register(DOMAIN_EMAIL_SUBMISSION_SETUP, new HsDomainEMailSubmissionSetupHostingAssetValidator()); register(DOMAIN_EMAIL_MAILBOX_SETUP, new HsDomainEMailMailboxSetupHostingAssetValidator()); + register(EMAIL_ADDRESS, new HsEMailAddressHostingAssetValidator()); } private static void register(final Enum type, final HsEntityValidator validator) { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsEMailAddressHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsEMailAddressHostingAssetValidator.java new file mode 100644 index 00000000..d77451e7 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsEMailAddressHostingAssetValidator.java @@ -0,0 +1,51 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; + +import java.util.regex.Pattern; + +import static java.util.Optional.ofNullable; +import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf; +import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty; + +class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator { + + private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$"; // also accepts legacy pac-names + private static final String EMAIL_ADDRESS_LOCAL_PART_REGEX = "[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+"; // RFC 5322 + private static final String EMAIL_ADDRESS_DOMAIN_PART_REGEX = "[a-zA-Z0-9.-]+"; + private static final String EMAIL_ADDRESS_FULL_REGEX = "^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "@" + EMAIL_ADDRESS_DOMAIN_PART_REGEX + "$"; + public static final int EMAIL_ADDRESS_MAX_LENGTH = 320; // according to RFC 5321 and RFC 5322 + + HsEMailAddressHostingAssetValidator() { + super( HsHostingAssetType.EMAIL_ADDRESS, + AlarmContact.isOptional(), + + stringProperty("local-part").matchesRegEx("^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "$").required(), + stringProperty("sub-domain").matchesRegEx("^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "$").optional(), + arrayOf( + stringProperty("target").maxLength(EMAIL_ADDRESS_MAX_LENGTH).matchesRegEx(UNIX_USER_REGEX, EMAIL_ADDRESS_FULL_REGEX) + ).required().minLength(1)); + } + + @Override + public void preprocessEntity(final HsHostingAssetEntity entity) { + super.preprocessEntity(entity); + super.preprocessEntity(entity); + if (entity.getIdentifier() == null) { + entity.setIdentifier(combineIdentifier(entity)); + } + } + + @Override + protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { + return Pattern.compile("^"+ Pattern.quote(combineIdentifier(assetEntity)) + "$"); + } + + private static String combineIdentifier(final HsHostingAssetEntity emailAddressAssetEntity) { + return emailAddressAssetEntity.getDirectValue("local-part", String.class) + + ofNullable(emailAddressAssetEntity.getDirectValue("sub-domain", String.class)).map(s -> "." + s).orElse("") + + "@" + + emailAddressAssetEntity.getParentAsset().getIdentifier(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java index 79841ae6..9df023a1 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java @@ -335,6 +335,34 @@ public class HsHostingAssetControllerRestTest { "config": {} } ] + """), + EMAIL_ADDRESS( + List.of( + HsHostingAssetEntity.builder() + .type(HsHostingAssetType.EMAIL_ADDRESS) + .parentAsset(HsHostingAssetEntity.builder() + .type(HsHostingAssetType.DOMAIN_EMAIL_MAILBOX_SETUP) + .identifier("example.org|MBOX") + .caption("some fake Domain-MBOX-Setup") + .build()) + .identifier("office@example.org") + .caption("some fake EMail-Address") + .config(Map.ofEntries( + entry("target", Array.of("xyz00", "xyz00-abc", "office@example.com")) + )) + .build()), + """ + [ + { + "type": "EMAIL_ADDRESS", + "identifier": "office@example.org", + "caption": "some fake EMail-Address", + "alarmContact": null, + "config": { + "target": ["xyz00","xyz00-abc","office@example.com"] + } + } + ] """); final HsHostingAssetType assetType; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java index ac011d68..37affab1 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java @@ -39,7 +39,9 @@ class HsHostingAssetPropsControllerAcceptanceTest { "DOMAIN_SETUP", "DOMAIN_DNS_SETUP", "DOMAIN_HTTP_SETUP", - "DOMAIN_EMAIL_SUBMISSION_SETUP" + "DOMAIN_EMAIL_SUBMISSION_SETUP", + "DOMAIN_EMAIL_MAILBOX_SETUP", + "EMAIL_ADDRESS" ] """)); // @formatter:on diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidatorRegistryUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidatorRegistryUnitTest.java index a041742b..3f6b4917 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidatorRegistryUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidatorRegistryUnitTest.java @@ -37,7 +37,9 @@ class HostingAssetEntityValidatorRegistryUnitTest { HsHostingAssetType.DOMAIN_SETUP, HsHostingAssetType.DOMAIN_DNS_SETUP, HsHostingAssetType.DOMAIN_HTTP_SETUP, - HsHostingAssetType.DOMAIN_EMAIL_SUBMISSION_SETUP + HsHostingAssetType.DOMAIN_EMAIL_SUBMISSION_SETUP, + HsHostingAssetType.DOMAIN_EMAIL_MAILBOX_SETUP, + HsHostingAssetType.EMAIL_ADDRESS ); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsEMailAddressHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsEMailAddressHostingAssetValidatorUnitTest.java new file mode 100644 index 00000000..878f45e3 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsEMailAddressHostingAssetValidatorUnitTest.java @@ -0,0 +1,114 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.mapper.Array; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_EMAIL_MAILBOX_SETUP; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ADDRESS; +import static net.hostsharing.hsadminng.hs.hosting.asset.TestHsHostingAssetEntities.TEST_MANAGED_SERVER_HOSTING_ASSET; +import static org.assertj.core.api.Assertions.assertThat; + +class HsEMailAddressHostingAssetValidatorUnitTest { + + final static HsHostingAssetEntity domainEmailMailboxSetup = HsHostingAssetEntity.builder() + .type(DOMAIN_EMAIL_MAILBOX_SETUP) + .identifier("example.org") + .build(); + static HsHostingAssetEntity.HsHostingAssetEntityBuilder validEntityBuilder() { + return HsHostingAssetEntity.builder() + .type(EMAIL_ADDRESS) + .parentAsset(domainEmailMailboxSetup) + .identifier("test@example.org") + .config(Map.ofEntries( + entry("local-part", "test"), + entry("target", Array.of("xyz00", "xyz00-abc", "office@example.com")) + )); + } + + @Test + void containsAllValidations() { + // when + final var validator = HostingAssetEntityValidatorRegistry.forType(EMAIL_ADDRESS); + + // then + assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( + "{type=string, propertyName=local-part, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$], required=true}", + "{type=string, propertyName=sub-domain, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$]}", + "{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$], maxLength=320}, required=true, minLength=1}"); + } + + @Test + void acceptsValidEntity() { + // given + final var emailAddressHostingAssetEntity = validEntityBuilder().build(); + final var validator = HostingAssetEntityValidatorRegistry.forType(emailAddressHostingAssetEntity.getType()); + + // when + final var result = validator.validateEntity(emailAddressHostingAssetEntity); + + // then + assertThat(result).isEmpty(); + } + + @Test + void rejectsInvalidProperties() { + // given + final var emailAddressHostingAssetEntity = validEntityBuilder() + .config(Map.ofEntries( + entry("local-part", "no@allowed"), + entry("sub-domain", "no@allowedeither"), + entry("target", Array.of("xyz00", "xyz00-abc", "garbage", "office@example.com")))) + .build(); + final var validator = HostingAssetEntityValidatorRegistry.forType(emailAddressHostingAssetEntity.getType()); + + // when + final var result = validator.validateEntity(emailAddressHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'EMAIL_ADDRESS:test@example.org.config.local-part' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowed' does not match", + "'EMAIL_ADDRESS:test@example.org.config.sub-domain' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowedeither' does not match", + "'EMAIL_ADDRESS:test@example.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$] but 'garbage' does not match any"); + } + + @Test + void rejectsInvalidIdentifier() { + // given + final var emailAddressHostingAssetEntity = validEntityBuilder() + .identifier("abc00-office") + .build(); + final var validator = HostingAssetEntityValidatorRegistry.forType(emailAddressHostingAssetEntity.getType()); + + // when + final var result = validator.validateEntity(emailAddressHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'identifier' expected to match '^\\Qtest@example.org\\E$', but is 'abc00-office'"); + } + + @Test + void validatesInvalidReferences() { + // given + final var emailAddressHostingAssetEntity = validEntityBuilder() + .bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM) + .parentAsset(TEST_MANAGED_SERVER_HOSTING_ASSET) + .assignedToAsset(TEST_MANAGED_SERVER_HOSTING_ASSET) + .build(); + final var validator = HostingAssetEntityValidatorRegistry.forType(emailAddressHostingAssetEntity.getType()); + + // when + final var result = validator.validateEntity(emailAddressHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'EMAIL_ADDRESS:test@example.org.bookingItem' must be null but is of type MANAGED_SERVER", + "'EMAIL_ADDRESS:test@example.org.parentAsset' must be of type DOMAIN_EMAIL_MAILBOX_SETUP but is of type MANAGED_SERVER", + "'EMAIL_ADDRESS:test@example.org.assignedToAsset' must be null but is of type MANAGED_SERVER"); + } +}