From c136064ff78967e365443ced58625b0c9868a9a8 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 10 Aug 2024 08:00:45 +0200 Subject: [PATCH] allow target /dev/null to as well as '+' in target and filter EMAIL_ADDRESS target related errors from final assertion --- .../HostingAssetEntitySaveProcessor.java | 8 ++++++-- .../HsEMailAddressHostingAssetValidator.java | 5 +++-- ...ilAddressHostingAssetValidatorUnitTest.java | 18 ++++++++++++++---- .../hsadminng/hs/migration/CsvDataImport.java | 3 ++- .../hs/migration/ImportHostingAssets.java | 8 +++++++- .../resources/migration/hosting/emailaddr.csv | 8 ++++---- 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntitySaveProcessor.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntitySaveProcessor.java index 5d7b9ddd..b622c2fa 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntitySaveProcessor.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntitySaveProcessor.java @@ -6,8 +6,10 @@ import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAsse import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import jakarta.persistence.EntityManager; +import java.util.Arrays; import java.util.Map; import java.util.function.Function; +import java.util.regex.Pattern; /** * Wraps the steps of the pararation, validation, mapping and revamp around saving of a HsHostingAsset into a readable API. @@ -40,12 +42,14 @@ public class HostingAssetEntitySaveProcessor { return this; } + // TODO.impl: remove once the migration of legacy data is done /// validates the entity itself including its properties, but ignoring some error messages for import of legacy data - public HostingAssetEntitySaveProcessor validateEntityIgnoring(final String ignoreRegExp) { + public HostingAssetEntitySaveProcessor validateEntityIgnoring(final String... ignoreRegExp) { step("validateEntity", "prepareForSave"); + final var ignoreRegExpPatterns = Arrays.stream(ignoreRegExp).map(Pattern::compile).toList(); MultiValidationException.throwIfNotEmpty( validator.validateEntity(entity).stream() - .filter(errorMsg -> !errorMsg.matches(ignoreRegExp)) + .filter(error -> ignoreRegExpPatterns.stream().noneMatch(p -> p.matcher(error).matches() )) .toList() ); return this; 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 index 97917f6f..77c32768 100644 --- 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 @@ -11,11 +11,12 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator { - private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$"; // also accepts legacy pac-names + private static final String TARGET_MAILBOX_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][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 + "$"; private static final String NOBODY_REGEX = "^nobody$"; + private static final String DEVNULL_REGEX = "^/dev/null$"; public static final int EMAIL_ADDRESS_MAX_LENGTH = 320; // according to RFC 5321 and RFC 5322 HsEMailAddressHostingAssetValidator() { @@ -25,7 +26,7 @@ class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator { stringProperty("local-part").matchesRegEx("^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "$").writeOnce().optional(), stringProperty("sub-domain").matchesRegEx("^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "$").writeOnce().optional(), arrayOf( - stringProperty("target").maxLength(EMAIL_ADDRESS_MAX_LENGTH).matchesRegEx(UNIX_USER_REGEX, EMAIL_ADDRESS_FULL_REGEX, NOBODY_REGEX) + stringProperty("target").maxLength(EMAIL_ADDRESS_MAX_LENGTH).matchesRegEx(TARGET_MAILBOX_REGEX, EMAIL_ADDRESS_FULL_REGEX, NOBODY_REGEX, DEVNULL_REGEX) ).required().minLength(1)); } 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 index 441200c6..386ba632 100644 --- 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 @@ -33,7 +33,13 @@ class HsEMailAddressHostingAssetValidatorUnitTest { .identifier("old-local-part@example.org") .config(new HashMap<>(ofEntries( entry("local-part", "old-local-part"), - entry("target", Array.of("xyz00", "xyz00-abc", "office@example.com")) + entry("target", Array.of( + "xyz00", + "xyz00-abc", + "xyz00-xyz+list", + "office@example.com", + "/dev/null" + )) ))); } @@ -46,7 +52,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest { assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( "{type=string, propertyName=local-part, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$], writeOnce=true}", "{type=string, propertyName=sub-domain, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$], writeOnce=true}", - "{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+)?@[a-zA-Z0-9.-]+$, ^nobody$], maxLength=320}, required=true, minLength=1}"); + "{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\.+_-]*)?$, ^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+)?@[a-zA-Z0-9.-]+$, ^nobody$, ^/dev/null$], maxLength=320}, required=true, minLength=1}"); } @Test @@ -69,7 +75,11 @@ class HsEMailAddressHostingAssetValidatorUnitTest { .config(new HashMap<>(ofEntries( entry("local-part", "no@allowed"), entry("sub-domain", "no@allowedeither"), - entry("target", Array.of("xyz00", "xyz00-abc", "garbage", "office@example.com"))))) + entry("target", Array.of( + "xyz00", + "xyz00-abc", + "garbage", + "office@example.com"))))) .build(); final var validator = HostingAssetEntityValidatorRegistry.forType(emailAddressHostingAssetEntity.getType()); @@ -80,7 +90,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest { assertThat(result).containsExactlyInAnyOrder( "'EMAIL_ADDRESS:old-local-part@example.org.config.local-part' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowed' does not match", "'EMAIL_ADDRESS:old-local-part@example.org.config.sub-domain' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowedeither' does not match", - "'EMAIL_ADDRESS:old-local-part@example.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+)?@[a-zA-Z0-9.-]+$, ^nobody$] but 'garbage' does not match any"); + "'EMAIL_ADDRESS:old-local-part@example.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\.+_-]*)?$, ^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+)?@[a-zA-Z0-9.-]+$, ^nobody$, ^/dev/null$] but 'garbage' does not match any"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java index 9112c000..2ce2e924 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java @@ -20,6 +20,7 @@ import org.springframework.transaction.support.TransactionTemplate; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ValidationException; import jakarta.validation.constraints.NotNull; import java.io.BufferedReader; import java.io.IOException; @@ -309,7 +310,7 @@ public class CsvDataImport extends ContextBasedTest { void logError(final Runnable assertion) { try { assertion.run(); - } catch (final AssertionError exc) { + } catch (final AssertionError | ValidationException exc) { logError(exc.getMessage()); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java index ead38997..e31e4d10 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java @@ -668,6 +668,9 @@ public class ImportHostingAssets extends ImportOfficeData { if (isImportingControlledTestData()) { expectError("zonedata dom_owner of mellis.de is old00 but expected to be mim00"); expectError("\nexpected: \"vm1068\"\n but was: \"vm1093\""); + expectError("['EMAIL_ADDRESS:webmaster@hamburg-west.l-u-g.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\.+_-]*)?$, ^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+)?@[a-zA-Z0-9.-]+$, ^nobody$, ^/dev/null$] but 'raoul.lottmann@example.com peter.lottmann@example.com' does not match any]"); + expectError("['EMAIL_ADDRESS:abuse@mellis.de.config.target' length is expected to be at min 1 but length of [[]] is 0]"); + expectError("['EMAIL_ADDRESS:abuse@ist-im-netz.de.config.target' length is expected to be at min 1 but length of [[]] is 0]"); } this.assertNoErrors(); } @@ -920,7 +923,10 @@ public class ImportHostingAssets extends ImportOfficeData { logError(() -> new HostingAssetEntitySaveProcessor(em, entry.getValue()) .preprocessEntity() - .validateEntityIgnoring("'EMAIL_ALIAS:.*\\.config\\.target' .*") + .validateEntityIgnoring( + "'EMAIL_ALIAS:.*\\.config\\.target' .*", + "'EMAIL_ADDRESS:.*\\.config\\.target' .*" + ) .prepareForSave() .saveUsing(entity -> persist(entry.getKey(), entity)) .validateContext() diff --git a/src/test/resources/migration/hosting/emailaddr.csv b/src/test/resources/migration/hosting/emailaddr.csv index 9f2c9768..924641fe 100644 --- a/src/test/resources/migration/hosting/emailaddr.csv +++ b/src/test/resources/migration/hosting/emailaddr.csv @@ -12,7 +12,7 @@ emailaddr_id;domain_id;localpart;subdomain;target 54760;4531;info;hamburg-west;peter.lottmann@example.com 54761;4531;lugmaster;hamburg-west;raoul.lottmann@example.com 54762;4531;postmaster;hamburg-west;raoul.lottmann@example.com -54763;4531;webmaster;hamburg-west;raoul.lottmann@example.com +54763;4531;webmaster;hamburg-west;raoul.lottmann@example.com peter.lottmann@example.com 54764;4531;;eliza;eliza@example.net 54765;4531;;;lug00 54766;4532;;;nomail @@ -22,10 +22,10 @@ emailaddr_id;domain_id;localpart;subdomain;target 54963;4581;abuse;;mim00 54964;4581;postmaster;;mim00 54965;4581;webmaster;;mim00 -54981;4587;abuse;;mim00 -54982;4587;postmaster;;mim00 +54981;4587;abuse;; +54982;4587;postmaster;;/dev/null 54983;4587;webmaster;;mim00 -54987;4589;abuse;;mim00 +54987;4589;abuse;;"" 54988;4589;postmaster;;mim00 54989;4589;webmaster;;mim00 55020;4600;abuse;;mim00