import-email-addresses #86
@ -6,8 +6,10 @@ import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAsse
|
|||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
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.
|
* 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;
|
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
|
/// 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");
|
step("validateEntity", "prepareForSave");
|
||||||
|
final var ignoreRegExpPatterns = Arrays.stream(ignoreRegExp).map(Pattern::compile).toList();
|
||||||
MultiValidationException.throwIfNotEmpty(
|
MultiValidationException.throwIfNotEmpty(
|
||||||
validator.validateEntity(entity).stream()
|
validator.validateEntity(entity).stream()
|
||||||
.filter(errorMsg -> !errorMsg.matches(ignoreRegExp))
|
.filter(error -> ignoreRegExpPatterns.stream().noneMatch(p -> p.matcher(error).matches() ))
|
||||||
.toList()
|
.toList()
|
||||||
);
|
);
|
||||||
return this;
|
return this;
|
||||||
|
@ -11,11 +11,12 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
|
|||||||
|
|
||||||
class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
|
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_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_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 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 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
|
public static final int EMAIL_ADDRESS_MAX_LENGTH = 320; // according to RFC 5321 and RFC 5322
|
||||||
|
|
||||||
HsEMailAddressHostingAssetValidator() {
|
HsEMailAddressHostingAssetValidator() {
|
||||||
@ -25,7 +26,7 @@ class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
|
|||||||
stringProperty("local-part").matchesRegEx("^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "$").writeOnce().optional(),
|
stringProperty("local-part").matchesRegEx("^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "$").writeOnce().optional(),
|
||||||
stringProperty("sub-domain").matchesRegEx("^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "$").writeOnce().optional(),
|
stringProperty("sub-domain").matchesRegEx("^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "$").writeOnce().optional(),
|
||||||
arrayOf(
|
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));
|
).required().minLength(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,13 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
|
|||||||
.identifier("old-local-part@example.org")
|
.identifier("old-local-part@example.org")
|
||||||
.config(new HashMap<>(ofEntries(
|
.config(new HashMap<>(ofEntries(
|
||||||
entry("local-part", "old-local-part"),
|
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(
|
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||||
"{type=string, propertyName=local-part, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$], writeOnce=true}",
|
"{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=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
|
@Test
|
||||||
@ -69,7 +75,11 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
|
|||||||
.config(new HashMap<>(ofEntries(
|
.config(new HashMap<>(ofEntries(
|
||||||
entry("local-part", "no@allowed"),
|
entry("local-part", "no@allowed"),
|
||||||
entry("sub-domain", "no@allowedeither"),
|
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();
|
.build();
|
||||||
final var validator = HostingAssetEntityValidatorRegistry.forType(emailAddressHostingAssetEntity.getType());
|
final var validator = HostingAssetEntityValidatorRegistry.forType(emailAddressHostingAssetEntity.getType());
|
||||||
|
|
||||||
@ -80,7 +90,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
|
|||||||
assertThat(result).containsExactlyInAnyOrder(
|
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.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.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
|
@Test
|
||||||
|
@ -20,6 +20,7 @@ import org.springframework.transaction.support.TransactionTemplate;
|
|||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.validation.ValidationException;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -309,7 +310,7 @@ public class CsvDataImport extends ContextBasedTest {
|
|||||||
void logError(final Runnable assertion) {
|
void logError(final Runnable assertion) {
|
||||||
try {
|
try {
|
||||||
assertion.run();
|
assertion.run();
|
||||||
} catch (final AssertionError exc) {
|
} catch (final AssertionError | ValidationException exc) {
|
||||||
logError(exc.getMessage());
|
logError(exc.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -668,6 +668,9 @@ public class ImportHostingAssets extends ImportOfficeData {
|
|||||||
if (isImportingControlledTestData()) {
|
if (isImportingControlledTestData()) {
|
||||||
expectError("zonedata dom_owner of mellis.de is old00 but expected to be mim00");
|
expectError("zonedata dom_owner of mellis.de is old00 but expected to be mim00");
|
||||||
expectError("\nexpected: \"vm1068\"\n but was: \"vm1093\"");
|
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();
|
this.assertNoErrors();
|
||||||
}
|
}
|
||||||
@ -920,7 +923,10 @@ public class ImportHostingAssets extends ImportOfficeData {
|
|||||||
logError(() ->
|
logError(() ->
|
||||||
new HostingAssetEntitySaveProcessor(em, entry.getValue())
|
new HostingAssetEntitySaveProcessor(em, entry.getValue())
|
||||||
.preprocessEntity()
|
.preprocessEntity()
|
||||||
.validateEntityIgnoring("'EMAIL_ALIAS:.*\\.config\\.target' .*")
|
.validateEntityIgnoring(
|
||||||
|
"'EMAIL_ALIAS:.*\\.config\\.target' .*",
|
||||||
|
"'EMAIL_ADDRESS:.*\\.config\\.target' .*"
|
||||||
|
)
|
||||||
.prepareForSave()
|
.prepareForSave()
|
||||||
.saveUsing(entity -> persist(entry.getKey(), entity))
|
.saveUsing(entity -> persist(entry.getKey(), entity))
|
||||||
.validateContext()
|
.validateContext()
|
||||||
|
@ -12,7 +12,7 @@ emailaddr_id;domain_id;localpart;subdomain;target
|
|||||||
54760;4531;info;hamburg-west;peter.lottmann@example.com
|
54760;4531;info;hamburg-west;peter.lottmann@example.com
|
||||||
54761;4531;lugmaster;hamburg-west;raoul.lottmann@example.com
|
54761;4531;lugmaster;hamburg-west;raoul.lottmann@example.com
|
||||||
54762;4531;postmaster;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
|
54764;4531;;eliza;eliza@example.net
|
||||||
54765;4531;;;lug00
|
54765;4531;;;lug00
|
||||||
54766;4532;;;nomail
|
54766;4532;;;nomail
|
||||||
@ -22,10 +22,10 @@ emailaddr_id;domain_id;localpart;subdomain;target
|
|||||||
54963;4581;abuse;;mim00
|
54963;4581;abuse;;mim00
|
||||||
54964;4581;postmaster;;mim00
|
54964;4581;postmaster;;mim00
|
||||||
54965;4581;webmaster;;mim00
|
54965;4581;webmaster;;mim00
|
||||||
54981;4587;abuse;;mim00
|
54981;4587;abuse;;
|
||||||
54982;4587;postmaster;;mim00
|
54982;4587;postmaster;;/dev/null
|
||||||
54983;4587;webmaster;;mim00
|
54983;4587;webmaster;;mim00
|
||||||
54987;4589;abuse;;mim00
|
54987;4589;abuse;;""
|
||||||
54988;4589;postmaster;;mim00
|
54988;4589;postmaster;;mim00
|
||||||
54989;4589;webmaster;;mim00
|
54989;4589;webmaster;;mim00
|
||||||
55020;4600;abuse;;mim00
|
55020;4600;abuse;;mim00
|
||||||
|
|
Loading…
Reference in New Issue
Block a user